Herramientas de usuario

Herramientas del sitio


cursos:yii:clases:activerecord:relaciones

CActiveRecord: relaciones

Podemos relacionar unos modelos con otros, de forma similar a como se relacionan las tablas correspondientes en la base de datos. Esto permitirá acceder a información relacionada con un modelo de forma muy sencilla y eficiente, ahorrando el trabajo de escribir las sentencias SQL correspondientes.

Declaración de las relaciones

Hay cuatro tipo de relaciones:

  1. BELONGS_TO: si la relación entre la tabla A y B es uno-a-muchos, entonces B pertenece a A (ej.: Post pertenece a User);
  1. HAS_MANY: si la relación entre la tabla A y B es uno-a-muchos, entonces A tiene muchos B (ej.: User tiene muchos Post);
  1. HAS_ONE: caso especial de HAS_MANY donde A tiene a lo sumo un B (ej.: User tiene a lo sumo un Profile);
  1. MANY_MANY: corresponde a la relación muchos-a-muchos en la base de datos Una tabla intermedia es necesaria para este tipo de relación.

Para declarar relaciones hay que sobreescribir el método relations() de CActiveRecord, que ha de devolver un array de configuraciones de relaciones, en el que cada elemento es de la forma:

'nombrerelacion'=>array('TipoRelación', 'ClassName', 'ForeignKey', ...opciones adicionales)
  • nombrerelacion es el nombre que se le da a la relación. Es conveniente utilizar nombres adecuados, en plural para las relaciones HAS_MANY y MANY_MANY, y en singular para las BELONGS_TO y HAS_ONE. Ejemplo: Entrada→comentarios, Entrada→usuario, Entrada→categorias
  • tipoRelación puede ser una de las constantes: self::BELONGS_TO, self::HAS_ONE, self::HAS_MANY y self::MANY_MANY;
  • ClassName es el nombre de la clase relacionada con ésta
  • ForeignKey especifica la(s) clave(s) externas que definen en la relación.
  • Opciones adicionales permiten aplicar filtros,scopes y otra configuración a la relación

Ejemplo:

/**
 * @return array relational rules.
 */
public function relations()
{
	return array(
		'comentarios' => array(self::HAS_MANY, 'Comentarios', 'entradas_id'),
		'usuario' => array(self::BELONGS_TO, 'Usuarios', 'usuarios_id'),
		'categoria' => array(self::BELONGS_TO, 'Categorias', 'categorias_id'),
		'valoraciones' => array(self::MANY_MANY, 'Etiquetas', 'ent_etiq(entradas_id,etiq_id)'),
	);
  //ent_etiq es la tabla que relaciona entradas y etiquetas (n-m)
}

Ejecutar las consultas relacionales

La manera más sencilla es acceder a una propiedad del modelo con el nombre de la relación. Si es la primera vez que se accede, se ejecutará la SQL correspondiente, que devuelve el modelo/modelos relacionados , y se guardará internamente por si se vuelve a solicitar . Esto se conoce como lazy loading, es decir, la consulta relacional es ejecutada sólo cuando los objetos relacionados son accedidos por primera vez. Ejemplo:

// recupera la entrada de id=10
$entrada=Entradas::model()->findByPk(10);
 
// recuperar el autor del post: una consulta relacional se ejecutará aquí
$usuario=$Entradas->usuario;
 
// Ya no se vuelve a ejecutar la SQL, porque se ha cargado en la instrucción anterior
echo $entrada->usuario->nombre;
 
//Accedemos a los comentarios
foreach($entrada->comentarios as $comentario) 
  echo $comentario->text;

El acceso con lazy loading es muy cómodo de usar, pero no es eficiente en algunos escenarios. Por ejemplo, si queremos acceder a la información del autor para N entradas, con este método se ejecutarían N SQL's (una para cada entrada para cargar su autor). En este caso, debemos utilizar el método eager loading, que consiste en forzar a que se carguen inicialmente ya los datos del autor de cada entrada. Para ellom utilizaremos el método with() junto con uno de los métodos find() o findAll() del modelo. Por ejemplo,

$entradas=Entradas::model()->with('usuario')->findAll();

Al hacerlo así, se ejecuta una única consulta SQL que accede a entradas y a autores y carga todos los datos.

Podemos especificar multiples nombres de relación en el método with(). Por ejemplo, el siguiente código cargará las entradas juntos con sus autores y sus categorías:

$entradas=Entradas::model()->with('usuario','categorias')->findAll();

También podemos utilizar este tipo de acceso en los CActiveDataprovider (utilizados en las acciones admin e index). Para ello, definiremos la propiedad criteria→with :

$dataprovider=new CActiveDataprovider('Entradas',array(
  'criteria'=>array(
   'with'=>array('usuario','categoria')
  )
));

Opciones adicionales

Permiten ajustar la forma en que se cargarán los datos, aplicando filtros, ordenación o scopes

class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'entradas'=>array(self::HAS_MANY, 'Entradas', 'usuarios_id'
                            'order'=>'??.fecha_alta DESC',
                            'with'=>'categoria'),
            'perfil'=>array(self::HAS_ONE, 'Perfil', 'usuarios_id'),
        );
    }
}

Ahora si accedemos a $usuario→entradas, obtendremos las entradas del usuario ordenadas de acuerdo a su hora de creación en orden descendiente. Cada instancia Entradas también tiene cargadas su categoría.

El ?? se pone cuando puede haber ambigüedad en la condición , al coincidir el nombre de la columna en otras tablas que puedan aparecer en la SQL. Yii sustituirá el ?? por el alias que corresponda.