6.2. Model Functions

From a PHP view, models are classes extending the AppModel class. The AppModel class is originally defined in the cake/ directory, but should you want to create your own, place it in app/app_model.php. It should contain methods that are shared between two or more models. It itself extends the Model class which is a standard Cake library defined in cake/libs/model.php.

[Important] Important

While this section will treat most of the often-used functions in Cake's Model, it's important to remember to use http://api.cakephp.org for a full reference.

6.2.1. User-Defined Functions

An example of a table-specific method in the model is a pair of methods for hiding/unhiding posts in a blog.

Example 6.2. Example Model Functions

<?php
class Post extends AppModel
{
   var $name = 'Post';

   function hide ($id=null)
   {
      if ($id) 
      {
          $this->id = $id;
          $this->saveField('hidden', '1');
      }
   }

   function unhide ($id=null)
   {
      if ($id) 
      {
          $this->id = $id;
          $this->saveField('hidden', '0');
      }
   }
}
?>


6.2.2. Retrieving Your Data

Below are a few of the standard ways of getting to your data using a model:

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

Returns the specified fields up to $limit records matching $conditions (if any), start listing from page $page (default is page 1). $conditions should look like they would in an SQL statement: $conditions = "race = 'wookie' AND thermal_detonators > 3", for example.

When the $recursive option is set to an integer value greater than 1, the findAll() operation will make an effort to return the models associated to the ones found by the findAll(). If your property has many owners who in turn have many contracts, a recursive findAll() on your Property model will return those associated models.

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

Returns the specified (or all if not specified) fields from the first record that matches $conditions.

When the $recursive option is set to an integer value between 1 and 3, the find() operation will make an effort to return the models associated to the ones found by the find(). The recursive find can go up to three levels deep. If your property has many owners who in turn have many contracts, a recursive find() on your Property model will return up to three levels deep of associated models.

findAllBy<fieldName>( $value);  
string   $value;

These magic functions can be used as a shortcut to search your tables for a row given a certain field, and a certain value. Just tack on the name of the field you wish to search, and CamelCase it. Examples (as used in a Controller) might be:

$this->Post->findByTitle('My First Blog Post');
$this->Author->findByLastName('Rogers');
$this->Property->findAllByState('AZ');
$this->Specimen->findAllByKingdom('Animalia');

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

findNeighbours( $conditions,  
  $field,  
  $value);  
string   $conditions;
array   $field;
string   $value;

Returns an array with the neighboring models (with only the specified fields), specified by $field and $value, filtered by the SQL conditions, $conditions.

This is useful in situations where you want 'Previous' and 'Next' links that walk users through some ordered sequence through your model entries. It only works for numeric and date based fields.

class ImagesController extends AppController
{
    function view($id)
    {
        // Say we want to show the image...

        $this->set('image', $this->Image->find("id = $id");

        // But we also want the previous and next images...

        $this->set('neighbours', $this->Image->findNeighbours(null, 'id', $id);

    }
}

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

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

Returns as a string a single field from the first record matched by $conditions as ordered by $order.

findCount( $conditions);  
string   $conditions;

Returns the number of records that match the given conditions.

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

This function is a shortcut to getting a list of key value pairs - especially handy for creating a html select tag from a list of your models. Use the $conditions, $order, and $limit parameters just as you would for a findAll() request. The $keyPath and $valuePath are where you tell the model 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->set(
    'Roles',
    $this->Role->generateList(null, 'role_name ASC', null, '{n}.Role.id', '{n}.Role.role_name')
);

//This would return something like:
array(
    '1' => 'Account Manager',
    '2' => 'Account Viewer',
    '3' => 'System Manager',
    '4' => 'Site Visitor'
);
read( $fields,  
  $id);  
string   $fields;
string   $id;

Use this function to get the fields and their values from the currently loaded record, or the record specified by $id.

Please note that read() operations will only fetch the first level of associated models regardless of the value of $recursive in the model. To gain additional levels, use find() or findAll().

query( $query);  
string   $query;
execute( $query);  
string   $query;

Custom SQL calls can be made using the model's query() and execute() methods. The difference between the two is that query() is used to make custom SQL queries (the results of which are returned), and execute() is used to make custom SQL commands (which require no return value).

Example 6.3. Custom Sql Calls with query()

<?php
class Post extends AppModel
{
    var $name = 'Post';

    function posterFirstName()
    {
        $ret = $this->query("SELECT first_name FROM posters_table
                                 WHERE poster_id = 1");
        $firstName = $ret[0]['first_name'];
        return $firstName;
    }
}
?>


6.2.3. Complex Find Conditions (using arrays)

Most of the model's finder 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, but if you need 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 discreet, manipulatable parts. This allows Cake 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:

Example 6.4. Basic find conditions array usage example:

$conditions = array("Post.title" => "This is a post");

//Example usage with a model:
$this->Post->find($conditions);

The structure is fairly self-explanatory: it will find any post where the title matches the string "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")

All that was added was '<>' before the expression. Cake can parse out any valid SQL comparison operator, including match expressions using LIKE, BETWEEN, or REGEX, as long as you leave a space between the operator an the expression or value. 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"))

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"))
)

By default, Cake 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"))
    )
)

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 nestable. Let's say you had a hasMany/belongsTo relationship between Posts and Authors, which would result in a LEFT JOIN on the find done on Post. Let's say you wanted to find all the posts that contained a certain keyword 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")
    )
)

6.2.4. Saving Your Data

To save data to your model, you need to supply it with the data you wish to save. The data handed to the save() method should be in the following form:

Array
(
    [ModelName] => Array
        (
            [fieldname1] => 'value'
            [fieldname2] => 'value'
        )
)

In order to get your data posted to the controller in this manner, it's easiest just to use the HTML Helper to do this, because it creates form elements that are named in the way Cake expects. You don't need to use it however: just make sure your form elements have names that look like data[Modelname][fieldname]. Using $html->input('Model/fieldname') is the easiest, however.

Data posted from forms is automatically formatted like this and placed in $this->data inside of your controller, so saving your data from web forms is a snap. An edit function for a property controller might look something like the following:

function edit($id)
{

   //Note: The property model is automatically loaded for us at $this->Property.

   // Check to see if we have form data...
   if (empty($this->data))
   {
        $this->Property->id = $id;
        $this->data = $this->Property->read();//populate the form fields with the current row
   }
   else
   {
      // Here's where we try to save our data. Automagic validation checking
      if ($this->Property->save($this->data['Property']))
      {
         //Flash a message and redirect.
         $this->flash('Your information has been saved.',
                     '/properties/view/'.$this->data['Property']['id'], 2);
      }
      //if some fields are invalid or save fails the form will render
   }
}

Notice how the save operation is placed inside a conditional: when you try to save data to your model, Cake automatically attempts to validate your data using the rules you've provided. To learn more about data validation, see Chapter "Data Validation". If you do not want save() to try to validate your data, use save($data, false).

Other useful save functions:

del( $id,  
  $cascade);  
string   $id;
boolean   $cascade;

Deletes the model specified by $id, or the current id of the model.

If this model is associated to other models, and the 'dependent' key has been set in the association array, this method will also delete those associated models if $cascade is set to true.

Returns true on success.

saveField( $name,  
  $value);  
string   $name;
string   $value;

Used to save a single field value.

getLastInsertId( );  
  ;

Returns the ID of the most recently created record.

6.2.5. Model Callbacks

We've added some model callbacks that allow you to sneak in logic before or after certain model operations. To gain this functionality in your applications, use the parameters provided and override these functions in your Cake models.

beforeFind( $conditions);  
string   $conditions;

The beforeFind() callback is executed just before a find operation begins. Place any pre-find logic in this method. When you override this in your model, return true when you want the find to execute, and false when you want it to abort.

afterFind( $results);  
array   $results;

Use this callback to modify results that have been returned from a find operation, or perform any other post-find logic. The parameter for this function is the returned results from the model's find operation, and the return value is the modified results.

beforeValidate( );  
  ;

Use this callback to modify model data before it is validated. It can also be used to add additional, more complex validation rules, using Model::invalidate(). In this context, model data is accessible via $this->data. This function must also return true, otherwise save() execution will abort.

beforeSave( );  
  ;

Place any pre-save logic in this function. This function executes immediately after model data has been validated (assuming it validates, otherwise the save() call aborts, and this callback will not execute), but before the data is saved. This function should also return true if you want the save operation to continue, and false if you want to abort.

One usage of beforeSave might be to format time data for storage in a specifc database engine:

// Date/time fields created by HTML Helper:
// This code would be seen in a view

$html->dayOptionTag('Event/start');
$html->monthOptionTag('Event/start');
$html->yearOptionTag('Event/start');
$html->hourOptionTag('Event/start');
$html->minuteOptionTag('Event/start');

/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

// Model callback functions used to stitch date
// data together for storage
// This code would be seen in the Event model:

function beforeSave()
{
    $this->data['Event']['start'] = $this->_getDate('Event', 'start');

    return true;
}

function _getDate($model, $field)
{
    return date('Y-m-d H:i:s', mktime(
        intval($this->data[$model][$field . '_hour']),
        intval($this->data[$model][$field . '_min']),
        null,
        intval($this->data[$model][$field . '_month']),
        intval($this->data[$model][$field . '_day']),
        intval($this->data[$model][$field . '_year'])));
}
afterSave( );  
  ;

Place any logic that you want to be executed after every save in this callback method.

beforeDelete( );  
  ;

Place any pre-deletion logic in this function. This function should return true if you want the deletion to continue, and false if you want to abort.

afterDelete( );  
  ;

Place any logic that you want to be executed after every deletion in this callback method.