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'));