Development

sfDoctrine (diff)

You must first sign up to be able to contribute.

Changes between Version 163 and Version 164 of sfDoctrine

Show
Ignore:
Author:
mahono (IP: 77.132.155.136)
Timestamp:
09/25/07 17:32:45 (10 years ago)
Comment:

sfDoctrinePlugin wiki page has now a new location

Legend:

Unmodified
Added
Removed
Modified
  • sfDoctrine

    v163 v164  
    1 = The sfDoctrine Plugin = 
    2 [[PageOutline]] 
     1= sfDoctrinePlugin = 
    32 
    4 == __Description__ == 
    5 This plugin allows symfony users to use doctrine instead of propel. This plugin '''fully integrates doctrine into symfony'''. You can use ''all'' the features in symfony with propel replaced by doctrine, thanks to the sfDoctrine plugin
     3This page was moved to a [wiki:sfDoctrinePlugin new location]. 
     4Please update your bookmarks
    65 
    7  * automatic model class creation from a `schema.yml` file 
    8  * all the functionalities of the admin generator 
    9  * compatibility with templates written for propel 
    10  * i18n (works exactly as in propel) 
    11  * automatic handling of `created_at` and `updated_at` fields 
    12  * query logging and timing 
    13  * loading of fixtures from yml 
    14  * double lists, check lists and all the many2many admin helpers 
    15  * object helpers 
    16  * use of the connections in databases.yml 
    17  * pagers 
    18   
    19 ''This plugin is in '''beta version'''. [../newticket Bug reports in the symfony trac] are welcome.'' Check the [wiki:sfDoctrineStatus] page for information about what versions are working as well as some hints about how to migrate to a newer version of sfDoctrine/doctrine. 
    20  
    21 == __Installation__ == 
    22  
    23 === Prerequisites === 
    24  
    25 Either symfony 1.0 or a subversion check out of symfony higher than 3525.  
    26  
    27 Doctrine requires PHP >= 5.1. '''The newest Doctrine segfaults on occasion with PHP < 5.2.2.''' It uses PDO which is bundled with PHP by default. Be sure to enable the PDO extension, as well as the database-specific DLL file, in your PHP.ini settings. See [http://php.net/pdo]. 
    28  
    29 === Download the plugin === 
    30  
    31 sfDoctrine is now a regular symfony plugin. Please do '''not''' install it via the command line. Currently only the subversion method is supported. 
    32  
    33 Download the plugin from svn by running: 
    34 {{{ 
    35 #!sh 
    36 > svn co http://svn.symfony-project.com/plugins/sfDoctrinePlugin 
    37 }}} 
    38 and place that in the `plugins` directory of your project. The doctrine library will be automatically downloaded as well.  
    39  
    40 Update regularly since sfDoctrine is updated rather frequently: 
    41 {{{ 
    42 #!sh 
    43 > svn update 
    44 }}} 
    45 You may also [browser:plugins/sfDoctrinePlugin browse the source code] of sfDoctrine. 
    46  
    47 ==== Setting the plugin as an svn:externals ==== 
    48  
    49 To have the plugin updated when you do a `svn update` run: 
    50 {{{ 
    51 #!sh 
    52 > svn propedit svn:externals plugins 
    53 }}} 
    54 and type: 
    55 {{{ 
    56 sfDoctrinePlugin  http://svn.symfony-project.com/plugins/sfDoctrinePlugin 
    57 }}} 
    58  
    59 == __Setup__ == 
    60 === Copy doctrine.yml === 
    61 In order to change common doctrine settings (such as turning table autocreation on and off), you'll need to edit doctrine.yml To do this, copy ''sfDoctrinePlugin/config/doctrine.yml'' to your project/config folder. This will override the plugin YML, and ensure that your settings arent overwritten when you update the plugin. 
    62  
    63 === Database Connections === 
    64 Setup your database connections in `databases.yml`. You will need to specify `sfDoctrineDatabase` as your class as in the example below.  
    65  
    66 {{{ 
    67 all: 
    68   myConnection: 
    69     class: sfDoctrineDatabase 
    70     param: 
    71       dsn: mysql://user:pass@localhost/mydb 
    72 }}} 
    73  
    74 Notice that you may add a "`encoding`" parameter which will be used for connection with `mysql`. ''If no encoding is provided, `UTF8` is assumed.'' 
    75  
    76 ==== Multiple Connections ==== 
    77 If you have declared only one doctrine connection in `databases.yml` then there is nothing to be done. 
    78  
    79 If you have several doctrine connections in `databases.yml` but want to use only one of them, you may set `default_database` (in `config/settings.yml`) to the name of that database. 
    80  
    81 If you have several connections, or if you want to load only some of you schema files, you may use a project configuration file named '''`schemas.yml`''' that resides in `config/` in the project directory to map the connections to your various schema files. The syntax is as follows: 
    82  
    83 {{{ 
    84 connection1: [schema1, schema2] 
    85 connection2: [myPlugin/schema3] 
    86 }}} 
    87  
    88 Here, `schema1.yml` and `schema2.yml` are supposed to be inside `config/doctrine/` folder in the project directory. On the other hand, `schema3.yml` is supposed to be in the plugin directory, namely in the `plugins/myPlugin/config/doctrine/` folder. 
    89  
    90 If no such `config/schemas.yml` configuration file is found, then ''all'' the schemas found under the various `config/doctrine` folders will be loaded. 
    91  
    92 === Schema yml setup === 
    93  
    94 ==== Advantages over Propel schema setup ==== 
    95  
    96  * only the connections in `databases.yml` are used; there is no equivalent to the `propel.ini` file (thank god) 
    97  * you can have foreign keys from one schema pointing to another one; an extreme use of the schemas files would be to have one schema per class 
    98  
    99 ==== schema.yml syntax ==== 
    100  
    101 The syntax of the schema files is totally different from the usual `schema.yml`. You will put the schema doctrine file inside `config/doctrine`. 
    102  
    103 Here comes an example from which you should be able to figure out most of the syntax. It models a table of publications. 
    104  
    105  
    106  * '''inheritance''' 1) Column aggregation: publications can be books or articles 2) Different tables: `SuperUser` is a subclass of `User` and its instances are stored in a different table 
    107  ''NOTE: the table creation is broken when you do column aggregation inheritance. Temporary fix it to add the child columns to the parent table definition.'' 
    108  * '''many to one''' an article or a book has an author  
    109  * '''many to many''' books can be reviewed by several users  
    110  * '''relations are inheritance-aware''' there are no reviews for the articles  
    111  * '''subclasses have proper fields''' the isbn field exists only for the subclass "book"  
    112  * '''i18n''' users have a localised "description" field  
    113  
    114 Notice that the '`id`' fields are automatically created by doctrine. Notice also that the `i18n` culture field and the inheritance key field are automatically created by sfDoctrine.  
    115  
    116 ===== Types ===== 
    117 You may use any doctrine type you like. Take a look at the [http://doctrine.pengus.net/doctrine/manual/new/?chapter=basic-schema-mapping#columns:data-types Doctrine type documentation] for the list of available types. 
    118  
    119 The default column type is `string` while the default type for foreign keys is `integer`. Notice also the short syntax `type(size)`. 
    120  
    121 The `enum` type is obtained by giving an array in the type field: `column: type: [a, b, c]`. 
    122  
    123 ===== '''Sample Schema''' ===== 
    124 {{{ 
    125 #!sh 
    126 User:  
    127   tableName: user 
    128   i18n: {class: UserI18n, cultureField: culture} 
    129   columns: 
    130     firstName: {columnName: first_name, type: string(100)} 
    131     last_name: {type: string(100)} 
    132     email:     # with a validator  
    133       type: string(150) 
    134       unique: true 
    135       email: true 
    136     created_at: timestamp #automatically populated 
    137     updated_at: timestamp #automatically populated 
    138  
    139 UserI18n: 
    140   tableName: user_i18n 
    141   columns: 
    142     description: {type: string, size: 100} # alternate syntax 
    143     another_localised_field: string(100) 
    144     other_one: string(200) 
    145  
    146 SuperUser: 
    147   tableName: super_user 
    148   inheritance: {extends: User} 
    149   columns: 
    150     password: string(50) 
    151  
    152 Publication: 
    153   tableName: publication 
    154   columns: 
    155     publication_date: {type: timestamp} 
    156     state: {type: [published, on_hold, reviewed]} 
    157     title: {type: string(100), default: Untitled} 
    158     author_id: 
    159       foreignClass: User 
    160       cascadeDelete: true 
    161  
    162 # note: column aggregation on table inheritance is currently broken. 
    163 #Book: 
    164 #  inheritance: {extends: Publication, keyField: class_key, keyValue: 1} 
    165 # 
    166 ##  Alternate syntax 
    167 ##  inheritance: {extends: Publication, keyFields: {class_key: 1}} 
    168 # 
    169 ##  Alternate syntax (multi-column key) 
    170 ##  inheritance: {extends: Publication, keyFields: {primary_key: 2, sub_key: 1}} 
    171 # 
    172 #  columns: 
    173 #    isbn:  {type: integer(10), primary: true} 
    174      
    175 #Article: 
    176 #  inheritance: {extends: Publication, keyField: class_key, keyValue: 2} 
    177 #  columns: 
    178 #    journalName: {type: string(50), columnName: journal_name} 
    179  
    180 BookReview: 
    181   tableName: book_reviews 
    182   columns: 
    183     review: string #no length param will default to max size allowed by db 
    184     reviewed_book_id: 
    185       foreignClass: Publication 
    186       localName: reviewers 
    187       counterpart: reviewers_id # counterpart is used for a hasOne relationship 
    188     reviewers_id: 
    189       foreignClass: User 
    190       localName: reviewed_books 
    191       counterpart: reviewed_book_id # see above counterpart 
    192 }}} 
    193  
    194 ===== '''Specifying indexes in schema''' ===== 
    195 {{{ 
    196 #!sh 
    197 User:  
    198   tableName: user 
    199   indexes: 
    200     user_email_idx: 
    201       fields: [ email ] 
    202     user_name_idx: 
    203       fields: [ first_name, last_name ] 
    204   columns: 
    205     firstName: {columnName: first_name, type: string(100)} 
    206     last_name: {type: string(100)} 
    207     email:     # with a validator  
    208       type: string(150) 
    209       unique: true 
    210       email: true 
    211  
    212 #  Will produce the following index: 
    213 #  $this->index('user_email_idx', array ('fields' => array (0 => 'email'),)); 
    214 #  $this->index('user_name_idx', array ('fields' => array (0 => 'first_name', 1=> 'last_name'),)); 
    215  
    216 }}} 
    217 ===== Build the Model ===== 
    218 Run the following command in a terminal: 
    219 {{{ 
    220 symfony doctrine-build-model 
    221 }}} 
    222 then be sure to clear the cache to avoid all manner of errors and warnings! 
    223 {{{ 
    224 symfony cc 
    225 }}} 
    226  
    227 ===== '''Table Creation''' ===== 
    228 ''note: the below tasks arent fully functional yet. (examples needed, also.)'' 
    229 Table auto-creation has been removed from Doctrine, so that foreign keys are now properly supported! 
    230 Tables (and foreign keys and indexes) are created with: 
    231 {{{ 
    232 symfony doctrine-insert-sql 
    233 }}} 
    234 If you wish to simply generate the insert sql, you can use: 
    235 {{{ 
    236 symfony doctrine-build-sql 
    237 }}} 
    238  
    239 ===== '''Loading Data''' ===== 
    240 Here are the [wiki:sfDoctrineFaq#HowtoloaddatawithsfDoctrine data loading instructions]. 
    241  
    242 === Doctrine Validators === 
    243 ''note: link to new doctrine manpage on validators needed, once this info has been added'' 
    244  
    245 All custom doctrine validators are supported by the sfDoctrine's yaml schema, as demonstrated by the User.email column in the sample schema. More is available in the [http://www.phpdoctrine.net/doctrine/manual/documentation2.php?chapter=Advanced+components+-+Validators Validators section ] of the Doctrine manual. 
    246  
    247  
    248  
    249 == __Pake Tasks__ == 
    250 {{{ 
    251 *doctrine-build-all         > doctrine build all - generate model and initialize database 
    252 *doctrine-build-all-load    > doctrine build all load - generate model, initialize database, and load data from fixtures 
    253   doctrine-build-db          > doctrine build database - initialize database 
    254   doctrine-build-model       > build Doctrine classes 
    255   doctrine-build-schema      > doctrine build schema - build schema from an existing database 
    256   doctrine-build-sql         > exports doctrine schemas to sql 
    257   doctrine-drop-all-tables   > doctrine drop all - drop all database tables 
    258   doctrine-drop-db           > doctrine drop database - drops database 
    259 *doctrine-dump-data         > dump data to yaml fixtures file 
    260   doctrine-generate-crud     > Creates Doctrine CRUD Module 
    261   doctrine-import            > converts propel schema.*ml into doctrine schema 
    262   doctrine-init-admin        > initialize a new doctrine admin module 
    263   doctrine-insert-sql        > insert sql for doctrine schemas in to database 
    264 *doctrine-load-data         > load data from yaml fixtures file 
    265   doctrine-load-nested-set   > load doctrine nested set data from nested set fixtures file 
    266 }}} 
    267  
    268 Items with * to the left of them are not fully functional 
    269  
    270 {{{ 
    271 doctrine-build-all           > Not sure what causes this to not work, but the individual pake tasks work when running them individually 
    272 doctrine-build-all-load > Broken for same reason as above 
    273 doctrine-load-data         > A bug in Doctrine causes the loading of the data to not work because we need the ability to create blank records. http://phpdoctrine.net/trac/ticket/378 
    274 doctrine-dump-data      > Has issues with dumping data from complex relationships 
    275 }}} 
    276  
    277 == __Usage__ == 
    278  
    279 === Doctrine Query Examples === 
    280  
    281  
    282 ==== Simple Queries ==== 
    283  
    284 {{{ 
    285 #!php 
    286 <?php 
    287  
    288 // start a new query 
    289 $q = new Doctrine_Query(); 
    290 // or 
    291 $q = Doctrine_Query::create()->etc... 
    292  
    293 // find some stuff 
    294 $stuff = $q->select('foo.baz')->from('myClass foo')->where('foo.bar = ?', 3)->execute(); 
    295  
    296 echo $stuff[0]['baz']; 
    297  
    298 // select only some columns 
    299 $q->select('u.first_name, u.last_name')->from('User u')->execute(); 
    300  
    301 // multiple where conditions (note array value syntax) 
    302 $stuff = $q->from('myClass foo')->where('foo.bar = ? AND foo.wee = ?', array(3, 'wee')->execute(); 
    303 $stuff = $q->from('myClass foo')->where('foo.bar = ? OR foo.wee = ? AND foo.doh = ?', array(3, 'wee', 'doh')->execute(); 
    304  
    305 // using getFirst() will only return 1 entry  
    306 $stuff = $q->select('foo.baz')->from('myClass foo')->where('foo.bar = ?', 3)->execute()->getFirst(); 
    307  
    308 echo $stuff['baz']; //no array offset needed because of getFirst() 
    309  
    310 // using orderBy(), default is ascending(ASC) 
    311 $q->from('User u')->where('u.dept_id = ?', $foo)->orderBy('u.last_name DESC')->execute(); 
    312  
    313 // using groupBy() 
    314 $q->from('User u')->where('u.dept_id = ?', $foo)->groupBy('u.first_name')->execute(); 
    315  
    316  
    317 // break up the query into multiple lines 
    318 $q->select('foo.doh')->from('myClass foo'); 
    319 $q->where('foo.bar = ?', $bar); 
    320 $q->execute(); 
    321  
    322 // extending the where clause in a separate line 
    323 $q->where('foo.bar = ?', $bar); 
    324 $q->addWhere('foo.doh = ?', $doh); 
    325 $q->execute(); 
    326  
    327  
    328 // Create new record 
    329 $record = new MyRecord(); // MyRecord extends Doctrine_Record 
    330 $record->set('key', 'value'); 
    331 $record->save(); // This will create a new entry in the table used by the MyRecord class 
    332  
    333 // using FETCH_ARRAY: retrieve collection as an associative array (column => value) 
    334 //note that additional objects get their own nested arrays 
    335 $results = $q->select('b.foo, b.bar, b.blah, a.heh')->from('Baz b')->innerJoin('b.Argh a')->execute(array(), Doctrine::FETCH_ARRAY); 
    336 print_r($results); 
    337 //results: 
    338 Array 
    339 ( 
    340   0 => 
    341     Array 
    342     ( 
    343       [foo] => wee 
    344       [bar] => argh  
    345       [blah] => pfeh 
    346       [Argh] => 
    347         Array 
    348         ( 
    349           [heh] => george 
    350         ) 
    351     ) 
    352 ) 
    353  
    354 // sfDoctrine does support transactions 
    355 $conn = Doctrine_Manager::connection(); 
    356 $conn->beginTransaction(); 
    357  
    358  // queries 
    359  
    360 $conn->commit(); 
    361  
    362 }}} 
    363  
    364 ==== Update/Delete ==== 
    365  
    366 {{{ 
    367 #!php 
    368 <?php 
    369  
    370 // update 
    371 $q->update('MyClass foo')->set('foo.bar', 33)->where('foo.baz = ?', 'doh')->execute(); 
    372  
    373 // update (escaped) 
    374 $q->update('MyClass foo')->set('foo.bar', '?','bar')->where('foo.baz = ?', 'doh')->execute(); 
    375  
    376 // delete 
    377 $item = $q->from('myClass foo')->where('foo.id = ?', $id)->execute(); 
    378 $item->delete(); 
    379  
    380 }}} 
    381  
    382 ==== From/Joins ==== 
    383 '''note that the array accessor syntax is recommended:''' 
    384 {{{ 
    385 #!php 
    386 echo $myrecord['myfield']; 
    387 }}} 
    388 This allows seamless transition to the [http://doctrine.pengus.net/doctrine/manual/new/?one-page=1#improving-performance:fetch-only-what-you-need much-faster Fetch_Array syntax]. Of course, if the relations arent already loaded (ie, you want to use lazy loading), this syntax won't work. 
    389  
    390  
    391 {{{ 
    392 #!php 
    393 <?php 
    394  
    395 // doSelectJoinAuthor() and doSelectJoinEditor() (impossible to do with propel) 
    396 $q->from('Article a LEFT JOIN a.Author au LEFT JOIN a.Editor e')->execute(); 
    397  
    398 // this is the recommended syntax 
    399 $stuff = $q->select('a.*, au.last_name')->from('Article a LEFT JOIN a.author au')->execute(); 
    400  
    401 echo $stuff[0]['author']['last_name']; 
    402 echo $stuff[0]['title'];    //note the absence of table accessor method, has to be omitted for first table in from() 
    403  
    404  
    405 // another doSelectJoin which is impossible with propel (cannot add table aliases with this shorthand syntax) 
    406 // for aliasing:  ...from('Article a LEFT JOIN a.author au LEFT JOIN au.address ad') 
    407 $stuff = $q->from('Article.author.address')->execute(); // *one* query to get the articles, their authors and the addresses of the authors (three tables) 
    408  
    409 foreach ($stuff as $thisRow) 
    410 { 
    411     // to get article title 
    412     echo $thisRow['title']; 
    413      
    414     // to get article author's last name 
    415     echo $thisRow['author']['last_name']; 
    416      
    417     // to get street name of article author 
    418     echo $thisRow['author']['address']['streetName']; 
    419 } 
    420  
    421 }}} 
    422  
    423 === Doctrine getTable Examples === 
    424 ''Note that sfDoctrine::getTable() is a shortcut to: Doctrine_Manager::getInstance()->getTable().'' 
    425 {{{ 
    426 #!php 
    427 <?php 
    428  
    429 // get all entries 
    430 $q = sfDoctrine::getTable('myClass')->findAll(); 
    431  
    432 // get entry by pk 
    433 $q = sfDoctrine::getTable('myClass')->find('primary key'); 
    434  
    435 // set primary key for key access 
    436 $q->setAttribute(Doctrine::ATTR_COLL_KEY, 'column_name'); 
    437 // now you can do 
    438 $q->get('column_name_value') 
    439  
    440  
    441 }}} 
    442  
    443  
    444 ==== Pager ==== 
    445 {{{ 
    446 #!php 
    447 <?php 
    448 //pager 
    449 $pager = new sfDoctrinePager('MyClass', 10); 
    450 $pager->getQuery()->where('category = ?', 'foo')->orderBy('created_at desc'); 
    451 $pager->setPage(2); 
    452 $pager->init(); 
    453 $pager->getResults(); 
    454 $pager->getResults('array'); //use the MUCH faster fetch_array syntax! 
    455 }}} 
    456 A beginners guide on how to use the pager is available at [wiki:sfDoctrinePager]. 
    457 ==== If you are used to Propel ==== 
    458 {{{ 
    459 #!php 
    460 <?php 
    461 // some propel equivalents 
    462  
    463 // retrieveByPk() 
    464 $stuff = Doctrine_Manager::getInstance()->getTable('myClass')->find($pk); 
    465  
    466 // doSelectOne() 
    467 $stuff = Doctrine_Query::create()->from('User')->where('User.email = ?', $email)->limit(1)->execute()->getFirst(); 
    468 }}} 
    469  
    470 === Propel compatibility mode === 
    471 {{{ 
    472 #!php 
    473 <?php 
    474 class myClass extends sfDoctrineRecord 
    475 { 
    476   $this->hasColumn('foo', integer); 
    477 } 
    478 ..... 
    479 $object = Doctrine_Manager::getInstance()->getTable('myClass')->find(1); 
    480 // the following three calls will return the same value 
    481 echo $object['foo']; //recommended for fetch_array compatibility 
    482 echo $object->getFoo(); // propel compatibility mode 
    483 echo $object->get('foo'); 
    484  
    485  
    486 // you can override the getters/setters with 2 methods in sfDoctrine. We have filters for the getters/setters and then the ability to completely override the getters/setters 
    487 function filterGetFoo() 
    488 function filterSetFoo() 
    489 function getFoo() 
    490 function setFoo() 
    491 }}} 
    492  
    493 Be careful when upgrading, if you have custom methods like getCategory() and you also have a column named category on the same model, then you will run in to problems, because getCategory() will override the getter for that model/column completely. You will need to rename that function in order for it to be a custom function and so it does not override the getter. 
    494 ==== Equivalent of peer methods ==== 
    495  
    496 The equivalent of the peer methods are either static methods in the doctrine record classes or regular methods in the Table classes: 
    497 {{{ 
    498 #!php 
    499 <?php 
    500 // static style 
    501 // in a 'Post' class 
    502 static public function findPublished() 
    503 { 
    504   $q = Doctrine_Query::create()->from('Post'); 
    505   return $q->where('Post.is_published = ?', true)->execute(); 
    506 } 
    507  
    508 // Table style 
    509 // in a class 'PostTable': 
    510 public function findPublished() 
    511 { 
    512   $q = $this->createQuery(); // the equivalent of Doctrine_Query::create()->from() 
    513   return $q->where('Post.is_published = ?', true)->execute(); 
    514 } 
    515  
    516 // in the controller (actions.class.php for example): 
    517 // static style 
    518 $posts = Post::findPublished(); 
    519 // table style 
    520 $posts = Doctrine_Manager::getInstance()->getTable('Post')->findPublished(); 
    521 }}} 
    522  
    523 ==== Determining if a record exists: ==== 
    524  
    525 Doctrine always returns a record object (albeit empty) when you query a foreign relation: 
    526 {{{ 
    527 #!php 
    528 <?php 
    529 $articleAuthor = $article->get('author'); // 'author' is a foreign key 
    530 if (!$articleAuthor->exists()) // doctrine will always return a record, albeit perhaps an empty one, so make sure to use the exists() method! 
    531   echo 'This article has no author!'; 
    532 }}} 
    533  
    534 ==== More Examples ==== 
    535  
    536 With the schema above one may then use the following syntax in the code: 
    537 {{{ 
    538 #!php 
    539 <?php 
    540 // elementary queries (no joins!) 
    541 $q = new Doctrine_Query; 
    542 $jackspublications = $q->from('Publication p')->where('p.author.firstName = ?', 'Jack')->execute(); 
    543 // try this one with propel ;-) 
    544 $q = new Doctrine_Query; 
    545 $authorsOfPublicationsReviewedByJack = $q->from('User u')->where('u.authoredPublications.reviewers.first_name = ?', 'Jack')->execute(); 
    546  
    547 // i18n 
    548 $author->setCulture('foo'); 
    549 $author->set('description', 'Bar'); 
    550 echo $author->get('description'); // "Bar" 
    551  
    552 // one to many 
    553 $author = $book->get('author'); echo $author['firstName']; 
    554  
    555 // many to one 
    556 $books = $author->get('authoredPublications'); foreach ($publications as $publication) echo $publication['publication_date']; 
    557  
    558 // many to many 
    559 $books = $user->get('authoredPublications'); echo count($publications); 
    560  
    561 // inheritance 
    562 Doctrine_Manager::getInstance()->getTable('Book')->findAll(); // you get only books, not articles 
    563 // all the articles written by John (not the books): 
    564 $articles = Doctrine_Query::create()->from('Article a')->where('a.author.id = ?', $john->get('id'))->execute(); 
    565 }}} 
    566  
    567 === Raw SQL examples === 
    568 If you need for some reason to run raw SQL, you can always use the underlying PDO engine to do so: 
    569  
    570 __From a table model class:__ 
    571 {{{ 
    572 #!php 
    573 <?php 
    574 class ObjectTable extends Doctrine_Table 
    575 { 
    576   public function findBySql($sql) 
    577   { 
    578     try  
    579       { 
    580         $res = $this->getConnection()->getDbh()->query($sql); 
    581         return $res->fetch(PDO::FETCH_ASSOC); 
    582       } 
    583       catch (PDOException $e) 
    584       { 
    585         die('PDO Exception: '.$e->getMessage()); 
    586       } 
    587   } 
    588 } 
    589  
    590 }}} 
    591  
    592 __From anywhere:__ 
    593 {{{ 
    594 #!php 
    595 <?php 
    596  
    597 try 
    598 { 
    599   $res = sfDoctrine::connection()->getDbh()->query($sql); 
    600   return $res->fetch(PDO::FETCH_ASSOC); 
    601 } 
    602 catch (PDOException $e) 
    603 { 
    604   die('PDO Exception: '.$e->getMessage()); 
    605 } 
    606  
    607 }}} 
    608  
    609 == __Object helpers__ == 
    610  
    611 It is perfectly all right to use helpers with sfDoctrine but you'll have to pass an array instead of a name of a getter. Here is a revisited example from the symfony documentation: 
    612 {{{ 
    613 #!php 
    614 <?php 
    615 // instead of object_select_tag($article, 'getAuthorId', 'related_class=Author') 
    616 object_select_tag($article, array('get', array('author_id')), 'related_class=Author') 
    617 }}} 
    618  
    619 For the object helpers, the general syntax is: 
    620 {{{ 
    621 #!php 
    622 <?php 
    623 // to represent functionName('arg1', 'arg2') you would use: 
    624 array('functionName',array('arg1', 'arg2')) 
    625 }}} 
    626  
    627 == __sfDoctrine Generators__ == 
    628  
    629 === Crud Generation === 
    630  
    631 {{{ 
    632 symfony doctrine-generate-crud <application> <module> <class> 
    633 }}} 
    634  
    635 === Admin Generation === 
    636  
    637 ==== Many to many relationships ==== 
    638 For many to many relationships you will have to replace for example `admin_check_list` by `doctrine_admin_check_list`. Besides, the `throughClass` parameter is not necessary. All in all it looks like: 
    639 {{{ 
    640       fields: 
    641         objects: doctrine_admin_check_list 
    642 }}} 
    643  
    644 ==== Foreign keys ==== 
    645 In the doctrine admin generator, '''you should never use the foreign key column names'''. Use only the foreign ''names''. So if you have a relation named "`Author`" and the foreign key is "`u_id`" you should use "`Author`" in `generator.yml`, and ''not'' "`u_id`". This holds for both the `display` and the `edit` part of the admin generator interface. Take a look at the [wiki:sfDoctrineFaq#Whatisthedifferencebetweenarelationnameandarelationcolumn FAQ] for more details and examples. 
    646  
    647 === Propel Model Import === 
    648  
    649 First move all your propel model classes outside of the autoloading scope. 
    650  
    651 ==== Import/export from a propel schema.xml ==== 
    652  
    653 You may try 
    654 {{{ 
    655 symfony doctrine-import 
    656 }}} 
    657  
    658 to import the bulk of your database description into a doctrine format. Inheritance and i18n are ''not'' imported. 
    659  
    660 {{{ 
    661 #!html 
    662 <div style="display:none"> 
    663 To export a doctrine model to propel you may use: 
    664 {{{ 
    665 symfony doctrine-export 
    666 }}} 
    667  
    668 Inheritance and i18n are ''not'' exported. 
    669 </div> 
    670 }}} 
    671  
    672 This command automatically generates doctrine description files and its subclasses from a `connection.yml` schema file. The generated files are all placed in `lib/model/doctrine`.  
    673  
    674 == __Advanced__ == 
    675  
    676 === Tuning (doctrine.yml) === 
    677 First look at doctrine.yml in the config folder of the sfDoctrine plugin. The file is self-documented and you will find all default attribute values. '''Do not edit the file! ''' 
    678  
    679 To change doctrine attributes, create a doctrine.yml file in one of the config folders of your symfony project. The config is overridable like other symfony configs, so you can put doctrine.yml files in any config folder. Here is an example of how to use doctrine.yml to control attributes for different environments and connections: 
    680    
    681 {{{ 
    682   test: 
    683     # Enable validation for all connections in test environment 
    684     attributes: 
    685       vld: on 
    686    
    687     # No validation for this connection in test environment 
    688     trustedConnection: 
    689       attributes: 
    690         vld: off 
    691 }}} 
    692  
    693 === Overriding `set` and `get` === 
    694  
    695 Instead of overriding the accessors you will use '''filters'''.  Filters have been removed from Doctrine (for speed?), but are planned to be re-implemented in sfDoctrine (''status of this?'').  The filter prefixes are, by default, '''filterGet''' and '''filterSet''' but those names are configurable. So if you would like to change the behaviour of the `set('foo')` setter you would proceed as follows: 
    696 {{{ 
    697 #!php 
    698 <?php 
    699 public function filterSetFoo($value) 
    700 { 
    701   return $value.'bar'; // every time you use set('foo', $s), you actually save $s.'bar' in the database 
    702 } 
    703  
    704 // similarly for the getters 
    705 public function filterGetFoo($value) 
    706 { 
    707   return "The value is $value"; 
    708 } 
    709  
    710  
    711 // NOTE: For underscore fields like `start_date`, you must keep the underscore (temp bug with Doctrine - 6/8/07) 
    712 public function filterGetStart_Date($value) 
    713 { 
    714   return "The value is $value"; 
    715 } 
    716  
    717 }}} 
    718  
    719  
    720  
    721 === Overriding save() === 
    722  
    723 For example say you have the table `User` and `Report`.  When a new `Report` is created, you want a counter field `User.report_count` to be incremented.  In Doctrine, we can use the '''EventListener''' function onInsert(). 
    724  
    725 '''lib/model/doctrine/Report.class.php''' 
    726 Note: this changed recently in Doctrine; now onInsert and onUpdate events are handled by Doctrine_Record, rather than Doctrine_EventListener, and the methods have been renamed to preInsert, postInsert, preUpdate, postUpdate. The previous example could be rewritten as: 
    727  
    728 {{{ 
    729 // This class would likely be part of your model 
    730 class Report_Record extends Doctrine_Record 
    731 { 
    732     public function postInsert($event) 
    733     { 
    734         $q = new Doctrine_Query(); 
    735         $q->select('u.report_count')->from('User u')->where('u.id = ?', $this->get('user_id')); 
    736         $row = $q->execute()->getFirst(); 
    737  
    738         $row->set('report_count', ($row->get('report_count') + 1)); 
    739         $row->save(); 
    740     } 
    741 } 
    742  
    743 }}} 
    744  
    745 And then when Report_Record::save is called on a record for the first time (which triggers an insert), the postInsert event will fire and the code will be executed. 
    746  
    747 === Hierarchical data === 
    748  
    749 Doctrine supports managing hierarchical data (i.e. Trees) and currently fully supports the Nested Set implementation. 
    750  
    751 You can read more about how to manage trees in doctrine [http://phpdoctrine.net/doctrine/manual/documentation2.php?chapter=Object+relational+mapping+-+Relations+-+Foreign+key+associations+-+Tree+structure here] 
    752  
    753 Below is an example of how to configure a tree within the `schema.yml` 
    754  
    755 {{{ 
    756 Tree: 
    757   tableName: tree 
    758   options: 
    759     treeImpl: NestedSet 
    760   columns: 
    761     name: {type: string, size: 1000} 
    762 }}} 
    763  
    764 Note that you can use the options namespace in the schema.yml to configure your model with any options, these are then accessed as follows: 
    765  
    766 {{{ 
    767 #!php 
    768 <?php 
    769 Doctrine_Manager::getInstance()->getTable('myClass')->getOption($name); 
    770 }}} 
    771  
    772 == __Links__ == 
    773 === [wiki:sfDoctrineFaq] === 
    774  
    775 === [wiki:sfDoctrinePager] === 
    776  
    777 === [wiki:sfGuardDoctrinePlugin] === 
    778 === Snippets === 
    779 All snippets should be created in [http://www.symfony-project.com/snippets symfony snippets]. 
    780 Here are the [http://www.symfony-project.com/snippets/snippets/tagged/doctrine/order_by/date current doctrine snippets].