CakePHP中文手册【翻译】-Model
Model
第1节
model是什么?
model是什么?是MVC中的M。
它能做什么?把逻辑从表现层中分离出来,独立应用程序的逻辑。
一般来说,model是一个数据库的访问入口,更特殊的是,它是某个特定数据库表会的访问入口。缺省的,每个model使用数据表,此表的表名是它自己的复数形式,例如,‘User‘model使用’users‘表。Model也可以包含数据验证规则,关联信息,以及指定到它使用的表的方法。下面是一个简单的User model在Cake中的样子:
User model实例,保存在app/models/user.php
<?php //AppModel gives you all of Cake's Model
functionality class User extends AppModel { // Its always good practice to include this
variable. var $name = 'User'; // This is used for validation, see Chapter
11. var $validate = array(); // You can also define associations.
// See section 6.3 for more information. var $hasMany = array('Image'
=> array('className'
=> 'Image') ); // You can also include your own functions: function makeInactive($uid) {
//Put your own
logic here... } } ?> |
第2节
Model 功能
从PHP的角度看,model扩展了AppModel类。AppModel类在cake/目录中定义,如果你想创建你自己的类,请把它放在app/app_model.php.它应该包含在2个或更多的model之间可共享的方法。它自己扩展了Model类,此类是一个标准的Cake库,定义在cake/libs/model.php中。
本节介绍大部分常用的Cake Model函数。记住使用http://api.cakephp.org获取全部参考,这是非常重要的。
用户定义函数
在Model中,某个特定表的函数的例子是一对用来隐藏/显示blog中的post的方法。
Model 函数实例
<?php class Post extends AppModel { var $name = 'Post'; function hide ($id=null) {
if ($id)
{
$this->id = $id;
$this->saveField('hidden', '1');
} } function unhide ($id=null) {
if ($id)
{
$this->id = $id;
$this->saveField('hidden', '0');
} } } ?> |
获取数据
下面是一些用来获取数据的标准model方法。
findAll
- string $conditions
- array $fields
- string $order
- int $limit
- int $page
- int $recursive
返回符合$conditions(如果有的话)并且上限为$limit条的指定字段,并从页面$page(缺省为页面1)开始列出。$conditions看起来和他们在SQL语句里的一样,例如$conditions = "race =
'wookie' AND thermal_detonators > 3"。
当$recursive设置为一个大于1的整数时,findAll()操作会尽可能返回与findAll()找到的那些相关联的model。如果你的财产(Property)有多个拥有者,并且他们拥有多个合同(contract),那么一个Property model的findAll()方法将递归返回这些相关的model。
find
- string $conditions
- array $fields
- string $order
- int $recursive
返回匹配$conditions的第一个记录的指定字段(或所有字段,如果没有指明)。
当$recursive选项设置为一个1到3之间的整数值,findAll()操作尽可能返回与findAll()找到的那些相关联的model。递归查找最深可到第3层。如果你的财产(Property)有多个拥有者,并且他们拥有多个合同(contract),那么一个Property model的递归find()方法将返回最多三层深的关联model。
findAllBy<fieldName>
- string $value
这些魔幻函数可以作为一个快捷方式来查询你的表,并获得一行给定了特定的字段,特定的值的记录。将你想查询的字段名字放上去,并使用骆驼峰的格式。实例(在Controller里)可以为
$this->Post->findByTitle('My First Blog Post'); $this->Author->findByLastName(' $this->Property->findAllByState('AZ'); $this->Specimen->findAllByKingdom('Animalia'); |
返回值是一个数组,并且按find()或findAll()那样的格式进行格式化.
findNeighbours
- string $conditions
- array $field
- string $value
返回一个相邻的Model数组(仅用指定的字段),并由$field和$value指定,由SQL条件$conditions进行过滤。
在这种情形下,它是非常价值的。你可以让用户通过'Previous'和'Next'的链接在你的model入口里获取有序的记录。它仅对数字和日期字段有效。
class ImagesController extends
AppController { function view($id) {
// Say we want to
show the image...
$this->set('image', $this->Image->find("id
= $id");
// But we also want
the previous and next images...
$this->set('neighbours', $this->Image->findNeighbours(null,
'id', $id); } } |
它会给我们一个全集的$image['Image']数组,并在view中接着有 $neighbours['prev']['Image']['id'] 以及$neighbours['next']['Image']['id'] 。
field
- string $name
- string $conditions
- string $order
返回一个字符串,即匹配$conditions 条件以及$order顺序的第一个记录的单个字段。
findCount
- string $conditions
返回与给定条件匹配的记录数。
generateList
- string $conditions
- string $order
- int $limit
- string $keyPath
- string $valuePath
本函数是可以快捷的得到一键/值列—特别是对手动创建一个model的HTML
select标记。使用$conditions, $order, 以及$limit这些参数来作出一个findAll()请求。通过$keyPath和$valuePath,告诉model在哪里找到生成列表的键与值。例如,如果你打算生成一列基于Role Model的角色,并且键为它们的整数id,完整的调用可以如下:
$this->set(
'Roles', $this->Role->generateList(null,
'role_name ASC', null, '{n}.Role.id', '{n}.Role.role_name') ); //This would return something like: array(
'1' => 'Account Manager',
'2' => 'Account Viewer',
'3' => 'System Manager',
'4' => 'Site Visitor' ); |
read
- string $fields
- string $id
使用本函数得到当前已加载记录的字段以及它们的值,或者为$id的记录。
请注意,read()操作仅仅获得第一层相关model值,与model里$recursive值没有关系。为了获取其他层,使用find()或findAll().
query
- string $query
execute
- string $query
可以使用model的query()和execute()来执行自定义的SQL调用。两者之间的区别是query()用来执行自定义的SQL查询(返回结果),而execute()用来执行自定义的SQL命令(不需返回值)。
使用query()的自定义SQL调用
<?php class Post extends AppModel { var $name = 'Post'; function posterFirstName() {
$ret = $this->query("SELECT
first_name FROM posters_table WHERE
poster_id = 1");
$firstName = $ret[0]['first_name'];
return $firstName; } } ?> |
复杂的查找条件(使用Array)
大多数model的查找调用会以这样那样的方式传入一组条件。解决此最简单的方式就是在SQL里使用WHERE子句,但是如果需要更多的控制,你可以使用数组。数组的使用可以使读取更加清楚,同时也更加简单,而且使查询的创建变得相当简单。本语法也将查询的元素(字段,值,操作等)变成严谨但可控制的部分。这让Cake可以生成最有效的查询,并保证恰当的SQL语法,以及脱离每个查询的独立部分。
最基础的是,一个基于数组的查询可以这样:
使用Array的基本查询条件实例
$conditions = array("Post.title" => "This is
a post"); //Example usage with a model: $this->Post->find($conditions); |
本结构自己就能非常好的解释它自己:它会查找任何title为"This is a post"的post。注意我们在前面可以把“title”作为字段名,但是在建立查询时,一直指定model名将会是一个优秀的实践,因为它改进了代码的清晰性,并帮助阻止未来的冲突,因为在未来你可能会选择改变你的schema。那么其他的匹配呢?同样简单。让我们查找一些title不是"This is a
post"的post:
array("Post.title" =>
"<> This is a post") |
就在表达式前增加一个'<>‘。Cake可以解析任何有效的SQL比较操作符,包括匹配表达式,如LIKE,BETWEEN,或REGEX,只要你在表达式或值等操作符之间留一个空格。这里还是有一个例外,那就是IN(…)样式的匹配。查找title为给定值的post可以是:
array("Post.title" => array("First post", "Second
post", "Third post")) |
在条件中,同样简单增加额外的条件和增加额外的键/值对到数组中:
array (
"Post.title" => array("First post", "Second post",
"Third post"),
"Post.created" => "> " . date('Y-m-d', strtotime("-2
weeks")) ) |
缺省的,Cake将多个条件组成AND,意思就是,上面的片段仅仅匹配那些在2周前创建的post,而且title需要匹配其中一个给定的值。虽说如此,我们可以很容易的查找那些匹配任何条件的post:
array ("or" => array (
"Post.title" => array("First
post", "Second post", "Third post"),
"Post.created" => "> " . date('Y-m-d', strtotime("-2
weeks")) ) ) |
Cake接受所有有效的SQL布尔操作,包括ND, OR, NOT, XOR等,并且它们可以为大写,也可以为小写,不管你喜欢哪种。这些条件也可无限嵌套。Posts和Authors之间的关系是hasMany/belongsTo,它会使POST上的查询有一个LEFT JOIN操作。你打算查找所有包含某个关键字或在2周前创建的Post,但是你想约定这些Post为Bob编写,可以是:
array ("Author.name" =>
"Bob", "or" => array (
"Post.title" => "LIKE %magic%",
"Post.created" => "> " . date('Y-m-d', strtotime("-2
weeks") ) ) |
保存你的数据
为了将数据保存到你的model中,你需要为它提供你想要保存的数据。用来处理的save()方法应该按下面的形式:
Array (
[ModelName] => Array
(
[fieldname1] => 'value'
[fieldname2] => 'value'
) ) |
为了让你的数据按这种方式发表在Controller上,最简单的方式就是使用HTML Helper来实现,因为它创建表单元素,这些元素都以Cake期望的方式命名的。当然,你也没有必要使用它:保证你的表单元素按类似data[Modelname][fieldname]的方式命名。最简单的方式是使用$html->input('Model/fieldname')。
表单Post的数据会自动格式化,并将$this->data 放入Controller里。因此保存web表单的数据是非常简单的。一个合适的controller编辑函数可能看起来如下:
function edit($id) { //Note: The property model is automatically
loaded for us at $this->Property. //
Check to see if we have form data... if (empty($this->data)) {
$this->Property->id = $id;
$this->data = $this->Property->read();//populate the form fields with the current
row } else {
// Here's where we
try to save our data. Automagic validation checking
if ($this->Property->save($this->data['Property']))
{
//Flash a message
and redirect.
$this->flash('Your information has been
saved.', '/properties/view/'.$this->data['Property']['id'], 2); }
//if some fields
are invalid or save fails the form will render } } |
注意,save操作是怎样在一个条件语句中放置的:当你试图将数据保存到model时,Cake自动试图根据你提供的规则验证数据。为了学到更多关于数据验证的知识,参看第12章。如果你不想让save()验证数据,使用save($data, false).
其他有用的存储函数:
- string $id
- boolean $cascade
删除指定$id的model,或者model的当前id。如果这个model和其他model相关联,并且'dependent'间已经在关联数组中设置,本方法也会删除那些关联的model,如果$cascade设为true的话。
如果成功返回true。
saveField
- string $name
- string $value
用来保存单个字段值。
getLastInsertId
返回最近创建记录的ID。
Model回调
我们已经加入了一些model回调(函数),它们允许你在某个model操作前或后悄悄的加点逻辑在里面。为了在应用程序中使用本功能,使用提供的参数,并且在Cake Model里覆盖这些函数。
beforeFind
- string $conditions
beforeFind()回调函数仅在一个查找操作之前执行。可以在本方法里放入任何预先查找的逻辑。当你在model里覆盖时,如果你想执行查找,返回true,若是想取消,返回false。
afterFind
- array $results
使用本回调会修改一个查询操作返回的结果,或者进行其他的post查找逻辑。Model查找操作返回的结果就是本函数的参数,并且返回值就是修改的结果。
beforeValidate
在model验证前使用此回调修改model数据。它也能用来增加额外的,更加复杂的验证规则,使用Model::invalidate()验证。在本上下文中,model数据可以通过$this->data访问。本函数也必须返回true,否则save()执行会终止。
beforeSave
在本函数中加入任何预保存的逻辑。本函数在model 数据验证后(假设它一验证,否则save()调用取消,并且本回调不会执行)立即执行。但是在数据保存之前,如果你想保存工作本函数也会返回true,本函数也应该返回true,如果你想让保存操作继续,如果取消,则返回false。
beforeSave的一个用法可以是格化式指定数据库引擎上的时间数据。
/ Date/time fields created by HTML Helper: // This code would be seen in a view $html->dayOptionTag('Event/start'); $html->monthOptionTag('Event/start'); $html->yearOptionTag('Event/start'); $html->hourOptionTag('Event/start'); $html->minuteOptionTag('Event/start'); /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ // Model callback functions used to stitch
date // data together for storage // This code would be seen in the Event
model: function beforeSave() { $this->data['Event']['start'] = $this->_getDate('Event',
'start'); return true; } function _getDate($model, $field) { return date('Y-m-d H:i:s', mktime(
intval($this->data[$model][$field . '_hour']),
intval($this->data[$model][$field . '_min']),
null,
intval($this->data[$model][$field . '_month']),
intval($this->data[$model][$field . '_day']),
intval($this->data[$model][$field . '_year']))); } |
- afterSave
在本回调方法中,每次保存之后放入你想执行的任何逻辑。
- beforeDelete
在本函数中放置任何预删除逻辑。如果你想让删除操作继续的话,本函数应该返回true。否则你想终止的话则返回false。
- afterDelete
在本回调方法里,每个删除操作之后,放置你想执行的任何逻辑。
第3节
Model 变量
当创建model时,你需要设置某些特定的变量来访问Cake的功能:
$primaryKey
如果model和一个数据库表相对应,并且表的主键的命名不是”id”,那么使用本变量告诉Cake你的主键名。
$recursive
它将设置Cake使用find()和findAll()操作获取相关联model数据的层数。
假想你有一个组(Group),每组有许多用户(User),而这些用户又有很多文章(Article)。
Model::recursive 选项
$recursive = 0 |
Cake 获取Group |
$recursive = 1 |
Cake 获取Group以及相关联的Users |
$recursive = 2 |
Cake 获取Group,
相关联的Users, 以及Users关联的Articles |
$transactional
告诉Cake是否为本model启用事务(例如,begin/commit/rollback)。设置为一个布尔值。仅当数据库支持时。[1]
$useTable
如果你想使用的数据库表不是复数格式的model名(以及你不打算修改表名),将本变量设置为你想让model使用的表名。
$validate
用来验证传递到model的数据。参看第12章。
$useDbConfig
记住了在/app/config/database.php配置的数据库设置了吗?使用本变量来切换他们-使用在数据库配置文件里创建的数据库链接变量的名称。你已经猜到,缺省的是‘defualt’。
第4节
关联
介绍
CakePHP最厉害的特性之一就是Model提供的关系映射。在CakePHP中,表之间的关系是通过关联处理的。关联就是相关逻辑单元之间的胶水。
在CakePHP有4中关联:
1.
hasOne
2.
hasMany
3.
belongsTo
4.
hasAndBelongsToMany
当定义model之间的关系时,Cake会自动获取与正在工作的model相关的model。例如,如果一个Post Model与Author Model相关,并使用hasMany关联,调用Controller的$this->Post->findAll()将会获得Post记录,同时也会获得与他们相关的Author记录。
为正确使用关联,建议遵循CakePHP命名规则(参考附录)。如果你使用了CakePHP的命名规则,你也可以使用scaffolding可视化你的应用数据,因为scaffolding检测并使用model之间的关联。当然,在没遵循Cake的命名规则时,你也可以一直自定义model关联来进行工作,但我们会在后面说明一些技巧。现在,让我们盯住这些规则吧。在这里我们关心的命名规则是主键,Model名,以及表名。
下面是Cake期望这些不同元素名称的概览:(参看附录获取更多关于命名的信息):
1.
外键:【单数model名】_id.例如,可以将指回到Author属于的Post的“author”表的外键命名为”post_id”.
2.
表名:【复数的对象名】,因为我们想存储Blog post和作者的信息,表名应该分别为"posts" 和"authors"
3.
Model名:【骆驼峰命名,表名的单数形式】。"posts" 表的Model名为"Post","authors"表的model名为"Author"
CakePHP的Scaffolding希望你的关联与你的列保持同样的顺序。因此如果你有一个Article
belongsTo(属于)3个其他的model(Author, Editor, 和Publisher),你需要3个键:uthor_id, editor_id, Publisher_id.Scaffolding期望你的关联和表中的键保持一样的顺序(例如,第一个是Author,第二个是Editor,最后是Publisher)。
为了描述这些关联如何工作,让我们继续使用Blog应用程序作为实例吧。假设我们打算为blog创建一个简单的用户管理系统。也假设它会在不跟踪Users的情况下进行,但是我们希望每个用户都有一个关联的Profile(User hasOne Profile).Users也会创建留言并且保留他们的关联(User hasMany Comments),一旦我们让用户系统运行,我们会将视线转移到使用hasAndBelongsToMany(Post hasAndBelongsToMany Tags)Posts以及与其相关的Tag对象。
定义和查询hasOne
为建立关联,我们假设你已经创建了User以及Profile Model。为了定义他们的hasOne关联,我们需要为Model增加一个数组告诉Cake它们是怎样关联的。这里是它们如何关联的:
/app/models/user.php hasOne
<?php class User extends AppModel { var $name = 'User'; var $hasOne = array('Profile'
=> array('className' => 'Profile',
'conditions' => '', 'order' => '',
'dependent' => true,
'foreignKey' => 'user_id' ) ); } ?> |
$hasOne数组是Cake用来创建User和Profile
model之间的关联的。数组中的每个键允许你更进一步的配置关联:
1.
className (必须): 关联的model 类名。
例如,我们会指定'Profile'
model类名。
2.
conditions:
SQL条件部分,它定义了关系。
我们可能会使用它来告诉Cake只关联Profile,此Profile有一个绿色的标记头,如果我们愿意。为了定义这个条件,可以为此键指定一个SQL条件作为其值:”Profile.header_color =
'green'"。
3.
order:关联model的顺序
例如,如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:"Profile.name ASC"。
4.
dependent:如果设置为true,当它销毁时,相关联的model也会销毁。
例如,如果"Cool
Blue"的Profile和"Bob"相关联,并且我们删除用户"Bob","Cool Blue"的Profile也会删除。
5.
foreignKey:指向关联的model的外键名。
在这里,你正在操作一个没有遵循Cake命名规则的数据库。
现在,当使用Profile model来执行 find()或findAll()时,我们应该也看到相关联的User model:
$user = $this->User->read(null,
'25'); print_r($user); //输出: Array (
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] =>
[username] => psychic
[password] => c4k3roxx
)
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
) ) |
定义和查询belongsTo
如果User想看到他的Profile,我们需要定义一个关联,因此Profile可以看见他自己的User。在Cake里,可以使用belongsTo关联实现。在Profile model里,我们可以如下操作:
/app/models/profile.php belongsTo
<?php class Profile extends AppModel { var $name = 'Profile'; var $belongsTo = array('User'
=> array('className' => 'User', 'conditions'
=> '', 'order' => '', 'foreignKey'
=> 'user_id' ) ); } ?> |
$belongsTo数组是Cake用来创建User和Profile model之间的关联的。数组中的每个键允许你更进一步的配置关联:
1.
className (必须):你想要关联的model的类名
例如:我们想指定‘User’model 类名。
2.
conditions:定义关系的SQL条件语句
我们可以使用它告诉Cake仅关联活动的User。为了实现它,将键值设置为"User.active = '1'"或者其他类似的。
3.
order:关联model的顺序。
如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:"User.last_name ASC"。
6.
foreignKey:
指向关联model的外键名。
在这里,你正在操作一个没有遵循Cake命名规则的数据库。
现在,当使用Profile model执行 find()或findAll(),我们应该也可以看到相关联的User model:
$profile = $this->Profile->read(null, '4'); print_r($profile); //output: Array (
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => [username] => psychic
[password] => c4k3roxx
) ) |
定义和查询hasMany
尽管User和Profile
model已经关联,并且工作正常,让我们再构建我们的系统,以至User记录和Comment记录关联。在User model中可以如下定义:
/app/models/user.php hasMany
<?php class User extends AppModel { var $name = 'User'; var $hasMany = array('Comment'
=> array('className'
=> 'Comment',
'conditions' =>
'Comment.moderated = 1', 'order' => 'Comment.created DESC', 'limit' => '5', 'foreignKey' => 'user_id',
'dependent' => true,
'exclusive' => false,
'finderQuery' => '' ) ); // Here's the hasOne relationship we defined earlier... var $hasOne = array('Profile'
=> array('className'
=> 'Profile',
'conditions' => '', 'order' => '',
'dependent' => true,
'foreignKey' => 'user_id' ) ); } ?> |
$hasMany数组是Cake用来创建User和Comment
model之间的关联的。数组中的每个键允许你更进一步的配置关联:
1.
className(必须):你想要关联的model的类名
例如:我们想指定‘Comment’model
类名。
2.
conditions:
定义关系的SQL条件语句
我们可以使用它告诉Cake仅关联通过管理的Comment。为了实现它,将键值设置为" Comment.moderated = 1"或者其他类似的。
3.
order: 关联model的顺序。
如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:"Comment.created DESC "。
4.
limit:你想让Cake获取关联model的最大数
在本例,我们不会获取所有用户的留言,仅仅为5.
5.
foreignKey:
指向关联model的外键名。
在这里,你正在操作一个没有遵循Cake命名规则的数据库。
6.
dependent:如果设置为true,当它销毁时,相关联的model也会销毁。
例如,如果"Cool
Blue"的Profile和"Bob"相关联,并且我们删除用户"Bob","Cool Blue"的Profile也会删除。
7.
exclusive:如果设置为true,所有关联的对象在没有让beforeDelete回调运行时在一个SQL语句中会被删掉,
对于比较简单的关联,使用起来很好,因为它能更快。
8.
finderQuery:指定一个完整的SQL语句获取关联。
对于复杂的依赖多个表的关联,这是一种优秀的方法。如果Cake的自动关联不工作,在这里,你可以定义它。
现在,当使用User model执行 find()或findAll(),我们应该也可以看到相关联的Comment model:
$user = $this->User->read(null,
'25'); print_r($user); //输出: Array (
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] =>
[username] => psychic
[password] => c4k3roxx
) [Profile]
=> Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
[Comment] => Array
(
[0] => Array ( [id] => 247 [user_id] => 25 [body] => The hasMany
assocation is nice to have. )
[1] => Array ( [id] => 256 [user_id] => 25
[body] => The
hasMany assocation is really nice to have. )
[2] => Array ( [id] => 269 [user_id] => 25 [body] => The hasMany
assocation is really, really nice to have. )
[3] => Array ( [id] => 285 [user_id] => 25 [body] => The hasMany
assocation is extremely nice to have. )
[4] => Array ( [id] => 286 [user_id] => 25 [body] => The hasMany
assocation is super nice to have. )
) ) |
在这里,当我们不打算记录处理过程时,定义"Comment
belongsTo User"关联也是一个相当不错的注意,因此2个model都能看到对方。当你试图使用scaffolding时,未定义2个model之间的关联常常是一个常见的难题。
定义和查询 hasAndBelongsToMany
现在你已经掌握了比较简单的关联,让我们转移到最后一个关联:hasAndBelongsToMany
(或 HABTM).最后一个是最难的,它会让你团团转,但是它也是其中最有用的一个。如果你有2个model,而这2个model又一个join表联系在一起,HABTM关联这个时候是有用的。Join表抓住了互相关联的各自的行。
hasMany 和hasAndBelongsToMany的区别是:对于hasMany,关联的model是不共享的。这对我们接下来的工作是非常有用:关联Post和Tag model。当一个Tag属于一个Post时,我们不会让他使用完,我们会继续将他和其他Post关联。
为了实现它,我们需要为这个关系建立正确的表。当然你需要为你的Tag
model转被一个“tags“表,Post准备一个”posts“表,但是你也需要为此关联创建一个join表。HABTM join表的命名规则是[复数的model1名]_[复数的model2名],这里,model名按字母排序。
HABTM Join 表: model样例以及他们的Join表
1.
Posts 和Tags: posts_tags
2.
Monkeys和IceCubes: ice_cubes_monkeys
3.
Categories和Articles: articles_categories
HABTM Join表至少需要包含2个他们连接的model的外键。在我们的例子中,"post_id" 和"tag_id"就是我们所需要的。
下面导出的SQL看起来是为我们的Posts HABTM Tags实例准备的哟:
-- -- Table structure for table `posts` -- CREATE TABLE `posts` (
`id` int(10) unsigned NOT NULL auto_increment, `user_id` int(10) default NULL,
`title` varchar(50) default NULL,
`body` text,
`created` datetime default NULL,
`modified` datetime default NULL,
`status` tinyint(1) NOT NULL default '0', PRIMARY KEY
(`id`) ) TYPE=MyISAM; --
-------------------------------------------------------- -- -- Table structure for table `posts_tags` -- CREATE TABLE `posts_tags` (
`post_id` int(10) unsigned NOT NULL default '0',
`tag_id` int(10) unsigned NOT NULL default '0', PRIMARY KEY
(`post_id`,`tag_id`) ) TYPE=MyISAM; --
-------------------------------------------------------- -- -- Table structure for table `tags` -- CREATE TABLE `tags` (
`id` int(10) unsigned NOT NULL auto_increment,
`tag` varchar(100) default NULL, PRIMARY KEY
(`id`) ) TYPE=MyISAM; |
随着我们的表已创建,让我们在Post model里定义关联:
/app/models/post.php hasAndBelongsToMany
<?php class Post extends AppModel { var $name = 'Post'; var $hasAndBelongsToMany = array('Tag'
=> array('className'
=> 'Tag',
'joinTable' =>
'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey'=> 'tag_id',
'conditions' => '',
'order' => '',
'limit' => '',
'uniq' => true,
'finderQuery' => '',
'deleteQuery' => '', ) ); } ?> |
$hasAndBelongsToMany数组是Cake用来创建Post和Tag model之间的关联的。数组中的每个键允许你更进一步配置关联:
1.
className (必须): 你想要关联的model的类名。例如:
在我们的例子中,我们想指定‘Tag’model 类名。
2.
joinTable:它是为没有遵循Cake命名规则的数据库而准备的。如果你的表不是【复数的model1】_【复数的model2】的词语顺序,你可以在这里指定你的表名。
3.
foreignKey:
指向当前model的JOIN表的外键名。
在这里,你正在操作一个没有遵循Cake命名规则的数据库。
4.
associationForeignKey:指向关联model的外键名。
5.
conditions:定义关系的SQL条件语句
我们可以使用它告诉Cake仅关联一个通过验证的Tag。为了实现它,将键值设置为" Tag.approved = 1"或者其他类似的。
6.
order:关联model的顺序。
例如,如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:" Tag.tag DESC "。
7.
limit: 你想让Cake获取关联model的最大数
用来限制获取关联的Tag数。
8.
uniq:如果设置为true,重复的关联对象会被访问者和查询方法忽略。
一般来说,如果关联不同,将他设置为true。"Awesomeness"的Tag仅可以分配到Post "Cake Model
Associations"一次,并且在结果数组中仅出现一次。
9.
finderQuery:指定一个完整的SQL语句获取关联。
对于复杂的依赖多个表的关联,这是一种优秀的方法。如果Cake的自动关联不工作,在这里,你可以定义它。
10.deleteQuery:一完整的SQL语句,用来删除HABTM model之间的关联。
如果你不愿意按Cake的方法完成删除操作或者你的创建是以某种方法自己定义的,你可以在这里提供你自己的查询来改变本方法完成操作任务。
现在,当执行Post model的find()或findAll()调用时,在这里我们也应该可以看到我们关联的Tag model:
$post = $this->Post->read(null,
'2'); print_r($post); //输出: Array (
[Post] => Array
(
[id] => 2
[user_id] => 25
[title] => Cake Model Associations
[body] => Time saving, easy, and powerful.
[created] => 2006-04-15 09:33:24
[modified] => 2006-04-15 09:33:24
[status] => 1
)
[Tag] => Array
(
[0] => Array ( [id] => 247 [tag] => CakePHP )
[1] => Array ( [id] => 256 [tag] => Powerful
Software )
) ) |
保存相关的Model数据
当你操作关联的model时,需要记住的一件重要事情是,保存model数据应该一直由相应的Cake model来完成。如果正在保存一个新的Post以及它的Comment(留言)的话,那么在保存的过程中,你需要使用Post和Comment model。
如果在系统中不存在一个关联的model(例如,你想同时保存一个新的Post和一个相关的Comment),你需要首先保存主要的,或者说是父model。为了知道它如何进行,让我们想象一下,在我们的PostsController中有一个动作(action),它保存新的Post和相关Comment。下面的动作实例假设你已经发布一个Post和Comment。
/app/controllers/posts_controller.php (部分)
function add() { if (!empty($this->data)) {
//We can save the
Post data:
//it should be in $this->data['Post']
$this->Post->save($this->data);
//Now, we'll need
to save the Comment data
//But first, we need to know the ID for the
//Post we just saved...
$post_id = $this->Post->getLastInsertId();
//Now we add this
information to the save data
//and save the comment.
$this->data['Comment']['post_id'] = $post_id;
//Because our Post
hasMany Comments, we can access
//the Comment model through the Post model:
$this->Post->Comment->save($this->data); } } |
尽管如此,如果父Model在系统中已经存在(例如,加一个Comment到一个已存在的Post),在保存之前你需要知道父Model的ID。你可以将此ID作为URL参数,或者作为表单中的一个隐藏元素。
/app/controllers/posts_controller.php (部分)
//Here's how it would look if the URL param
is used... function addComment($post_id) { if (!empty($this->data)) {
//You might want to
make the $post_id data more safe,
//but this will suffice for a working example..
$this->data['Comment']['post_id'] = $post_id; //Because our Post hasMany Comments, we can access
//the Comment model through the Post model:
$this->Post->Comment->save($this->data); } } |
如果ID作为一个表单的隐藏元素传递的话,你可能想命名这个字段(如果你正在使用HtmlHelper),以至在发出的数据里它会中止,在这里,它应该为:
如果Post的ID是在 $post['Post']['id']...
<?php echo $html->hidden('Comment/post_id', array('value'
=> $post['Post']['id'])); ?> |
按这种方式,父Post model的ID可以在$this->data['Comment']['post_id']中访问,并且都为$this->Post->Comment->save($this->data)所准备。
如果你正保存多个子model,这些相同的基本技术都会工作,仅需要将那些save()调用放在一个循环(记住使用Model::Create()清除model信息哟)中。
总之,如果你正保存关联的数据(belongsTo, hasOne,
及 hasMany relations关系),重要的一点是得到父model的ID,并将他保存到子model。
保存 hasAndBelongsToMany 关系
保存hasOne, belongsTo, 以及hasMany的关联model是非常简单的:你仅需要将外键设置为关联model的ID。一旦完成,你也仅需要调用model的save()方法,这样所有的东东都会正确的关联了。
但是对于hasAndBelongsToMany,就有点晦涩了,但是我们已经特意让它尽可能简单。继续我们的例子,我们需要一些表单,这些表单将Tag关联到Post。现在让我们创建一个表单,此表单创建post,以及将他们和一个存在的tag列表关联。
事实上,你可能喜欢创建一个表单来创建新的tag,并将他们在空中关联-但是出于简单,我们仅需要为你说明如何将他们关联,并让你得到它。
在Cake中,当你正保存它自己的model,tag名(如果你在使用HTML
helper)应该是'Model/field_name'.让我们从创建post的表单部分开始吧:
创建post的/app/views/posts/add.thtml 表单
<h1>Write a New Post</h1> <table>
<tr>
<td>Title:</td>
<td><?php
echo html->input('Post/title')?></td>
</tr>
<tr>
<td>Body:<td>
<td><?php
echo $html->textarea('Post/title')?></td> </tr> <tr>
<td colspan="2">
<?php echo $html->hidden('Post/user_id',
array('value'=>$this->controller->Session->read('User.id')))?>
<?php echo $html->hidden('Post/status' ,
array('value'=>'0'))?>
<?php echo $html->submit('Save Post')?>
</td>
</tr> </table> |
当前用的表单仅创建Post记录。让我们增加一些代码使你将给定的Post绑定到一个或多个Tag:
/app/views/posts/add.thtml (增加的Tag 关联代码)
<h1>Write a New Post</h1> <table>
<tr>
<td>Title:</td>
<td><?php
echo $html->input('Post/title')?></td>
</tr>
<tr>
<td>Body:</td>
<td><?php
echo $html->textarea('Post/title')?></td>
</tr>
<tr>
<td>Related
Tags:</td>
<td><?php
echo $html->selectTag('Tag/Tag', $tags, null, array('multiple' =>
'multiple')) ?>
</td>
</tr>
<tr>
<td colspan="2"> <?php
echo $html->hidden('Post/user_id',
array('value'=>$this->controller->Session->read('User.id')))?> <?php
echo $html->hidden('Post/status' , array('value'=>'0'))?> <?php
echo $html->submit('Save Post')?>
</td>
</tr> </table> |
对于一个调用,为了让controller的$this->Post->save()保存新Post和它关联的Tag之间的联系,字段名必须是"Tag/Tag"形式(已显示的名字属性看起来是'data[ModelName][ModelName][]'),已提交的数据必须为一个ID,或者关联记录的ID数组。因为我们在这里正使用多个选择,所以提交的Tag/Tag数据应该为ID数组。
这里的$tags变量仅是一个数组,里面的键是可能的Tag的ID,值为多选元素里的显示的Tag名。
使用bindModel() 和 unbindModel()在空中改变关联
当创建应用程序时,你偶尔可能希望为一些特别的情况改变model关联信息。如果model文件里的关联设置给你太多(或不够)信息,你可以使用2个model函数为下一次的查找绑定和解除绑定关联。
让我们建立一些model,以至我们可以看看bindModel()和unbindModel()是如何工作的。我们将从2个model开始:
leader.php 和
follower.php
<?php class Leader extends AppModel { var $name = 'Leader'; var $hasMany = array(
'Follower' => array(
'className' => 'Follower',
'order' => 'Follower.rank'
)
); } ?> <?php class Follower extends AppModel { var $name = 'Follower'; } ?> |
现在,在LeadersController中,我们可以使用Leader Model中的find()方法准备一个Leader以及它的关联Follower。从上面可以看到,Leader model的关联数组定义了一个"Leader hasMany
Followers"的关系。出于描述的目的,让我们使用unbindModel()去除那个关联中间controller。
leaders_controller.php (部分)
function someAction() { //This fetches Leaders, and their
associated Followers $this->Leader->findAll(); //Let's remove the hasMany... $this->Leader->unbindModel(array('hasMany'
=> array('Follower'))); //Now a using a find function will return
Leaders, with no Followers $this->Leader->findAll(); //NOTE: unbindModel only affects the very
next find function.
//An additional find call will use the configured association
information.
//We've already used findAll() after unbindModel(), so this will fetch
//Leaders with associated Followers once again... $this->Leader->findAll(); } |
unbindModel()函数对其他关联类似:仅需要改变关联类型名以及model 类名。unbindModel()的基本使用方法如下:
普通的
unbindModel() 实例
$this->Model->unbindModel(array('associationType'
=> array('associatedModelClassName'))); |
既然我们成功移除了空中的一个关联,还是让我们增加一个吧。还没有Principle的Leader需要一些关联的Principle。我们的Principle model的文件还不太丰富,除了$name变量语句。让我们把这些Principle关联到Leader吧(而且为了跟踪查找函数哟):
leaders_controller.php (部分)
funciton anotherAction() { //There is no Leader hasMany Principles in
the leader.php model file, so
//a find here, only fetches Leaders. $this->Leader->findAll(); //Let's use bindModel() to add a new
association to the Principle model: $this->Leader->bindModel(
array('hasMany' => array( 'Principle' => array( 'className' =>
'Principle' )
)
)
); //Now that we're associated correctly, we
can use a single find function
//to fetch Leaders with their associated principles: $this->Leader->findAll(); } |
bindModel()函数可以便于创建一个新的关联,但是如果你想改变给定关联的排列顺序或则其他参数,它也会有用。
你已经有了它。bindModel的基本使用是封装一个普通的关联数组,在此数组里,它的键会在你试图创建的关联后命名:
普通
bindModel() 实例
$this->Model->bindModel(
array('associationName' => array( 'associatedModelClassName'
=> array( // normal association keys go here... )
)
)
); |
请注意,你的表需要正确的键(或者正确配置的关联数组)来绑定model。