1# Upgrade from 1.1 to 1.2
2
3This document details the changes made to Doctrine 1.2 to make it easier for you
4to upgrade your projects to use this version.
5
6## Removed Attribute String Support
7
8The string support of `Doctrine_Configurable::getAttribute()` and
9`Doctrine_Configurable::setAttribute()` have been removed.
10
11The reason is that the performance of this simple convenience feature is
12significant and it is totally unneeded.
13
14The following code is no longer supported.
15
16    [php]
17    $connection->setAttribute('use_native_enum', true);
18
19You must now always use the following code.
20
21    [php]
22    $connection->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true);
23
24## Removed all Deprecated Functions
25
26Removed all functions labeled `@deprecated` in the doc blocks.
27
28Affected Classes
29
30* Doctrine_Query
31* Doctrine_Query_Abstract
32
33## Renamed Methods
34
35* Doctrine_Query_Abstract::getSql() to getSqlQuery()
36* Doctrine_Search_Query::getSql() to getSqlQuery()
37* Doctrine_Query_Abstract::*getCountQuery
38*
39*
40*
41*
42*
43() to getCountSqlQuery()
44* Doctrine_RawSql::getCountQuery() to getCountSqlQuery()
45
46## Added ability to configure table class
47
48Added ability to specify the base `Doctrine_Table` class to use if no custom
49`UserModelTable` class exists.
50
51    [php]
52    $manager->setAttribute(Doctrine_Core::ATTR_TABLE_CLASS, 'MyTable');
53
54Now just make sure the class exists somewhere and is loaded.
55
56    [php]
57    class MyTable extends Doctrine_Table
58    {
59
60    }
61
62This attribute value will be used for the following new option if you don't
63specify it.
64
65    [php]
66    $builder = new Doctrine_Import_Builder();
67    $builder->setOption('baseTableClassName', 'MyBaseTable');
68
69## Added ability to configure query class
70
71Before Doctrine 1.2 it was hard coded internally to always use the
72`Doctrine_Query` class whenever you instantiate a new query. Now you can
73configure which class this is to use by setting the `Doctrine_Core::ATTR_QUERY_CLASS`
74attribute.
75
76    [php]
77    class MyQuery extends Doctrine_Query
78    {
79
80    }
81
82    $manager->setAttribute(Doctrine_Core::ATTR_QUERY_CLASS, 'MyQuery');
83
84    $q = Doctrine_Query::create();
85
86    echo get_class($q); // MyQuery
87
88## Changed Doctrine_Parser_Xml::arrayToXml() to be static
89
90Now the `arrayToXml()` method can be called directly because it is static.
91
92    [php]
93    $array = array(
94      'key1' => 'value',
95      'key2' => 'value'
96    );
97    $xml = Doctrine_Parser_Xml::arrayToXml($array);
98
99## Refactored Migrations to better handle multiple connections
100
101Now when working with `Doctrine_Migration` instance you can specify as the
102second argument to the constructor the connection instance to use for the
103migrations.
104
105Migrations for different databases should be handled with a different set of
106migration classes.
107
108Previous method of finding connection based on the table name is flawed since
109databases could have the same table name multiple times.
110
111    [php]
112    $conn = Doctrine_Manager::connection();
113    $migration = new Doctrine_Migration('/path/to/migrations', $conn);
114
115## Added option for save cascading
116
117Added a new attribute to control whether cascading save operations are done by
118default. Previous to Doctrine 1.2 they were always cascaded.
119
120As of 1.2 you have the option to disable cascading saves and will only cascade
121if the record is dirty. The cost of this is that you can't cascade and save
122records who are dirty that are more then one level deep in the hierarchy.
123
124See: http://trac.doctrine-project.org/ticket/1623
125
126You can disable cascading saves with the following code.
127
128    [php]
129    $manager->setAttribute(Doctrine_Core::ATTR_CASCADE_SAVES, false);
130
131Disabling this will increase performance significantly when saving objects.
132
133## Added Doctrine_Core::setPath()
134
135Now you can specify the path to your Doctrine libraries if Doctrine.php is
136outside of the location of your libraries.
137
138So if `Doctrine.php` is located at `/path/to/Doctrine.php` and the actual
139libraries are at `/path/to/the/doctrine/libs` you would need to do the
140following.
141
142    [php]
143    Doctrine_Core::setPath('/path/to/the/doctrine/libs');
144
145## Ability to clear an individual reference
146
147Previously the `Doctrine_Record::clearRelated()` would only allow the clearing
148of ALL references. It will now accept a relationship name and you can clear an
149individual reference.
150
151    [php]
152    $user->clearRelated('Phonenumber');
153
154## Check related exists
155
156Often you want to check if a relationship exists in the database, but if it
157doesn't exist you get a newly created blank relationship that will try to be
158saved when you call save on the parent record. Use the new `relatedExists()`
159method to check to avoid this behavior.
160
161    [php]
162    if ($user->relatedExists('Profile')) {
163      // do something if the user has a profile
164    }
165
166## Reverse Engineered Columns
167
168If Doctrine does not recognize a column from a database when reverse engineering
169a schema, instead of throwing an exception, it will default to a string.
170
171This allows custom column types or proprietary column types to be reverse
172engineered without stopping the schema from being built completely.
173
174## Oracle Adapter Persistent Connections
175
176The `Doctrine_Adapter_Oracle` now will use persistent connections if specified.
177
178    [php]
179    $info = array(
180      'oracle:dbname=SID;charset=NLS_CHARACTERSET;persistent=true',
181      'usr',
182      'pass'
183    );
184
185    Doctrine_Manager::connection($info, 'conn_name');
186
187## New Class/File Prefix Option for Model Builder
188
189You can now set a prefix for your generated models and choose to not have the
190generated filename include that prefix as well.
191
192    [php]
193    $builder = new Doctrine_Import_Builder();
194    $builder->setOption('classPrefixFiles', false);
195    $builder->setOption('classPrefix', 'MyClassPrefix_');
196
197Without the first option you'd get a file like `MyClassPrefix_ModelName.php` but
198now you will get `ModelName.php` with a class named `MyClassPrefix_ModelName`
199inside.
200
201## Expanded Magic Finders to Multiple Fields
202
203You can now `findBy` multiple fields and specify conditions between the fields.
204
205    [php]
206    $user = $userTable->findOneByUsernameAndPassword('jwage', md5('changeme'));
207
208Or you could do something like the following and find admin users and moderator
209users.
210
211    [php]
212    $users = $userTable->findByIsAdminOrIsModerator(true, true);
213
214You can mix the conditions.
215
216    [php]
217    $users = $userTable->findByIsAdminAndIsModeratorOrIsSuperAdmin(true, true, true);
218
219> **CAUTION**
220> These are very limited magic finders and it is always recommended to expand
221> your queries to be manually written DQL queries. These methods are meant for
222> only quickly accessing single records, no relationships, and are good for
223> prototyping code quickly.
224
225## Custom Collection Class
226
227You can now specify a custom child class to use for all collections inside
228Doctrine.
229
230    [php]
231    $manager->setAttribute(Doctrine_Core::ATTR_COLLECTION_CLASS, 'MyCollection');
232
233    $phonenumbers = $user->Phonenumbers;
234    echo get_class($phonenumbers); // MyCollection
235
236Now define the simple child class.
237
238    [php]
239    class MyCollection extends Doctrine_Collection
240    {
241
242    }
243
244This option can be set at the manager, connection and table levels.
245
246## Custom Hydrators
247
248As of Doctrine 1.2 it is now possible to register your own custom data
249hydrators. The core hydration process has been decoupled to proper drivers and
250now you can register your own to handle the hydration process.
251
252First lets register our custom hydrator class.
253
254    [php]
255    $manager->registerHydrator('MyHydrator', 'Doctrine_Hydrator_MyHydrator');
256
257So now we need to define a hydrator class named `MyHydrator` and it must
258implement a method named `hydrateResultSet($stmt)` method which accepts a
259query statement object.
260
261    [php]
262    class Doctrine_Hydrator_MyHydrator extends Doctrine_Hydrator_Abstract
263    {
264        public function hydrateResultSet($stmt)
265        {
266            return $stmt->fetchAll(PDO::FETCH_ASSOC);
267        }
268    }
269
270Now you can run a query like the following which would use the new `MyHydrator`.
271
272    [php]
273    $q->execute(array(), 'MyHydrator');
274
275## Custom Connections
276
277You can now write and register your own custom Doctrine connection drivers and
278adapters.
279
280    [php]
281    class Doctrine_Connection_Test extends Doctrine_Connection_Common
282    {
283    }
284
285    class Doctrine_Adapter_Test implements Doctrine_Adapter_Interface
286    {
287      // ... all the methods defined in the interface
288    }
289
290Now we can register this with Doctrine so we can utilize it as our connection.
291
292    [php]
293    $manager->registerConnectionDriver('test', 'Doctrine_Connection_Test');
294
295Now you can utilize that type of connection in your DSN when connecting.
296
297    [php]
298    $conn = $manager->openConnection('test://username:password@localhost/dbname');
299
300Now if we were to check what classes are used for the connection you will notice
301that they are the classes we defined above.
302
303    [php]
304    echo get_class($conn); // Doctrine_Connection_Test
305    echo get_class($conn->getDbh()); // Doctrine_Adapter_Test
306
307## Doctrine Extensions
308
309Doctrine now has support for creating, loading and testing extensions in to your
310projects.
311
312First we need to simply tell `Doctrine` where the extensions are being loaded
313from.
314
315    [php]
316    Doctrine_Core::setExtensionsPath('/path/to/extensions');
317
318Now we can check out one of the first available extensions in to our extensions
319directory and then register it.
320
321    $ svn co http://svn.doctrine-project.org/extensions/Sortable/branches/1.2-1.0/ /path/to/extensions/Sortable
322
323The directory structure of this extension looks like the following.
324
325    Sortable/
326    	lib/
327    		Doctrine/
328    			Template/
329    				Listener/
330    					Sortable.php
331    				Sortable.php
332    	tests/
333    		run.php
334    		Template/
335    			SortableTestCase.php
336
337You can even run the tests that come bundled with it. We just need to tell your
338CLI where your Doctrine code is.
339
340    $ export DOCTRINE_DIR=/path/to/doctrine
341
342> **NOTE**
343> The above path to Doctrine must be the path to the main folder, not just the
344> lib folder. In order to run the tests it must have access to the `tests`
345> directory included with Doctrine.
346
347Now you can run the tests included.
348
349    $ cd /path/to/extensions/Sortable/tests
350    $ php run.php
351
352It should output something like the following.
353
354    Doctrine Unit Tests
355    ===================
356    Doctrine_Template_Sortable_TestCase.............................................passed
357
358    Tested: 1 test cases.
359    Successes: 26 passes.
360    Failures: 0 fails.
361    Number of new Failures: 0
362    Number of fixed Failures: 0
363
364    Tests ran in 1 seconds and used 13024.9414062 KB of memory
365
366Now if you want to use the extension in your project you will need register the
367extension with Doctrine and setup the extension autoloading mechanism.
368
369First lets setup the extension autoloading.
370
371    [php]
372    spl_autoload_register(array('Doctrine', 'extensionsAutoload'));
373
374Now you can register the extension and the classes inside that extension will be
375autoloaded.
376
377    [php]
378    $manager->registerExtension('Sortable');
379
380> **NOTE**
381> If you need to register an extension from a different location you can specify
382> the full path to the extension directory as the second argument to the
383> `registerExtension()` method.
384
385## Generator Cascading Delete Configuration
386
387It is now possible to configure the cascading delete operation of a
388`Doctrine_Record_Generator`. For example you can now configure the `I18n`
389behavior to use app level cascade deletes instead of database level.
390
391    [yml]
392    Article:
393      actAs:
394        I18n:
395          fields: [title, body]
396          appLevelDelete: true
397      columns:
398        title: string(255)
399        body: clob
400
401You can also completely disable cascading deletes by using the `cascadeDelete`
402option and setting it to `false.`
403
404## Column Aggregation Key Column
405
406The column aggregation key column is now automatically indexed.
407
408    [yml]
409    User:
410      tableName: users
411      columns:
412        username: string(255)
413        password: string(255)
414
415    Employee:
416      inheritance:
417        extends: User
418        type: column_aggregation
419
420The above schema would add a `type` column to the `User` model. This is a flag
421tell Doctrine which subclass each record in the database belongs to. This column
422is now automatically indexed where before it was not.
423
424    [sql]
425    CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(255), password VARCHAR(255), type VARCHAR(255));
426    CREATE INDEX type_idx ON users (type);
427
428## On Demand Hydration
429
430You can now utilize a new hydration mode which utilizes much less memory. It only
431hydrates one object in to memory at a time. So it uses less memory and is faster
432for larger results.
433
434    [php]
435    // Returns instance of Doctrine_Collection_OnDemand
436    $result = $q->execute(array(), Doctrine_Core::HYDRATE_ON_DEMAND);
437    foreach ($result as $obj) {
438        // ...
439    }
440
441`Doctrine_Collection_OnDemand` hydrates each object one at a time as you iterate
442over it.
443
444## Aggregate Values Hydration
445
446Now aggregate/calculated values can only be found in the root component of your
447queries. Previously they could be found in both the root component and the
448relationship. This causes lots of problems, for example this query.
449
450    [php]
451    $master = Doctrine_Query::create()
452			  ->select('m.*, s.bar AS joe')
453			  ->from('Ticket_DC24_Master m')
454			  ->innerJoin('m.Ticket_DC24_Servant s')
455			  ->where('m.id = 1')
456			  ->fetchOne();
457
458This produces a data structure like this.
459
460    Array
461    (
462        [id] => 1
463        [foo] => 6
464        [servant_id] =>
465        [Ticket_DC24_Servant] => Array
466            (
467                [id] =>
468                [bar] =>
469                [joe] => 6
470            )
471
472        [joe] => 6
473    )
474
475Now we have a blank `Ticket_DC24_Servant` instance and if we were to try and call
476`save()` like below we would get an error.
477
478    [php]
479    $master->some_field = 'test';
480    $master->save();
481
482Now as of 1.2 the structure will only look like this.
483
484    Array
485    (
486        [id] => 1
487        [foo] => 6
488        [servant_id] => 1
489        [joe] => 6
490    )
491
492## PEAR Style Model Loading and Generation
493
494Doctrine 1.2 now has the ability to generate PEAR style naming conventions for your
495models and can autoload them.
496
497First we have a new method for setting the path to our models for the autoloader.
498
499    [php]
500    Doctrine_Core::setModelsDirectory('/path/to/my/models');
501
502Make sure you have registered the `modelsAutoload()` method:
503
504    [php]
505    spl_autoload_register(array('Doctrine', 'modelsAutoload'));
506
507Now when you ask for a class named `My_Test_Model` it will try and autoload it
508from `/path/to/my/models/My/Test/Model.php`.
509
510So now you can autoload PEAR style models, but what about generating them? For this
511we had to add a new option to the model builder called `pearStyle`. When this option
512is enabled it will string replace any `_` in the path to the file and replace it with the directory
513separator `/` right before creating the directories and writing the file.
514
515Here I'll show an example how you can use the options to convert this schema and
516what it would result in.
517
518    [yml]
519    Article:
520      columns:
521        title: string(255)
522        content: clob
523
524    Article_Category:
525      columns:
526        name: string(255)
527
528Now if we configure a schema importer like the following.
529
530    [php]
531    $import = new Doctrine_Import_Schema();
532    $import->setOptions(array(
533        'pearStyle' => true,
534        'baseClassesDirectory' => null,
535        'baseClassPrefix' => 'Base_',
536        'classPrefix' => 'MyProject_Models_',
537        'classPrefixFiles' => true
538    ));
539    $import->importSchema('schema.yml', 'yml', 'lib');
540
541This will result in some files and directories like the following.
542
543    lib/
544      MyProject/
545        Models/
546          Article/
547            Category.php
548          Article.php
549          Base/
550            Article/
551              Category.php
552            Article.php
553
554So now you can easily use our autoloader or your own to load our models with the
555PEAR style naming convention.
556
557    [php]
558    spl_autoload_register(array('Doctrine', 'autoload'));
559    Doctrine_Core::setModelsDirectory('lib');
560
561Now if we use this code.
562
563    [php]
564    $article = MyProject_Models_Article();
565
566It will autoload the file from `lib/MyProject/Models/Article.php`
567
568## Customizing Column Validators
569
570Often when you generate models from a database, you want to customize and improve
571your models by overriding the `setTableDefinition()` and tweaking things. To make
572this easier we added a new method to customize column options but not completely
573override it.
574
575    [php]
576    class User extends BaseUser
577    {
578        public function setTableDefinition()
579        {
580            parent::setTableDefinition();
581
582            $this->setColumnOptions('username', array('unique' => true));
583        }
584    }
585
586## Resetting Manager Instances
587
588Sometimes when performing unit tests using Doctrine, you will want to reset
589the static manager instance held in `Doctrine_Manager`.
590
591We've added a static method `resetInstance()` and a public method `reset()`
592to help you with this.
593
594    [php]
595    $manager1 = Doctrine_Manager::getInstance();
596    Doctrine_Manager::resetInstance();
597    $manager2 = Doctrine_Manager::getInstance();
598
599    // $manager1 !== $manager2
600
601You can also simply reset an instance back to the state when it was first created.
602
603    [php]
604    $manager->reset();
605
606## Registering Custom CLI Tasks
607
608Thanks to Dan Bettles, he contributed some refactorings to the Doctrine 1 CLI
609to allow us to register custom tasks with the `registerTask()` method.
610
611    [php]
612    $cli = new Doctrine_Cli($config);
613    $cli->registerTask('/path/to/MyCustomTask', 'my-task');
614    $cli->run($_SERVER['argv']);
615
616Now you can execute:
617
618    $ php doctrine my-task
619
620Be sure to define the class like this.
621
622    [php]
623    class MyCustomTask extends Doctrine_Task
624    {
625        public $description       =   'My custom task',
626               $requiredArguments =   array('arg1' => 'Required first argument.'),
627               $optionalArguments =   array('arg2' => 'Optional second argument.');
628
629        public function execute()
630        {
631            $arg1 = $this->getArgument('arg1');
632            $arg2 = $this->getArgument('arg2');
633        }
634    }
635
636## Doctrine Nested Set Hierarchy Structure
637
638When working with the `NestedSet` behavior in Doctrine 1.2 it has some very nice
639features that make working with hierarchical data very easy. One of the things
640it has always missed is a way to hydrate that data in to a hierarchical structure.
641With 1.2 this is now possible with some new hydration types.
642
643    [php]
644    $categories = Doctrine_Core::getTable('Category')
645        ->createQuery('c')
646        ->execute(array(), Doctrine_Core::HYDRATE_RECORD_HIERARCHY);
647
648Now you can access the children of a record by accessing the mapped value property
649named `__children`. It is named with the underscores prefixed to avoid any conflicts.
650
651    [php]
652    foreach ($categories->getFirst()->get('__children') as $child) {
653        // ...
654    }
655
656You can also execute this structure using array hydration and get back the same
657structure except as an array.
658
659    [php]
660    $results = Doctrine_Core::getTable('NestedSetTest_SingleRootNode')
661        ->createQuery('n')
662        ->execute(array(), Doctrine_Core::HYDRATE_ARRAY_HIERARCHY);
663
664If you have an existing `Doctrine_Colletion` instance you can convert that to a
665hierarchy as well.
666
667    [php]
668    $hierarchy = $coll->toHierarchy();
669
670The hierarchy a collection can be converted to an array as well.
671
672    [php]
673    print_r($hierarchy->toArray());
674
675## Moved Doctrine to Doctrine_Core
676
677For integration purposes we have deprecated the `Doctrine` class and moved it to
678`Doctrine_Core`. The old class still exists and extends `Doctrine_Core` for BC.
679
680## Specify Relationship Foreign Key Name
681
682Up until now, Doctrine would always try and generate a foreign key name for you
683for your relationships. Sometimes you may want to customize this name or Doctrine
684generates a name that is too long for you. You can customize the foreign key name
685now with the `foreignKeyName` option.
686
687    [php]
688    public function setUp()
689    {
690        $this->hasOne('User', array(
691            'local' => 'user_id',
692            'foreign' => 'id',
693            'foreignKeyName' => 'user_id_fk'
694        ));
695    }
696
697Or in YAML you can do the following.
698
699    [yml]
700    Profile:
701      columns:
702      # ...
703        user_id: integer
704      relations:
705        User:
706          foreignKeyName: user_id_fk
707
708## Sluggable Provider Option
709
710You can now use a provider option to the `Sluggable` behavior so you can customize
711the PHP code that is used to generate a slug for a record.
712
713    [yml]
714    Article:
715    # ...
716      actAs:
717        Sluggable:
718          provider: [MyClass, provideSlug]
719
720Now you must have some PHP code like this.
721
722    [php]
723    class MyClass
724    {
725        public static function provideSlug(Doctrine_Record $record)
726        {
727            // return something
728        }
729    }
730
731## Migrations Primary Key Convenience Methods
732
733To ease the process of creating and dropping primary keys in Doctrine when using
734migrations we've implemented two convenience methods named `createPrimaryKey()`
735and `dropPrimaryKey()`.
736
737You can use create primary keys like the following.
738
739    [php]
740    $columns = array(
741        'id' => array(
742            'type' => 'integer',
743            'autoincrement' => true
744         )
745    );
746    $this->createPrimaryKey('my_table', $columns);
747
748If you want to drop the primary key you can do the following.
749
750    [php]
751    $this->dropPrimaryKey('my_table', array('id'));
752
753You can also use the automation helper with these methods.
754
755    [php]
756    class MyMigration extends Doctrine_Migration_Base
757    {
758        public function migrate($direction)
759        {
760            $columns = array(
761                'id' => array(
762                    'type' => 'integer',
763                    'autoincrement' => true
764                 )
765            );
766            $this->primaryKey('my_table', $columns);
767        }
768    }
769
770The above migration will create the primary key when migrating up and drop it when
771migrating down.
772
773## Fixed changeColumn() Argument Order in Migrations
774
775Previously the order of `changeColumn()` was not in the order you would expect.
776
777    [php]
778    public function changeColumn($tableName, $columnName, $length = null, $type = null, array $options = array())
779
780Notice how the `$length` is before `$type`. Everywhere else in Doctrine that's
781how it is. So for this version we have fixed that.
782
783    [php]
784    public function changeColumn($tableName, $columnName, $type = null, $length = null, array $options = array())
785
786## Ordering Relationships
787
788It is now possible to set a default order by for your relationships. The order by
789is automatically included in the final SQL when writing DQL queries or lazily
790fetching relationships.
791
792    [yml]
793    User:
794      columns:
795        username: string(255)
796        password: string(255)
797      relations:
798        Articles:
799          class: Article
800          local: id
801          foreign: user_id
802          type: many
803          foreignType: one
804          orderBy: title ASC
805
806    Article:
807      columns:
808        title: string(255)
809        content: clob
810        user_id: integer
811
812Now if we were to do the following PHP we'll get the SQL with an order by.
813
814    [php]
815    $q = Doctrine::getTable('User')
816        ->createQuery('u')
817        ->leftJoin('u.Articles a');
818
819    echo $q->getSqlQuery() . "\n\n";
820
821Now you should see this SQL query.
822
823    [sql]
824    SELECT u.id AS u__id, u.username AS u__username, u.password AS u__password, a.id AS a__id, a.title AS a__title, a.content AS a__content, a.user_id AS a__user_id FROM user u LEFT JOIN article a ON u.id = a.user_id ORDER BY a.title ASC
825
826Or if you lazily fetch the `Articles` they will be lazily loaded with the order by.
827
828    [php]
829    $user = Doctrine::getTable('User')->find(1);
830    $articles = $user->Articles;
831
832This would execute the following SQL query.
833
834    [sql]
835    SELECT a.id AS a__id, a.title AS a__title, a.content AS a__content, a.user_id AS a__user_id FROM article a WHERE (a.user_id IN (?)) ORDER BY a.title ASC
836
837You can also specify the default order by for a model itself instead of on the relationship.
838
839    [yml]
840    Article:
841      options:
842        orderBy: title ASC
843      columns:
844        title: string(255)
845
846Now any query involving the `Article` model will have that order by.
847
848## Result Cache Improvements
849
850In Doctrine when you use result caching, it stores the results of a query in the
851cache driver the first time it executes and retrieves it from the cache for all
852subsequent requests. The key for this cache entry in the driver is automatically
853generated so it is hard for you to identify a single entry and clear it manually.
854
855In Doctrine 1.2 we added the ability to set the result cache hash/key used.
856
857    [php]
858    $q = Doctrine_Query::create()
859        ->from('User u')
860        ->useResultCache(true, 3600, 'user_list');
861
862If you want to manually clear the item from the query object you can do.
863
864    [php]
865    $q->clearResultCache();
866
867Or if you have the cache driver you could delete it by the key.
868
869    [php]
870    $cacheDriver->delete('user_list');
871
872You can also use the `setResultCacheHash()` to set the key used to store the cache
873entry.
874
875    [php]
876    $q = Doctrine_Query::create()
877        ->from('User u')
878        ->useResultCache(true, 3600);
879
880    $q->setResultCacheHash('user_list');
881
882You can also now delete cache entries from the cache drivers using PHP regular
883expressions.
884
885    [php]
886    $cacheDriver = new Doctrine_Cache_Apc();
887
888    $cacheDriver->save('my_cache_one');
889    $cacheDriver->save('my_cache_two');
890    $cacheDriver->save('my_cache_three');
891
892    echo $cacheDriver->deleteByRegex('/my_cache_.*/'); // 3
893
894If you're not a regular expression master and just want to simply delete with
895some wild cards then you can use the `*` character with the normal `delete()`
896method and we'll build the regular expression for you and call `deleteByRegex()`.
897
898    [php]
899    echo $cacheDriver->delete('my_cache_*');
900
901Since `preg_match()` is pretty slow the above example would not perform well
902with lots of cache keys to compare to the regular expression so you can
903alternatively use the `deleteByPrefix()` and `deleteBySuffix()` method if
904that is sufficient enough for you.
905
906    [php]
907    echo $cacheDriver->deleteByPrefix('my_cache_');
908
909## BLOB and File Handle Resources
910
911It is now possible to provide the contents for a blob column through a file
912handle resource.
913
914    [php]
915    $file = new File();
916    $file->binary_data = file('/path/to/file');
917    $file->save();
918
919## Symfony sfYaml External
920
921Doctrine is now using the Symfony Component `sfYaml` for our YAML parsing as an
922SVN external. So it will always be up to date and receive bug fixes from Symfony.
923
924## Better replace() Support
925
926The `Doctrine_Record::replace()` had a lot of issues previously as it did not
927behave the same as if you were to call `save()`, `update()`, or `insert()`.
928Now when you call `replace()` all the same events will be triggered and the
929saving of the graph will be triggered just as if you were using `save()`.
930
931    [php]
932    $user = new User();
933    $user->username = 'jonwage';
934    $user->password = 'changeme2';
935    $user->replace();
936
937## Added hardDelete() method to SoftDelete
938
939Something missing from the `SoftDelete` behavior was the ability to force the
940deletion of a record in the database. For this we have added a new `hardDelete()`
941method.
942
943    [php]
944    $user = new User();
945    $user->username = 'jwage';
946    $user->password = 'changeme';
947    $user->save();
948    $user->delete() // Doesn't actually delete, sets deleted_at flag
949
950    $user->hardDelete(); // Will actually delete the record from the database.
951
952## Added MySQL SET Support
953
954Just like the `enum` type for MySQL we also now support the `set` type. It basically
955very similar to `enum` but instead of being able to store only one value you can
956store multiple of the possible values.
957
958    [yml]
959    User:
960      columns:
961        username: string(255)
962        password: string(255)
963        permissions:
964          type: set
965          values: [admin, member, moderator, banned]
966
967Now you can do the following:
968
969    [php]
970    $user = new User();
971    $user->username = 'jwage';
972    $user->password = 'changeme';
973    $user->permissions = array('admin', 'member');
974
975## Models Autoloading
976
977In Doctrine 1.2 the models autoloading was moved to a custom autoloader for more
978flexibility and not forcing the models autoloading on users if they don't want to
979use it. If you still wish to use the Doctrine model autoloading then you must
980use the following code:
981
982    [php]
983    spl_autoload_register(array('Doctrine_Core', 'modelsAutoload'));