Как стать автором
Обновить

Doctrine Behavior на примере собственного плагина

Symfony
Здравствуй, хабралюд.

Вступление


С выходом 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» — это и есть шаблон для расширения основной модели к которой привязан этот самый бехейвер. Содержание файла таково:
  1. <?php
  2. /**
  3. * Superable (rateable) behavior
  4. *
  5. * @author Igor S. Chernyshev
  6. */
  7. class Superable extends Doctrine_Template {
  8. protected $_options = array();
  9. protected $_plugin;
  10. public function __construct(array $options = array())
  11. {
  12. parent::__construct($options);
  13. }
  14. public function setTableDefinition()
  15. {
  16. $this->hasColumn('average_rating', 'float', '4', array(
  17. 'notnull' => true,
  18. 'default' => 0
  19. ));
  20. $this->hasColumn('total_votes', 'integer', '4', array(
  21. 'notnull' => true,
  22. 'default' => 0
  23. ));
  24. $this->hasColumn('votes_sum', 'integer', '4', array(
  25. 'notnull' => true,
  26. 'default' => 0
  27. ));
  28. }
В конструктор передаются различного рода опции, которые мы отправляем в родительский конструктор. Метод setTableDefinition () описывает необходимые, дополнительные поля к основной модели (average_rating — средний рейтинг, total_votes — общее количество голосов, votes_sum — общая сумма всех голосов). Метод setTableDefinition () вызывается автоматически при построении моделей (php symfony doctrine: build-model (forms, filters)).

Далее в вашу schema.yml к модели, которая нуждается в бехейвере дописываем actAs, выглядит это примерно так:
  1. Photo:
  2. actAs:
  3. Superable:
  4. columns:
  5. id:
  6. type: integer(4)
  7. primary: true
  8. autoincrement: true
Затем
  1. php symfony cc
  2. php symfony doctrine:build-model
  3. php symfony doctrine:build-forms
  4. php symfony doctrine:build-filters
  5. php symfony doctrine:build-sql
После этих не хитрых действий в «data/sql/schema.sql» можно увидеть, что к таблице Photo добавлено три дополнительных поля описанных в setTableDefinition ().
Что ж, расширить модель у нас уже получилось, но нужна ещё дополнительная таблица, которая будет хранить в себе всю историю голосов за объекты модели Photo. Для это понадобиться создать файл «plugins/sfSuperablePlugin/lib/doctrine/generator/SuperableGenerator.class.php» со следующим содержанием:
  1. <?php
  2. /**
  3. * Superable (rateable) behavior
  4. *
  5. * @author Igor S. Chernyshev
  6. */
  7. class SuperableGenerator extends Doctrine_Record_Generator
  8. {
  9. public function __construct(array $options = array())
  10. {
  11. $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options);
  12. }
  13. public function initOptions()
  14. {
  15. $builderOptions = array('suffix' => '.class.php',
  16. 'baseClassesDirectory' => 'base',
  17. 'generateBaseClasses' => true,
  18. 'generateTableClasses' => true,
  19. 'baseClassName' => 'sfDoctrineRecord');
  20. $this->setOption('builderOptions', $builderOptions);
  21. $this->setOption('className', '%CLASS%Superable');
  22. $this->setOption('generateFiles', true);
  23. $this->setOption('generatePath', sfConfig::get('sf_lib_dir') .
  24. DIRECTORY_SEPARATOR . 'model' .
  25. DIRECTORY_SEPARATOR.'doctrine');
  26. }
  27. public function setTableDefinition()
  28. {
  29. $this->setTableName($this->_options['table']->getTableName() . '_superable');
  30. $this->hasColumn('id', 'integer', 4, array(
  31. 'type' => 'integer',
  32. 'primary' => true,
  33. 'autoincrement' => true,
  34. 'length' => '4',
  35. ));
  36. $this->hasColumn($this->getRelationLocalKey(), 'integer', 4, array(
  37. 'type' => 'integer',
  38. 'length' => '4',
  39. ));
  40. $this->hasColumn('user_id', 'integer', 4, array(
  41. 'type' => 'integer',
  42. 'length' => '4',
  43. ));
  44. $this->hasColumn('vote', 'integer', 4, array(
  45. 'type' => 'integer',
  46. 'length' => '4',
  47. ));
  48. $this->hasColumn('created_at', 'date');
  49. $this->addListener(new SuperableListener());
  50. }
  51. public function generateClassFromTable(Doctrine_Table $table)
  52. {
  53. $definition = array();
  54. $definition['columns'] = $table->getColumns();
  55. $definition['tableName'] = $table->getTableName();
  56. $definition['actAs'] = $table->getTemplates();
  57. $definition['relations'] = $table->getRelations();
  58. return $this->generateClass($definition);
  59. }
  60. public function getRelationLocalKey()
  61. {
  62. return $this->_options['table']->getTableName() . '_id';
  63. }
  64. public function buildRelation()
  65. {
  66. $this->buildForeignRelation('Votes');
  67. $this->buildLocalRelation(ucfirst($this->_options['table']->getTableName()));
  68. }
  69. }
  70. ?>
Этот генератор как раз отвечает за генерирование новых моделей (в моём случае 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» с таким содержанием:
  1. <?php
  2. /**
  3. * Superable (rateable) behavior
  4. *
  5. * @author Igor S. Chernyshev
  6. */
  7. class SuperableListener extends Doctrine_Record_Listener
  8. {
  9. public function preInsert(Doctrine_Event $event)
  10. {
  11. $event->getInvoker()->created_at = date('Y-m-d', time());
  12. }
  13. }
  14. ?>
Я думаю, что тут прекрасно видно, что слушатель этот при сохранение записи в таблицу с историей каждому объекту устанавливает актуальную дату на момент сохранения.

Теперь для того что бы новая модель (PhotoSuperable) генерировалась при построении моделей, форм и фильтров необходимо инициализировать её в шаблоне (Superable.class.php) добавив метод:
  1. public function setUp()
  2. {
  3. $this->hasMany($this->getTable()->getComponentName() . 'Superable as Votes',
  4. array('local' => 'id',
  5. 'foreign' => $this->getTable()->getTableName() . '_id'));
  6. $this->_plugin->initialize($this->_table);
  7. }
И строчку в конструктор:
  1. public function __construct(array $options = array())
  2. {
  3. parent::__construct($options);
  4. $this->_plugin = new SuperableGenerator();
  5. }
После всех этих действий опять приступаем к:
  1. php symfony cc
  2. php symfony doctrine:build-model
  3. php symfony doctrine:build-forms
  4. php symfony doctrine:build-filters
  5. php symfony doctrine:build-sql
И видим, что в сгенерированном schema.sql появилась новая таблица photo_superable со следующими полями: id, photo_id, user_id, vote, created_at.

Осталось только в шаблон добавить непосредственно методы для голосования и так далее, для этого в Superable.class.php:
  1. /********************
  2. * Template methods *
  3. ********************/
  4. protected $_names = array();
  5. protected $_vote;
  6. protected $_userId;
  7. /**
  8. * Initialize properties for superable methods
  9. *
  10. * @param int $vote Values of vote
  11. * @param int $userId User's ID
  12. <div style="font: normal normal 1em/1.2em monospac
Теги:symfonydoctrinebehaviourpluginjquerysuperableratebleratingрейтингплагин
Хабы: Symfony
Всего голосов 16: ↑14 и ↓2+12
Просмотры116

Похожие публикации

Лучшие публикации за сутки