Models
Introduction
Models are used in CakePHP applications for data access. Models most commonly represent a database table, though they can be used to access files, LDAP records, iCal events, or rows in a CSV file.
Models can also be associated in order to make data access easier. If you find that every time you fetch data with your Recipe model, you’d also like to fetch its related Author and Ingredient entries, model associations can help.
New in CakePHP is DataSources and Behaviors. DataSources form a unified foundation for CakePHP models. This allows for greater model interaction, including associations. Model behaviors provide a way to mix in functionality. Specifying behaviors for models gives you access to commonly needed data manipulation logic: things like tree pruning, upload handling, and more.
Fully functional model classes can look pretty bare:
<?php
class Ingredient extends AppModel {
var $name = 'Ingredient';
}
?>
<?phpclass Ingredient extends AppModel {var $name = 'Ingredient';}?>
Notice that CakePHP models extend a special AppModel class: the parent for all models in your application. Like the AppController (and AppHelper), this special class is a perfect place to put logic shared amongst all models. The AppModel class should be created at /app/app_model.php, and should extend the Model class.
With just a few lines of code, you have full CRUD (create, retrieve, update, delete) functionality. Most of this comes from the model’s grandparent class Model. Once you have a model defined, you can utilize it in your controller. Here’s a basic example of a model being used ($this->Ingredient) to fetch a list of Ingredient records to hand to the view:
<?php
class IngredientsController extends AppController {
function index() {
//Hand a list of all ingredients to the view:
$this->set(
'ingredients',
$this->Ingredient->findAll()
);
}
}
?>
<?phpclass IngredientsController extends AppController {function index() {//Hand a list of all ingredients to the view:$this->set('ingredients',$this->Ingredient->findAll());}}?>
Using models to fetch and save data can save you serious headaches. CakePHP models provide a standard and centralized way to approach your data store, while at the same time offering a level of security for your application.
Model classes can be expanded to include a number of attributes and methods to enhance their functionality. These attributes and methods will be explained next.
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';
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");
$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");
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);
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
)
)
)
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)))
find
find(string $conditions, array $fields, string $order, int $recursive);
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);
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
) 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)
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')));
// 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')));
findAllBy
findAllBy<fieldName>(string $value)
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);
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
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 ) );
$conditions = array('Article.status' => 'published');$field = array('date', 'id');$value = '2008-03-24';$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 )
)
);
// note that the comparison was made on date field, and that// the id values were not used to determine neighboring dataArray([prev] => Array ([Article] =>Array ([date] => 2008-03-20, [id] => 99 )),[next] => Array ( [Article] =>array( [date] => 2008-03-27, [id] => 15 )));
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);
)
}
}
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);)}}
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)
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)
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)
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'
);
$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');
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)
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);
$conditions = array("Post.title" => "This is a post");//Example usage with a model:$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")
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")
)
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"))
)
array("Post.title" => array("First post", "Second post", "Third post"),"Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks")))
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"))
)
)
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 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")
)
)
array("Author.name" => "Bob", "or" => array("Post.title" => "LIKE %magic%","Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks")))
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'
)
)
Array([ModelName] => Array([fieldname1] => 'value'[fieldname2] => 'value'))
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);
}
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);}
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:
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;
$this->Ingredient->save($newData);$newIngredientId = $this->Ingredient->id;
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).
Used to save a single field value. Set the ID of the model ($this->ModelName->id = $id) just before calling saveField().
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")
);
$this_year = date('Y-m-d h:i:s', strtotime('-1 year'));$this->Baker->updateAll(array('Baker.approved' => true),array('Baker.created' => "<= $this_year"));
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.
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.
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.
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.
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.
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.
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).
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.
Place any logic that you want to be executed after every deletion in this callback method.
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'
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = 'Profile';var $hasMany = array('Recipe' => array('className' => 'Recipe','conditions' => 'Recipe.approved = 1','order' => 'Recipe.created DESC'));}?>
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
hasOne: the *other* model contains the foreign key.Apple hasOne Banana => bananas.apple_idUser hasOne Profile => profiles.user_idDoctor 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 Profile model in /app/models/profile.php, or the association won’t work.
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = 'Profile';
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = 'Profile';}?>
There are two ways to describe this relationship in your model files. The simplest method is to set the $hasOne attribute to a string containing the classname of the associated model, as we’ve done above.
If you need more control, you can define your associations using array syntax. For example, you might want to sort related rows in descending order according to a date, or you might want to limit the association to include only certain records.
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = array(
'Profile' => array(
'className' => 'Profile',
'conditions' => 'Profile.published = 1',
'dependent' => true
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = array('Profile' => array('className' => 'Profile','conditions' => 'Profile.published = 1','dependent' => true));}?>
Possible keys for hasOne association arrays include:
- className: the classname of the model being associated to the current model. If you’re defining a ‘User hasOne Profile’ relationship, the className key should equal ‘Profile.’
- foreignKey: the name of the foreign key found in the other model. This is especially handy if you need to define multiple hasOne relationships. The default value for this key is the underscored, singular name of the other model, suffixed with ‘_id’.
- conditions: An SQL fragment used filter related m
login to add a comment