CakePHP 的 Active Record 模式
我想使用 CakePHP 构建一个状态引擎,我意识到我需要某种 Active Record 模式。因此,我首先构建了一个行为,它允许我检索对象来代替关联数组。
安装
要下载它,请访问 https://github.com/boutinb/Active-Record-for-CakePHP 我只在 CakePHP 2.x 上测试过它。
- 将 ActiveRecordBehavior.php 复制到您的 Behavior 文件夹中
- 告诉您的模型使用它:`$actsAs = array('ActiveRecord' => array())`
- 当您使用 `find('all')` 或 `find('first')` 函数时,添加选项 `'activeRecord' => true`
我选择这种方式,因为我不想在每次调用 find 函数时都检索对象。但是可以使用另一种方式:在行为的构造函数中添加选项 `'allFind' => true`,如果您在 find 之后不想要对象,请添加 `'activeRecord' => false`(此可能性尚未经过全面测试:我担心 cake 有时会生成一个需要关联数组的 `'find'` 调用)。
使用方法
当您检索一个对象记录时,您可以这样使用它:假设您有以下模型
- Post (title, message) belongsTo Writer, hasMany Comments, hasAndBelongsToMany Tags
- Writer (name) hasMany Posts, belongsTo WriterGroup
- WriterGroup (name) hasMany Writers
- Comment (message) belongsTo Post
- Tag (name) hasAndBelongsToMany Posts
调用 `find('first')` 或 `find('all')` 来检索帖子,并且对于一个帖子,您可以执行以下操作
- `$message = $post->message`:这将检索帖子的消息
- `$post->message = 'Hallo'`:这将更新帖子的消息
- `$writer = $post->Writer`:这将检索 Writer Active Record 对象
- `$comments = $post->Comments`:这将检索 ActiveRecordAssociation 对象
该行为在 belongsTo/hasOne 关联和 hasMany/hasAndBelongsToMany 关联之间有所区别
对于 belongsTo 和 hasOne 关联,将检索关联指向的 Active Record 对象,您可以直接使用它:例如 `$post->Writer->WriterGroup->name`
对于 hasMany 和 hasAndBelongsToMany 关联,将检索 ActiveRecordAssociation 对象。该对象的类实现了 IteratorAggregate、Countable 和 ArrayAccess 接口,因此您可以将其用作数组
- `foreach ($post->Comments as $comment) {}`
- `count($post->Comments);`
- `$comments = $post->Comments; $first_comment = $comments[0];`
但 ActiveRecordAssociation 类也具有 3 个函数
- `$comments->add($new_comment);`
- `$comments->remove($first_comment);`
- `$comments->replace($first_comment, $new_comment);`
为了让开发人员清楚地看到两种关联之间的区别,我建议对 hasMany 和 hasAndBelongsToMany 关联使用复数名称,对 hasOne 和 belongsTo 关联使用单数名称。
扩展 ActiveRecord 类
默认情况下,您检索的对象是 ActiveRecord 类的对象。但是您当然可以扩展此类以适用于一个模型。默认情况下,该行为将在 ModelActiveRecord 子文件夹中查找名为 `'AR'` 的类,例如:`ARPost` 或 `ARComment`(`'AR'` 前缀和子文件夹名称可以在行为构造函数选项中更改)。在文件 `ARPost.php` 中
`
App::import('Model\Behavior','ActiveRecord');
classARTPostextendsActiveRecord{
public$var;
publicfunctionfunc(){...}
}
`
如果您需要使用构造函数
`
publicfunction__construct(array$record,array$options=array()){
parent::__construct($record,$options);
...
}
`
然后您可以在代码中使用 `$post->var` 和 `$post->func()`。您也可以创建新对象
`
$post=newARPost(array(
'title'=>'Mytitle',
'message'=>'OK',
'Writer'=>$writer));
`
这里变得非常棒:您可以直接说 `'Writer' => $writer`,而不是告诉帖子的 `writer_id` 应该为 `$writer->id`。您还可以执行以下操作
`
$post->Comments=array($comment);
`
这将自动将 `$comment->post_id` 设置为正确的值。
有用函数
我意识到在使用 hasMany(或 hasOne 关联)时,当您执行以下操作时
`
$post->Comments->remove($comment);
or
$post->Comments=null;
`
您不仅想从 `$post` 中删除 `$comment`,而且大多数情况下,您想删除 `$comment`。我认为如果自动完成这一点会非常方便。为此,如果您在关联定义中将 `'deleteWhenNotAssociated'` 设置为 `true`,该行为将自动删除从关联中删除的所有记录。
该行为还提供删除、刷新和撤消 Active Record 的可能性
`
$post->delete();//deletethispostrecord
$post->refresh();//querythevaluesofpostinthedatabase.
$post->undo();//undoallmodificationdoneinthe$postrecord.
`
对 Active Record 所做的修改不会发送到数据库。这仅在调用 `save()` 方法时执行。但 `$post->save()` 仅会保存对 post 记录的修改,而不会保存其关联记录中的修改。要保存您所做的所有修改(显式和隐式),请使用 `$post->saveAll()` 或 `ActiveRecord::saveAll()` 方法。此外,`saveAll()` 会确保以正确的顺序保存记录。例如
`
$comment=newARComment(array('message'=>'Newmessage'));
$post=newARPost(array('title'=>'Newtitle','message'=>'NewMessage','Writer'=>$writer));
$post->Comment=array($comment);
ActiveRecord::saveAll();
`
然后 `saveAll()` 会确保首先创建 `$post`,以便其 ID 可以设置到 `$comment->post_id`。
使用此 Active Record 模式真正棒的是,您不再需要担心键以及如何构建关联数组以确保 CakePHP 正确保存您的数据(特别是对于 hasAndBelongsToMany 关联!)。
进一步扩展
我还需要一个拥有 ActiveRecord 子类的可能性。例如,我有一个 Action 模型,但我需要为每种类型的操作定义子类。子类操作可以使用(子)模型,也可以不使用。为此,我告诉该行为检查模型是否具有函数 `getActiveRecordProperties()`,如果有,则在构建新的 ActiveRecord 之前调用它。此函数告诉该行为必须调用哪个实际的 ActiveAction 名称,使用哪个模型以及使用哪些数据。以下是一个示例
我的 Action 模型有一个 `type` 列。此列将确定必须调用哪种 Active Record 类。然后在我的 Action 模型中,我添加了此函数
`
publicfunctiongetActiveRecordProperties(&$record){
$type=$record[$this->alias]['type'];
$active_record_name='AR'.$type.'Action';
$model=$this;
App::import('Model\ActiveRecord',$active_record_name);
returnarray('active_record_name'=>$active_record_name,'record'=>$record,'model'=>$model);
}
`
我的 ARAction 如下所示
`
abstractclassARActionextendsAppActiveRecord{
abstractpublicfunctionexecute(ARUserState$user_state,$parameter);
}
`
SendEmail 子操作如下所示
`
classARSendEmailActionextendsARAction{
publicfunctionexecute(ARUserState$user_state,$parameter){
....
}
}
`
然后,如果我的 Action 表中有一条类型为 `'SendEmail'` 的记录,`Action->find()` 将返回一个 `ARSendEmailAction` 类的对象。在调用 `execute()` 时,它将调用正确的函数,该函数将发送电子邮件。在这里,`ARSendEmailAction` 使用与 `ARAction` 相同的模型,但如果需要,我可以将其设置为另一个模型。