Seguimos aprendiendo Yii2. Hoy aprenderemos a realizar un formulario completo, recoger datos y mostrarlos por pantalla. Para el ejemplo vamos a suponer que el usuario va a rellenar una ficha que contiene los datos de su Trabajo Fin de Grado.
Si no has leído los capítulos anteriores de esta serie sobre Yii2, te recomiendo que te pongas al día:
Creando el modelo.
Representaremos los datos que pediremos al usuario con una clase modelo que llamaremos DatosTrabajo
.
Utilizaremos las convenciones de Yii para la autocarga de clases (aka Class Autoloading) que, básicamente, son dos:
- Cada clase debe tener un namespace único (p ej
foo\bar\DatosTrabajo
) - Cada clase se debe guardar en un fichero separado cuyo path se determina por el siguiente algoritmo.
// $className is a fully qualified class name without the leading backslash $classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php');
Por ejemplo, si una Class y Namespace es foo\bar\MyClass
, el alias para el fichero de la clase correspondiente sería @foo/bar/MyClass.php
.
Vamos a crear por tanto nuestro fichero models/DatosTrabajo.php
con el siguiente contenido. El código está documentado para que se entienda
<?php namespace app\models; use Yii; use yii\base\Model; // utilizada habitualmente para representar modelos no asociados a BD's // use yii\db\ActiveRecord // utilizada habitualmente para representar modelos que se corresponden con tablas class DatosTrabajo extends Model { public $titulo1; // contendrá el título principal (normalmente, en castellano) public $titulo2; // contendrá el título traducido (normalmente, en inglés) public $autor; // contendrá el nombre y apellidos del autor public $director; // contendrá el nombre y apellidos del director/tutor public $resumen1; // contendrá el resumen en el idioma principal (normalmente, en castellano) public $resumen2; // contendrá el resumen traducido (normalmente, en inglés) public $idioma; // contendrá el idioma principal en que está redactado el trabajo public $anyo; // contendrá el año de la defensa del Trabajo public $notas; // contendrá notas adicionales que el autor desee hacer constar public $palClave; // contendrá una palabra clave public $estudio; // contendrá el estudio public $centro; // contendrá el centro responsable del estudio public $departamento; // contendrá el departamento asociado al estudio public $esEspecial; // contendrá un booleano indicando si es un trabajo "especial" (acompañado de materiales físicos) public $licencia; // contendrá el valor de la licencia que ha indicado el autor /** * Método que devuelve un conjunto de reglas para validar los datos. * Esto permitirá que podamos llamar al método "validate" del modelo tal que así: * $model = new DatosTrabajo(); * $model->autor = "Miguel" * $model->email = "email@unizar.es" * if ($model->validate()){ * { * // Bien!! * } else { * // Fallo! * use $model->getErrors() * } * */ public function rules() { return [ [['titulo1', 'autor', 'email'], 'required'], // titulo, autor e email son obligatorios ['email', 'email'], // email debe ser una dirección de correo sintáticamente correcta ]; } }
Creando la acción
A continuación vamos a crear la nueva action en nuestro controlador site
que usará el modelo que acabamos de definir. Para ello volvemos a editar el fichero controllers/SiteController.php
y tendremos que añadir dos cosas.
Al principio del fichero, decirle que use nuestro recién creado modelo app\models\DatosTrabajo
// use anteriores ... use app\models\DatosTrabajo;
Y el código de la propia acción:
/** * @return string */ public function actionNuevoTrabajo() { $model = new DatosTrabajo(); if ($model->load(Yii::$app->request->post()) && $model->validate()) { // tenemos datos válidos en $model // aquí haríamos algo con ellos return $this->render('entry-confirm', ['model' => $model]); } else { // dos posibles situaciones, // - es el primer acceso y aún no tenemos los datos // - hay algń error de validación. return $this->render('entry', ['model' => $model]); } }
La acción instancia un objeto DatosTrabajo
. Después intenta completar el modelo con los datos de $_POST
, proporcionados por Yii mediante yii\web\Request::post()
.
Si el modelo ha sido completado con éxito (p.e. el usuario ha enviado el formulario HTML), la acción llama al método validate()
para asegurarse de que los datos introducidos son válidos.
La expresión Yii::$app
representa la instancia de aplicación, que es un objeto singleton globalmente accesible. También es un service locator que proporciona componentes como request, response, db, etc. para facilitar funconalidades específicas. En el código anterior, el componente request
se utiliza para acceder a los datos de $_POST
.
Si todo es correcto, la acción renderizará la vista llamada entry-confirm
para confirmar al usuario el envío con éxito de los datos. Si no se han enviado datos o los datos contienen errores, la vista entry
será llamada, mostrando el formulario junto con cualquier mensaje de error.
Por simplicidad mostramos la página de confirmación cuando el envío es válido. En la práctica se debería considerar el uso de refresh()
o redirect()
para evitar problemas de reenvío de formularios. Lo veremos más adelante.
Creación de las vistas
Hemos hecho alusión a dos ficheros de vistas: entry
y entry-confirm
.
Vamos a crear el fichero views/site/entry-confirm.php
que mostrará los datos recibidos:
<?php use yii\helpers\Html; ?> <p>Has introducido la siguiente información:</p> <ul> <li><label>Título (idioma principal)</label>: <?= Html::encode($model->titulo1) ?></li> <li><label>Título (idioma secundario)</label>: <?= Html::encode($model->titulo2) ?></li> <li><label>Autor</label>: <?= Html::encode($model->autor) ?></li> <li><label>Director</label>: <?= Html::encode($model->director) ?></li> <li><label>Resumen (idioma principal)</label>: <?= Html::encode($model->resumen1) ?></li> <li><label>Resumen (idioma secundario)</label>: <?= Html::encode($model->resumen2) ?></li> <li><label>Idioma</label>: <?= Html::encode($model->idioma) ?></li> <li><label>Año</label>: <?= Html::encode($model->anyo) ?></li> <li><label>Notas</label>: <?= Html::encode($model->notas) ?></li> <li><label>Palabra clave</label>: <?= Html::encode($model->palClave) ?></li> <li><label>Estudio</label>: <?= Html::encode($model->estudio) ?></li> <li><label>Centro</label>: <?= Html::encode($model->centro) ?></li> <li><label>Departamento</label>: <?= Html::encode($model->departamento) ?></li> <li><label>¿Trabajo especial?</label>: <?= Html::encode($model->esEspecial) ?></li> <li><label>Licencia</label>: <?= Html::encode($model->licencia) ?></li> </ul>
Y también el fichero views/site/entry.php
que renderizará el formulario para captar los datos con el siguiente contenido:
<?php use yii\helpers\Html; use yii\widgets\ActiveForm; ?> <?php $form = ActiveForm::begin(); ?> <?= $form->field($model, 'titulo1') ?> <?= $form->field($model, 'titulo2') ?> <?= $form->field($model, 'autor') ?> <?= $form->field($model, 'director') ?> <?= $form->field($model, 'resumen1') ?> <?= $form->field($model, 'resumen2') ?> <?= $form->field($model, 'idioma') ?> <?= $form->field($model, 'anyo') ?> <?= $form->field($model, 'notas') ?> <?= $form->field($model, 'palClave') ?> <?= $form->field($model, 'estudio') ?> <?= $form->field($model, 'centro') ?> <?= $form->field($model, 'departamento') ?> <?= $form->field($model, 'esEspecial') ?> <?= $form->field($model, 'licencia') ?> <div class="form-group"> <?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?> </div> <?php $form = ActiveForm::end(); ?>
Esta vista utiliza el widget ActiveForm
para generar el formulario HTML. Los métodos begin()
y end()
del widget generan las etiquetas de apertura y cierre del formulario respectivamente. Entre las llamadas a los dos métodos se crean campos con la llamada al método field()
. Después de los dos campos, llamamos al método yii\helpers\Html::submitButton()
para generar el botón de envío.
Ahora ya podemos ir a http://unizaryii.docker.localhost:8000/nuevo-trabajo y vemos el formulario para rellenar:
Como vemos, las etiquetas (labels) del formulario tienen el valor con el que hemos llamado el campo en el modelo. ¿Y si deseamos cambiarlas a algo más legible?
Una forma de conseguirlo sería editar la vista views/site/entry.php
tal que así:
... <!-- ANTES --> <!-- <?= $form->field($model, 'titulo1') ?> --> <!-- DESPUES --> <?= $form->field($model, 'titulo1')->textInput()->label('Título idioma principal') ?--> ...
Y ahora el formulario se renderiza con este nuevo valor de la etiqueta y se vería así:
De esta forma únicamente cambiaremos el label en esa vista. Podemos hacerlo de otra forma, que sería más elegante, modificando el modelo. De esta forma en todas las vistas que usen ese modelo tendríamos Para ello tendríamos que editar models/DatosTrabajo.php
y añadir una nueva función attributeLabels()
tal que así:
public function attributeLabels() { return[ 'titulo1' => 'Título (idioma principal)', 'titulo2' => 'Título (idioma secundario)', 'autor' => 'Autor', 'director' => 'Director', 'resumen1' => 'Resumen (idioma principal)', 'resumen2' => 'Resumen (idioma secundario)', 'idioma' => 'Idioma', 'anyo' => 'Año', 'notas' => 'Notas', 'palClave' => 'Palabra clave', 'estudio' => 'Grado / Máster', 'centro' => 'Centro', 'departamento' => 'Departamento', 'esEspecial' => '¿Es un trabajo especial? (Incluye materiales físicos adicionales)', 'licencia' => 'Licencia', ]; }
Más adelante veremos cómo preparar la aplicación para multi-idioma y tendremos que realizar algunos cambios adicionales.
De momento el formulario quedaría así:
A continuación vamos a introducir un pequeño cambio más. Vamos a validar el modelo para que los campos titulo1, titulo2 y autor deban tener una longitud mínima.
Modificaremos la función rules()
en models/DatosTrabajo.php
con una nueva línea:
public function rules() { return [ [['titulo1', 'autor', 'email'], 'required'], // titulo, autor e email son obligatorios ['email', 'email'], // email debe ser una dirección de correo sintáticamente correcta // comprobar que son strings de longitud entre 10 y 100 [['titulo1', 'titulo2','autor'], 'string', 'length' => [10, 100]], ]; }
Ahora si intentamos introducir una cadena de longitud inadecuada, se quejará:
En el próximo post veremos cómo añadir una Base de datos que nos permitirá almacenar y mostrar los trabajos enviados.
Almacenando nuestros avances en un sistema de control de versiones Git
Emplearemos github para almacenar nuestro proyecto.
Para ello creamos un nuevo proyecto en github.com/new. Lo inicializaremos sin ficheros .gitignore
ni README
(los añadiremos más tarde). En mi caso creo https://github.com/miguel-martin/yii2deposito
Después en nuestro ordenador, nos iremos a la raiz de nuestro proyecto (ojo, NO a la carpeta web, sino al raíz (en mi caso /Users/miguelm/Sites/testyii2/docker4php/demo) y allí añadimos el remoto:
# ve al raiz de tu proyecto Yii2 cd /Users/miguelm/Sites/testyii2/docker4php/demo # incializar git git init # añadir el origen remoto git remote add origin https://github.com/miguel-martin/yii2deposito.git
Antes de realizar nuestro primer commit, nos aseguramos de que tengamos los ficheros README.md
y .gitignore
en local.
Mi fichero .gitignore
es así:
# phpstorm project files .idea # netbeans project files nbproject # zend studio for eclipse project files .buildpath .project .settings # windows thumbnail cache Thumbs.db # composer vendor dir /vendor # composer itself is not needed composer.phar # Mac DS_Store Files .DS_Store # phpunit itself is not needed phpunit.phar # local phpunit config /phpunit.xml tests/_output/* tests/_support/_generated #vagrant folder /.vagrant
Y como fichero README.md
tengo:
Yii2 - Deposito =============== Este proyecto es un ejemplo sencillo de aplicación escrita en Yii2 para conocer las posibilidades de este Framework. Para poder seguir bien el tutorial deberías tener unos mínimos conocimientos de PHP. Descripción ----------- Puedes ir siguiendo el ejemplo en [leccionespracticas.com](https://www.leccionespracticas.com) + [Preparación del entorno de desarrollo con Dockers](http://www.leccionespracticas.com/?p=2594) + [Primeros pasos con Yii2](http://www.leccionespracticas.com/?p=2604) + [Formulario y control de versiones](http://www.leccionespracticas.com/?p=2634)
Ahora ya lo tenemos preparado y podemos hacer nuestro primer commit:
# añadir todos los ficheros git add . # creamos nuestro primer commit git commit -m "Creación de la base. +info en http://www.leccionespracticas.com/?p=2634" # hacer un push de la rama "master" al remoto "origin" # y "set up" tracking (así en el futuro solo tendremos que hacer git push y git pull # +info aqui https://mislav.net/2010/07/git-tips/) git push -u origin master