Статьи

Как программировать с Yii2: виноватое поведение

Конечный продукт
Что вы будете создавать

Если вы спрашиваете: «Что такое Yii?» Посмотрите мой предыдущий учебник: Введение в Yii Framework , который рассматривает преимущества Yii и включает обзор того, что нового в Yii 2.0, выпущенном в октябре 2014 года.

В этой серии «Программирование с Yii2» я расскажу читателям, как использовать обновленную версию Yii2 Framework для PHP. В этом руководстве мы познакомим вас с другим интересным поведением Yii2: помогите автоматизировать общую задачу веб-разработки, состоящую в назначении, созданном и обновленном user_ids, для всех моделей в вашем веб-приложении с использованием DRY-кодирования и Yii2 BlameableBehavior. Мы также создадим журнал, в котором будут записываться, кто обновлял таблицу состояния для каждого внесенного изменения.

Для этих примеров мы продолжим предполагать, что мы создаем структуру для публикации простых обновлений статуса, например, наш собственный мини-Twitter.

Просто напоминание, я участвую в комментариях ниже. Мне особенно интересно, если у вас есть другие подходы, дополнительные идеи или вы хотите предложить темы для будущих уроков.

Yii2 Поведения по сути являются миксинами . Википедия описывает миксины как «класс, который содержит комбинацию методов из других классов. То, как такая комбинация выполняется, зависит от языка, но не от наследования».

Yii описывает их так:

Присоединение поведения к компоненту «внедряет» методы и свойства поведения в компонент, делая эти методы и свойства доступными, как если бы они были определены в самом классе компонента.

Yii2 предлагает несколько встроенных поведений, большинство из которых мы будем документировать, в том числе медленный (см. Программирование с Yii2: медленное поведение ), обвиняемый и отметка времени (в ближайшее время, проверьте страницу серии ). Поведения — это простой способ повторного использования общего кода во многих ваших моделях данных без необходимости повторять код во многих местах. Внедрение поведения в модель часто может быть сделано всего двумя строками кода. По мере увеличения количества моделей в вашем приложении поведение становится все более полезным.

Blameable позволяет нам легко реализовать часто необходимую задачу назначения текущего вошедшего в систему пользователя для вставок и обновлений в модели ActiveRecord, автоматически устанавливая свойства для created_by и updated_by .

В программировании с использованием Yii2: авторизация С помощью фильтра контроля доступа мы реализовали наше собственное обвиняемое поведение в двух частях. Сначала мы создали миграцию, чтобы добавить поле created_by в нашу таблицу Status:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<?php
 
use yii\db\Schema;
use yii\db\Migration;
 
class m150128_003709_extend_status_table_for_created_by extends Migration
{
    public function up()
    {
      $tableOptions = null;
      if ($this->db->driverName === ‘mysql’) {
          $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
      }
      $this->addColumn(‘{{%status}}’,’created_by’,Schema::TYPE_INTEGER.’ NOT NULL’);
      $this->addForeignKey(‘fk_status_created_by’, ‘{{%status}}’, ‘created_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
    }

Во-вторых, мы присвоили поле user_id текущему user_id в действии create StatusController:

1
2
3
4
5
6
public function actionCreate()
   {
       $model = new Status();
 
       if ($model->load(Yii::$app->request->post())) {
         $model->created_by = Yii::$app->user->getId();

Реализация поведения Blameable сделает это автоматически для нас и может быть легко добавлено ко всем моделям ActiveRecord в веб-приложении.

Во-первых, нам нужно еще раз расширить таблицу Status с помощью миграции для поддержки поля updated_by .

1
2
3
4
5
Jeff$ ./yii migrate/create extend_status_table_for_updated_by
Yii Migration Tool (based on Yii v2.0.2)
 
Create new migration ‘/Users/Jeff/Sites/hello/migrations/m150209_200619_extend_status_table_for_updated_by.php’?
New migration created successfully.

Вот код миграции, который мы будем использовать:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
 
use yii\db\Schema;
use yii\db\Migration;
 
class m150209_200619_extend_status_table_for_updated_by extends Migration
{
    public function up()
    {
      $tableOptions = null;
      if ($this->db->driverName === ‘mysql’) {
          $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
      }
      $this->addColumn(‘{{%status}}’,’updated_by’,Schema::TYPE_INTEGER.’ NOT NULL’);
      $this->addForeignKey(‘fk_status_updated_by’, ‘{{%status}}’, ‘updated_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
    }
 
    public function down()
    {
      $this->dropForeignKey(‘fk_status_updated_by’,'{{%status}}’);
      $this->dropColumn(‘{{%status}}’,’updated_by’);
    }
}

Если вы попытаетесь запустить эту миграцию с существующими данными в вашей базе данных, вы получите ошибку при попытке создать индекс внешнего ключа, потому что updated_by равен 0 и не существует в пользовательской таблице.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
hello Jeff$ ./yii migrate/up
Yii Migration Tool (based on Yii v2.0.2)
 
Total 1 new migration to be applied:
    m150209_200619_extend_status_table_for_updated_by
 
Apply the above migration?
*** applying m150209_200619_extend_status_table_for_updated_by
    > add column updated_by integer NOT NULL to table {{%status}} … done (time: 0.042s)
    > add foreign key fk_status_updated_by: {{%status}} (updated_by) references {{%user}} (id) …Exception ‘yii\db\IntegrityException’ with message ‘SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`hello`.`#sql-22f_1d0`, CONSTRAINT `fk_status_updated_by` FOREIGN KEY (`updated_by`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
The SQL being executed was: ALTER TABLE `status` ADD CONSTRAINT `fk_status_updated_by` FOREIGN KEY (`updated_by`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE’
 
in /Users/Jeff/Sites/hello/vendor/yiisoft/yii2/db/Schema.php:532
 
Error Info:
Array
(
    [0] => 23000
    [1] => 1452
    [2] => Cannot add or update a child row: a foreign key constraint fails (`hello`.`#sql-22f_1d0`, CONSTRAINT `fk_status_updated_by` FOREIGN KEY (`updated_by`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
)

Мы могли бы обойти это, обновив данные вручную в процессе миграции, а затем добавив внешний ключ. Однако, поскольку это тестовая платформа, проще всего выполнить миграцию вниз на три шага — сброс таблицы состояния и ее тестовых данных — и затем выполнить миграцию снова:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
hello Jeff$ ./yii migrate/down 3
Yii Migration Tool (based on Yii v2.0.2)
 
Total 3 migrations to be reverted:
    m150128_233458_extend_status_table_for_slugs
    m150128_003709_extend_status_table_for_created_by
    m141201_013120_create_status_table
 
Revert the above migrations?
*** reverting m150128_233458_extend_status_table_for_slugs
    > drop column slug from table {{%status}} … done (time: 0.009s)
*** reverted m150128_233458_extend_status_table_for_slugs (time: 0.013s)
 
*** reverting m150128_003709_extend_status_table_for_created_by
    > drop foreign key fk_status_created_by from table {{%status}} … done (time: 0.010s)
    > drop column created_by from table {{%status}} … done (time: 0.008s)
*** reverted m150128_003709_extend_status_table_for_created_by (time: 0.019s)
 
*** reverting m141201_013120_create_status_table
    > drop table {{%status}} … done (time: 0.001s)
*** reverted m141201_013120_create_status_table (time: 0.002s)
 
Migrated down successfully.
 
hello Jeff$ ./yii migrate/up 4
Yii Migration Tool (based on Yii v2.0.2)
 
Total 4 new migrations to be applied:
    m141201_013120_create_status_table
    m150128_003709_extend_status_table_for_created_by
    m150128_233458_extend_status_table_for_slugs
    m150209_200619_extend_status_table_for_updated_by
 
Apply the above migrations?
*** applying m141201_013120_create_status_table
    > create table {{%status}} … done (time: 0.007s)
*** applied m141201_013120_create_status_table (time: 0.010s)
 
*** applying m150128_003709_extend_status_table_for_created_by
    > add column created_by integer NOT NULL to table {{%status}} … done (time: 0.007s)
    > add foreign key fk_status_created_by: {{%status}} (created_by) references {{%user}} (id) … done (time: 0.008s)
*** applied m150128_003709_extend_status_table_for_created_by (time: 0.016s)
 
*** applying m150128_233458_extend_status_table_for_slugs
    > add column slug string NOT NULL to table {{%status}} … done (time: 0.007s)
*** applied m150128_233458_extend_status_table_for_slugs (time: 0.008s)
 
*** applying m150209_200619_extend_status_table_for_updated_by
    > add column updated_by integer NOT NULL to table {{%status}} … done (time: 0.007s)
    > add foreign key fk_status_updated_by: {{%status}} (updated_by) references {{%user}} (id) … done (time: 0.007s)
*** applied m150209_200619_extend_status_table_for_updated_by (time: 0.015s)
 
Migrated up successfully.

Далее мы прикрепим BlameableBehavior к нашей модели состояния. В models / Status.php мы добавляем BlameableBehavior после Sluggable:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
class Status extends \yii\db\ActiveRecord
{
      const PERMISSIONS_PRIVATE = 10;
      const PERMISSIONS_PUBLIC = 20;
   
      public function behaviors()
          {
              return [
                  [
                      ‘class’ => SluggableBehavior::className(),
                      ‘attribute’ => ‘message’,
                      ‘immutable’ => true,
                      ‘ensureUnique’=>true,
                  ],
                  [
                      ‘class’ => BlameableBehavior::className(),
                      ‘createdByAttribute’ => ‘created_by’,
                      ‘updatedByAttribute’ => ‘updated_by’,
                  ],
              ];
          }

Мы также должны включить поведение Blameable в верхней части нашей модели:

1
2
3
4
5
6
7
<?php
 
namespace app\models;
 
use Yii;
use yii\behaviors\SluggableBehavior;
use yii\behaviors\BlameableBehavior;

Затем мы удаляем обязательное правило для created_by в правилах модели:

1
2
3
4
public function rules()
   {
       return [
           [[‘message’, ‘created_at’, ‘updated_at’,’created_by’], ‘required’],

Как это:

1
2
3
4
public function rules()
   {
       return [
           [[‘message’, ‘created_at’, ‘updated_at’], ‘required’],

Это позволяет валидации пройти успешно и перейти к поведению.

Мы также можем закомментировать или удалить присвоение StatusController созданного_ в действии создания:

1
2
3
4
5
6
public function actionCreate()
   {
       $model = new Status();
 
       if ($model->load(Yii::$app->request->post())) {
         //$model->created_by = Yii::$app->user->getId();

Как только все эти изменения будут завершены, мы можем написать новую запись статуса:

Форма «Создать статус», ожидающая реализации Blameable Behavior

И мы можем заглянуть в табличное представление с помощью PHPMyAdmin и посмотреть настройки made_by и updated_by:

Таблица состояния после обновлений достоверного поведения

Когда создается сообщение о статусе, мы всегда будем знать, кто создал первую запись. Но с Blameable Behaviors мы будем знать только, кто последний обновил запись.

Давайте пройдемся по простой реализации журнала, чтобы записать идентификатор человека, который делает каждое обновление. Затем вы можете легко просмотреть историю обновлений или расширить ее до полного журнала изменений.

Во-первых, нам нужно создать миграцию для StatusLog :

1
2
3
4
5
6
hello Jeff$ ./yii migrate/create create_status_log_table
Yii Migration Tool (based on Yii v2.0.2)
 
Create new migration ‘/Users/Jeff/Sites/hello/migrations/m150209_204852_create_status_log_table.php’?
 
New migration created successfully.

Затем мы кодируем миграцию, чтобы включить реляционные поля для полей идентификатора таблицы Status и User updated_by :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
 
use yii\db\Schema;
use yii\db\Migration;
 
class m150209_204852_create_status_log_table extends Migration
{
    public function up()
    {
          $tableOptions = null;
          if ($this->db->driverName === ‘mysql’) {
              $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
          }
 
          $this->createTable(‘{{%status_log}}’, [
            ‘id’ => Schema::TYPE_PK,
            ‘status_id’ => Schema::TYPE_INTEGER.’
            ‘updated_by’ => Schema::TYPE_INTEGER.’
            ‘created_at’ => Schema::TYPE_INTEGER .
          ], $tableOptions);
          $this->addForeignKey(‘fk_status_log_id’, ‘{{%status_log}}’, ‘status_id’, ‘{{%status}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
          $this->addForeignKey(‘fk_status_log_updated_by’, ‘{{%status_log}}’, ‘updated_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
      }
 
 
    public function down()
    {
      $this->dropForeignKey(‘fk_status_updated_by’,'{{%status_log}}’);
      $this->dropForeignKey(‘fk_status_id’,'{{%status_log}}’);
      $this->dropColumn(‘{{%status_log}}’,’updated_by’);
    }
}

Далее запустим миграцию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
hello Jeff$ ./yii migrate/up
Yii Migration Tool (based on Yii v2.0.2)
 
Total 1 new migration to be applied:
    m150209_204852_create_status_log_table
 
Apply the above migration?
*** applying m150209_204852_create_status_log_table
    > create table {{%status_log}} … done (time: 0.008s)
    > add foreign key fk_status_log_id: {{%status_log}} (status_id) references {{%status}} (id) … done (time: 0.008s)
    > add foreign key fk_status_log_updated_by: {{%status_log}} (updated_by) references {{%user}} (id) … done (time: 0.008s)
*** applied m150209_204852_create_status_log_table (time: 0.028s)
 
Migrated up successfully.

Самый быстрый способ создать модель для StatusLog (и файлов CRUD, чтобы мы могли легко просматривать таблицу) — использовать генератор кода Yii2, Gii. Вы видели, как я использовал это в прошлых уроках.

Посетите http: // localhost: 8888 / hello / gii и создайте модель с этими настройками:

Генератор кода Gii для модели журнала состояний

Вот настройки CRUD:

Gii Code Generator для файлов статуса CRUD

Далее мы расширяем событие AfterSave в модели Status:

01
02
03
04
05
06
07
08
09
10
11
12
13
public function afterSave($insert,$changedAttributes)
       {
           parent::afterSave($insert,$changedAttributes);
           // when insert false, then record has been updated
           if (!$insert) {
             // add StatusLog entry
             $status_log = new StatusLog;
             $status_log->status_id = $this->id;
             $status_log->updated_by = $this->updated_by;
             $status_log->created_at = time();
             $status_log->save();
           }
       }

Этот метод вызывает родительскую функциональность по умолчанию для afterSave но затем создает новую запись StatusLog при каждом обновлении строки Status:

Теоретически, мы могли бы также расширить BlameableBehavior, но, поскольку вы должны убедиться, что есть модель журнала для каждой модели ActiveRecord, с которой вы ее используете, казалось, проще встроить эту функциональность в Status.

Если вы обновите несколько записей о статусе, вы сможете просмотреть StatusLog с помощью CRUD Gii. На рисунке ниже показаны два изменения, внесенные Status.id 1.

Журнал состояния CRUD Browser - журнал обновлений

Если вы хотите пойти дальше, было бы относительно просто расширить это до таблицы редакций, содержащей предыдущий и новый текст статуса, для поддержки функциональности отката.

Я надеюсь, вам понравилось узнавать о Yii2 Behaviors и Blameable. Далее мы рассмотрим поведение временных меток, которое сокращает объем кода, который необходимо писать для каждой новой модели, для обычной операции создания временных отметок для вставок и обновлений.

Следите за будущими уроками в моей серии «Программирование с Yii2», поскольку я продолжаю изучать различные аспекты фреймворка. Вы также можете проверить мою серию «Построение стартапа с помощью PHP», использующую расширенный шаблон Yii2, когда я создаю реальное приложение.

Я приветствую тематические и тематические запросы. Вы можете опубликовать их в комментариях ниже или написать мне по электронной почте на моем сайте Lookahead Consulting .

Если вы хотите узнать, когда появится следующий учебник по Yii2, следуйте за мной @reifman в Твиттере или зайдите на страницу моего инструктора . Моя страница инструктора будет включать все статьи из этой серии, как только они будут опубликованы.