imsamurai/active-record-for-cakephp

CakePHP 的活动记录实现

1.3.1 2014-07-10 11:27 UTC

README

Build Status Coverage Status Latest Stable Version Total Downloads Latest Unstable Version License

安装

我想用 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相同的模型,但如果需要,我可以将其设置为另一个。