3.7.3 Retrieving Your Data

3.7.3.1 find

find($type, $params)

$type is either 'all', 'first', 'count', 'neighbors' or 'threaded'. 'first' is the default find type.

$params is an array with any of the following available options as keys:

array(
	'conditions' => array('Model.field' => $thisValue), //array of conditions
	'recursive' => 1, //int
	'fields' => array('Model.field1', 'Model.field2'), //array of field names
	'order' => 'Model.created', //string or array defining order
	'group' => array('Model.field'), //fields to GROUP BY
	'limit' => n, //int
	'page' => n //int
)
  1. array(
  2. 'conditions' => array('Model.field' => $thisValue), //array of conditions
  3. 'recursive' => 1, //int
  4. 'fields' => array('Model.field1', 'Model.field2'), //array of field names
  5. 'order' => 'Model.created', //string or array defining order
  6. 'group' => array('Model.field'), //fields to GROUP BY
  7. 'limit' => n, //int
  8. 'page' => n //int
  9. )

If you are using find('list'), the 'fields' key in $params defines the key, value and group

// generated list will be indexed by Post.id, with value of Post.title
$this->Post->find('list', array('fields'=>'Post.title'));
 
// generated list will be indexed by Post.slug, with value of Post.title
$this->Post->find('list', array('fields'=>array('Post.slug', 'Post.title')));
 
// generated list will be grouped by Post.author_id, and each group indexed by Post.id, with value of Post.title
$this->Post->find('list', array('fields'=>array('Post.id', 'Post.title', 'Post.author_id')));
  1. // generated list will be indexed by Post.id, with value of Post.title
  2. $this->Post->find('list', array('fields'=>'Post.title'));
  3. // generated list will be indexed by Post.slug, with value of Post.title
  4. $this->Post->find('list', array('fields'=>array('Post.slug', 'Post.title')));
  5. // generated list will be grouped by Post.author_id, and each group indexed by Post.id, with value of Post.title
  6. $this->Post->find('list', array('fields'=>array('Post.id', 'Post.title', 'Post.author_id')));

If you are using find('neighbors'), a 'field' key in $params defines the field to analyze, and a 'value' key in the $params array defines the value to look at to determine the next and previous. Note that the 'field' and 'value' keys are not used for find('all') and this is a special case for find('neighbors').

// assuming we have id's from 1-10, we'll see prev set to 1 and next set to 3
$this->Post->id = 2;
$one = $this->Post->find('neighbors');
// To get the neighboring data using a different field..
$two = $this->Post->find('neighbors', array('field'=>'Post.title', 'value'=>$data['Post']['title']));
  1. // assuming we have id's from 1-10, we'll see prev set to 1 and next set to 3
  2. $this->Post->id = 2;
  3. $one = $this->Post->find('neighbors');
  4. // To get the neighboring data using a different field..
  5. $two = $this->Post->find('neighbors', array('field'=>'Post.title', 'value'=>$data['Post']['title']));

For backwards compatibility, find also accepts the previous syntax:

find(string $conditions, array $fields, string $order, int $recursive)

3.7.3.2 findAll

findAll(string $conditions, array $fields, string $order, int $limit, int $page, int $recursive)

findAll has been deprecated, use find('all') instead.

Returns the specified fields up to $limit records matching $conditions (if any), start listing from page $page (default is page 1). If there are no matching fields, an empty array is returned.

The $conditions should be formed just as they would in an SQL statement: $conditions = "Pastry.type LIKE '%cake%' AND Pastry.created_on > '2007-01-01'", for example. Prefixing conditions with the model's name ('Pastry.type' rather than just 'type') is always a good practice, especially when associated data is being fetched in a query.

Setting the $recursive parameter to an integer forces findAll() to fetch data according to the behavior described in the Model Attributes $recursive section outlined earlier. Do not forget to manually add the required foreign key columns to the $fields array as described there.

Data from findAll() is returned in an array, following this basic format:

Array
(
    [0] => Array
        (
            [ModelName] => Array
                (
                    [id] => 83
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

            [AssociatedModelName] => Array
                (
                    [id] => 1
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )
        )
    [1] => Array
        (
            [ModelName] => Array
                (
                    [id] => 85
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

            [AssociatedModelName] => Array
                (
                    [id] => 2
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )
        )
)

3.7.3.3 findAllBy

findAllBy<fieldName>(string $value)

These magic functions can be used as a shortcut to search your tables by a certain field. Just add the name of the field (in CamelCase format) to the end of these functions, and supply the criteria for that field as the first parameter.

3.7.3.4 findBy

findBy<fieldName>(string $value)

These magic functions can be used as a shortcut to search your tables by a certain field. Just add the name of the field (in CamelCase format) to the end of these functions, and supply the criteria for that field as the first parameter.

PHP5 findAllBy<x> Example Corresponding SQL Fragment
$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’

PHP4 users have to use this function a little differently due to some case-insensitivity in PHP4:

PHP4 findAllBy<x> Example Corresponding SQL Fragment
$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’

The returned result is an array formatted just as it would be from find() or findAll().

3.7.3.5 findNeighbours

findNeighbours(string $conditions, mixed $field, string $value)

findNeighbours has been deprecated, use find('neighbors') instead.

This shortcut method creates an array containing values helpful in generating 'Previous' and 'Next' links in a view.

The method determines which data rows to return based on the values submitted in the $field and $value parameters. Further refinement can be done with the $conditions parameter.

For example, if you call the function like this:

$conditions = array('Article.status' => 'published');
$field = array('date', 'id');
$value = '2008-03-24';
$this->Article->findNeighbours( $conditions, $field, $value ) );
  1. $conditions = array('Article.status' => 'published');
  2. $field = array('date', 'id');
  3. $value = '2008-03-24';
  4. $this->Article->findNeighbours( $conditions, $field, $value ) );

The resulting array will contain values for the 'date' and 'id' fields from the articles who have a status of "published", and whose dates are just before and after the date '2008-03-24'.

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

Note that the comparison was made on date field, and that the id values were not used to determine neighboring data.

This method can also be called with the $field value being a single string. When an array is used, the first field listed will be the field used in the comparison query.

class ImagesController extends AppController {
    function view($id) {
        // Say we want to be able to show the image...
        $this->set('image', $this->Image->findById($id);

        // But we also want links to the previous and next images...
        $this->set(
            'neighbors', 
            $this->Image->findNeighbours(null, 'id', $id);
        )
    }
}
  1. class ImagesController extends AppController {
  2. function view($id) {
  3. // Say we want to be able to show the image...
  4. $this->set('image', $this->Image->findById($id);
  5. // But we also want links to the previous and next images...
  6. $this->set(
  7. 'neighbors',
  8. $this->Image->findNeighbours(null, 'id', $id);
  9. )
  10. }
  11. }

This gives us the full $image['Image'] array, along with $neighbors['prev']['Image']['id'] and $neighbors['next']['Image']['id'] for use in the view.

3.7.3.6 query

query(string $query)

Custom SQL calls can be made using the model's query() method.

If you’re ever using custom SQL queries in your application, be sure to check out CakePHP’s Sanitize library, which aids in cleaning up user-provided data from injection and cross-site scripting attacks.

3.7.3.7 generateList

generateList(string $conditions, string $order, int $limit, string $keyPath, string $valuePath)

generateList is deprecated and replaced by usage of find('list'), or find('all') combined with a call to Set::combine().

This function is a shortcut to getting a list of key/value pairs - especially handy for creating an HTML select tag from a list of your models. Use the $conditions, $order, and $limit parameters just as you would for a findAll() request.

If $primaryKey and $displayField have been set in the model, you don’t need to supply the last two parameters, as they act as $keyPath and $keyValue, respectively. Additionally, if neither $keyPath nor $displayField have been supplied, CakePHP will try to load the information using ‘title’ or ‘name’.

The $keyPath and $valuePath specify where to find the keys and values for your generated list. For example, if you wanted to generate a list of roles based on your Role model, keyed by their integer ids, the full call might look something like:

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

//This would return something like:
array(
    '1' => 'Head Honcho',
    '2' => 'Marketing',
    '3' => 'Department Head',
    '4' => 'Grunt'
);
  1. $this->Role->generateList(
  2. null,
  3. 'role_name ASC',
  4. null,
  5. '{n}.Role.id',
  6. '{n}.Role.role_name'
  7. );
  8. //This would return something like:
  9. array(
  10. '1' => 'Head Honcho',
  11. '2' => 'Marketing',
  12. '3' => 'Department Head',
  13. '4' => 'Grunt'
  14. );

Many people are a little bewildered by the ‘{n}’ syntax used by generateList(). Fret not, for it serves as a place holder for switching between model DataSources, covered later on in this chapter.

3.7.3.8 findCount

findCount(string $conditions, int $recursive)

This method has been deprecated, use find('count').

Returns the number of records that match the given conditions. Use the $recursive parameter to have CakePHP fetch more (or fewer) levels of associated models.

3.7.3.9 field

field(string $name, string $conditions, string $order)

Returns the value of a single field, specified as $name, from the first record matched by $conditions as ordered by $order.

3.7.3.10 Complex Find Conditions

Most of the model's find calls involve passing sets of conditions in one way or another. The simplest approach to this is to use a WHERE clause snippet of SQL. If you find yourself needing more control, you can use arrays.

Using arrays is clearer and easier to read, and also makes it very easy to build queries. This syntax also breaks out the elements of your query (fields, values, operators, etc.) into discrete, manipulatable parts. This allows CakePHP to generate the most efficient query possible, ensure proper SQL syntax, and properly escape each individual part of the query.

At it's most basic, an array-based query looks like this:

$conditions = array("Post.title" => "This is a post");
//Example usage with a model:
$this->Post->find($conditions);
  1. $conditions = array("Post.title" => "This is a post");
  2. //Example usage with a model:
  3. $this->Post->find($conditions);

The structure here is fairly self-explanatory: it will find any post where the title equals "This is a post". Note that we could have used just "title" as the field name, but when building queries, it is good practice to always specify the model name, as it improves the clarity of the code, and helps prevent collisions in the future, should you choose to change your schema.

What about other types of matches? These are equally simple. Let's say we wanted to find all the posts where the title is not "This is a post":

array("Post.title <>" => "This is a post")
  1. array("Post.title <>" => "This is a post")

Notice the '<>' that follows the field name. CakePHP can parse out any valid SQL comparison operator, including match expressions using LIKE, BETWEEN, or REGEX, as long as you leave a space between field name and the operator. The one exception here is IN (...)-style matches. Let's say you wanted to find posts where the title was in a given set of values:

array(
	"Post.title" => array("First post", "Second post", "Third post")
)
  1. array(
  2. "Post.title" => array("First post", "Second post", "Third post")
  3. )

To do a NOT IN(...) match to find posts where the title is not in the given set of values:

array(
	"NOT" => array( "Post.title" => array("First post", "Second post", "Third post") )
)
  1. array(
  2. "NOT" => array( "Post.title" => array("First post", "Second post", "Third post") )
  3. )

Adding additional filters to the conditions is as simple as adding additional key/value pairs to the array:

array (
	"Post.title" => array("First post", "Second post", "Third post"),
	"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
  1. array (
  2. "Post.title" => array("First post", "Second post", "Third post"),
  3. "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
  4. )

You can also create finds that compare two fields in the database

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

This above example will return posts where the created date is equal to the modified date (ie it will return posts that have never been modified).

By default, CakePHP joins multiple conditions with boolean AND; which means, the snippet above would only match posts that have been created in the past two weeks, and have a title that matches one in the given set. However, we could just as easily find posts that match either condition:

array( "or" => array (
	"Post.title" => array("First post", "Second post", "Third post"),
	"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
    )
)
  1. array( "or" => array (
  2. "Post.title" => array("First post", "Second post", "Third post"),
  3. "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
  4. )
  5. )

Cake accepts all valid SQL boolean operations, including AND, OR, NOT, XOR, etc., and they can be upper or lower case, whichever you prefer. These conditions are also infinitely nest-able. Let's say you had a belongsTo relationship between Posts and Authors. Let's say you wanted to find all the posts that contained a certain keyword (“magic”) or were created in the past two weeks, but you want to restrict your search to posts written by Bob:

array (
	"Author.name" => "Bob", 
	"or" => array (
		"Post.title LIKE" => "%magic%",
		"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
	)
)
  1. array (
  2. "Author.name" => "Bob",
  3. "or" => array (
  4. "Post.title LIKE" => "%magic%",
  5. "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
  6. )
  7. )

Cake can also check for null fields. In this example, the query will return records where the post title is not null:

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

To handle BETWEEN queries, you can use the following:

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

Note: CakePHP will quote the numeric values depending on the field type in your DB.

You can create very complex conditions, by nesting multiple condition arrays:

array(

   'OR' => array(
      array('Company.name' => 'Future Holdings'),
      array('Company.name' => 'Steel Mega Works')
   ),

   'AND' => array(
      array(

         'OR'=>array(
            array('Company.status' => 'active'),     

            'NOT'=>array(
               array('Company.status'=> array('inactive', 'suspended'))
            )
         )
     )
   )
);

  1. array(
  2. 'OR' => array(
  3. array('Company.name' => 'Future Holdings'),
  4. array('Company.name' => 'Steel Mega Works')
  5. ),
  6. 'AND' => array(
  7. array(
  8. 'OR'=>array(
  9. array('Company.status' => 'active'),
  10. 'NOT'=>array(
  11. array('Company.status'=> array('inactive', 'suspended'))
  12. )
  13. )
  14. )
  15. )
  16. );
Which produces the following SQL:

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

FROM
   `companies` AS `Company`
WHERE
   ((`Company`.`name` = 'Future Holdings')
   OR
   (`Company`.`name` = 'Steel Mega Works'))
AND
   ((`Company`.`status` = 'active')
   OR (NOT (`Company`.`status` IN ('inactive', 'suspended'))))

  1. SELECT `Company`.`id`, `Company`.`name`,
  2. `Company`.`description`, `Company`.`location`,
  3. `Company`.`created`, `Company`.`status`, `Company`.`size`
  4. FROM
  5. `companies` AS `Company`
  6. WHERE
  7. ((`Company`.`name` = 'Future Holdings')
  8. OR
  9. (`Company`.`name` = 'Steel Mega Works'))
  10. AND
  11. ((`Company`.`status` = 'active')
  12. OR (NOT (`Company`.`status` IN ('inactive', 'suspended'))))