创建模型行为
现在的web应用中,有许多相似的解决方案。龙头产品例如google的Gmail,有这两个的UI模式。其中一个就是软删除。不需要点击成吨的确认进行永久删除,Gmail允许我们将信息立刻标记为删除,然后可以很容易的撤销它。相同的行为可以应用于任何对象上,例如博客帖子、评论等等。
下边我们来创建一个行为,它将允许我们将模型标记为删除,选择还未删除的模型,删除模型,以及所有的模型。在本小节中,我们将会进行一个测试驱动的开发方法,来计划这个行为,并测试实现是否正确。
准备
- 按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的
yii2-app-basic
应用。 - 创建两个数据库分别用于工作和测试。
- 在你的主应用
config/db.php
中配置Yii来使用第一个数据库。确认测试应用使用第二个数据库tests/codeception/config/config.php
。 - 创建一个新的migration:
<?php
use yii\db\Migration;
class m160427_103115_create_post_table extends Migration
{
public function up()
{
$this->createTable('{{%post}}', [
'id' => $this->primaryKey(),
'title' => $this->string()->notNull(),
'content_markdown' => $this->text(),
'content_html' => $this->text(),
]);
}
public function down()
{
$this->dropTable('{{%post}}');
}
}
- 应用migration到工作和测试数据库上:
./yii migrate
tests/codeception/bin/yii migrate
- 创建
Post
模型:
<?php
namespace app\models;
use app\behaviors\MarkdownBehavior;
use yii\db\ActiveRecord;
/**
* @property integer $id
* @property string $title
* @property string $content_markdown
* @property string $content_html
*/
class Post extends ActiveRecord
{
public static function tableName()
{
return '{{%post}}';
}
public function rules()
{
return [
[['title'], 'required'],
[['content_markdown'], 'string'],
[['title'], 'string', 'max' => 255],
];
}
}
如何做...
准备一个测试环境,为Post
模型定义fixtures。创建文件tests/codeception/unit/fixtures/PostFixture.php
:
<?php
namespace app\tests\codeception\unit\fixtures;
use yii\test\ActiveFixture;
class PostFixture extends ActiveFixture
{
public $modelClass = 'app\models\Post';
public $dataFile = '@tests/codeception/unit/fixtures/data/post.php';
}
- 添加一个fixture数据到
tests/codeception/unit/fixtures/data/post.php
:
<?php
return [
[
'id' => 1,
'title' => 'Post 1',
'content_markdown' => 'Stored *markdown* text 1',
'content_html' => "<p>Stored <em>markdown</em> text 1</p>\n",
],
];
- 然后,我们需要创建一个测试用例,
tests/codeception/unit/MarkdownBehaviorTest.php
:
<?php
namespace app\tests\codeception\unit;
use app\models\Post;
use app\tests\codeception\unit\fixtures\PostFixture;
use yii\codeception\DbTestCase;
class MarkdownBehaviorTest extends DbTestCase
{
public function testNewModelSave()
{
$post = new Post();
$post->title = 'Title';
$post->content_markdown = 'New *markdown* text';
$this->assertTrue($post->save());
$this->assertEquals("<p>New <em>markdown</em> text</p>\n", $post->content_html);
}
public function testExistingModelSave()
{
$post = Post::findOne(1);
$post->content_markdown = 'Other *markdown* text';
$this->assertTrue($post->save());
$this->assertEquals("<p>Other <em>markdown</em> text</p>\n", $post->content_html);
}
public function fixtures()
{
return [
'posts' => [
'class' => PostFixture::className(),
]
];
}
}
- 运行单元测试:
codecept run unit MarkdownBehaviorTest
Ensure that tests has not passed:
Codeception PHP Testing Framework v2.0.9
Powered by PHPUnit 4.8.27 by Sebastian Bergmann and
contributors.
Unit Tests (2)
----------------------------------------------------------------
-----------
Trying to test ...
MarkdownBehaviorTest::testNewModelSave Error
Trying to test ...
MarkdownBehaviorTest::testExistingModelSave Error
----------------------------------------------------------------
-----------
Time: 289 ms, Memory: 16.75MB
- 现在我们需要实现行为,将它附加到模型上,并确保测试通过。创建一个新的文件夹
behaviors
。在这个文件夹中,创建一个MarkdownBehavior
类:
<?php
namespace app\behaviors;
use yii\base\Behavior;
use yii\base\Event;
use yii\base\InvalidConfigException;
use yii\db\ActiveRecord;
use yii\helpers\Markdown;
class MarkdownBehavior extends Behavior
{
public $sourceAttribute;
public $targetAttribute;
public function init()
{
if (empty($this->sourceAttribute) ||
empty($this->targetAttribute)) {
throw new InvalidConfigException('Source and target must be set.');
}
parent::init();
}
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_INSERT => 'onBeforeSave',
ActiveRecord::EVENT_BEFORE_UPDATE => 'onBeforeSave',
];
}
public function onBeforeSave(Event $event)
{
if
($this->owner->isAttributeChanged($this->sourceAttribute)) {
$this->processContent();
}
}
private function processContent()
{
$model = $this->owner;
$source = $model->{$this->sourceAttribute};
$model->{$this->targetAttribute} =
Markdown::process($source);
}
}
- 附加行为到Post模型上:
class Post extends ActiveRecord
{
...
public function behaviors()
{
return [
'markdown' => [
'class' => MarkdownBehavior::className(),
'sourceAttribute' => 'content_markdown',
'targetAttribute' => 'content_html',
],
];
}
}
- 运行测试并确保通过:
Codeception PHP Testing Framework v2.0.9
Powered by PHPUnit 4.8.27 by Sebastian Bergmann and
contributors.
Unit Tests (2)
----------------------------------------------------------------
-----------
Trying to test ...
MarkdownBehaviorTest::testNewModelSave Ok
Trying to test ...
MarkdownBehaviorTest::testExistingModelSave Ok
----------------------------------------------------------------
-----------
Time: 329 ms, Memory: 17.00MB
- 完成了。我们已经创建了一个可复用的行为,并可以使用它用于所有未来的项目中,只需要将它连接到一个模型上。
工作原理...
首先看下测试用例。因为我们希望使用模型集,我们定义了fixtures。每次测试方法被执行的时候,一个fixture集合被放到了数据库中。
我们准备单元测试用以说明行为是如何工作的:
- 首先,我们测试一个新的模型内容的处理。这个行为会将source属性中的markdown格式的文本,转换为HTML,并存储在target属性中。
- 第二,我们对更新已有模型的内容进行测试。在修改了markdown内容以后,保存这个模型,我们可以得到更新后的HTML内容。
现在,我们转到有趣的实现细节上。在行为中,我们可以添加我们自己的方法,它将会被混合到附带有行为的模型中。此外,我们可以订阅拥有者的组件事件。我们使用它添加一个自己的监听:
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_INSERT => 'onBeforeSave',
ActiveRecord::EVENT_BEFORE_UPDATE => 'onBeforeSave',
];
}
现在,我们可以实现这个监听器:
public function onBeforeSave(Event $event)
{
if ($this->owner->isAttributeChanged($this->sourceAttribute))
{
$this->processContent();
}
}
在所有的方法中,我们可以使用owner
属性来获取附带有行为的对象。一般情况下,我们可以附加任何行为到我们的模型、控制器、应用,以及其它继承了yii\base\Component
类的组件。此外,我们可以重复附加一个行为到模型上,用以处理不同的属性:
class Post extends ActiveRecord
{
...
public function behaviors()
{
return [
[
'class' => MarkdownBehavior::className(),
'sourceAttribute' => 'description_markdown',
'targetAttribute' => 'description_html',
],
[
'class' => MarkdownBehavior::className(),
'sourceAttribute' => 'content_markdown',
'targetAttribute' => 'content_html',
],
];
}
}
此外,我们可以像yii\behaviors\TimestampBehavior
继承yii\base\AttributeBehavior
,用以为任何事件更新指定的属性。
参考
为了了解更多关于行为和事件,参考如下页面:
- http://www.yiiframework.com/doc-2.0/guide-concept-behaviors.html
- http://www.yiiframework.com/doc-2.0/guide-concept-events.html
欲了解更多关于markdown语法的信息,参考http://daringfireball.net/projects/markdown/。
此外,参考本章中的制作可发布的扩展小节。