3.7.3 Recuperando tus Datos

3.7.3.1 find

find($tipo, $parametros)

$tipo es 'all', 'first', 'count', 'neighbors', 'list' o 'threaded'. 'first' es el tipo de búsqueda predeterminado.

$parametros es un array con cualquiera de las siguientes opciones disponibles como claves:

array(
	'conditions' => array('Model.field' => $thisValue), //array de condiciones
	'recursive' => 1, //int
	'fields' => array('Model.field1', 'Model.field2'), //array de nombres de campos
	'order' => 'Model.created', //string o array definiendo el orden
	'group' => array('Model.field'), //campos para GROUP BY
	'limit' => n, //int
	'page' => n //int
)
  1. array(
  2. 'conditions' => array('Model.field' => $thisValue), //array de condiciones
  3. 'recursive' => 1, //int
  4. 'fields' => array('Model.field1', 'Model.field2'), //array de nombres de campos
  5. 'order' => 'Model.created', //string o array definiendo el orden
  6. 'group' => array('Model.field'), //campos para GROUP BY
  7. 'limit' => n, //int
  8. 'page' => n //int
  9. )

Si estás utilizando find('list'), la clave 'fields' en $parametros define la clave, valor y grupo

// la lista generada será indexada por Post.id, con valor de Post.title
$this->Post->find('list', array('fields'=>'Post.title'));
 
// la lista generada será indexada por Post.slug, con valor de Post.title
$this->Post->find('list',
                  array(
                    'fields'=>array('Post.slug',
                                    'Post.title')
                       )
                 );
 
// la lista generada será agrupoada por Post.author_id, y cada grupo indexado por Post.id, con valor de Post.title
$this->Post->find('list',
                  array(
                    'fields'=> array('Post.id',
                                     'Post.title',
                                     'Post.author_id')
                       )
                 );
  1. // la lista generada será indexada por Post.id, con valor de Post.title
  2. $this->Post->find('list', array('fields'=>'Post.title'));
  3. // la lista generada será indexada por Post.slug, con valor de Post.title
  4. $this->Post->find('list',
  5. array(
  6. 'fields'=>array('Post.slug',
  7. 'Post.title')
  8. )
  9. );
  10. // la lista generada será agrupoada por Post.author_id, y cada grupo indexado por Post.id, con valor de Post.title
  11. $this->Post->find('list',
  12. array(
  13. 'fields'=> array('Post.id',
  14. 'Post.title',
  15. 'Post.author_id')
  16. )
  17. );

Si estás utilizando find('neighbors'), la clave 'field' en $parametros define el campo a analizar, y la clave 'value' en el array $parametros define el valor a mirar para determinar el siguiente y el anterior. Notar que las claves 'field' y 'value' no son usadas para find('all') y este es un caso especial para find('neighbors').

// asumiendo que tenermos id's de 1 a 10, veremos  assuming we have id's from 1-10, veremos <em>prev</em> establecido a 1 y <em>next</em> establecido a 3
$this->Post->id = 2;
$one = $this->Post->find('neighbors');
// para obtener los datos vecinos utilizando un campo diferente...
$two = $this->Post->find('neighbors',
                         array(
                           'field'=> 'Post.title',
                           'value'=> $data['Post']['title'])
                        );
  1. // asumiendo que tenermos id's de 1 a 10, veremos assuming we have id's from 1-10, veremos <em>prev</em> establecido a 1 y <em>next</em> establecido a 3
  2. $this->Post->id = 2;
  3. $one = $this->Post->find('neighbors');
  4. // para obtener los datos vecinos utilizando un campo diferente...
  5. $two = $this->Post->find('neighbors',
  6. array(
  7. 'field'=> 'Post.title',
  8. 'value'=> $data['Post']['title'])
  9. );

Para compatibilidad hacia atraś, find también acepta la sintasix previa:

find(string $condiciones, array $campos, string $orden, int $recursivo)

3.7.3.2 findAll

findAll(string $condiciones, array $campos, string $orden, int $limite, int $pagina, int $recursivo)

findAll está obsoleto; en su lugar utiliza find('all').

Devuelve los campos especificados hasta un número de registros dado por $limite que cumplen $condiciones (si hay alguna), comenzando a listar por la página $pagina (por defecto página 1). Si no hay campos que cumplan las restricciones, se devuelve un array vacío.

El parámetro $condiciones debería estár formado tal y como estaría en una sentencia SQL, por ejemplo: $condiciones = "Pastry.type LIKE '%cake%' AND Pastry.created_on > '2007-01-01'". Poner como prefijo el nombre del modelo a las condiciones ('Pastry.type' en vez de símplemente 'type') siempre es una buena práctica, especialmente cuando se están recuperando datos asociados en una consulta.

Establecer el parámetro $recursivo a un entero fuerza a findAll() a recuperar los datos según el comportamiento descrito en la sección Atributos del Modelo que se ve más adelante. No olvides añadir a mano las columnas con clave foránea necesarias al array $campos como se describe en dicha sección.

Los datos de findAll() se devuelven en un array, siguiendo este formato básico:

Array
(
    [0] => Array
        (
            [NombreModelo] => Array
                (
                    [id] => 83
                    [campo1] => valor1
                    [campo2] => valor2
                    [campo3] => valor3
                )

            [NombreModeloAsociado] => Array
                (
                    [id] => 1
                    [campo1] => valor1
                    [campo2] => valor2
                    [campo3] => valor3
                )
        )
    [1] => Array
        (
            [NombreModelo] => Array
                (
                    [id] => 85
                    [campo1] => valor1
                    [campo2] => valor2
                    [campo3] => valor3
                )

            [NombreModeloAsociado] => Array
                (
                    [id] => 2
                    [campo1] => valor1
                    [campo2] => valor2
                    [campo3] => valor3
                )
        )
)

3.7.3.3 findAllBy

findAllBy<nombreCampo>(string $valor)

Estas funciones mágias pueden ser usadas como atajos para buscar en tus tablas por cierto campo. Simplemente añade el nombre del campo (en formato CamelCase) al final del nombre de esas funciones (<nombreCampo>) y proporciona los criterios para ese campo como primer parámetro.

3.7.3.4 findBy

findBy<nombreCampo>(string $valor)

Estas funciones mágicas pueden ser usadas como atajo en la búsqueda en tus tablas por cierto campo. Simplemente añade el nombre del campo (en forma CamelCase) al final de las funciones (<nombreCampo>), y proporciona los criterios para ese campo como primer parámetro.

Ejemplo findAllBy<x> en PHP5 Fragmento SQL Correspondiente
$this->Product->findAllByOrderStatus(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLastName(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUserName(‘psychic’); User.user_name = ‘psychic’

Los usuarios de PHP4 han de utilizar esta función de manera un poco diferente debido a cierto case-insensitivity en PHP4:

Ejemplo findAllBy<x> en PHP4 Fragmento SQL Correspondiente
$this->Product->findAllByOrder_status(‘3’); Product.order_status = 3
$this->Recipe->findAllByType(‘Cookie’); Recipe.type = ‘Cookie’
$this->User->findAllByLast_name(‘Anderson’); User.last_name = ‘Anderson’
$this->Cake->findById(7); Cake.id = 7
$this->User->findByUser_name(‘psychic’); User.user_name = ‘psychic’

El resultado devuelto es un array formateado tal y como sería en find() o findAll().

3.7.3.5 findNeighbours

findNeighbours(string $condiciones, mixed $campo, string $valor)

findNeighbours está obsoleto; en su lugar utiliza find('neighbors').

Este método atajo crea un array conteniendo valores útiles para la generación de enlaces 'Anterior' y 'Siguiente' en una vista.

El método determina qué filas de datos devolver basandose en los valores pasados en los parámetros $campo y $valor. Pueden realizarse refinamientos a mayores con el parámetro $condiciones.

Por ejemplo, si llamas a la función de la siguiente manera:

$condiciones = array('Articulo.estado' => 'publicado');
$campo = array('fecha', 'id');
$valor = '2008-03-24';
$this->Articulo->findNeighbours( $condiciones, $campo, $valor ) );
  1. $condiciones = array('Articulo.estado' => 'publicado');
  2. $campo = array('fecha', 'id');
  3. $valor = '2008-03-24';
  4. $this->Articulo->findNeighbours( $condiciones, $campo, $valor ) );

El array resultante contendrá valores para los campos fecha e id de los artículos que tienen el estado a publicado, y cuyas fechas están justo antes y después de la fecha 2008-03-24.

Array
(
    [prev] =>
             Array ([Articulo] => 
                                Array ([fecha] => 2008-03-20,
                                          [id] => 99 )
    ),
    [next] =>
             Array ( [Articulo] => 
                                Array( [fecha] => 2008-03-27,
                                          [id] => 15 )
    )
);

Observar que la comparación fue realizada en el campo fecha, y que los valores de id no fueron usados para determinar los datos vecinos.

Este método puede también ser llamado con una simple cadena de caracteres como valor $campo. Cuando se utiliza un array, el primer campo listado será el usado para la comparación en la consulta.

class ImagesController extends AppController {
    function view($id) {
        // Digamos que queremos poder mostrar la imagen...
        $this->set('image', $this->Image->findById($id));

        // Pero también queremos enlaces a las imagenes anterior y siguiente...
        $this->set(
            'vecinos', 
            $this->Image->findNeighbours(null, 'id', $id);
        )
    }
}
  1. class ImagesController extends AppController {
  2. function view($id) {
  3. // Digamos que queremos poder mostrar la imagen...
  4. $this->set('image', $this->Image->findById($id));
  5. // Pero también queremos enlaces a las imagenes anterior y siguiente...
  6. $this->set(
  7. 'vecinos',
  8. $this->Image->findNeighbours(null, 'id', $id);
  9. )
  10. }
  11. }

Esto nos da el array completo $image['Image'], junto con $vecinos['prev']['Image']['id'] y $vecinos['next']['Image']['id'] para ser usados en la vista.

3.7.3.6 query

query(string $consulta)

Se pueden realizar llamadas SQL personalizadas usando el método query() del modelo.

Si alguna vez usas consultas SQL personalizadas en tu aplicación, no olvides leer la sección Desinfección de Datos (Sanitization) de CakePHP, la cual ayuda a limpiar datos de usuario de injection y ataques de cross-site scripting.

query() utiliza el nombre de la tabla en la consulta como clave del array de datos devueltos, en vez del nombre del modelo. Por ejemplo:

$this->Fotografia->query("SELECT * FROM fotografias LIMIT 2;");
  1. $this->Fotografia->query("SELECT * FROM fotografias LIMIT 2;");

debería devolver

Array
(
    [0] => Array
        (
            [fotografías] => Array
                (
                    [id] => 1304
                    [user_id] => 759
                )
        )

    [1] => Array
        (
            [fotografías] => Array
                (
                    [id] => 1305
                    [user_id] => 759
                )
        )
)
  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [fotografías] => Array
  6. (
  7. [id] => 1304
  8. [user_id] => 759
  9. )
  10. )
  11. [1] => Array
  12. (
  13. [fotografías] => Array
  14. (
  15. [id] => 1305
  16. [user_id] => 759
  17. )
  18. )
  19. )

Para usar el nombre del modelo como clave del array, y obtener un resultado consistente con el devuelto por los métodos Find, la consulta puede ser reescrita:

$this->Fotografia->query("SELECT * FROM fotografia AS Fotografia LIMIT 2;");
  1. $this->Fotografia->query("SELECT * FROM fotografia AS Fotografia LIMIT 2;");

la cual devuelve

Array
(
    [0] => Array
        (
            [Fotografia] => Array
                (
                    [id] => 1304
                    [user_id] => 759
                )
        )

    [1] => Array
        (
            [Fotografia] => Array
                (
                    [id] => 1305
                    [user_id] => 759
                )
        )
)
  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [Fotografia] => Array
  6. (
  7. [id] => 1304
  8. [user_id] => 759
  9. )
  10. )
  11. [1] => Array
  12. (
  13. [Fotografia] => Array
  14. (
  15. [id] => 1305
  16. [user_id] => 759
  17. )
  18. )
  19. )

3.7.3.7 generateList

generateList(string $condiciones, string $orden, int $limite, string $rutaClave, string $rutaValor)

generateList está obsoleto y reemplazado por find('list'), o find('all') combinado con una llamada a Set::combine().

Esta función es un atajo para obtener una lista de pares clave/valor, especialmente útil para crear una etiqueta select en HTML a partir de una lista de tus modelos. Usa los parámetros $condiciones, $orden y $limite tal y como lo harías con una llamada a findAll().

Si se le han dado valores en el modelo a $primaryKey y $displayField, no necesitas suministrar los dos últimos parámetros, ya que funcionan como $keyPath y $keyValue respectivamente. Adicionalmente, si ni $keyPath ni $displayField han sido suministrados, CakePHP intentará cargar la información usando 'title' o 'name'.

$keyPath y $valuePath especifican dónde encontrar las claves y valores para nuestra lista generada. Por ejemplo, si quieres generar una lista de roles basados en tu modelo Rol, con clave el entero 'ids', la llamada completa sería algo parecido a:

$this->Rol->generateList(
    null, 
    'nombre_rol ASC', 
    null, 
    '{n}.Rol.id', 
    '{n}.Rol.nombre_rol'
);

// Esto devolvería algo como:
array(
    '1' => 'Head Honcho',
    '2' => 'Marketing',
    '3' => 'Department Head',
    '4' => 'Grunt'
);
  1. $this->Rol->generateList(
  2. null,
  3. 'nombre_rol ASC',
  4. null,
  5. '{n}.Rol.id',
  6. '{n}.Rol.nombre_rol'
  7. );
  8. // Esto devolvería algo como:
  9. array(
  10. '1' => 'Head Honcho',
  11. '2' => 'Marketing',
  12. '3' => 'Department Head',
  13. '4' => 'Grunt'
  14. );

A mucha gente le desconcierta la sintaxis '{n}' utilizada por generateList(). No te preocupes, sirve como marcador para cambiar entre DataSources (orígenes de datos) del modelo, tratado más adelante en este capítulo.

3.7.3.8 findCount

findCount(string $condiciones, int $recursivo)

Este método está obsoleto; en su lugar utiliza find('count').

Devuelve el número de registros que cumplen las condiciones dadas en $condiciones. Usa el parámetro $recursivo para hacer que CakePHP traiga más (o menos) niveles de modelos asociados.

3.7.3.9 field

field(string $nombre, string $condiciones, string $orden)

Devuelve el valor de un campo singular, especificado en $name, del primer registro que cumpla $condiciones estando ordenado por $orden.

3.7.3.10 Condiciones Complejas de Búsqueda

La mayoría de las llamadas de búsqueda del modelo involucran pasar conjuntos de condiciones de una u otra manera. La aproximación más simple a ello es utilizar la cláusula WHERE de SQL. Si ves que necesitas más control, puedes utilizar arrays.

Usar arrays permite una lectura más clara y fácil, y también hace muy fácil la construcción de consultas. Esta sintaxis también particiona los elementos de tu consulta (campos, valores, operadores, etc.) en partes discretas y manipulables. Esto permite a CakePHP generar la consulta más eficiente posible, asegurar una sintaxis SQL apropiada, y formatear apropiadamente cada parte individual de la consulta.

En su forma más básica, una consulta basada en array es así:

$condiciones = array("Articulo.title" => "Esto es un artículo");
// Ejemplo de uso con un modelo:
$this->Articulo->find($condiciones);
  1. $condiciones = array("Articulo.title" => "Esto es un artículo");
  2. // Ejemplo de uso con un modelo:
  3. $this->Articulo->find($condiciones);

La estructura aquí es bastante autoexplicativa: buscará cualquier artículo donde el título sea igual a "Esto es un artículo". Notar que podríamos haber utilizado como nombre de campo simplemente 'title', pero cuando se construyen consultas es buena práctica especificar siempre el nombre del modelo (en este caso, Articulo), ya que mejora la claridad del código y ayuda a prevenir colisiones en el futuro, en cuyo caso deberías modificar tu esquema de tablas.

¿Qué hay sobre otros tipos de condiciones? Estas son igualmente simples. Digamos que queremos buscar todos los artículos donde el título no sea 'Esto no es un artículo':

array("Articulo.title <>" => "Esto no es un artículo")
  1. array("Articulo.title <>" => "Esto no es un artículo")

Notar el '<>' que está detrás del nombre del campo. CakePHP puede analizar sintácticamente cualquier operador de comparación en SQL, incluyendo las expresiones usando LIKE, BETWEEN, o REGEX, siempre y cuando dejes un espacio entre el nombre del campo y el operador. La unica excepción aquí es la condición de búsqueda del tipo IN (...). Digamos que querías buscar artículos donde el título estaba dentro de un conjunto dado de valores:

array(
	"Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo")
)
  1. array(
  2. "Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo")
  3. )

Para realizar una búsqueda con condición NOT IN(...) para encontrar artículos cuyo título no está en el conjunto de valores dado:

array(
	"NOT" => array( "Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo") )
)
  1. array(
  2. "NOT" => array( "Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo") )
  3. )

Añadir filtros adicionales a las condiciones es tan simple como añadir pares clave/valor adicionales al array:

array (
	"Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo"),
	"Articulo.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
  1. array (
  2. "Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo"),
  3. "Articulo.created >" => date('Y-m-d', strtotime("-2 weeks"))
  4. )

También puedes crear búsquedas que comparen dos campos en la base de datos:

array("Articulo.created = Articulo.modified")
  1. array("Articulo.created = Articulo.modified")

Este ejemplo de arriba devolverá artículos en los cuales la fecha de creación es igual a la fecha de modificación (p.e. devolverá artículos que nunca han sido modificados).

Por defecto, CakePHP junta múltiples condiciones con AND booleano; es decir, las condiciones de más arriba sólo coincidirán con artículos que han sido creados en las últimas dos semanas (-2 weeks), y posean un título que coincida con alguno de los dados en el conjunto ("Primer artículo",...). No obstante, podemos igualmente buscar artículos que coincidan con cualquiera de las condiciones:

array(
   "or" => array (
      "Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo"),
      "Articulo.created >" => date('Y-m-d', strtotime("-2 weeks"))
   )
)
  1. array(
  2. "or" => array (
  3. "Articulo.title" => array("Primer artículo", "Segundo artículo", "Tercer artículo"),
  4. "Articulo.created >" => date('Y-m-d', strtotime("-2 weeks"))
  5. )
  6. )

Cake acepta todas las operaciones booleanas de SQL válidas, incluyendo AND, OR, NOT, XOR, etc..., y pueden estar en mayúsculas o minúsculas, como prefieras. Estas condiciones son también infinitamente anidables. Digamos que tienes una relación belongsTo entre Articulos y Autores. Digamos que quieres buscar todos los artículos que contienen una cierta palabra (p.e. "magico") o que han sido creados en las últimas dos semanas, pero quieres restringir tu búsqueda a artículos escritos por Pedro:

array (
    "Autor.name" => "Pedro", 
    "or" => array (
        "Articulo.title LIKE" => "%magico%",
        "Articulo.created >" => date('Y-m-d', strtotime("-2 weeks"))
    )
)
  1. array (
  2. "Autor.name" => "Pedro",
  3. "or" => array (
  4. "Articulo.title LIKE" => "%magico%",
  5. "Articulo.created >" => date('Y-m-d', strtotime("-2 weeks"))
  6. )
  7. )

Cake también puede comprobar campos nulos (null). En este ejemplo, la consulta devolverá registros en los que el título del artículo no es nulo:

array (
    "not" => array (
        "Articulo.title" => null,
    )
)
  1. array (
  2. "not" => array (
  3. "Articulo.title" => null,
  4. )
  5. )

Para manejar consultas con BETWEEN, puedes usar lo siguiente:

array('Articulo.id BETWEEN ? AND ?' => array(1,10))
  1. array('Articulo.id BETWEEN ? AND ?' => array(1,10))

Nota: CakePHP entrecomillará los valores numéricos dependiendo del tipo de campo definido en tu base de datos.

Puedes crear condiciones muy complejas anidando múltiples arrays de condiciones:

array(
   'OR' => array(
      array('Compania.name' => 'Emporio Futuro'),
      array('Compania.name' => 'Megatrabajos de Acero')
   ),
   'AND' => array(
      array(
         'OR'=>array(
            array('Compania.status' => 'activo'),
            'NOT'=>array(
               array('Compania.status'=> array('inactivo', 'suspendido'))
            )
         )
     )
   )
);

  1. array(
  2. 'OR' => array(
  3. array('Compania.name' => 'Emporio Futuro'),
  4. array('Compania.name' => 'Megatrabajos de Acero')
  5. ),
  6. 'AND' => array(
  7. array(
  8. 'OR'=>array(
  9. array('Compania.status' => 'activo'),
  10. 'NOT'=>array(
  11. array('Compania.status'=> array('inactivo', 'suspendido'))
  12. )
  13. )
  14. )
  15. )
  16. );

Las cuales producen el siguiente código SQL:

SELECT `Compania`.`id`, `Compania`.`name`, 
`Compania`.`description`, `Compania`.`location`, 
`Compania`.`created`, `Compania`.`status`, `Compania`.`size`

FROM
   `companias` AS `Compania`
WHERE
   ((`Compania`.`name` = 'Emporio Futuro')
   OR
   (`Compania`.`name` = 'Megatrabajos de Acero'))
AND
   ((`Compania`.`status` = 'activo')
   OR (NOT (`Compania`.`status` IN ('inactivo', 'suspendido'))))
  1. SELECT `Compania`.`id`, `Compania`.`name`,
  2. `Compania`.`description`, `Compania`.`location`,
  3. `Compania`.`created`, `Compania`.`status`, `Compania`.`size`
  4. FROM
  5. `companias` AS `Compania`
  6. WHERE
  7. ((`Compania`.`name` = 'Emporio Futuro')
  8. OR
  9. (`Compania`.`name` = 'Megatrabajos de Acero'))
  10. AND
  11. ((`Compania`.`status` = 'activo')
  12. OR (NOT (`Compania`.`status` IN ('inactivo', 'suspendido'))))