Table of Contents : Il manuale

Models

 

Introduzione

I Modelli sono usati nelle applicazioni CakePHP per l'accesso ai dati. Comunemente i modelli rappresentano una tabella di database, ma possono essere anche usati per accedere a file, record LDAP, eventi iCal o righe in un file CVS.

I Modelli possono essere anche associati tra loro, allo scopo di rendere l'accesso ai dati più facile. Se vi trovate nella condizione che ogni volta che eseguite una fetch di dati per mezzo del modello Recipe, dovete anche eseguirlo sulle entry di Author ed Ingredient, l'associazione di modelli vi può essere di aiuto.

Nuovi sono in CakePHP i DataSource ed i Behavior. I DataSource formano un base comune per i modelli CakePHP. Questo permette una maggiore integrazione dei modelli, inclusa l'associazione. I behavior del modello forniscono un sistema per aggiungere delle funzionalità. Specificare un behavior per i modelli vi fornisce un accesso ad una logica di manipolazione di cui potete avere bisogno: parliano di cose del tipo tree pruning, gestione degli upload ed altro.

Una classe di un modello pienamente funzionale, anche se piuttosto nuda, può somigliare a questa:

<?php

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

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

Notate che i modelli CakePHP estendono una classe speciale AppModel: il genitore per tutti i modelli della vostra applicazione. Come AppController (ed AppHelper), questa classe speciale è il punto perfetto per inserire la logica condivisa da tutti i modelli dell'applicazione. La classe AppModel dovrebbe essere creata in /app/app_model.php e dovrebbe estendere la classe Model.

Soltanto con poche linee di codice, avete ottenuto pienamente le funzionalità CRUD (create, retrieve, update, delete). La maggior parte di questo è fornito dalla classe genitore dei modelli: Model. Una volta che avete un modello definito, potete utilizzarlo in un vostro controller. Segue un semplice esempio di come un modello ($this->Ingredient) può essere usato per ritrovare una lista di record Ingredient da gestire nella vista:

<?php

class IngredientsController extends AppController {

    function index() {
        //Hand a list of all ingredients to the view:
        $this->set(
            'ingredients',
            $this->Ingredient->findAll()
        );
    }
}

?>
  1. <?php
  2. class IngredientsController extends AppController {
  3. function index() {
  4. //Hand a list of all ingredients to the view:
  5. $this->set(
  6. 'ingredients',
  7. $this->Ingredient->findAll()
  8. );
  9. }
  10. }
  11. ?>

Utilizzare i modelli per ritrovare e salvare i dati può risparmiarvi molti mal di testa. I modelli di CakePHP forniscono un percorso standard e centralizzato per memorizzare i vostri dati, ma allo stesso tempo forniscono un livello di sicurezza per la vostra applicazione.

Le classi dei modelli possono essere espanse per includere un numero di attributi e motodi che aumentano le loro funzionalità. Questi attributi e metodi verranno spiegati di seguito.

Automagic model fields

Se denominate alcuni campi in accordo alle convenzioni, CakePHP potrà effettuare del lavoro per voi.

created & modified (o updated)

Questi due campi sono gestiti automaticamente dalle chiamate al metodo save() del modello Model di CakePHP. Ogni volta che si creano dei nuovi record, il campo created verrà automaticamente inserito, mentre il campo modified sarà aggiornato ogni qualvolta saranno fatte delle modifiche. Si noti che un campo denominato updated avrà lo stesso comportamente del campo modified.

Sia l'uno che l'altro, di questi campi speciali, dovranno essere del tipo datetime ed avere valore di default impostato a NULL.

Uso delle chiave primarie come UUID

Se la chiave primaria per un modello ('id' o altro valore impostato per mezzo della variabile $primaryKey) viene definita come una char(26), CakePHP creerà automaticamente un UUID da utilizzarsi per l'impostazione del campo.

Questo comportamento è utile specialmente per applicazioni che utilizzano più database.

Attributi dei modelli

Per una lista completa degli attributi dei modelli e per la loro descrizione visitate le CakePHP API. http://api.cakephp.org/1.2/class_model.html.

$useDbConfig

Il nome della configurazione di database che questo modello usa. Le configurazioni sono specificate nel file /app/config/database.php come classi. Il valore di preimpostato è 'default'.

$useTable

Il nome della tabella di database che il modello utilizza. Il nome della tabella preimpostato è la versione in minuscolo, plurale del nome della classe del modello. Potete impostare questo attributo ad un nome alternativo, o impostarlo a false nel caso non vogliate che il modello abbia una tabella di database corrispondente.

$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 Banan