本来想写model常用方法的使用,但是发现别人写的比我还要好。所以我就没有写了。
但是要指出部分内容是有误的。
find与findAll的区别不是find返回的是第一条符合条件的数据。
find是findAll,findFirst,findCount实际使用的方法。而这三个方法只是一个简化的后的方法。而find默认是返回findFirst形式的数据。
find的另类用法
$this->{model}->find('all',
array(
'conditions' => null,//Where的条件
'fields' => null,//显示的字段
'joins' => array(), //关联的表
'limit' => null,//显示的数据条数
'offset' => null,//移动的条数
'order' => null, //排序的条件
'page' => null, //页数
'group' => null, //Group By的条件
)
);
我用bindModel与unbindModel的形式
$reviseModel=array('belongTo'=>array('user'));//定义需要修改那部分的model
$this->{model}->unbindModel($reviseModel);//禁止使用revisedModel中定义的model
$this->{model}->find();//搜索数据
$this->{model}->bindModel($reviseModel);//重新加载之前禁止使用的model
以下是别人写的cakephp的model教程
Section 1 What is a model?
什么是model呢?model就是MVC模式中的M。
Model用来做什么呢?它将领域逻辑从表现层和独立的业务逻辑中剥离出来。
Model通常是数据库的访问介质,而且更明确的指定于某一张表(译注:在rails框架中model扮演着active record的角色,因此同时承担着DAO对象的职责)。默认情况下,model使用的表名为model名字的复数形式,比如'User'这个model 对应的表名为'users'。Model可以包含数据校验规则,关联关系以及针对这张表的业务逻辑。下面展示了User model在cake中的具体定义:
<?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...
}
}
?>
(译注:关于Model应该是贫血型,失学型还是充血型的论战持续至今尚无明显得胜的一方,本文中的这句话:Model将领域逻辑从表现层和独立的业务逻辑中剥离出来 说得还比较清晰的,起码在cake里面他就是这么遵循的)
Section 2 Model Functions
从PHP的角度来看Model,只是一个继承了AppModel的类。AppModel类在/cake目录中已经定义了,如果你希望创建一个自己的 AppModel基类,你可以放在app/app_model.php位置。AppModel中定义的方法将被所有model所继承并共享。而 AppModel则继承于Cake Lib中的Model类,文件为cake/libs/model.php。
本章将会讨论到大量已经在Cake Model类中定义的方法,所以这里提醒,http://api.cakephp.org 将是您非常重要的参考。
用户定义的Model函数
下面是一个用户定义的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');
}
}
}
?>
获取你需要的数据
findAll
string $conditions
array $fields
string $order
int $limit
int $page
int $recursive
[$conditions Type: string] 检索条件,就是sql中where子句,就像这样 $conditions = "race = 'wookie' AND thermal_detonators > 3"。
[$fields Type: array] 检索属性,就是投影,指定所有你希望返回的属性。 (译注:这里我没有使用字段这个亲SQL的名字而是用了属性这个亲对象的名字,我相信很多PHPer更熟悉基于Sql和DTO的开发模式,但是引入Model的一个初衷就是将关系数据库转换为对象操作,所以投影查询应该理解为检索对象的某些属性)
[$order Type: string] 排序属性 指定了oder by的属性名 [TODO:check whether the multiple order field be supported]
[$limit Type: int] 结果集数据条目限制
[$page Type: int] 结果集分页index,默认为返回第一页
[$recursive Type: int] 递归当设为大于1的整数时,将返回该model关联的其他对象。(译注:如果你使用过类似于Hibernate这样的ORM工具的话,不难理解这个属性就是 LazyLoad属性,但也不完全等同,数字的值代表级联查询的层次数,例如这样,user.country.city.address.id)
string $conditions
array $fields
string $order
int $recursive
string $value
这是一个非常有魔力的方法,你可以看成是一个快速按照某个属性检索数据的方法,下面是在controller中的一段示例代码:
<?
$this->Post->findByTitle('My First Blog Post');
$this->Author->findByLastName('Rogers');
$this->Property->findAllByState('AZ');
$this->Specimen->findAllByKingdom('Animalia'); ?>
返回类型和find() findAll()一样,是一个array。
string $conditions
array $field
string $value
这个方法首先是通过conditions过滤获取结果集,然后根据field=value查找符合的对象(仅包含$field中指定的属性),最终返回该对象以及该对象的上一个对象和下一个对象。这在诸如相册这样的应用场景中将会非常有作用,你可以方便的获取当前相片,上一张和下一张这样的结果集合。注意:该方法只能作用于数值型和日期时间型属性。下面是示例代码:
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']对象的array,以及$neighbours['prev']['Image']['id']和$neighbours['next']['Image']['id']。
field
string $name
string $conditions
string $order
返回conditions过滤后按order排序的第一个结果中的name属性值,返回类型为string。
string $conditions
返回conditions过滤后结果集的数量。即select count .....
generateList
string $conditions
string $order
int $limit
string $keyPath
string $valuePath
generateList方法可以非常快捷的获取一个key-value这样的list,其实就是其他语言中的map类型,在web领域中,下拉框可能将是该方法最大的受益者。$conditions, $order 和 $limit这3个参数的用法和findAll()没有区别,$keyPath 和 $valuePath是model中用来填充结果集中key和value的属性。举个例子,在权限操作中,角色的定义往往是id - name这样的组合,参见下面的示例代码:
<?
$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
根据$fields中的属性名从当前对象中读出对应的值,或者是$id指定的对象中读取。注意:read()方法仅级联读取该对象的一级关联对象,不管 model中的$recursive的值为多少。如果你是希望获取所有级联属性的话,请使用find()或者findAll()方法。
query
string $query
execute
string $query
有的时候希望执行自定义的sql语句或者是出于性能上的考虑要优化sql语句,则可以使用query(string $query)和execute(string $query)方法,这两个方法不同的地方在于execute方法无返回值,适用于执行脚本而不是查询结果集。
Custom Sql Calls with query()
<?php
class Post extends AppModel
{
var $name = 'Post';
function posterFirstName()
{
$ret = $this->query("SELECT first_name FROM posters_table
WHERE poster_id = 1");
$firstName = $ret[0]['first_name'];
return $firstName;
}
}
?>
复合查询条件(使用array)
绝大多数的Model查询方法中都会通过各种方式来传递一组条件。最简单的方式莫过于使用一个SQL中的where子句,不过如果你希望获得更多的控制,你可以使用array。使用rray的好处是代码会非常的清晰和易读,并且可以比较方便的构造查询。而且这样的语法可以保证你的查询组成元素(属性,值,运算符等)被拆分为精巧可控的几部分。这可以保证Cake能够生成绝大部分非常有效率的查询语句,并且避免了SQL语法上易犯的错误。
先看看非常基础的基于array的查询:
简单查询条件array示例:
$conditions = array("Post.title" => "This is a post");
这个结构恐怕是不言自明了:该查询返回所有title为"This is a post"的Post对象。注意,只使用"title"作为查询的属性名也是可以的,但是在构造查询的时候,使用 [ModelName.FieldName]这样的写法,一者可以让你的代码更加清晰,二者可以防止命名冲突,所以请遵循这个Good Practice。那其他的检索形式如何呢?同样很简单,比如我们想检索所有title不是"This is a post"的Post对象:
array("Post.title" => "<> This is a post")
唯一增加的就是'<>'这个表达式。Cake能够解析所有的合法SQL比较运算符,包括LIKE, BETWEEN或者是正则表达式,注意,运算符和值或者表达式之间需要有一个空格。唯一不同的操作是IN操作。我们看下具体的例子:
array("Post.title" => array("First post", "Second post", "Third post"))
为conditions增加更多filter就像往array中添加一对key-value那样简单:
<?
array
(
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks"))
)
?>
默认情况下,Cake使用'AND'来连接多个条件;这意味着,上面的示例代码检索结果为过去2周内title为"First post", "Second post"或者"Third post"的记录。当然我们也同样可以使用其他的逻辑运算符来连接查询条件:
<?
array
("or" =>
array
(
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks"))
)
)
?>
Cake可以使用任何SQL语句中允许的逻辑运算符,如AND, OR, NOT, XOR等等,并且大小写不敏感(Conditions可以无限嵌套,但我并不推荐使用这样的magic code)。Posts和Authors两个对象间分别是hasMany/belongsTo的关系(熟悉Hibernate的同学或许更喜欢一对多多对一的叫法)。假设你希望检索所有过去两周内发布的posts或者是包含有指定关键字的posts,并且你希望限定作者为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来完成这个任务,因为HTML Helper会为提交的Form封装成Cake希望的这种形式。当然你可以不使用,只要页面上的元素的name被设置成date[Modelname] [fieldname]形式就ok了。不过我还是要说,$html->input('Model/fieldname')是最方便的办法。
(译注:OGNL的php版,太棒了,我的意见是如果不嫌麻烦的话尽量使用类似OGNL的做法,因为tag产生的页面在设计期是无法预览的,我相信web应用前台页面的设计的复杂性并不亚于后台业务逻辑)
从页面Form中提交的数据自动被格式化并且注入到controller中的$this->data变量,所以保存从web页面提交的数据只是举手之劳。下面我们来看一段示例代码:
<?
function edit($id)
{
//Note: The property model is automatically loaded for us at $this->Property.
// Check to see if we have form data...
if (emptyempty($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
}
}
?>
注意保存数据前会触发model的校验机制,更多关于数据校验的内容请参见Chapter 12。如果你不希望进行数据校验,可以使用save($date, false)。
其他一些有用的数据保存函数
del
string $id
boolean $cascade
删除指定id的model对象,或者当前model对象。
如果$cascade设为true,则会级联删除当前model所关联的所有model中,设为'dependent'的model对象。删除成功返回true
saveField
string $name
string $value
保存单个属性值。
getLastInsertId
返回最后当前ID属性的nextValue。
[ToDo 扩展支持多种主键生成策略 例如sequence UUID]
Model中的回调函数
我们在model中增加了一些回调函数以帮助你在model操作前后能够织入一些业务逻辑(原文为sneak in,借用了AOP中的织入一词,因为从操作来看这些回调函数等同于AOP中的advice)。为了获得这样的能力,需要使用model中的一些参数并且在你的model中覆写这些方法。
beforeFind
string $conditions
beforeFind()回调函数是在model的find方法执行前的前置操作。你可以加入任何检索前的业务逻辑。你覆写该方法只要保证在前置操作成功后返回true来执行真正的find方法,返回false中断find方法就可以了。(译注:在一些复杂场景中,需多次持久化的情况下请慎用)
afterFind
array $results
使用afterFind回调函数能够更改find方法的返回结果集,或者在检索动作完成后加上一些业务逻辑。该函数的参数$results为经过回调函数处理以后的find检索结果集。
beforeValidate
beforeValidate回调函数能够在model校验数据之前更改model中的一些数据。同样也可以用来在model校验之前加入更为复杂的额外校验规则。和beforeFind一样,必须保证返回true来调用真正的操作,返回false来中断校验乃至save操作。
beforeSave
和先前介绍的回调函数类似,在校验完成之后,保存动作之前加入额外的处理(如果校验失败是不会触发该回调函数的)。返回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
[code]
保存完成后执行的动作。[ToDo 如果保存出错是否会触发?]
[code]
beforeDelete
afterDelete
不需要多说了吧,删除操作前后的回调函数。
Section 3 Model中的变量
当你创建一个新的model的时候,有很多特殊的变量可以设置,从而是model具备Cake赋予的相应操作。
$primaryKey
如果主键字段不为'id',COC无法发挥的时候,你可以通过该变量来指定主键字段名字。
$recursive
这个我们上面已经介绍过了,指定了model级联关联对象的深度。
想象这样一个场景,Group下的User下面还有各自的Articles。
$recursive = 0 Cake只会检索Group对象
$recursive = 1 Cake会检索包含User对象的Group对象
$recursive = 2 Cake会检索完成的包含Article的Group对象
$transactional
决定Model是否允许事务处理,true 或者 false。注意,仅在支持事务的数据库上有效。
$useTable
和$primaryKey一样,如果你的表名不能和model匹配的话,而且你也不想或者不能修改表名,则可以通过该变量来指定数据表名。
$validate
该变量为一个array,表示一组校验规则,详细请参见Chapter 12。
$useDbConfig
还记得我们在配制一章介绍的如何配置数据库连接变量吗?可以通过该变量非常方便的切换数据库连接,默认的是什么?猜一下,当然就是'default',rails就是让你忘却那些难以记忆的配置。
Section 4 [重头戏]关联对象
简介
CakePHP中提供的一个非常重要的功能就是关联数据表间的映射。在CakePHP中,关联表通过association(关联)来处理。关联是这些逻辑上相关的单元间的胶水一般。
CakePHP中一共有4种类型的关联:
hasOne
hasMany
belongsTo
hasAndBelongsToMany
一旦model间的关联关系被定义,Cake能够自动检索你当前操作的model中包含的关联对象。也就是将基于关系数据库的数据映射为基于对象的领域模型。举个例子,Post和Author之间是hasMany(一对多)关系,当我们在controller中通过$this->Post- >findAll()来检索所有的Post对象时,也会同时把所有关联的Author对象检索出来。
遵循CakePHP 的命名约定是正确定义关联关系的有利保证(参见附录 B)。如果你使用了CakePHP的命名约定,可以通过脚手架来可视化你的数据,因为脚手架会自动侦测并使用你定义的关联关系,这样也能某种程度上供你检查是否定义正确。当然,你也可以完全不使用命名约定来定义关联,不过我们稍候再介绍关于这种定义。现在我们仅关注使用命名约定的情况下的关联定义。命名约定中我们需要考虑的有这3个内容,外键,model名字,表名。
这里先简单的介绍一下关于这3者的要求,更为详细的请查看附录:
外键:[单数形式的model名字]_id 假设在"authors"表中有Post的外键关联,则外键字段名字应该为 "post_id"。
表名:[复数形式的model名字] 表名必须为model名字的复数形式,例如:"posts" "authors"。
Model的名字:[驼峰法命名 单数形式]。
CakePHP 的脚手架希望你的关联定义的顺序和表中的外键字段的顺序是一致的。所以如果我有一个Article对象[belongsTo(属于)]另外3个对象(Author Editor Publisher)的话,我需要3个外键author_id, editor_id, publisher_id。脚手架要求你model中对应的关联和数据库中的列顺序保持一致。
为了更好的描述关联对象是如何运作的,让我们继续以Blog为应用场景来介绍。假设我们现在需要为Blog系统创建一个简单的用户管理模块。我们假设我们不需要跟踪用户情况,但是我们希望每个用户都一个个人记录(用户 [hasOne] 个人记录)。用户可以创建多条评论记录(用户 [hasMany] 评论记录)。同样的,我们的文章会被分配到多个tag,同时每个tag都包含多篇文章,也就是多对多关系 (文章 [hasAndBelongsToMany] Tag)。
hasOne 关联的定义与查询
假设你已经准备好了User和Profile两个model,让我们来定义他们之间的关联。hasOne关联的定义是通过在model中增加一个array来实现的。下面是示例代码:
/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变量是一个array,Cake通过该变量来构建User与Profile之间的关联。我们来看每一个元素代表的意义:
* className (required):关联对象的类名,上面代码中我们设为'Profile'表示关联的是Profile对象。
* conditions: 关联对象的选择条件,(译注:类似hibernate中的formula)。具体到我们的例子来看,假设我们仅关联Profile的header color为绿色的文件记录,我们可以这样定义conditions,"Profile.header_color = 'green'"。
* order: 关联对象的排序方式。假设你希望关联的对象是经过排序的,你可以为order赋值,就如同SQL中的order by子句:"Profile.name ASC"。
* dependent:这是个布尔值,如果为true,父对象删除时会级联删除关联子对象。在我们的Blog中,如果"Bob"这个用户被删除了,则关联的Profile都会被删除。类似一个外键约束。
* foreignKey:指向关联Model的外键字段名。仅在你不遵循Cake的命名约定时需要设置。
现在,现在当我们使用find() findAll()检索User对象时,你会发现关联的Profile对象也被检索回来,非常的方便:
$user = $this->User->read(null, '25');
print_r($user);
//output:
Array
(
[User] => Array
(
[id] => 25
[first_name] => John
[last_name] => Anderson
[username] => psychic
[password] => c4k3roxx
)
[Profile] => Array
(
[id] => 4
[name] => Cool Blue
[header_color] => aquamarine
[user_id] = 25
)
)