Table of Contents : El manual

Modelos

 

Introducción

Los Modelos son usados en aplicaciones CakePHP para acceder a los datos. Los modelos comunmente representan tablas de la base de datos, aunque pueden ser también usados para acceder a archivos, regstros LDAP, eventos ical, o filas de un archivo CSV.

Los modelos pueden estar asociados a otros modelos para así facilitar el acceso a los datos de la aplicación. Si nos encontramos con que cada vez que iteramos sobre los datos de diferentes recetas desearíamos también obtener los datos relacionados (como el Autor de la receta), entonces las asociaciones entre modelos son ideales.

En CakePHP 1.2 se incorporaron los conceptos de DataSources (fuentes de datos) y Behaviors (comportamientos). Los DataSources forman una fuente fundamental (y unificada) para los modelos de CakePHP. Esto permite una mayor interacción entre modelos, incluyendo asociaciones. Los Behaviors proveen una forma de incorporar funcionalidad a nuestros modelos. Agregando behaviors a los modelos podemos acceder a formas comunes de manipulación de datos, como árboles, manejar cargas (upload), y más.

Un modelo completo en CakePHP puede parecer muy sencillo:

<?php

class Ingredient extends AppModel {
    var $name = 'Ingredient';
}

?>
  1. <?php
  2. class Ingredient extends AppModel {
  3. var $name = 'Ingredient';
  4. }
  5. ?>

Nótese que los modelos en CakePHP heredan de una clase especial, AppModel: la clase madre de todo los modelos en nuestra aplicación. Como otras clases similares AppController y AppHelper, este es un lugar perfecto para escribir lógica compartida entre los modelos. El AppModel debe ser creado en /app/app_model.php, y debe a su vez descender de la clase Model.

Con unas cuantas líneas de código, obtenemos operaciones CRUD (conocidas como alta, baja y modificación) funcionando. La mayor parte de la implementación de esta funcionalidad proviene de la clase Model, de la cual AppModel desciende. Una vez que tenemos un modelo definido, podemos utilizarlo en nuestros controladores. Este es un ejemplo de como usar un modelo ($this->Ingredient) para iterar y manejar una lista archivos ingredientes.

<?php

class IngredientsController extends AppController {
    function index() {
        // Enviar la lista de ingredientes a la vista:
        $this->set(
            'ingredients',
            $this->Ingredient->findAll()
        );
    }
}

?>
  1. <?php
  2. class IngredientsController extends AppController {
  3. function index() {
  4. // Enviar la lista de ingredientes a la vista:
  5. $this->set(
  6. 'ingredients',
  7. $this->Ingredient->findAll()
  8. );
  9. }
  10. }
  11. ?>

Utilizar modelos para leer y modificar datos puede ahorrarnos varios dolores de cabeza. Los modelos en CakePHP ofrecen una forma centralizada y estándar de manipular nuestras fuentes de almacenamiento, al mismo tiempo que nos ofrecen un alto nivel de seguridad para nuestra aplicación.

Las clases que representan a los modelos pueden expandirse para incluir un importante número de atributos y métodos para así extender aún más su funcionalidad, lo cual se explicará más adelante.

Automagic Model Fields

CakePHP can do some work for you if you name a few fields in your database tables accordingly.

created & modified (or updated)

These two fields are automatically managed by CakePHP model save() calls. A newly created row’s created field will be automatically populated, and its modified field is updated each time changes are made. Note that a field named updated will have the same behavior as the modified field.

Both these special fields need to be datetime fields, with the default value set to NULL.

Primary keys used as UUIDs

If the primary key (‘id’ or overridden by setting $primaryKey) for a model is defined as char(36), CakePHP will automatically create a UUID to use for that field on creation.

This feature is especially helpful for applications that span multiple databases.

Model Attributes

For a complete list of model attributes and their descriptions visit the CakePHP API. Check out http://api.cakephp.org/1.2/class_model.html.

$useDbConfig

The name of the database configuration this model uses. Configurations are specified in /app/config/database.php as class variables. Defaults to 'default'.

$useTable

The name of the database table this model uses. The default table used is the lower-cased, plural version of the model’s classname. Set this attribute to the name of an alternate table, or set it to false if you wish the model to have no database table at all.

$primaryKey

If the model is tied to a database table, this attribute holds the name of the column of the tables primary key. Defaults to 'id'.

$displayField

This attribute is used to define the field the model will use for display in scaffolding elements. When associated data is presented (i.e., which Ingredient you’d like to associate to a Recipe) scaffolding will use the row’s ID, 'name', or 'title' fields unless $displayField has been specified.

For example, if you want the 'username' field of the model to show rather than the default, include the following line of code in your model:

	var $displayField = 'username'; 
  1. var $displayField = 'username';

$recursive

An integer specifying the number of levels you wish CakePHP to fetch associated model data in find() and findAll() operations.

Imagine your application features Groups which belong to a domain and have many Users which in turn have many Articles. You can set $recursive to different values based on the amount of data you want back from a $this->Group->find() call:

  • -1 - Cake fetches Group data only, no joins.
  • 0 - Cake fetches Group data and its domain
  • 1 - Cake fetches a Group, its domain and its associated Users
  • 2 - Cake fetches a Group, its domain, its associated Users, and the Users' associated Articles

Set it no higher than you need–having CakePHP fetch data you aren’t going to use unnecessarily slows your app.

$order

The default ordering of data for any find operation. Possible values include:

$order = "field"
$order = "Model.field";
$order = "Model.field asc";
$order = "Model.field ASC";
$order = "Model.field DESC";
$order = array("Model.field" => "asc", "Model.field2" => "DESC");
  1. $order = "field"
  2. $order = "Model.field";
  3. $order = "Model.field asc";
  4. $order = "Model.field ASC";
  5. $order = "Model.field DESC";
  6. $order = array("Model.field" => "asc", "Model.field2" => "DESC");

Value of the primary key ID of the record that this model is currently pointing to. If you are saving model data inside of a loop, set $id to null ($this->ModelName->id = null) in order to ready the model class for the next set of data.

$data

The container for the model’s fetched data. While data returned from a model class is normally used as returned from a find() call, you may need to access information stored in $data inside of model callbacks.

$_schema

Contains metadata describing the model’s database table fields. Each field is described by:

  • name
  • type (integer, string, datetime, etc.)
  • null
  • default value
  • length

$validate

This attribute holds rules that allow the model to make data validation decisions before saving. Keys named after fields hold regex values allowing the model to try to make matches.

For more information on validation, see the Data Validation chapter later on in this manual.

$name

The name of this model. CakePHP model names are CamelCased and singular. CakePHP users on PHP4 should always declare $name in order to avoid the quirks involved with PHP4 class-naming.

$cacheQueries

If set to true, data fetched by the model during a single request is cached. This caching is in-memory only, and only lasts for the duration of the request. Any duplicate requests for the same data is handled by the cache.

$belongsTo, $hasOne, $hasMany, $hasAndBelongsToMany

These model attributes will be covered later in this chapter. They hold arrays that define how this model is related to others.

$actsAs

This attribute is also covered later on. The $actsAs attribute stores the names of Behaviors this model uses.

Model Methods

For a complete list of model methods and their descriptions visit the CakePHP API. Check out http://api.cakephp.org/1.2/class_model.html.

Retrieving Your Data

findAll
findAll(string $conditions, array $fields, string $order, int $limit, int $page, int $recursive);
  1. findAll(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). 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.

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
                )
        )
)
  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [ModelName] => Array
  6. (
  7. [id] => 83
  8. [field1] => value1
  9. [field2] => value2
  10. [field3] => value3
  11. )
  12. [AssociatedModelName] => Array
  13. (
  14. [id] => 1
  15. [field1] => value1
  16. [field2] => value2
  17. [field3] => value3
  18. )
  19. )
  20. [1] => Array
  21. (
  22. [ModelName] => Array
  23. (
  24. [id] => 85
  25. [field1] => value1
  26. [field2] => value2
  27. [field3] => value3
  28. )
  29. [AssociatedModelName] => Array
  30. (
  31. [id] => 2
  32. [field1] => value1
  33. [field2] => value2
  34. [field3] => value3
  35. )
  36. )
  37. )
find
find(string $conditions, array $fields, string $order, int $recursive);
  1. find(string $conditions, array $fields, string $order, int $recursive);

Same as in findAll(), except that find only returns the first record matching the supplied $conditions.

The find methods were recently consolidated and find() can now be used to fulfil all of your find-ing needs. There is a slightly different syntax to the calls:

find($type, $params);
  1. find($type, $params);

$type is either 'all', 'list' or 'first'. 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
	'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. 'limit' => n, //int
  7. 'page' => n //int
  8. )

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')));
findAllBy
findAllBy<fieldName>(string $value)
  1. 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.

findBy
findBy<fieldName>(string $value);
  1. 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().

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

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'.

// note that the comparison was made on date field, and that
// the id values were not used to determine neighboring data
Array
(
    [prev] => Array ([Article] => 
             Array ([date] => 2008-03-20, [id] => 99 )
    ),
    [next] => Array ( [Article] => 
               array( [date] => 2008-03-27, [id] => 15 )
    )
);
  1. // note that the comparison was made on date field, and that
  2. // the id values were not used to determine neighboring data
  3. Array
  4. (
  5. [prev] => Array ([Article] =>
  6. Array ([date] => 2008-03-20, [id] => 99 )
  7. ),
  8. [next] => Array ( [Article] =>
  9. array( [date] => 2008-03-27, [id] => 15 )
  10. )
  11. );

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.

field
field(string $name, string $conditions, string $order)
  1. 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.

findCount
findCount(string $conditions, int $recursive)
  1. findCount(string $conditions, int $recursive)

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.

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

This is deprecated and will be replaced by usage of find('list') on it's own 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.

query and execute
query(string $query), execute(string $query)
  1. query(string $query), execute(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 supply no return value).

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

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 discreet, 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 prefixes the expression. 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 the operator and the 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")
)
  1. array(
  2. "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. (
  3. "Post.title" => array("First post", "Second post", "Third post"),
  4. "Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks"))
  5. )

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
  2. ("or" =>
  3. array
  4. (
  5. "Post.title" =>
  6. array("First post", "Second post", "Third post"),
  7. "Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks"))
  8. )
  9. )

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 hasMany/belongsTo relationship between Posts and Authors, which would result in a LEFT JOIN. 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", "or" => array
  3. (
  4. "Post.title" => "LIKE %magic%",
  5. "Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks")
  6. )
  7. )

Saving Your Data

CakePHP makes saving model data a snap. Data ready to be saved should be passed to the model’s save() method using the following basic format:

Array
(
    [ModelName] => Array
        (
            [fieldname1] => 'value'
            [fieldname2] => 'value'
        )
)
  1. Array
  2. (
  3. [ModelName] => Array
  4. (
  5. [fieldname1] => 'value'
  6. [fieldname2] => 'value'
  7. )
  8. )

Most of the time you won’t even need to worry about this format: CakePHP’s HtmlHelper, FormHelper, and find methods all package data in this format. If you’re using either of the helpers, the data is also conveniently available in $this->data for quick usage.

Here’s a quick example of a controller action that uses a CakePHP model to save data to a database table:

function edit($id) {
    //Has any form data been POSTed?
    if(!empty($this->data)) {
        //If the form data can be validated and saved...
        if($this->Recipe->save($this->data['Recipe'])) {
            //Set a session flash message and redirect.
            $this->Session->setFlash("Recipe Saved!");
            $this->redirect('/recipes');
            exit(0);
        }
    }

    //If no form data, find the recipe to be edited
    //and hand it to the view.
    $this->set('recipe', $this->Recipe->findById($id);
}
  1. function edit($id) {
  2. //Has any form data been POSTed?
  3. if(!empty($this->data)) {
  4. //If the form data can be validated and saved...
  5. if($this->Recipe->save($this->data['Recipe'])) {
  6. //Set a session flash message and redirect.
  7. $this->Session->setFlash("Recipe Saved!");
  8. $this->redirect('/recipes');
  9. exit(0);
  10. }
  11. }
  12. //If no form data, find the recipe to be edited
  13. //and hand it to the view.
  14. $this->set('recipe', $this->Recipe->findById($id);
  15. }

One additional note: when save is called, the data passed to it in the first parameter is validated using CakePHP validation mechanism (see the Data Validation chapter for more information). If for some reason your data isn’t saving, be sure to check to see if some validation rules aren’t being broken.

There are a few other save-related methods in the model that you’ll find useful:

save(array $data = null, boolean $validate = true, array $fieldList = array())

Featured above, this method saves array-formatted data. The second parameter allows you to sidestep validation, and the third allows you to supply a list of model fields to be saved. For added security, you can limit the saved fields to those listed in $fieldList.

Once a save has been completed, the ID for the object can be found in the $id attribute of the model object - something especially handy when creating new objects.

$this->Ingredient->save($newData);

$newIngredientId = $this->Ingredient->id;
  1. $this->Ingredient->save($newData);
  2. $newIngredientId = $this->Ingredient->id;
create(array $data = array())

This method initializes the model class for saving new information. If you’re ever calling save() inside of a loop (in order to create many records at once), make sure to call create() just before save().

If you supply the $data parameter (using the array format outlined above), the newly created model will be ready to save with that data (accessible at $this->data).

saveField(string $fieldName, string $fieldValue, $validate = false)

Used to save a single field value. Set the ID of the model ($this->ModelName->id = $id) just before calling saveField().

updateAll(array $fields, array $conditions)

Updates many records in a single call. Records to be updated are identified by the $conditions array, and fields to be updated, along with their values, are identified by the $fields array.

For example, if I wanted to approve all bakers who have been members for over a year, the update call might look something like:

$this_year = date('Y-m-d h:i:s', strtotime('-1 year'));

$this->Baker->updateAll(
    array('Baker.approved' => true),
    array('Baker.created' => "<= $this_year")
);
  1. $this_year = date('Y-m-d h:i:s', strtotime('-1 year'));
  2. $this->Baker->updateAll(
  3. array('Baker.approved' => true),
  4. array('Baker.created' => "<= $this_year")
  5. );
remove(int $id = null, boolean $cascade = true);
del(int $id = null, boolean $cascade = true);

Deletes the record identified by $id. By default, also deletes records dependent on the record specified to be deleted.

For example, when deleting a User record that is tied to many Recipe records:

  • if $cascade is set to true, the related Recipe records are also deleted.
  • if $cascade is set to false, the Recipe records will remain after the User has been deleted.
deleteAll(mixed $conditions, $cascade = true)

Same as with del() and remove(), except that deleteAll() deletes all records that match the supplied conditions. The $conditions array should be supplied as an SQL fragment or array.

Model Callbacks

If you want to sneak in some logic just before or after a CakePHP model operation, use model callbacks. These functions can be defined in model classes (including your AppModel) class. Be sure to note the expected return values for each of these special functions.

beforeFind(mixed $queryData)

Called before any find-related operation. The $queryData passed to this callback contains information about the current query: conditions, fields, etc.

If you do not wish the find operation to begin (possibly based on a decision relating to the $queryData options), return false.

You might use this callback to restrict find operations based on a user’s role, or make caching decisions based on the current load.

afterFind(array $results)

Use this callback to modify results that have been returned from a find operation, or to perform any other post-find logic. The $results parameter passed to this callback contains the returned results from the model's find operation.

The return value for this callback should be the (possibly modified) results for the find operation that triggered this callback.

This callback might be used to pretty up date or currency formats.

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 the current save() execution will abort.

beforeSave()

Place any pre-save logic in this function. This function executes immediately after model data has been successfully validated, but just before the data is saved. This function should also return true if you want the save operation to continue.

This callback is especially handy for any data-massaging logic that needs to happen before your data is stored. If your storage engine needs dates in a specific format, access it at $this->data and modify it.

afterSave(boolean $created)

If you have logic you need to be executed just after every save operation, place it in this callback method.

The value of $created will be true if a new object was created (rather than an update).

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.

onError()

Called if any problems occur.

User-Defined Functions

While CakePHP’s model functions should get you where you need to go, don’t forget that model classes are just that: classes that allow you to write your own methods.

Any operation that handles the saving and fetching of data is best housed in your model classes.

Associations

 

Introduction

One of the most powerful features of CakePHP is the relational mapping provided by the model. In CakePHP, the links between models are handled through associations.

Defining relations between different objects in your application should be a natural process. For example: in a recipe database, a recipe may have many reviews, reviews have a single author, and authors may have many recipes. Defining the way these relations work allows you to access your data in an intuitive and powerful way.

The purpose of this section is to show you how to plan for, define, and utilize associations between models in CakePHP.

Because the most common form of storage in web applications is a relational database, most of what we’ll cover in this manual will be in a database related context.

The four association types in CakePHP are: hasOne, hasMany, belongsTo, and hasAndBelongsToMany (HABTM).

Association Type Example
hasOne A user has one profile.
hasMany Users in a system can have multiple recipes.
belongsTo A recipe belongs to a user.
hasAndBelongsToMany Recipes have, and belong to many tags.

Associations are defined by creating a class variable named after the association you are defining. The class variable can sometimes be as simple as a string, but can be as complete as a multidimensional array used to define association specifics.

<?php

class User extends AppModel {
    var $name = 'User';
    var $hasOne = 'Profile';
    var $hasMany = array(
        'Recipe' => array(
            'className'  => 'Recipe',
            'conditions' => 'Recipe.approved = 1',
            'order'      => 'Recipe.created DESC'
        )
    );
}

?>
  1. <?php
  2. class User extends AppModel {
  3. var $name = 'User';
  4. var $hasOne = 'Profile';
  5. var $hasMany = array(
  6. 'Recipe' => array(
  7. 'className' => 'Recipe',
  8. 'conditions' => 'Recipe.approved = 1',
  9. 'order' => 'Recipe.created DESC'
  10. )
  11. );
  12. }
  13. ?>

hasOne

Let’s set up a User model with a hasOne relationship to a Profile model.

First, your database tables need to be keyed correctly. For a hasOne relationship to work, one table has to contain a foreign key that points to a record in the other. In this case the profiles table will contain a field called user_id. The basic pattern is:

hasOne: the *other* model contains the foreign key.

Apple hasOne Banana      => bananas.apple_id
User hasOne Profile      => profiles.user_id
Doctor hasOne Mentor     => mentors.doctor_id
  1. hasOne: the *other* model contains the foreign key.
  2. Apple hasOne Banana => bananas.apple_id
  3. User hasOne Profile => profiles.user_id
  4. Doctor hasOne Mentor => mentors.doctor_id

The User model file will be saved in /app/models/user.php. To define the ‘User hasOne Profile’ association, add the $hasOne property to the model class. Remember to have a P