Herramientas de usuario

Herramientas del sitio


cursos:yii2:modelos

Crear y configurar Modelos

Podemos definirlos manualmente, pero es más rápido y cómodo utilizar el generador (gii). Este generador solamente está habilitado en entorno de desarrollo. (YII_ENV = “dev”)

Generar Modelos

  • Entramos en
    http://localhost/miaplicacion/?r=gii
  • Seleccionamos “Model Generator”
  • Escribimos el nombre de la tabla
  • El nombre del modelo será igual, con la primera letra en mayúsculas. Se puede cambiar si se desea
  • Namespace: por defecto es app\models. Si utilizamos módulos o trabajamos con la plantilla advanced, se puede cambiar por el que se desee
  • Seleccionamos el resto de opciones según deseemos: Activar i18n, crear relaciones, crear Active Query…
  • Pulsamos “Preview”
  • Si va todo bien, aparecerá el nombre del fichero que generará. Pulsamos “Generate”. Si ya existía, y vamos a volverlo a generar porque hemos hecho cambios en la tabla, tenemos que marcar la casilla “overwrite” antes de pulsar “Generate”

Repetimos esta operación para todas las tablas deseadas.Si queremos generar modelos para todas las tablas de la base de datos, podemos escribir “*” como nombre de tabla

IMPORTANTE: Antes de generar los modelos se debe comprobar que en el base de datos están definidas correctamente las relaciones entre las tablas. De esta forma, se crearán las relaciones entre los modelos generados

Configurar Modelos

Una vez generados los modelos, se deben revisar para terminarlos de configurar segun las necesidades de nuestra aplicación.

En app/models/MODELO.php:

Reglas de validación de datos

Revisamos el método rules(), para añadir o modificar reglas de validación de los datos del modelo. El generador de código hereda las que se pueden deducir de la base de datos, pero podemos añadir otras. Ver Activerecord: Reglas de Validación

Eventos

Yii utiliza eventos, que se “disparan” antés y después de las actualizaciones del modelo, de forma similar a los triggers de la base de datos. Para definirlos, basta con sobreescribir el método con el nombre del evento. Es importante que al final del método se devuelva como resultado la ejecución del método parent.

beforeSave, beforeDelete

Se ejecuta antes de actualizar o borrar el modelo en la base de datos. Podemos utilizarlo para reasignar valor a los atributos, hacer validaciones que sean complicadas de definir en rules…

  function beforeSave($insert) {
    if(strlen($this->password)!=32)
      $this->password=md5($this->password);
    if($this->isNewRecord) $this->fecha_alta=date('Y-m-d');
    if($this->.... and $this->.... or $this->...) {
       $this->addError('estado','Estado no permitido porque....')
       return false;
    }
    return parent::beforeSave($insert);
  }

afterSave, afterDelete

Se ejecuta antes de actualizar o borrar el modelo en la base de datos. Se puede utilizar, por ejemplo, para enviar un correo de alta, ya que la actualización ha sido correcta

afterFind

Se ejecuta después de leer los datos de la base de datos. Se pueden reasignar atributos, según se desee. Por ejemplo, convertir atributos almacenados como string en formato json a objeto o array

Relaciones

Si están correctamente definidas en la BD, se habrán generado automáticamente. Si no es así, habrá que introducirlas manualmente. Ver relaciones. Las relaciones se crean como métodos con el nombre getMODELO() ,que devuelve un objeto ActiveQuery diferente según sea una relación 1-1 o 1-n:

// $titulo->comentarios será un array de instancias del modelo Comentarios que se corresponde con los comentarios del libro
public function getComentarios() {
        return $this->hasMany(\app\models\Comentarios::className(), ['entradas_id' => 'id']);
}
 
// $titulo->autor será una instancia del modelo Autores que se corresponde con el autor del libro
public function getAutor() {
        return $this->hasOne(\app\models\Autores::className(), ['autores_id' => 'id']);
}

ATENCIÓN: gii genera las relaciones siempre con el nombre del modelo, esté en plural o singular. Si, por ejemplo, el modelo Titulos se relaciona con un modelo de Autores (hasOne), gii generará la relación como getAutores. Es conveniente que renombremos el método a getAutor, ya que es más claro referenciar al autor como $titulo→autor, que como $titulo→autores, ya que solamente es uno

Acceso a los modelos relacionados

Para acceder a los datos relacionados con un modelos podemos utilizar el nombre del método $x→getRelacion() o la propiedad equivalente ($x→relacion , sin get…), pero hay una diferencia: la propiedad devuelve una instancia (en el caso de hasOne) o un array(si es hasMany) de objetos relacionados, mientras que si se llama al método directamente, se obtiene siempre un objeto ActiveQuery, lo que permite personalizar el acceso, añadiendo filtros, ordenación, etc…

$titulo->comentarios;  //Devuelve array de modelos Comentario
$coment=$titulos->getComentarios();  //Devuelve un ActiveQuery de los comentarios del título
$comentpend=$titulos->getComentarios()->where('estado="P")->orderBy('fecha'); //comentarios del titulo pendientes de aprobar ordenados por fecha 

Si utilizamos el acceso mediante la propiedad, se hará la consulta SQL y se guardan los datos en la caché, de forma que si volvemos a preguntar por ellos no se ejecuta de nuevo la SQL. Si utilizamos el método, cada vez que la llamemos se ejecutará la consulta SQL. (Consultas dinámicas).

Por otra parte, utilizar el método es muy útil para vistas de tipo maestro-detalle, ya que podemos, por ejemplo, mostrar un Grid con datos relacionados creando un ActiveDataProvider fácilmente a partir de la relación:

public function actionview($id) {
  $model=Titulos::findOne($id);
  // $comentarios se podrá utilizar directamente en un Grid en la vista
  $comentarios=new ActiveDataProvider(['query'=>$model->getComentarios()->with('usuarios')->orderBy('fecha')]);
  return $this->render('view',['model'=>$model,'comentarios'=>$comentarios]);
 
}

También es posible crear relaciones con parámetros, que permitirán acceder de forma cómoda a diversos subconjuntos de los modelos relacionados:

public function getComentariosEstado($estado) {
        return $this->hasMany(\app\models\Comentarios::className(), ['entradas_id' => 'id'])->where('estado=:estado',['estado'=>$estado]);
 
}
 
$comentpendientes=$model->getComentariosEstado('P');
Relaciones many-to-many

Para especificar una relación muchos a muchos entre dos modelos, lo haremos utilizando el método viaTable de la relación, especificando la tabla intermedia que conecta los dos modelos:

// Relación muchos a muchos: devuelve los usuarios que han hecho comentarios al título
public function getUsuarios() {
        return $this->hasMany(\app\models\Usuarios::className(), ['id' => 'usuarios_id'])->viaTable('comentarios', ['titulo_id' => 'id']);
}

Optimización de accesos a la base de datos

Es muy habitual que en una vista mostremos datos de varios modelos relacionados. Por ejemplo, en el caso de la acción view de titulos, mostraremos datos de Titulos, de Comentarios y de Usuarios (los que han hecho los comentarios) Si hacemos algo así:

foreach($model->getComentarios()->all() as $comentario) {
  echo $comentario->texto;
  echo ' '.$comentario->usuario->nombre;
}

se ejecutará una SQL para acceder a los comentarios del titulo, y tantas SQL como comentarios tenga el título, para acceder al model usuario y poder mostrar su nombre. Esto no es óptimo, ya que las bases de datos relacionales me permiten crear consultas combinadas entre varias tablas y obtener los datos en una sola llamada de forma más óptima. Para hacer esto en Yii, podemos utilizar el método with() de ActiveQuery:

foreach($model->getComentarios()->with('usuario')->all() as $comentario) {
  echo $comentario->texto;
  echo ' '.$comentario->usuario->nombre;
}

De esta forma, solamente se ejecuta una consulta SQL, relacionando las tablas comentarios y usuarios. Para ver detalles de esta característica, pincha aquí

Etiquetas

Modificaremos el método “attributeLabels” para escribir correctamente la descripción de cada atributo. Esta descripción aparecerá en los formularios, grids, vistas de detalle, etc…

Descripción por defecto

Es muy conveniente definir el método _ _tostring(), que devolverá uno o varios atributos que describen al objeto.

  public function __tostring(){
    return $this->titulo;
  }

De esta forma generalizamos la forma en que se muestra la descripción de los modelos de una clase. En cualquier parte podremos poner:

  $autor=Autores::findOne(23);
  echo $autor;  //Como $autor es un objeto, se llamará a __tostring() y se mostrará lo que devuelva

Propiedades adicionales

Podemos añadir nuevas propiedades al modelo, que pueden ser resultado de cálculos, combinación de otras propiedades, resultados de consultas a la base de datos, etc… Estas propiedades se pueden utilizar en las vistas (Grid, Detailview…), y en cualquier otra parte que las necesitemos. Para ello, basta con definir un método de la forma getPROPIEDAD:

public function getNombreCompleto(){
  return $this->nombre.' '.$this->apellidos;
}
...
public function getNumComentarios(){
  return count($this->comentarios);
}
 
echo $usuario->nombrecompleto;
echo $usuario->numcomentarios;

Atributos que admiten únicamente una lista de valores

Es habitual que tengamos atributos que admiten únicamente una lista restringida de valores, y que cada uno de ellos tenga un significado. Por ejemplo: el atributo estado de Usuarios, podría admitir los valores: P=Pendiente de validar, A=Activo, B=Bloqueado. Para que en las vistas podamos mostar la descripción de estos valores, o mostrar un desplegable en los formularios con los valores posibles, haremos lo siguiente:

En el modelo definiremos una variable estática que devuelve un array con todos los valores permitidos, de la forma valor⇒descripcion:

class Usuarios {
...
public static $estadoOptions= ['A'=>'Activo','B'=>'Baja'];
...

Definimos también un método getATRIBUTOText() que me devolverá el texto asociado al valor actual del atributo:

public function getEstadoText(){
     return self::$estadoOptions[$this->estado] ?? '';
}

Así, en las vistas podremos utilizar $model→estadoText para mostrar la descripción del estado actual:

$model->estado='A';
echo $model->estadoText;  //Devolverá 'Activo'

Si lo utilizamos en widgets (GridView, DetailView…) podemos añadir el atributo 'estadoText'⇒'Estado' al método attributeLabels(), para que la etiqueta salga correctamente.

Y en los formularios podremos utilizar Usuarios::$estadoOptions como lista de valores de un desplegable, o de un RadioList

lookup: Método para devolver la lista de valores utilizables por modelos relacionados

Cuando los valores de un atributo se corresponden con los de otro modelo (tiene una relación “hasOne” (que procede de una foreign key en la base de datos), es práctico definir un método que devuelva los valores posibles a utilizar en los desplegables del modelo relacionado.

Ejemplo: si el modelo Usuarios tiene una relación hasOne con Departamentos, definiremos el método lookup() en Departamentos. El método map de ArrayHelper devuelve un array de la forma 'valor'⇒'etiqueta' a partir del array de modelos que devuelve find()→all():

public static function lookup($condition=''){
  return ArrayHelper::map(self::find()->where($condition)->all(),'id','descripcion')
}

De esta forma, en las vistas de usuarios, podremos utilizar Departamentos::lookup() para el 'filter' del grid o el dropDown de los formularios