martes, 19 de abril de 2011

Custom Find DRY

Una de las cosas que más me gusta de los custom find en CakePHP es la flexibilidad que pueden proporcionar a partir de una sintaxis única y sencilla.

Recientemente me he dado cuenta de cómo usarlos para evitar duplicaciones innecesarias de código.

Como ya sabrás, un método para custom find tiene esta signatura básica:

function _findCustom($state, $query, $results = array())
Este método se llamaría públicamente así

$model->find('custom' [, $options]);

La llamada $model->find('custom') hace, a grandes rasgos, lo siguiente:

  1. Normaliza las opciones que se han pasado para crear un array, que contiene los datos necesarios para generar la query que se hará a la base de datos.
  2. Llama a _findCustom para incluir las modificaciones que éste método haga en la query.
  3. Convierte la query en SQL y obtiene el resultado de la base de datos
  4. Llama una segunda vez a _findCustom para procesar el array de resultados.

Lo interesante es que nada nos impide llamar desde otros métodos del modelo a _findCustom pasándole el parámetro $state (que puede ser 'before' o 'after', según queremos obtener el resultado de la modificación de $query o de $results, respectivamente.

¿Para qué sirve? Supongamos que tenemos un _findCustom más o menos complejo en el que especificamos varios joins, conditions, etc, para un cierto tipo de búsqueda. Supongamos también que necesitamos hacer un cierto número de variantes de esa búsqueda, cambiando tan sólo algunas condiciones.

Pues bien, en ese caso podríamos llamar internamente desde nuestro segundo método _find a _findCustom con los parámetros extra para que genere el query (o bien, obtener el query y luego modificarlo). Por ejemplo:

function _findVariant($state, $query, $array()) {
    if ($state === 'before') {
         $extraQuery = array('conditions' => array('field' => 'value'));
         $query = Set::merge($query, $extraQuery);
         $query = $this->_findCustom('before', $query);
    }
    return $results;
}

martes, 11 de enero de 2011

Custom find en Behaviors

Trabajando en un Behavior me surgió la pregunta de si es posible definir Custom Finds en un Behavior para que sean usados por el modelo.

El caso es que encontré este código de Nick Baker, en el cual se encuentra una técnica para lograrlo. Una vez vista es obvia (pero había que verla, claro). Copio el código de Nick para explicarlo. En este caso, define una búsqueda que será find('range'), para lo cual hay que crear un método _findRange() en el Behavior.

La técnica tiene dos pasos:

El primero es mapear el método del behavior con un método para el modelo, lo cual se hace con la siguiente línea en las propiedades del Behavior:


var $mapMethods = array('/^_findRange$/' => '_findRange');

En el método setup hay que añadir la una clave al array de _findMethods del modelo que se pasa:

$Model->_findMethods['range'] = true;

Finalmente, la signatura del método es un poco distinta, pues hay que considerar dos parámetros extra

public function _findSearch(&$model, $method, $state, $query, $results = array())

Es decir, CakePHP pasa automáticamente los parámetros $model y $method, antes de los parámetros habituales del find.

A partir de ahora podremos usar el método model->find('range') en el modelo al que hayamos asociado el Behavior que lo contiene.