Doctrine Templates / Behaviours (for symfony/ullright)
http://doctrine.readthedocs.org/en/latest/en/manual/behaviors.html
http://darrendev.blogspot.co.at/2010/03/creating-doctrine-custom-behaviour-part.html
Templates
Templates allow you to add fields and methods to multiple models.
Templates are also the base class for more sophisticated behaviours including additional tables with relations.
The base class is "Doctrine_Template".
Add a column
plugins/ullCorePlugin/lib/behaviours/ullTestable/ullTestable.class.php or anywhere in a lib directory.
Let's add a column to the base model.
class ullTestable extends Doctrine_Template { public function setTableDefinition() { $this->hasColumn('my_column', 'string', 128); } }
Usage in schema.yml
TestTable: actAs: ullTestable:
Rebuild model and TestTable has now a string column "my_column"
Using options
Set the option defaults in the behaviour template and use options in the template
class ullTestable extends Doctrine_Template { // Defaults protected $_options = array( 'column_name' => 'my_column', 'column_type' => 'string', ); public function setTableDefinition() { $this->hasColumn( $this->_options['column_name'], $this->_options['column_type'] ); } }
Usage in schema.yml
TestTable: actAs: ullTestable: column_name: my_foo_column
Rebuild model and TestTable has now a string (from defaults) column called "my_foo_column"
Delegate Methods
Add methods to the invoking model.
class ullTestable extends Doctrine_Template { public function setTableDefinition() { $this->hasColumn('my_column', 'string', 128); } // Delegated method public function getMyColumnUpper() { // invoker = the record $invoker = $this->getInvoker(); return strtoupper($invoker->my_column); } }
Usage:
<?php $record = new TestTable; $record->my_column = 'Foo'; echo $record->getMyColumnUpper(); // Return "FOO"
Delegate Table Methods
class ullTestable extends Doctrine_Template { public function setTableDefinition() { $this->hasColumn('my_column', 'string', 128); } // Delegated table method - Add "TableProxy" suffix! public function findByMyColumnTableProxy($string) { $q = new Doctrine_Query; $q ->from('TestTable t') ->where('t.my_column = ?', $string) ; return $q->execute(); //use something like this to access table functions of generated submodels //$subrecord = Doctrine::getTable($this->_plugin->getOption('className')) // ->findOneById($subid); } }
Usage:
<?php $record = new TestTable; $record->my_column = 'Foo'; $record->save(); $records = Doctrine::getTable('TestTable')->findByMyColumn('Foo'); var_dump($records->toArray());
Behaviours
Behaviours allow more complex functionality with extra tables and relations.
Record generator
Doctrine provides "Doctrine_Record_Generator".
Example: allow to add multiple email addresses to a base model
plugins/ullCorePlugin/lib/behaviours/ullTestable/ullTestableRecordGenerator.class.php or anywhere in a lib directory.
class ullTestableRecordGenerator extends Doctrine_Record_Generator { /** * Merge options with template options * * @param string $options */ public function __construct($options) { $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options); } /** * Set additional options */ public function initOptions() { // When used on a model named "TestTable" the result is a model called "TestTableEmail" $this->setOption('className', '%CLASS%Email'); } public function setTableDefinition() { $this->hasColumn('email', 'string', 255, array( 'email' => true, // Email as primary key to allow multiple email addresses per parent model 'primary' => true )); $this->hasColumn('created_at', 'timestamp', null, array('notnull' => true)); } public function buildRelation() { $this->buildForeignRelation('Emails'); $this->buildLocalRelation(); } }
Now configure the behaviour in the template:
class ullTestable extends Doctrine_Template { /** * Register the behaviour = record generator * @param array $options */ public function __construct(array $options = array()) { parent::__construct($options); $this->_plugin = new ullTestableRecordGenerator($this->_options); } /** * Initialize the behavior */ public function setUp() { $this->_plugin->initialize($this->_table); } }
Usage:
$record = new TestTable; $record['Emails'][]['email'] = 'bar@example.com'; $record['Emails'][]['email'] = 'baz@example.com'; $record->save();
Note: the model "TestTableEmail" is created on the fly and not instanciable.
To allow this, configure to generate model files on disk:
plugins/ullCorePlugin/lib/behaviours/ullTestable/ullTestableRecordGenerator.class.php:
class ullTestable extends Doctrine_Template { ... /** * Set additional options */ public function initOptions() { $this->setOption('className', '%CLASS%Email'); $this->setOption('generateFiles', true); $this->setOption('generatePath', sfConfig::get('sf_lib_dir') . '/model/doctrine'); } ...
Record Listeners
Record listeners allow behaviours to use Doctrine record event and hooks like "preUpdate()" and so on.
plugins/ullCorePlugin/lib/behaviours/ullTestable/ullTestableRecordListener.class.php or anywhere in a lib directory.
class ullTestableRecordListener extends Doctrine_Record_Listener { protected $_options = array() ; /** * Constructor * * @param array $options */ public function __construct(array $options) { $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options); } /** * preInsert hook * * Set the email's created_at date * @see Doctrine_Record_Listener::preInsert() */ public function preInsert(Doctrine_Event $event) { // record is the associated owner record of the behaviour // in our example "TestTable" $record = $event->getInvoker(); if (count($record->Emails)) { foreach ($record->Emails as $email) { if (!$email->exists()) { $email->created_at = date('c'); } } } }
Now configure the listener in the template:
class ullTestable extends Doctrine_Template { ... /** * Add record listener * * @see Doctrine_Template::setTableDefinition() */ public function setTableDefinition() { $this->addListener(new ullTestableRecordListener($this->_options)); } ... }
More about events and hooks: http://doctrine.readthedocs.org/en/latest/en/manual/event-listeners.html
Record Filters
Record filters allow to transparently set and get additional non-native properties of a record.
A classic example would be internationalisation (i18n). One could transparently access translated record properties which are delegated to a translation table.
Template:
class ullTestable extends Doctrine_Template { public function setUp() { $this->_table->unshiftFilter(new ullTestableRecordFilter($this->_options)); } }
RecordFilter
class ullTestableRecordFilter extends Doctrine_Record_Filter { protected $_options = array() ; public function __construct(array $options) { $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options); } public function filterSet(Doctrine_Record $record, $name, $value) { echo "Setting $name $value\n"; return $record; } public function filterGet(Doctrine_Record $record, $name) { echo "Getting $name\n"; return 'foo'; } }