imsamurai / active-record-for-cakephp
CakePHP 的活动记录实现
Requires
README
安装
我想用 CakePHP 构建一个状态引擎,我意识到我需要一种活动记录模式。所以我首先构建了一个行为,允许我使用对象而不是关联数组来检索对象。
我仅在 cakePHP 2.3.x,2.4.x 上测试了它
Composer(例如版本 1.0.0)
{ "require": { "imsamurai/active-record-for-cakephp": "1.0.0" } }
它安装在 Plugin
目录中(与 composer.json 同级),因此您可能希望将 Plugin/Task
添加到忽略文件中。
或克隆
cd my_cake_app/app
git clone git://github.com/imsamurai/active-record-for-cakephp.git Plugin/ActiveRecord
或如果您使用 git add 作为子模块
cd my_cake_app
git submodule add "git://github.com/imsamurai/active-record-for-cakephp.git" "app/Plugin/ActiveRecord"
然后在 Config/bootstrap.php 中添加插件加载
CakePlugin::load('ActiveRecord', array('bootstrap' => true, 'routes' => false));
使用方法
- 告诉您的模型使用它:$actsAs = array('ActiveRecord' => array())
- 当您使用 find('all') 或 find('first') 函数时,添加选项 'activeRecord' => true
我选择这种方式,因为我不想在调用 find 函数时总是检索对象。但也可以用另一种方式使用它:在行为构造函数中添加选项 'allFind' => true,如果您不想在 find 之后得到对象,则添加 'activeRecord' => false(这个可能性还没有彻底测试:我担心 cake 有时生成一个需要关联数组的 'find' 调用)。
如何使用它
当您检索对象记录时,您可以这样使用它:假设您有以下模型
- Post (title, message) 属于 Writer,具有 Comments,与 Tags 有 hasAndBelongsToMany 关联
- Writer (name) 有 Posts,属于 WriterGroup
- WriterGroup (name) 有 Writers
- Comment (message) 属于 Post
- Tag (name) 与 Posts 有 hasAndBelongsToMany 关联
调用 find('first') 或 find('all') 来检索帖子,然后用一个帖子做以下操作
- $message = $post->message : 这检索帖子的消息
- $post->message = 'Hallo' : 这更新帖子的消息
- $writer = $post->Writer : 这检索 Writer ActiveRecord 对象
- $comments = $post->Comments : 这检索 ActiveRecordAssociation 对象
该行为在 belongsTo/hasOne 关联和 hasMany/hasAndBelongsToMany 关联之间做出区分
-
对于 belongsTo 和 hasOne 关联,检索指向关联的 ActiveRecord 对象,可以直接使用:例如 $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类。但当然,您可以为此模型扩展此类。默认情况下,行为将查找名为'Model/ActiveRecord'的子文件夹中的名为'AR'的类,例如:ARPost或ARComment('AR'前缀和子文件夹名称可以在行为构造函数选项中更改)。在ARPost.php文件中
<?php
App::import('Model'.DS.'Behavior', 'ActiveRecord');
class ARTPost extends ActiveRecord {
public $var;
public function func() {...}
}
?>
如果您需要使用构造函数
public function __construct(array $record, array $options = array()) {
parent::__construct($record, $options);
...
}
那么您可以在代码中使用$post->var和$post->func()。您也可以创建新对象
$post = new ARPost(array(
'title' => 'My title',
'message' => 'OK',
'Writer' => $writer));
这里变得非常方便:在告诉帖子writer_id应该是$writer->id的地方,您可以直接说'Writer' => $writer您也可以这样做
$post->Comments = array($comment);
这将自动将$comment->post_id设置到正确的值。
有用的函数
我意识到在使用hasMany(或hasOne关联)时,当您这样做
$post->Comments->remove($comment);
或
$post->Comments = null;
您不仅想要从$post中移除$comment,而且大多数情况下您还想要删除$comment。我认为如果这可以自动完成,将会非常方便。为此,如果您在关联定义中将'deleteWhenNotAssociated'设置为true,则行为将自动删除从关联中删除的所有记录。
行为还提供了删除、刷新和撤销ActiveRecord的可能性
$post->delete(); // delete this post record
$post->refresh(); // query the values of post in the database.
$post->undo(); // undo all modification done in the $post record.
在active记录中进行的修改不会发送到数据库。这仅在调用save()方法时才会完成。但$post->save()只会保存帖子记录的修改,而不会保存其关联记录的修改。要保存您所做的所有修改(显式和隐式),请使用$post->saveAll()或ActiveRecord::saveAll()方法。此外,saveAll()会确保记录按正确的顺序保存。例如
$comment = new ARComment(array('message' => 'New message'));
$post = new ARPost(array('title' => 'New title', 'message' => 'New Message', 'Writer' => $writer));
$post->Comment = array($comment);
ActiveRecord::saveAll();
然后saveAll()会确保首先创建$post,以便将其id设置到$comment->post_id。
Active Record模式真正令人喜爱的是,您不再需要担心键和如何构建关联数组,以确保cakePHP可以正确保存您的数据(特别是对于hasAndBelongsToMany关联!)
进一步扩展
我还需要有ActiveRecord的子类。例如,我有一个Action模型,但我需要为每种动作定义子类。一个子类动作可以使用(子)模型或不使用。为此,我告诉行为检查模型是否有getActiveRecordProperties()函数,如果有,则在构建新的ActiveRecord之前调用它。此函数告诉行为必须调用哪个真实的ActiveAction名称,以及使用哪个模型和哪个数据。以下是一个示例
我的模型Action有一个类型列。此列将确定它必须调用哪个ActiveRecord类。然后在Action模型中,我添加了这个函数
public function getActiveRecordProperties(&$record) {
$type = $record[$this->alias]['type'];
$name = 'AR' . $type . 'Action';
$model = $this;
App::import('Model'.DS.'ActiveRecord', $name);
return array('name' => $name, 'Record' => $record, 'Model' => $model);
}
我的ARAction看起来像这样
abstract class ARAction extends AppActiveRecord {
abstract public function execute(ARUserState $user_state, $parameter);
}
发送电子邮件子动作看起来像这样
class ARSendEmailAction extends ARAction {
public function execute(ARUserState $user_state, $parameter) {
....
}
}
然后,如果我的Action表中有一个类型为'SendEmail'的记录,Action->find()将返回Class ARSendEmailAction的对象。当调用execute()时,它将调用正确的一个,将发送电子邮件。在这里,ARSendEmailAction使用与ARAction相同的模型,但如果需要,我可以将其设置为另一个。