Здравствуй, хабралюд.
С выходом symfony 1.4 разработчики фреймворка фактически обязали нас использовать вместо привычного Propel, новую, мною неизведанную ORM Doctrine. Нет, конечно они не заставляют использовать Doctrine, при желании в 1.4 можно подключить и Propel, но как мне показалось — если разработчики такого масштаба сделали Doctrine по–умолчанию в своём фреймворке, то значит это говорит о большей пригодности нежели Propel. Я не стал противиться ещё по той причине, что просто напросто хотелось чего–нибудь нового и стал работать с Doctrine.
В связи с появившейся задачей, с желанием повысить собственную квалификацию и просто из интереса решил попробовать на себе, что такое Doctrine Behaviors, а полученным опытом поделиться с вами. Как писать плагины для symfony framework'a я уже рассказывал, на этот раз хотелось бы рассказать о написании плагина, который использует «Doctrine Behavior».
Всё это будет рассматриваться на примере плагина рейтинга. То есть плагин позволяет голосовать (получать средний рейтинг, количество всех голосов и много другого) за любой объект модели у которого есть поле ID. Так как бехейверы имеют тенденцию в своих названиях оканчиваться на «able» (Timestampable, Commentable) я назвал свой плагин (он же по совместительству бехейвер) — Superable (sfSuperablePlugin).
Итак, в директории «plugins» создаем директорию «sfSuperablePlugin». В ней «lib/doctrine/template», а внутри этой директории файл «Superable.class.php» — это и есть шаблон для расширения основной модели к которой привязан этот самый бехейвер. Содержание файла таково:
Далее в вашу schema.yml к модели, которая нуждается в бехейвере дописываем actAs, выглядит это примерно так:
Что ж, расширить модель у нас уже получилось, но нужна ещё дополнительная таблица, которая будет хранить в себе всю историю голосов за объекты модели Photo. Для это понадобиться создать файл «plugins/sfSuperablePlugin/lib/doctrine/generator/SuperableGenerator.class.php» со следующим содержанием:
Если вы заметили в методе setTableDefinition () вызывается метод $this→addListener (new SuperableListener ()) — это слушатель, а что он делает вы сейчас узнаете. Создайте файл «plugins/sfSuperablePlugin/lib/doctrine/listener/SuperableListener.class.php» с таким содержанием:
Теперь для того что бы новая модель (PhotoSuperable) генерировалась при построении моделей, форм и фильтров необходимо инициализировать её в шаблоне (Superable.class.php) добавив метод:
Осталось только в шаблон добавить непосредственно методы для голосования и так далее, для этого в Superable.class.php:
Вступление
С выходом symfony 1.4 разработчики фреймворка фактически обязали нас использовать вместо привычного Propel, новую, мною неизведанную ORM Doctrine. Нет, конечно они не заставляют использовать Doctrine, при желании в 1.4 можно подключить и Propel, но как мне показалось — если разработчики такого масштаба сделали Doctrine по–умолчанию в своём фреймворке, то значит это говорит о большей пригодности нежели Propel. Я не стал противиться ещё по той причине, что просто напросто хотелось чего–нибудь нового и стал работать с Doctrine.
В связи с появившейся задачей, с желанием повысить собственную квалификацию и просто из интереса решил попробовать на себе, что такое Doctrine Behaviors, а полученным опытом поделиться с вами. Как писать плагины для symfony framework'a я уже рассказывал, на этот раз хотелось бы рассказать о написании плагина, который использует «Doctrine Behavior».
Поехали
Всё это будет рассматриваться на примере плагина рейтинга. То есть плагин позволяет голосовать (получать средний рейтинг, количество всех голосов и много другого) за любой объект модели у которого есть поле ID. Так как бехейверы имеют тенденцию в своих названиях оканчиваться на «able» (Timestampable, Commentable) я назвал свой плагин (он же по совместительству бехейвер) — Superable (sfSuperablePlugin).
Итак, в директории «plugins» создаем директорию «sfSuperablePlugin». В ней «lib/doctrine/template», а внутри этой директории файл «Superable.class.php» — это и есть шаблон для расширения основной модели к которой привязан этот самый бехейвер. Содержание файла таково:
- <?php
-
- /**
- * Superable (rateable) behavior
- *
- * @author Igor S. Chernyshev
- */
- class Superable extends Doctrine_Template {
- protected $_options = array();
- protected $_plugin;
-
- public function __construct(array $options = array())
- {
- parent::__construct($options);
- }
-
- public function setTableDefinition()
- {
- $this->hasColumn('average_rating', 'float', '4', array(
- 'notnull' => true,
- 'default' => 0
- ));
- $this->hasColumn('total_votes', 'integer', '4', array(
- 'notnull' => true,
- 'default' => 0
- ));
- $this->hasColumn('votes_sum', 'integer', '4', array(
- 'notnull' => true,
- 'default' => 0
- ));
- }
В конструктор передаются различного рода опции, которые мы отправляем в родительский конструктор. Метод setTableDefinition () описывает необходимые, дополнительные поля к основной модели (average_rating — средний рейтинг, total_votes — общее количество голосов, votes_sum — общая сумма всех голосов). Метод setTableDefinition () вызывается автоматически при построении моделей (php symfony doctrine: build-model (forms, filters)).Далее в вашу schema.yml к модели, которая нуждается в бехейвере дописываем actAs, выглядит это примерно так:
- Photo:
- actAs:
- Superable:
- columns:
- id:
- type: integer(4)
- primary: true
- autoincrement: true
Затем
- php symfony cc
- php symfony doctrine:build-model
- php symfony doctrine:build-forms
- php symfony doctrine:build-filters
- php symfony doctrine:build-sql
После этих не хитрых действий в «data/sql/schema.sql» можно увидеть, что к таблице Photo добавлено три дополнительных поля описанных в setTableDefinition ().Что ж, расширить модель у нас уже получилось, но нужна ещё дополнительная таблица, которая будет хранить в себе всю историю голосов за объекты модели Photo. Для это понадобиться создать файл «plugins/sfSuperablePlugin/lib/doctrine/generator/SuperableGenerator.class.php» со следующим содержанием:
-
- <?php
-
- /**
- * Superable (rateable) behavior
- *
- * @author Igor S. Chernyshev
- */
- class SuperableGenerator extends Doctrine_Record_Generator
- {
- public function __construct(array $options = array())
- {
- $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options);
- }
-
- public function initOptions()
- {
- $builderOptions = array('suffix' => '.class.php',
- 'baseClassesDirectory' => 'base',
- 'generateBaseClasses' => true,
- 'generateTableClasses' => true,
- 'baseClassName' => 'sfDoctrineRecord');
-
- $this->setOption('builderOptions', $builderOptions);
- $this->setOption('className', '%CLASS%Superable');
- $this->setOption('generateFiles', true);
- $this->setOption('generatePath', sfConfig::get('sf_lib_dir') .
- DIRECTORY_SEPARATOR . 'model' .
- DIRECTORY_SEPARATOR.'doctrine');
- }
-
- public function setTableDefinition()
- {
- $this->setTableName($this->_options['table']->getTableName() . '_superable');
- $this->hasColumn('id', 'integer', 4, array(
- 'type' => 'integer',
- 'primary' => true,
- 'autoincrement' => true,
- 'length' => '4',
- ));
- $this->hasColumn($this->getRelationLocalKey(), 'integer', 4, array(
- 'type' => 'integer',
- 'length' => '4',
- ));
- $this->hasColumn('user_id', 'integer', 4, array(
- 'type' => 'integer',
- 'length' => '4',
- ));
- $this->hasColumn('vote', 'integer', 4, array(
- 'type' => 'integer',
- 'length' => '4',
- ));
- $this->hasColumn('created_at', 'date');
-
- $this->addListener(new SuperableListener());
- }
-
- public function generateClassFromTable(Doctrine_Table $table)
- {
- $definition = array();
- $definition['columns'] = $table->getColumns();
- $definition['tableName'] = $table->getTableName();
- $definition['actAs'] = $table->getTemplates();
- $definition['relations'] = $table->getRelations();
-
- return $this->generateClass($definition);
- }
-
- public function getRelationLocalKey()
- {
- return $this->_options['table']->getTableName() . '_id';
- }
-
- public function buildRelation()
- {
- $this->buildForeignRelation('Votes');
- $this->buildLocalRelation(ucfirst($this->_options['table']->getTableName()));
- }
- }
-
- ?>
-
Этот генератор как раз отвечает за генерирование новых моделей (в моём случае PhotoSuperable.class.php, PhotoSuperableTable.class.php), форм, фильтров и так далее. В setTableDefinition () мы опять таки описываем поля, которые будут сгенерированы для новой таблицы. Метод $this→_options['table']→getTableName () возвращает имя таблицы модели к которой привязан бехейвер (в данном случае, если actAs стоит у таблицы photo — метод соответственно вернет photo). В методе initOptions () всё прозрачно, как мне кажется. В нём описывается как будет назван класс модели, куда будет сохранен и так далее. Все остальные методы, опять же, с говорящими названиями, которые я описывать не буду.Если вы заметили в методе setTableDefinition () вызывается метод $this→addListener (new SuperableListener ()) — это слушатель, а что он делает вы сейчас узнаете. Создайте файл «plugins/sfSuperablePlugin/lib/doctrine/listener/SuperableListener.class.php» с таким содержанием:
- <?php
-
- /**
- * Superable (rateable) behavior
- *
- * @author Igor S. Chernyshev
- */
- class SuperableListener extends Doctrine_Record_Listener
- {
- public function preInsert(Doctrine_Event $event)
- {
- $event->getInvoker()->created_at = date('Y-m-d', time());
- }
- }
-
- ?>
Я думаю, что тут прекрасно видно, что слушатель этот при сохранение записи в таблицу с историей каждому объекту устанавливает актуальную дату на момент сохранения.Теперь для того что бы новая модель (PhotoSuperable) генерировалась при построении моделей, форм и фильтров необходимо инициализировать её в шаблоне (Superable.class.php) добавив метод:
- public function setUp()
- {
- $this->hasMany($this->getTable()->getComponentName() . 'Superable as Votes',
- array('local' => 'id',
- 'foreign' => $this->getTable()->getTableName() . '_id'));
-
- $this->_plugin->initialize($this->_table);
- }
И строчку в конструктор:
- public function __construct(array $options = array())
- {
- parent::__construct($options);
- $this->_plugin = new SuperableGenerator();
- }
После всех этих действий опять приступаем к:
- php symfony cc
- php symfony doctrine:build-model
- php symfony doctrine:build-forms
- php symfony doctrine:build-filters
- php symfony doctrine:build-sql
И видим, что в сгенерированном schema.sql появилась новая таблица photo_superable со следующими полями: id, photo_id, user_id, vote, created_at.Осталось только в шаблон добавить непосредственно методы для голосования и так далее, для этого в Superable.class.php:
- /********************
- * Template methods *
- ********************/
-
- protected $_names = array();
- protected $_vote;
- protected $_userId;
-
- /**
- * Initialize properties for superable methods
- *
- * @param int $vote Values of vote
- * @param int $userId User's ID
- <div style="font: normal normal 1em/1.2em monospac