Update from 1.0 to 1.1 ====================== This document details the changes made to Doctrine 1.1 to make it easier for you to upgrade your projects to use this version. Doctrine Record --------------- isValid() & isModified() Optional Deep Parameter ------------------------------------------------ When you use the `Doctrine_Record::isValid()` and `Doctrine_Record::isModified()` methods you can optionally pass a `$deep` argument with a value of true so that Doctrine will check any references that exist. So if a reference record is invalid or modified then the parent object will return as such. Here is an example using `isModified()`: [php] $user = new TUser(); $mail = new Email(); $mail->address = 'test'; $user->emails[] = $mail; // Returns true because the referenced email model is modified $modified = $user->isModified(true); Here is an example using `isValid()`: [php] $user = new User(); $mail = new Email(); $user->name = 'floriank'; $user->emails[] = $mail; // Returns false because the referenced Email object does not have an address set $valid = $user->isValid(true); [r5098](http://trac.doctrine-project.org/changeset/5098) - Added ability to define custom accessors/mutators for a Doctrine record [php] public function setTableDefinition() { $this->hasColumn('username', 'string'); } public function setUp() { $this->hasAccessor('username', 'customUsernameAccessor'); $this->hasMutator('username', 'customUsernameMutator'); } public function customUsernameAccessor() { return $this->_get('username'); } public function customUsernameMutator($value) { $this->_set('username', $value); } [r5236](http://trac.doctrine-project.org/changeset/5236) - Added ability to alias record listeners [php] $this->addListener(new My_Listener_FooListener($this->_options), 'FooListener'); No aliasing and no options usage: [yml] Foo: columns: bar: string(50) listeners: [My_Listener_FooListener] No aliasing and using options [yml] Foo: columns: bar: string(50) listeners: My_Listener_FooListener: useOptions: true Using alias definition [yml] Foo: columns: bar: string(50) listeners: FooListener: class: My_Listener_FooListener useOptions: true [r5236](http://trac.doctrine-project.org/changeset/5236) - Added ability to enable/disable record listeners Possibility to remove all listeners of all record listeners [php] Doctrine::getTable('Foo')->getRecordListener()->setOption('disabled', true); Possibility to remove some listeners of all record listeners [php] Doctrine::getTable('Foo')->getRecordListener()->setOption('disabled', array('preSerialize', 'postHydrate')); Possibility to remove all listeners of a single record listener [php] Doctrine::getTable('Foo')->getRecordListener()->get('FooListener') ->setOption('disabled', true); Possibility to remove some listeners of a single record listener [php] Doctrine::getTable('Foo')->getRecordListener()->get('FooListener') ->setOption('disabled', array('preSave', 'postInsert')); [r5241](http://trac.doctrine-project.org/changeset/5240) - Added ability to work with mapped values as if they were part of the record. It is useful to store transient data into record (this data is not persisted to database). [php] class Foo extends Doctrine_Record { public function setTableDefinition() { $this->mapValue('name'); } // ... } $foo = new Foo(); $foo->name = 'guilhermeblanco'; echo $foo->name; // prints: guilhermeblanco [r5014](http://trac.doctrine-project.org/changeset/5014) - Added support for `FROM User u WHERE u.id IN ?` in the Doctrine_Query api. [php] $q = Doctrine_Query::create() ->from('User u') ->where('u.id IN ?', array(1, 2, 3)); $users = $q->execute(); [r5019](http://trac.doctrine-project.org/changeset/5019) - `unlink()` and `link()` have been changed to not delete references until `save()` is called on the object. Also, fixed synchronizeWithArray() to synchronize many to many relationships. [php] // Does not link until save() $user = Doctrine::getTable('User')->find(1); $user->link('Groups', array(1, 2, 3)); $user->save(); // Third argument will force it to save instantly $user = Doctrine::getTable('User')->find(1); $user->link('Groups', array(1, 2, 3), true); $user = Doctrine::getTable('User')->find(1); $userArray = array('Group' => array(1, 2, 3)); $user->synchronizeWithArray($userArray); $user->save(); You can also use the same structure with the `fromArray()` function: [php] $user->fromArray($userArray); $user->save(); [r5034](http://trac.doctrine-project.org/changeset/5034) - You can now retrieve the old values of records through the getModified() function. By default it returns an array of fieldName => newValue but if you specify getModified(true) it will return the old values. [php] $users = Doctrine::getTable('User')->findAll(); $user = $users->getFirst(); $user->name = 'zYne-'; $oldValues = $user->getModified(true); /* array( 'name' => 'zYne', ) */ $newValues = $user->getModified(false); /* array( 'name' => 'zYne-', ) */ [r5035](http://trac.doctrine-project.org/changeset/5035) - Added ability to use custom setters with fromArray() [php] public function setTableDefinition() { $this->hasColumn('password'); } public function setEncryptedPassword($password) { return $this->_set('password', md5($password)); } $user->fromArray(array('encrypted_password' => 'changeme')); Will invoke the custom setter setEncryptedPassword() Default Options --------------- [r5027](http://trac.doctrine-project.org/changeset/5039) - Added support for setting default charset and collate for tables. Set globally on a Doctrine_Manager instance. [php] $manager->setCollate('utf8_unicode_ci'); $manager->setCharset('utf8'); The same can be set on the Doctrine_Connection and Doctrine_Record levels. [php] $connection->setCollate('utf8_unicode_ci'); $connection->setCharset('utf8'); public function setTableDefinition() { $this->setCollate('utf8_unicode_ci'); $this->setCharset('utf8'); } [r5030](http://trac.doctrine-project.org/changeset/5039) - Added support for setting default options for columns and auto added identifier columns. The following is now possible on Doctrine_Manager, Doctrine_Connection and Doctrine_Record. [php] $manager->setAttribute(Doctrine::ATTR_DEFAULT_COLUMN_OPTIONS, array('type' => 'string', 'length' => 255, 'notnull' => true)); You can also customize the values that make up the auto added identifier column as well. [php] // %s in the name is replaced with the table name. $manager->setAttribute(Doctrine::ATTR_DEFAULT_IDENTIFIER_OPTIONS, array('name' => '%s_id', 'type' => 'string', 'length' => 16)); [r5079](http://trac.doctrine-project.org/changeset/5079) - Added ability to retrieve the modified properties from the last transaction with the Doctrine_Record::getLastModified() method [php] $user = new User(); $user->username = 'jwage'; print_r($user->getModified()); // array('username' => 'jwage') $user->save(); // getModified() returns the current modified properties and in this case // now no propeties are modified. print_r($user->getModified()); // array() // you can retrieve the last modified properties like the following print_r($user->getLastModified()); // array('username' => 'jwage') Doctrine Query -------------- [r5031](http://trac.doctrine-project.org/changeset/5031) - A query can now be transformed from an update() or delete() to a select() or any combination. Run a query once to update a field then turn it to a select and execute it. [php] $q = Doctrine_Query::create() ->update('User u') ->set('u.password', '?', 'newpassword') ->where('u.username = ?', 'jwage'); $q->execute(); // Change it to a select query $q->select(); $user = $q->fetchOne(); [r5018](http://trac.doctrine-project.org/changeset/5018) - Added `Doctrine::ATTR_AUTO_FREE_QUERY_OBJECTS` for auto freeing query objects after execution [php] $manager->setAttribute('auto_free_query_objects'); $q = Doctrine_Query::create() ->from('User u') $users = $q->execute(); // $q->free() is triggered [r5121](http://trac.doctrine-project.org/changeset/5121) - Added new Doctrine_Query_Abstract::getFlattenedParams() to replace Doctrine_Query_Abstract::getParams() and Doctrine_Query_Abstract::getParams() now returns the raw unmodified array of query parameters. Doctrine Collection ------------------- [r5032](http://trac.doctrine-project.org/changeset/5032) - You can now convert a Doctrine_Collection in to a key value array made up of the values from the two specified columns. [php] $q = Doctrine_Query::create() ->from('User u'); $users = $q->execute(); $array = $users->toKeyValueArray('id', 'name'); print_r($array); /* array( 4 => 'zYne', 5 => 'Arnold Schwarzenegger', 6 => 'Michael Caine', 7 => 'Takeshi Kitano', 8 => 'Sylvester Stallone', 9 => 'Kurt Russell', 10 => 'Jean Reno', 11 => 'Edward Furlong', ) */ Doctrine Validation ------------------- [r5033](http://trac.doctrine-project.org/changeset/5033) - You can now specify a unique validator on a set of fields. The argument of the unique() function can either be an array of fields or an argument for each field. [php] public function setTableDefinition() { $this->hasColumn('username', 'string', 255); $this->hasColumn('email_address', 'string', 255); $this->unique('username', 'email_address'); } Registering Validators ---------------------- In Doctrine 1.1 you now have the ability to register custom validators so that Doctrine is aware of them. You can also get the allowed validators. [php] // Register a new validator $manager = Doctrine_Manager::getInstance(); $manager->registerValidators('MyValidator'); // Get array of validators $validators = $manager->getValidators(); E-Mail Validator ---------------- You now have the ability to configure the Email validator to not check the mx record of the e-mail address. [php] class User extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('username', 'string', 255); $this->hasColumn('password', 'string', 255); $this->hasColumn('email_address', 'string', 255, array('email' => array('check_mx' => false))); } } Doctrine Hydration ------------------ [r5016](http://trac.doctrine-project.org/changeset/5016) - A performance change was made to the hydration process which should yield some improvements when hydrating large result sets. Doctrine Schema Files --------------------- You are now able to pass extra attributes through the YAML schema parser to store any custom column information and you have access to it through the Doctrine_Table. [yml] User: columns: username: type: string(255) extra: test: 123 password: type: string(255) You can now access the data with the following php code: [php] $username = Doctrine::getTable('User')->getDefinitionOf('username'); echo $username['extra']['test']; // 123 Generated Models ---------------- Doctrine will now generate phpDoc property tags with the generated base classes. [yml] User: columns: username: string(255) password: string(255) Phonenumber: columns: user_id: integer phonenumber: string relations: User: foreignAlias: Phonenumbers Generates the following User and Phonenumber class [php] /** * BaseUser * * This class has been auto-generated by the Doctrine ORM Framework * * @property string $username * @property string $password * @property Doctrine_Collection $Phonenumbers * * @package ##PACKAGE## * @subpackage ##SUBPACKAGE## * @author ##NAME## <##EMAIL##> * @version SVN: $Id: Builder.php 5270 2008-12-05 20:47:43Z jwage $ */ abstract class BaseUser extends Doctrine_Record { public function setTableDefinition() { $this->setTableName('user'); $this->hasColumn('username', 'string', 255, array('type' => 'string', 'length' => '255')); $this->hasColumn('password', 'string', 255, array('type' => 'string', 'length' => '255')); } public function setUp() { $this->hasMany('Phonenumber as Phonenumbers', array('local' => 'id', 'foreign' => 'user_id')); } } /** * BasePhonenumber * * This class has been auto-generated by the Doctrine ORM Framework * * @property integer $user_id * @property string $phonenumber * @property User $User * * @package ##PACKAGE## * @subpackage ##SUBPACKAGE## * @author ##NAME## <##EMAIL##> * @version SVN: $Id: Builder.php 5270 2008-12-05 20:47:43Z jwage $ */ abstract class BasePhonenumber extends Doctrine_Record { public function setTableDefinition() { $this->setTableName('phonenumber'); $this->hasColumn('user_id', 'integer', null, array('type' => 'integer')); $this->hasColumn('phonenumber', 'string', null, array('type' => 'string')); } public function setUp() { $this->hasOne('User', array('local' => 'user_id', 'foreign' => 'id')); } } Versionable Behavior -------------------- [r5097](http://trac.doctrine-project.org/changeset/5097) - Added ability to disable automatic deleting of versions when a record is deleted [php] $this->actAs('Versionable', array('deleteVersions' => false)); And the yaml version: [yml] actAs: Versionable: deleteVersions: false Migrations ---------- In Doctrine 1.1 we have made a few changes to migrations. * Generated migration classes are prefixed with a timestamp instead of a incremented integer. * Added possibility for automation of up/down methods. * Re-ordered arguments for addColumn() so that type is first before length. * Added `Doctrine_Migration_Diff` tool. * Migration classes should now extend a class named `Doctrine_Migration_Base` instead of `Doctrine_Migration`. Here is an example of how you can automate the opposite up or down of a migration api method. [php] public function migrate($direction) { $this->column($direction, 'table_name', 'column_name', 'string', '255'); } The above example will create the column when $direction == up and will remove the column when $direction == down. * Introduced new CLI task generate-migrations-diff for producing differences between your generated PHP models and your YAML schema files. SoftDelete ---------- In Doctrine 1.1 the SoftDelete behavior was changed slightly to store a `deleted_at` timestamp to indicate a deleted record rather than a `deleted` boolean flag. We still kept the BC, so you're `deleted` boolean column will still be supported as long as you have defined in your schema: Foo: actAs: SoftDelete: name: deleted type: boolean columns: bar: string By doing that, you'll not need to update your existent database schema. Otherwise, you will need to modify your existing database schema and rename the `deleted` column to `deleted_at` and modify the type to be a timestamp. Timestamps ---------- PostgreSQL now uses TIMESTAMPS with timezone by default for timestamps. TIME defaults to TIME without timezone and TIMESTAMP to TIMESTAMP with timezone. Time validation is now accepting time zones and milliseconds accordingly to the ISO 8601. Searchable ---------- You now have the ability to pass an array of options for the search analyzers. For example the Utf8 analyzer can accept an encoding option: [php] BlogPost: actAs: Searchable: analyzer: Doctrine_Search_Analyzer_Utf8 analyzer_options: encoding: utf-8 columns: title: string(255) body: clob