创建模型行为
现在的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:
<?phpuse 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 migratetests/codeception/bin/yii migrate
- 创建
Post模型:
<?phpnamespace 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:
<?phpnamespace 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:
<?phpreturn [['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:
<?phpnamespace 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 MarkdownBehaviorTestEnsure that tests has not passed:Codeception PHP Testing Framework v2.0.9Powered by PHPUnit 4.8.27 by Sebastian Bergmann andcontributors.Unit Tests (2)---------------------------------------------------------------------------Trying to test ...MarkdownBehaviorTest::testNewModelSave ErrorTrying to test ...MarkdownBehaviorTest::testExistingModelSave Error---------------------------------------------------------------------------Time: 289 ms, Memory: 16.75MB
- 现在我们需要实现行为,将它附加到模型上,并确保测试通过。创建一个新的文件夹
behaviors。在这个文件夹中,创建一个MarkdownBehavior类:
<?phpnamespace 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.9Powered by PHPUnit 4.8.27 by Sebastian Bergmann andcontributors.Unit Tests (2)---------------------------------------------------------------------------Trying to test ...MarkdownBehaviorTest::testNewModelSave OkTrying 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/。
此外,参考本章中的制作可发布的扩展小节。
