Containable Behavior
Lunes, 23 Noviembre 2009
Hasta ahora, en el desarrollo de aplicaciones web nos hemos encontrado siempre con el problema de la cantidad de datos que genera CakePHP en las consultas a los modelos.
En nuestros desarrollos siempre definimos las relaciones en los modelos y al hacer consultas find tenemos el problema de que un nivel de recursividad demasiado elevado nos genera demasiada información, ralentizando la ejecución debido a los joins generados, y un nivel inferior no alcanza para obtener las relaciones que deseamos utilizar. Además, en ocasiones, queremos obtener únicamente un modelo de entre todas las relaciones que componen una determinada entidad.
Esto nos llevaba a tener que utilizar llamadas a la función unbind (o bind, si es el caso) del modelo al vuelo, previamente a la utilización de un find, o bien a tener que dividir la consulta en dos llamadas anidadas en un bucle, reduciendo el nivel de recursividad en ambas. La primera de las soluciones quizás complica excesivamente el desarrollo y la segunda reduce el rendimiento de la aplicación.
El Containable Behavior de CakePHP nos resuelve este problema, ya que nos permite filtrar y limitar los modelos que generan las consultas a los modelos, mejorando el rendimiento de la aplicación con un sencillo modo de empleo.
Para utilizar este comportamiento lo primero que tendremos que hacer es definirlo en el modelo, ya sea utilizando la variable $actAs en la clase modelo, o bien, definiéndolo al vuelo en el controlador:
- En el modelo:
class Noticia extends AppModel { var $actsAs = array('Containable'); } - En el controlador:
$this->Noticia->Behaviors->attach('Containable');
Una vez definido el comportamiento, ya podemos utilizarlo.
Si queremos utilizar un find sin recursividad, es decir:
$this->Noticia->recursive = -1;
$this->Noticia->find('all');
// o bien: $this->Noticia->find('all', array('recursive' => -1));
podríamos utilizar lo siguiente:
$this->Noticia->find('all', array('contain' => false));
o bien, utilizando la siguiente sintaxis:
$this->Noticia->contain();
$this->Noticia->find('all');
A continuación veremos un ejemplo con múltiples opciones de utilización:
/**
* Usuario->Perfil
* Usuario->Cuenta->ResumenCuenta
* Usuario->Noticia->AdjuntoNoticia->HistorialAdjuntoNoticia->NotasHistorial
* Usuario->Noticia->Tag
*/
$this->Usuario->find('all', array(
'contain'=>array(
'Perfil',
'Cuenta' => array(
'ResumenCuenta'
),
'Noticia' => array(
'AdjuntoNoticia' => array(
'fields' => array('id', 'nombre'),
'HistorialAdjuntoNoticia' => array(
'NotasHistorial' => array(
'fields' => array('id', 'nota')
)
)
),
'Tag' => array(
'conditions' => array('Tag.nombre LIKE' => '%feliz%')
)
)
)
));
Utilizando esta configuración obtendremos todos los campos de los modelos Usuario, Perfil, Cuenta y ResumenCuenta; y también las Noticias, aunque de los Adjuntos y su Historial únicamente los campos id y nombre. Y, por último, aquellos Tags que incluyan en su nombre la cadena “feliz”. Las demás relaciones del modelo, si las tuviera, no serían resultado de la búsqueda si no se especifican explícitamente.
Es importante tener en cuenta que incluir una condición a este nivel (por ejemplo, al nivel de Tag) sólo afecta al modelo en el que se ha introducido. Por lo tanto, los usuarios seguirán mostrándose igual aunque el array de Tags esté vacío. Se podrían incluir condiciones al find por el procedimiento común para filtrar elementos, aunque deberemos tener en cuenta la recursividad del find, en el caso de que ésta se establezca, pues incluir un modelo en la variable contain no implica que se pueda establecer condiciones a ese nivel si la recursividad no llega hasta él. Además, debemos tener en cuenta que si no establecemos una recursividad adecuada, no podremos llegar a la obtención de los modelos establecidos. Es recomendable no especificar ningún nivel de recursividad al utilizar este behavior.
En el caso de consultas paginadas, se utilizará la variable contain dentro de la función paginate al igual que hemos hecho en el find.
Existen algunas opciones de configuración del comportamiento, que pueden consultarse en el cookbook, documento del que se ha obtenido la información para la publicación de este post.

No. 1 — Noviembre 27th, 2009 a 2:39 pm
Lo que no consigo es limitar los campos del propio modelo que estamos usando.
Quizá es que no me he enterao bien
No. 2 — Diciembre 1st, 2009 a 8:26 am
El containable behavior, por lo que he podido probar, no permite introducir el propio modelo dentro de la variable contain, pues intenta encontrar una relación entre el modelo sobre el que se realiza la consulta y los modelos introducidos en esa variable y, como era de esperar, no la encuentra y lanza un error.
La única manera que he encontrado de limitar los campos del propio modelo es utilizar la opción fields del find (o paginate). Por ejemplo, $this->Usuario->find(’all’, array(’contain’=>array(’Perfil’), ‘fields’ => array(’Usuario.id’)));
Aunque en este caso, habría que tener cuidado con los campos que se ponen en este array y los que se incluyen en el contain, pues serán los primeros los que finalmente utilice el find, por lo tanto, si queremos filtrar los campos del propio modelo la variable contain debería usarse para establecer las relaciones que queremos obtener y el fields de la función de consulta para filtrar los campos.
Personalmente prefiero sacrificar los campos obtenidos en el propio modelo por obtener un código más limpio y mejor estructurado utilizando únicamente la estructura contain. Supongo que dependerá del caso particular de la aplicación.
Please continue discussion on the forum: link