Doctrine Behavior on the example of its own plugin

    Hello habralyud.

    Introduction


    With the release of symfony 1.4, the developers of the framework actually obliged us to use instead of the usual  Propel , a new, unknown to me ORM Doctrine . No, of course they do not force you to use Doctrine, if you wish in 1.4, you can also connect Propel, but it seemed to me that if developers of this scale made Doctrine by default in their framework, then this means more suitability than Propel. I didn’t resist even for the reason that I just wanted something new and started working with Doctrine.

    In connection with the task that has arisen, with the desire to improve my own qualifications and just out of interest I decided to try on myself what Doctrine Behaviors is, and share the experience gained with you. How to write plugins for symfony framework'aI already told you , this time I would like to talk about writing a plugin that uses "Doctrine Behavior".

    Go


    All this will be considered as an example of a rating plugin. That is, the plugin allows you to vote (get an average rating, the number of all votes and much more) for any model object that has an ID field. Since the behaviors tend to end with “able” (Timestampable, Commentable) in their names, I named my plug-in (it’s also part-time) - Superable (sfSuperablePlugin).

    So, in the “plugins” directory, create the “sfSuperablePlugin” directory. It has “lib / doctrine / template”, and inside this directory the file “Superable.class.php” is the template for extending the main model to which this very behaver is attached. The content of the file is as follows:
    1.  
    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.  
    11.   publicfunction __construct(array $options = array())
    12.   {
    13.     parent::__construct($options);
    14.   }
    15.  
    16.   publicfunction setTableDefinition()
    17.   {
    18.     $this->hasColumn('average_rating', 'float', '4', array(
    19.       'notnull' => true,
    20.       'default' => 0
    21.     ));
    22.     $this->hasColumn('total_votes', 'integer', '4', array(
    23.       'notnull' => true,
    24.       'default' => 0
    25.     ));
    26.     $this->hasColumn('votes_sum', 'integer', '4', array(
    27.       'notnull' => true,
    28.       'default' => 0
    29.     ));
    30.   }
    Various options are passed to the constructor, which we send to the parent constructor. The setTableDefinition () method describes the necessary additional fields to the main model (average_rating - average rating, total_votes - total number of votes, votes_sum - total amount of all votes). The setTableDefinition () method is called automatically when building models (php symfony doctrine: build-model (forms, filters)).

    Further, in your schema.yml, we add actAs to a model that needs a behever, it looks something like this:
    1. Photo:
    2.   actAs:
    3.     Superable:
    4.   columns:
    5.     id:
    6.       type: integer(4)
    7.       primary: true
    8.       autoincrement: true
    Then
    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
    After these tricky actions in “data / sql / schema.sql”, you can see that three additional fields described in setTableDefinition () are added to the Photo table .
    Well, we have already succeeded in expanding the model, but we need an additional table that will store the entire history of votes for the objects of the Photo model  . To do this, you need to create the file "plugins / sfSuperablePlugin / lib / doctrine / generator / SuperableGenerator.class.php" with the following contents:
    1.  
    2.  
    3. /**
    4.  * Superable (rateable) behavior
    5.  *
    6.  * @author Igor S. Chernyshev
    7.  */
    8. class SuperableGenerator extends Doctrine_Record_Generator
    9. {
    10.   publicfunction  __construct(array $options = array())
    11.   {
    12.     $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options);
    13.   }
    14.  
    15.   publicfunction initOptions()
    16.   {
    17.     $builderOptions = array('suffix' => '.class.php',
    18.                             'baseClassesDirectory' => 'base',
    19.                             'generateBaseClasses' => true,
    20.                             'generateTableClasses' => true,
    21.                             'baseClassName' => 'sfDoctrineRecord');
    22.  
    23.     $this->setOption('builderOptions', $builderOptions);
    24.     $this->setOption('className', '%CLASS%Superable');
    25.     $this->setOption('generateFiles', true);
    26.     $this->setOption('generatePath', sfConfig::get('sf_lib_dir') .
    27.                                      DIRECTORY_SEPARATOR . 'model' .
    28.                                      DIRECTORY_SEPARATOR.'doctrine');
    29.   }
    30.  
    31.   publicfunction setTableDefinition()
    32.   {
    33.     $this->setTableName($this->_options['table']->getTableName() . '_superable');
    34.     $this->hasColumn('id', 'integer', 4, array(
    35.       'type' => 'integer',
    36.       'primary' => true,
    37.       'autoincrement' => true,
    38.       'length' => '4',
    39.     ));
    40.     $this->hasColumn($this->getRelationLocalKey(), 'integer', 4, array(
    41.       'type' => 'integer',
    42.       'length' => '4',
    43.     ));
    44.     $this->hasColumn('user_id', 'integer', 4, array(
    45.       'type' => 'integer',
    46.       'length' => '4',
    47.     ));
    48.     $this->hasColumn('vote', 'integer', 4, array(
    49.       'type' => 'integer',
    50.       'length' => '4',
    51.     ));
    52.     $this->hasColumn('created_at', 'date');
    53.  
    54.     $this->addListener(new SuperableListener());
    55.   }
    56.  
    57.   publicfunction generateClassFromTable(Doctrine_Table $table)
    58.   {
    59.     $definition = array();
    60.     $definition['columns']   = $table->getColumns();
    61.     $definition['tableName'] = $table->getTableName();
    62.     $definition['actAs']     = $table->getTemplates();
    63.     $definition['relations'] = $table->getRelations();
    64.  
    65.     return $this->generateClass($definition);
    66.   }
    67.  
    68.   publicfunction getRelationLocalKey()
    69.   {
    70.     return $this->_options['table']->getTableName() . '_id';
    71.   }
    72.  
    73.   publicfunction buildRelation()
    74.   {
    75.     $this->buildForeignRelation('Votes');
    76.     $this->buildLocalRelation(ucfirst($this->_options['table']->getTableName()));
    77.   }
    78. }
    79.  
    80. ?>
    81.  
    This generator is just responsible for generating new models (in my case PhotoSuperable.class.php, PhotoSuperableTable.class.php), forms, filters and so on. In setTableDefinition (), we again describe the fields that will be generated for the new table. The method $ this → _options ['table'] → getTableName () returns the name of the model table to which the behaver is attached (in this case, if actAs is on the photo table, the method will return photo accordingly). In the initOptions () method, everything is transparent, as it seems to me. It describes how the model class will be named, where it will be saved, and so on. All other methods, again, with speaking names that I will not describe.

    If you notice in the setTableDefinition () methodthe method $ this → addListener (new SuperableListener ())  is called - this is the listener, and what you are doing now you will find out. Create the file “plugins / sfSuperablePlugin / lib / doctrine / listener / SuperableListener.class.php” with the following contents:
    1.  
    2. /**
    3.  * Superable (rateable) behavior
    4.  *
    5.  * @author Igor S. Chernyshev
    6.  */
    7. class SuperableListener extends Doctrine_Record_Listener
    8. {
    9.   publicfunction preInsert(Doctrine_Event $event)
    10.   {
    11.     $event->getInvoker()->created_at = date('Y-m-d', time());
    12.   }
    13. }
    14.  
    15. ?>
    I think that it is clearly visible here that this listener, when saving an entry in the history table to each object, sets the current date at the time of saving.

    Now, in order for the new model ( PhotoSuperable ) to be generated when building models, forms and filters, you need to initialize it in the template (Superable.class.php) by adding a method:
    1.   publicfunction setUp()
    2.   {
    3.     $this->hasMany($this->getTable()->getComponentName() . 'Superable as Votes',
    4.                    array('local'   => 'id',
    5.                          'foreign' => $this->getTable()->getTableName() . '_id'));
    6.  
    7.     $this->_plugin->initialize($this->_table);
    8.   }
    And a line in the constructor:
    1.   publicfunction __construct(array $options = array())
    2.   {
    3.     parent::__construct($options);
    4.     $this->_plugin  = new SuperableGenerator();
    5.   }
    After all these steps again to begin: And we see that in the generated schema.sql a new table  photo_superable with the following fields: id, photo_id, user_id, vote , created_at. It remains only to add directly to the template methods for voting and so on, for this in Superable.class.php:
    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


    1.   /********************
    2.    * Template methods *
    3.    ********************/
    4.  
    5.   protected $_names = array();
    6.   protected $_vote;
    7.   protected $_userId;
    8.  
    9.   /**
    10.    * Initialize properties for superable methods
    11.    *
    12.    * @param int $vote Values of vote
    13.    * @param int $userId User's ID

    Also popular now: