Introducción
Una de las herramientas que provee el Framework CakePHP para inflar los modelos con responsabilidad son los handlers, estas son funciones que se ejecutan cuando se dispara un evento. Pero no todo es color de rosa, y la mala utilización de los mismos puede generar más de un inconveniente, sumado a esto la documentación sobre algunos de estos handlers no es del toda clara y completa.
Es por esto que vamos a explicar lo más detalladamente posible una forma posible de manejar los datos en la función afterFind() de los modelos de manera que no tengamos resultados inesperados.
La definición de la función
Revisando la documentación oficial de CakePHP (http://book.cakephp.org/2.0/en/models/callback-methods.html#afterfind) podemos ver que esta función recibe 2 argumentos:
– El primero $results que es un array con el cual vamos a trabajar para modificar los datos antes de pasar los mismos al controlador.
– El segundo argumento es $primary que remitiendonos nuevamente a la documentación «indica si el modelo originó la consulta o no». En principio podemos preguntarnos para qué queremos tener esta información en el handler pero luego de habernos chocado contra algunos resultados inesperados entendemos el por qué de este argumento. Este argumento nos va a decir de alguna manera la forma que va a tener el array $results para poder trabajarlo de forma correcta.
Cuando se dispara la función
Esta función se llama cuando se llama al modelo en cuestión para recuperar datos de la base de datos. En principio podemos suponer que eso se sucede cuando hacemos una llamada de la forma $this->Model->find() desde el controlador o en su defecto $this->find() desde el modelo, pero esta no es la única situación en la cual se va a ejecutar nuestra función y acá comienza nuestro (por ahora) problema.
Una función afterFind() se llama también cuando:
– Hacemos un $this->Model->read(); desde el controlador, o en su defecto $this->read() desde el modelo.
– Cuando se le pide información al modelo por haber hecho un find o un read de un modelo relacionado (si seteamos la recursividad adecuada). Por ejemplo si el modelo Post tiene una asociación $hasMany con el modelo Comment.
<?php class PostsController extends AppController { function index() { $this->Post->recursive = 2; $posts = $this->Post->find('all',array()); } } ?>
Es natural pensar que además de la función afterFind() del modelo Post también se va a ejecutar la función afterFind() del modelo Comment.
El problema
El principal problema que se nos presenta es el saber con qué forma va a llegar el argumento $results y esta es una cosa que a mi parecer le falta una vuelta de rosca al cakePHP. La forma de $results va a depende de el origen de la llamada del afterFind().
Esto puede variar entre estas variaciones:
– Cuando $primary es true:
array( '0' => array( 'Model' => array( 'id' => 1 ) ) ) // Cuando $primary es false nos chocamos con un formato de $results para cada asociación hasOne array( 'id' => 1 ) HABTM array( '0' => array( 'id' => 1 ) ) hasOne, hasMany, belongsTo array( '0' => array( 'Model' => array( 'id' => 1 ) ) ) HABTM, hasMany array( '0' => array( 'Model' => array( '0' => array( 'id' => 1 ) ) ) )
Esto produciría una cantidad de ifs inmanejables dentro del afterFind() del modelo.
La solución
Crear un set de funciones: afterFind() llama a _afterFind() alojadas ambas en AppModel y que esta función chequee el formato de $results y llame a la función doAfterFind() definida en el Modelo y envíando al mismo los datos con un mismo formato definido:
<?php class AppModel extends Model { public function afterFind($data, $primary = false) { return $this->_afterFind($data, $primary); } function _afterFind($data, $primary) { if ( $primary ) { foreach ( $data as $key => $val ) { if ( isset($val[$this->alias]) ) { $data[$key][$this->alias] = $this->doAfterFind( $data[$key][$this->alias] ); } } } else { if ( isset($data[$this->primaryKey]) ) { $data = $this->doAfterFind( $data ); } else { foreach ( $data as $key => $val ) { if ( isset($val[$this->alias]) ) { if ( isset($val[$this->alias][$this->primaryKey]) ) { $data[$key][$this->alias] = $this->doAfterFind( $data[$key][$this->alias] ); } else { foreach ( $data[$key][$this->alias] as $key2=> $val2 ) { $data[$key][$this->alias][$key2] = $this->doAfterFind( $data[$key][$this->alias][$key2] ); } } } } } } return $data; } } ?> <?php class Servicio extends AppModel { function doAfterFind($data) { if (isset($data['created']) && !empty($data['created'])) { $created = date_create($data['created']); $data['created'] = date_format($created, 'd-m-Y H:i'); } if (isset($data['modified']) && !empty($data['modified'])) { $modified = date_create($data['modified']); $data['modified'] = date_format($modified, 'd-m-Y H:i'); } if (isset($data['fecha_inicio']) && !empty($data['fecha_inicio'])) { $fecha_inicio = date_create($data['fecha_inicio']); $data['fecha_inicio'] = date_format($fecha_inicio, 'd-m-Y H:i'); } return $data; } } ?>