Есть много подобных решений в современных веб-приложениях. Ведущие продукты, такие как Google Gmail, определяют хорошие шаблоны пользовательского интерфейса. Один из них-мягкое удаление. Вместо постоянного удаления с тоннами подтверждений, Gmail позволяет нам сразу пометить сообщения как удаленные, а затем легко отменить его. Такое же поведение можно применить к любому объекту, например к записям в блоге, комментариям и т. д. Давайте создадим поведение, которое позволит отмечать модели как удаленные, восстанавливать модели, выбирать еще не удаленные модели, удаленные модели и все модели. В этом рецепте мы будем следовать тестовому подходу к разработке для планирования поведения и тестирования, если реализация верна.
1 Создайте новое приложение с помощью диспетчера пакетов Composer, как описано в официальном руководстве по адресу http://www.yiiframework.com/doc-2.0/guide-start-installation.html. По русски http://yiiframework.domain-na.me/doc/guide/2.0/ru/start-installation.
2 Создание двух баз данных для работы и тестирования.
3 Настройте Yii для использования первой базы данных в основном приложении в config/db.php. Убедитесь, что тестовое приложение использует вторую базу данных в tests/codeception/config/config.php.
4 Создание новой миграции:
<?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}}');
}
}
5 Применить миграции рабочей и тестовой базы данных:
./yii migrate
tests/codeception/bin/yii migrate
6 Создание модели 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. Создать файл 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';
}
1 Файл добавить данные фикстуры для 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",
],
];
2 Затем, нам нужно создать тест, 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(),
]
];
}
}
3 Запустите unit тесты:
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
4 Теперь нам нужно реализовать поведение, присоединить его к модели и убедиться, что тест пройден. Создайте новый каталог, поведение. В этом каталоге создайте класс 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);
}
}
5 Давайте прикрепим поведение к модели Post:
class Post extends ActiveRecord
{
public function behaviors()
{
return [
'markdown' => [
'class' => MarkdownBehavior::className(),
'sourceAttribute' => 'content_markdown',
'targetAttribute' => 'content_html',
],
];
}
}
6 Запустите тест и убедитесь, что он проходит:
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
7 Вот и все. Мы создали многоразовое поведение и можем использовать его для всех будущих проектов, просто подключив его к модели.
Начнем с теста. Поскольку мы хотим использовать набор моделей, мы определяем fixtures. Набор fixtures помещается в “базу данных " при каждом выполнении метода тестирования. Мы готовим модульные тесты для определения того, как должно работать поведение:
- Во-первых, мы тестируем обработку контента новой модели. Поведение должно преобразовать текст Markdown из исходного атрибута в HTML и сохранить второй в целевой атрибут.
- Во-вторых, мы проводим тестирование для обновления содержания существующей модели. После изменения содержимого Markdown и сохранения модели мы должны получить обновленное содержимое HTML. Теперь перейдем к интересным деталям реализации. В behavior мы можем добавить собственные методы, которые будут смешаны с моделью, к которой прикреплено поведение. Кроме того, мы можем подписаться на события компонента владельца. Мы используем его, чтобы добавить свой слушатель:
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\base\AttributeBehavior как yii\behaviors\TimestampBehavior для обновления заданных атрибутов для любых событий.
Дополнительные сведения о поведении и событиях см. на следующих страницах:
- http://www.yiiframework.com/doc-2.0/guide-concept-behaviors.html по русски http://yiiframework.domain-na.me/doc/guide/2.0/ru/concept-behaviors
- http://www.yiiframework.com/doc-2.0/guide-concept-events.html по русски http://yiiframework.domain-na.me/doc/guide/2.0/ru/concept-events
Дополнительные сведения о синтаксисе Markdown см. в разделе http://daringfireball.net/projects/markdown/.
Кроме того, обратитесь к рецепту Созданию расширений распределения этой главы.