Development

sfDoctrinePlugin0.1 (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of sfDoctrinePlugin0.1

Show
Ignore:
Author:
Jonathan.Wage (IP: 98.193.184.47)
Timestamp:
10/19/07 08:01:44 (10 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • sfDoctrinePlugin0.1

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