Coverage for Doctrine_Table

Back to coverage report

1 <?php
2 /*
3  *  $Id: Table.php 3206 2007-11-22 22:02:35Z zYne $
4  *
5  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16  *
17  * This software consists of voluntary contributions made by many individuals
18  * and is licensed under the LGPL. For more information, see
19  * <http://www.phpdoctrine.com>.
20  */
21
22 /**
23  * Doctrine_Table   represents a database table
24  *                  each Doctrine_Table holds the information of foreignKeys and associations
25  *
26  *
27  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
28  * @package     Doctrine
29  * @subpackage  Table
30  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
31  * @version     $Revision: 3206 $
32  * @link        www.phpdoctrine.com
33  * @since       1.0
34  */
35 class Doctrine_Table extends Doctrine_Configurable implements Countable
36 {
37     /**
38      * @var array $data                                 temporary data which is then loaded into Doctrine_Record::$_data
39      */
40     protected $_data             = array();
41
42     /**
43      * @var mixed $identifier   The field names of all fields that are part of the identifier/primary key
44      */
45     protected $_identifier = array();
46
47     /**
48      * @see Doctrine_Identifier constants
49      * @var integer $identifierType                     the type of identifier this table uses
50      */
51     protected $_identifierType;
52
53     /**
54      * @var Doctrine_Connection $conn                   Doctrine_Connection object that created this table
55      */
56     protected $_conn;
57
58     /**
59      * @var array $identityMap                          first level cache
60      */
61     protected $_identityMap        = array();
62
63     /**
64      * @var Doctrine_Table_Repository $repository       record repository
65      */
66     protected $_repository;
67
68     /**
69      * @var array $columns                  an array of column definitions,
70      *                                      keys are column names and values are column definitions
71      *
72      *                                      the definition array has atleast the following values:
73      *
74      *                                      -- type         the column type, eg. 'integer'
75      *                                      -- length       the column length, eg. 11
76      *
77      *                                      additional keys:
78      *                                      -- notnull      whether or not the column is marked as notnull
79      *                                      -- values       enum values
80      *                                      -- notblank     notblank validator + notnull constraint
81      *                                      ... many more
82      */
83     protected $_columns          = array();
84
85     /**
86      * @var array $_fieldNames            an array of field names. used to look up field names
87      *                                    from column names.
88      *                                    keys are column names and values are field names
89      */
90     protected $_fieldNames    = array();
91     
92     /**
93      * 
94      * @var array $_columnNames             an array of column names
95      *                                      keys are field names and values column names.
96      *                                      used to look up column names from field names.
97      *                                      this is the reverse lookup map of $_fieldNames.
98      */
99     protected $_columnNames = array();
100
101     /**
102      * @var integer $columnCount            cached column count, Doctrine_Record uses this column count in when
103      *                                      determining its state
104      */
105     protected $columnCount;
106
107     /**
108      * @var boolean $hasDefaultValues       whether or not this table has default values
109      */
110     protected $hasDefaultValues;
111
112     /**
113      * @var array $options                  an array containing all options
114      *
115      *      -- name                         name of the component, for example component name of the GroupTable is 'Group'
116      *
117      *      -- parents                      the parent classes of this component
118      *
119      *      -- declaringClass               name of the table definition declaring class (when using inheritance the class
120      *                                      that defines the table structure can be any class in the inheritance hierarchy,
121      *                                      hence we need reflection to check out which class actually calls setTableDefinition)
122      *
123      *      -- tableName                    database table name, in most cases this is the same as component name but in some cases
124      *                                      where one-table-multi-class inheritance is used this will be the name of the inherited table
125      *
126      *      -- sequenceName                 Some databases need sequences instead of auto incrementation primary keys,
127      *                                      you can set specific sequence for your table by calling setOption('sequenceName', $seqName)
128      *                                      where $seqName is the name of the desired sequence
129      *
130      *      -- enumMap                      enum value arrays
131      *
132      *      -- inheritanceMap               inheritanceMap is used for inheritance mapping, keys representing columns and values
133      *                                      the column values that should correspond to child classes
134      *
135      *      -- type                         table type (mysql example: INNODB)
136      *
137      *      -- charset                      character set
138      *
139      *      -- foreignKeys                  the foreign keys of this table
140      *
141      *      -- checks                       the check constraints of this table, eg. 'price > dicounted_price'
142      *
143      *      -- collation                    collation attribute
144      *
145      *      -- indexes                      the index definitions of this table
146      *
147      *      -- treeImpl                     the tree implementation of this table (if any)
148      *
149      *      -- treeOptions                  the tree options
150      *
151      *      -- queryParts                   the bound query parts
152      *
153      *      -- versioning
154      */
155     protected $_options      = array('name'           => null,
156                                      'tableName'      => null,
157                                      'sequenceName'   => null,
158                                      'inheritanceMap' => array(),
159                                      'enumMap'        => array(),
160                                      'type'           => null,
161                                      'charset'        => null,
162                                      'collation'      => null,
163                                      'treeImpl'       => null,
164                                      'treeOptions'    => null,
165                                      'indexes'        => array(),
166                                      'parents'        => array(),
167                                      'queryParts'     => array(),
168                                      'versioning'     => null,
169                                      );
170
171     /**
172      * @var Doctrine_Tree $tree                 tree object associated with this table
173      */
174     protected $_tree;
175
176     /**
177      * @var Doctrine_Relation_Parser $_parser   relation parser object
178      */
179     protected $_parser;
180
181     /**
182      * @var array $_templates                   an array containing all templates attached to this table
183      */
184     protected $_templates   = array();
185
186     /**
187      * @var array $_filters                     an array containing all record filters attached to this table
188      */
189     protected $_filters     = array();
190
191     /**
192      * @var array $_invokedMethods              method invoker cache
193      */
194     protected $_invokedMethods = array();
195
196
197
198     /**
199      * the constructor
200      *
201      * @throws Doctrine_Connection_Exception    if there are no opened connections
202      * @param string $name                      the name of the component
203      * @param Doctrine_Connection $conn         the connection associated with this table
204      */
205     public function __construct($name, Doctrine_Connection $conn, $initDefinition = false)
206     {
207         $this->_conn = $conn;
208
209         $this->setParent($this->_conn);
210
211         $this->_options['name'] = $name;
212         $this->_parser = new Doctrine_Relation_Parser($this);
213
214         if ($initDefinition) {
215             $record = $this->initDefinition();
216     
217             $this->initIdentifier();
218     
219             $record->setUp();
220     
221             // if tree, set up tree
222             if ($this->isTree()) {
223                 $this->getTree()->setUp();
224             }
225         }
226         $this->_filters[]  = new Doctrine_Record_Filter_Standard();
227         $this->_repository = new Doctrine_Table_Repository($this);
228     }
229     
230     /**
231      * Initializes the in-memory table definition.
232      *
233      * @param string $name
234      */
235     public function initDefinition()
236     {        
237         $name = $this->_options['name'];
238         if ( ! class_exists($name) || empty($name)) {
239             throw new Doctrine_Exception("Couldn't find class " . $name);
240         }
241         $record = new $name($this);
242
243         $names = array();
244
245         $class = $name;
246
247         // get parent classes
248
249         do {
250             if ($class === 'Doctrine_Record') {
251                 break;
252             }
253
254             $name = $class;
255             $names[] = $name;
256         } while ($class = get_parent_class($class));
257
258         if ($class === false) {
259             throw new Doctrine_Table_Exception('Unknown component.');
260         }
261
262         // reverse names
263         $names = array_reverse($names);
264         // save parents
265         array_pop($names);
266         $this->_options['parents'] = $names;
267
268         // create database table
269         if (method_exists($record, 'setTableDefinition')) {
270             $record->setTableDefinition();
271             // get the declaring class of setTableDefinition method
272             $method = new ReflectionMethod($this->_options['name'], 'setTableDefinition');
273             $class = $method->getDeclaringClass();
274             
275         } else {
276             $class = new ReflectionClass($class);
277         }
278
279         $this->_options['joinedParents'] = array();
280
281         foreach (array_reverse($this->_options['parents']) as $parent) {
282
283             if ($parent === $class->getName()) {
284                 continue;
285             }
286             $ref = new ReflectionClass($parent);
287             
288             if ($ref->isAbstract()) {
289                 continue;
290             }
291             $parentTable = $this->_conn->getTable($parent);
292
293             $found = false;
294             $parentColumns = $parentTable->getColumns();
295
296             foreach ($parentColumns as $columnName => $definition) {
297                 if ( ! isset($definition['primary'])) {
298                     if (isset($this->_columns[$columnName])) {
299                         $found = true;
300                         break;
301                     } else {
302                         if ( ! isset($parentColumns[$columnName]['owner'])) {
303                             $parentColumns[$columnName]['owner'] = $parentTable->getComponentName();
304                         }
305
306                         $this->_options['joinedParents'][] = $parentColumns[$columnName]['owner'];
307                     }
308                 } else {
309                     unset($parentColumns[$columnName]);
310                 }
311             }
312
313             if ($found) {
314                 continue;
315             }
316
317             foreach ($parentColumns as $columnName => $definition) {    
318                 $fullName = $columnName . ' as ' . $parentTable->getFieldName($columnName);
319                 $this->setColumn($fullName, $definition['type'], $definition['length'], $definition, true);
320             }
321
322             break;
323         }
324         
325         $this->_options['joinedParents'] = array_values(array_unique($this->_options['joinedParents']));
326
327         $this->_options['declaringClass'] = $class;
328
329         // set the table definition for the given tree implementation
330         if ($this->isTree()) {
331             $this->getTree()->setTableDefinition();
332         }
333
334         $this->columnCount = count($this->_columns);
335         
336         if ( ! isset($this->_options['tableName'])) {
337             $this->_options['tableName'] = Doctrine::tableize($class->getName());
338         }
339         
340         return $record;
341     }
342     
343     /**
344      * Initializes the table identifier(s)/primary key(s)
345      *
346      */
347     public function initIdentifier()
348     {
349         switch (count($this->_identifier)) {
350             case 0:
351                 if ( ! empty($this->_options['joinedParents'])) {
352                     $root = current($this->_options['joinedParents']);
353                     
354                     $table = $this->_conn->getTable($root);
355                 
356                     $this->_identifier = $table->getIdentifier();
357
358                     $this->_identifierType = ($table->getIdentifierType() !== Doctrine::IDENTIFIER_AUTOINC)
359                                             ? $table->getIdentifierType() : Doctrine::IDENTIFIER_NATURAL;
360
361                     // add all inherited primary keys
362                     foreach ((array) $this->_identifier as $id) {
363                         $definition = $table->getDefinitionOf($id);
364
365                         // inherited primary keys shouldn't contain autoinc
366                         // and sequence definitions
367                         unset($definition['autoincrement']);
368                         unset($definition['sequence']);
369
370                         // add the inherited primary key column
371                         $fullName = $id . ' as ' . $table->getFieldName($id);
372                         $this->setColumn($fullName, $definition['type'], $definition['length'],
373                                 $definition, true);
374                     }
375                 } else {
376                     $definition = array('type' => 'integer',
377                                         'length' => 20,
378                                         'autoincrement' => true,
379                                         'primary' => true);
380                     $this->setColumn('id', $definition['type'], $definition['length'], $definition, true);
381                     $this->_identifier = 'id';
382                     $this->_identifierType = Doctrine::IDENTIFIER_AUTOINC;
383                 }
384                 $this->columnCount++;
385                 break;
386             case 1:
387                 foreach ($this->_identifier as $pk) {
388                     $columnName = $this->getColumnName($pk);
389                     $e = $this->_columns[$columnName];
390
391                     $found = false;
392
393                     foreach ($e as $option => $value) {
394                         if ($found) {
395                             break;
396                         }
397
398                         $e2 = explode(':', $option);
399
400                         switch (strtolower($e2[0])) {
401                             case 'autoincrement':
402                             case 'autoinc':
403                                 $this->_identifierType = Doctrine::IDENTIFIER_AUTOINC;
404                                 $found = true;
405                                 break;
406                             case 'seq':
407                             case 'sequence':
408                                 $this->_identifierType = Doctrine::IDENTIFIER_SEQUENCE;
409                                 $found = true;
410
411                                 if ($value) {
412                                     $this->_options['sequenceName'] = $value;
413                                 } else {
414                                     if (($sequence = $this->getAttribute(Doctrine::ATTR_DEFAULT_SEQUENCE)) !== null) {
415                                         $this->_options['sequenceName'] = $sequence;
416                                     } else {
417                                         $this->_options['sequenceName'] = $this->_conn->getSequenceName($this->_options['tableName']);
418                                     }
419                                 }
420                                 break;
421                         }
422                     }
423                     if ( ! isset($this->_identifierType)) {
424                         $this->_identifierType = Doctrine::IDENTIFIER_NATURAL;
425                     }
426                 }
427
428                 $this->_identifier = $pk;
429
430                 break;
431             default:
432                 $this->_identifierType = Doctrine::IDENTIFIER_COMPOSITE;
433         }
434     }
435     
436     /**
437      * Gets the owner of a column.
438      * The owner of a column is the name of the component in a hierarchy that
439      * defines the column.
440      *
441      * @param string $columnName   The column name 
442      * @return string  The name of the owning/defining component
443      */
444     public function getColumnOwner($columnName)
445     {
446         if (isset($this->_columns[$columnName]['owner'])) {
447             return $this->_columns[$columnName]['owner'];
448         } else {
449             return $this->getComponentName();
450         }
451     }
452     
453     /**
454      * Checks whether a column is inherited from a component further up in the hierarchy.
455      *
456      * @param $columnName  The column name
457      * @return boolean  TRUE if column is inherited, FALSE otherwise.
458      */
459     public function isInheritedColumn($columnName)
460     {
461         return (isset($this->_columns[$columnName]['owner']));
462     }
463     
464     /**
465      * Checks whether a field is part of the table identifier/primary key field(s).
466      *
467      * @param string $fieldName  The field name
468      * @return boolean  TRUE if the field is part of the table identifier/primary key field(s), 
469      *                  FALSE otherwise.
470      */
471     public function isIdentifier($fieldName)
472     {
473         return ($fieldName === $this->getIdentifier() || 
474                 in_array($fieldName, (array) $this->getIdentifier()));
475     }
476
477     public function getMethodOwner($method)
478     {
479         return (isset($this->_invokedMethods[$method])) ?
480                       $this->_invokedMethods[$method] : false;
481     }
482     
483     public function setMethodOwner($method, $class)
484     {
485         $this->_invokedMethods[$method] = $class;
486     }
487
488     /**
489      * getTemplates
490      * returns all templates attached to this table
491      *
492      * @return array     an array containing all templates
493      */
494     public function getTemplates()
495     {
496         return $this->_templates;
497     }
498
499     /**
500      * export
501      * exports this table to database based on column and option definitions
502      *
503      * @throws Doctrine_Connection_Exception    if some error other than Doctrine::ERR_ALREADY_EXISTS
504      *                                          occurred during the create table operation
505      * @return boolean                          whether or not the export operation was successful
506      *                                          false if table already existed in the database
507      */
508     public function export()
509     {
510         $this->_conn->export->exportTable($this);
511     }
512
513     /**
514      * getExportableFormat
515      * returns exportable presentation of this object
516      *
517      * @return array
518      */
519     public function getExportableFormat($parseForeignKeys = true)
520     {
521         $columns = array();
522         $primary = array();
523
524         foreach ($this->getColumns() as $name => $definition) {
525
526             if (isset($definition['owner'])) {
527                 continue;
528             }
529
530             switch ($definition['type']) {
531                 case 'enum':
532                     if (isset($definition['default'])) {
533                         $definition['default'] = $this->enumIndex($name, $definition['default']);
534                     }
535                     break;
536                 case 'boolean':
537                     if (isset($definition['default'])) {
538                         $definition['default'] = $this->getConnection()->convertBooleans($definition['default']);
539                     }
540                     break;
541             }
542             $columns[$name] = $definition;
543
544             if (isset($definition['primary']) && $definition['primary']) {
545                 $primary[] = $name;
546             }
547         }
548         $options['foreignKeys'] = array();
549
550
551         if ($parseForeignKeys && $this->getAttribute(Doctrine::ATTR_EXPORT)
552                 & Doctrine::EXPORT_CONSTRAINTS) {
553
554             $constraints = array();
555
556             $emptyIntegrity = array('onUpdate' => null,
557                                     'onDelete' => null);
558
559             foreach ($this->getRelations() as $name => $relation) {
560                 $fk = $relation->toArray();
561                 $fk['foreignTable'] = $relation->getTable()->getTableName();
562
563                 if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) {
564                     if ($relation->hasConstraint()) {
565                         throw new Doctrine_Table_Exception("Badly constructed integrity constraints.");
566                     }
567                     continue;
568                 }
569
570                 $integrity = array('onUpdate' => $fk['onUpdate'],
571                                    'onDelete' => $fk['onDelete']);
572
573                 if ($relation instanceof Doctrine_Relation_LocalKey) {
574                     $def = array('local'        => $relation->getLocal(),
575                                  'foreign'      => $relation->getForeign(),
576                                  'foreignTable' => $relation->getTable()->getTableName());
577
578                     if (($key = array_search($def, $options['foreignKeys'])) === false) {
579                         $options['foreignKeys'][] = $def;
580                         $constraints[] = $integrity;
581                     } else {
582                         if ($integrity !== $emptyIntegrity) {
583                             $constraints[$key] = $integrity;
584                         }
585                     }
586                 }
587             }
588
589             foreach ($constraints as $k => $def) {
590                 $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def);
591             }
592         }
593
594         $options['primary'] = $primary;
595
596         return array('tableName' => $this->getOption('tableName'),
597                      'columns'   => $columns,
598                      'options'   => array_merge($this->getOptions(), $options));
599     }
600
601     /**
602      * exportConstraints
603      * exports the constraints of this table into database based on option definitions
604      *
605      * @throws Doctrine_Connection_Exception    if something went wrong on db level
606      * @return void
607      */
608     public function exportConstraints()
609     {
610         try {
611             $this->_conn->beginTransaction();
612
613             foreach ($this->_options['index'] as $index => $definition) {
614                 $this->_conn->export->createIndex($this->_options['tableName'], $index, $definition);
615             }
616             $this->_conn->commit();
617         } catch (Doctrine_Connection_Exception $e) {
618             $this->_conn->rollback();
619             throw $e;
620         }
621     }
622
623     /**
624      * getRelationParser
625      * return the relation parser associated with this table
626      *
627      * @return Doctrine_Relation_Parser     relation parser object
628      */
629     public function getRelationParser()
630     {
631         return $this->_parser;
632     }
633
634     /**
635      * __get
636      * an alias for getOption
637      *
638      * @param string $option
639      */
640     public function __get($option)
641     {
642         if (isset($this->_options[$option])) {
643             return $this->_options[$option];
644         }
645         return null;
646     }
647
648     /**
649      * __isset
650      *
651      * @param string $option
652      */
653     public function __isset($option)
654     {
655         return isset($this->_options[$option]);
656     }
657
658     /**
659      * getOptions
660      * returns all options of this table and the associated values
661      *
662      * @return array    all options and their values
663      */
664     public function getOptions()
665     {
666         return $this->_options;
667     }
668
669     /**
670      * addForeignKey
671      *
672      * adds a foreignKey to this table
673      *
674      * @return void
675      */
676     public function addForeignKey(array $definition)
677     {
678         $this->_options['foreignKeys'][] = $definition;
679     }
680
681     /**
682      * addCheckConstraint
683      *
684      * adds a check constraint to this table
685      *
686      * @return void
687      */
688     public function addCheckConstraint($definition, $name)
689     {
690         if (is_string($name)) {
691             $this->_options['checks'][$name] = $definition;
692         } else {
693             $this->_options['checks'][] = $definition;
694         }
695
696         return $this;
697     }
698
699     /**
700      * addIndex
701      *
702      * adds an index to this table
703      *
704      * @return void
705      */
706     public function addIndex($index, array $definition)
707     {
708         $this->_options['indexes'][$index] = $definition;
709     }
710
711     /**
712      * getIndex
713      *
714      * @return array|boolean        array on success, FALSE on failure
715      */
716     public function getIndex($index)
717     {
718         if (isset($this->_options['indexes'][$index])) {
719             return $this->_options['indexes'][$index];
720         }
721
722         return false;
723     }
724     
725     /** 
726      * DESCRIBE WHAT THIS METHOD DOES, PLEASE!
727      *
728      * @todo Name proposal: addRelation
729      */
730     public function bind($args, $type)
731     {
732         $options = array();
733         $options['type'] = $type;
734
735         if ( ! isset($args[1])) {
736             $args[1] = array();
737         }
738
739         // the following is needed for backwards compatibility
740         if (is_string($args[1])) {
741             if ( ! isset($args[2])) {
742                 $args[2] = array();
743             } elseif (is_string($args[2])) {
744                 $args[2] = (array) $args[2];
745             }
746
747             $classes = array_merge($this->_options['parents'], array($this->getComponentName()));
748
749
750             $e = explode('.', $args[1]);
751             if (in_array($e[0], $classes)) {
752                 if ($options['type'] >= Doctrine_Relation::MANY) {
753                     $options['foreign'] = $e[1];
754                 } else {
755                     $options['local'] = $e[1];
756                 }
757             } else {
758                 $e2 = explode(' as ', $args[0]);
759                 if ($e[0] !== $e2[0] && ( ! isset($e2[1]) || $e[0] !== $e2[1])) {
760                     $options['refClass'] = $e[0];
761                 }
762
763                 $options['foreign'] = $e[1];
764             }
765
766             $options = array_merge($args[2], $options);
767
768             $this->_parser->bind($args[0], $options);
769         } else {
770             $options = array_merge($args[1], $options);
771             $this->_parser->bind($args[0], $options);
772         }
773     }
774
775     /**
776      * hasRelation
777      *
778      * @param string $alias      the relation to check if exists
779      * @return boolean           true if the relation exists otherwise false
780      */
781     public function hasRelation($alias)
782     {
783         return $this->_parser->hasRelation($alias);
784     }
785
786     /**
787      * getRelation
788      *
789      * @param string $alias      relation alias
790      */
791     public function getRelation($alias, $recursive = true)
792     {
793         return $this->_parser->getRelation($alias, $recursive);
794     }
795
796     /**
797      * getRelations
798      * returns an array containing all relation objects
799      *
800      * @return array        an array of Doctrine_Relation objects
801      */
802     public function getRelations()
803     {
804         return $this->_parser->getRelations();
805     }
806
807     /**
808      * createQuery
809      * creates a new Doctrine_Query object and adds the component name
810      * of this table as the query 'from' part
811      *
812      * @param string Optional alias name for component aliasing.
813      *
814      * @return Doctrine_Query
815      */
816     public function createQuery($alias = '')
817     {
818         if ( ! empty($alias)) {
819             $alias = ' ' . trim($alias);
820         }
821         return Doctrine_Query::create($this->_conn)->from($this->getComponentName() . $alias);
822     }
823
824     /**
825      * getRepository
826      *
827      * @return Doctrine_Table_Repository
828      */
829     public function getRepository()
830     {
831         return $this->_repository;
832     }
833
834     /**
835      * setOption
836      * sets an option and returns this object in order to
837      * allow flexible method chaining
838      *
839      * @see Doctrine_Table::$_options   for available options
840      * @param string $name              the name of the option to set
841      * @param mixed $value              the value of the option
842      * @return Doctrine_Table           this object
843      */
844     public function setOption($name, $value)
845     {
846         switch ($name) {
847         case 'name':
848         case 'tableName':
849             break;
850         case 'enumMap':
851         case 'inheritanceMap':
852         case 'index':
853         case 'treeOptions':
854             if ( ! is_array($value)) {
855                 throw new Doctrine_Table_Exception($name . ' should be an array.');
856             }
857             break;
858         }
859         $this->_options[$name] = $value;
860     }
861
862     /**
863      * getOption
864      * returns the value of given option
865      *
866      * @param string $name  the name of the option
867      * @return mixed        the value of given option
868      */
869     public function getOption($name)
870     {
871         if (isset($this->_options[$name])) {
872             return $this->_options[$name];
873         }
874         return null;
875     }
876
877     /**
878      * getColumnName
879      *
880      * returns a column name for column alias
881      * if the actual name for the alias cannot be found
882      * this method returns the given alias
883      *
884      * @param string $alias         column alias
885      * @return string               column name
886      */
887     public function getColumnName($fieldName)
888     {
889         if (isset($this->_columnNames[$fieldName])) {
890             return $this->_columnNames[$fieldName];
891         }
892         return $fieldName;
893     }
894     
895     /**
896      *
897      *
898      */
899     public function getColumnDefinition($columnName)
900     {
901         if ( ! isset($this->_columns[$columnName])) {
902             return false;
903         }
904         return $this->_columns[$columnName];
905     }
906     
907     /**
908      * getColumnAlias
909      * 
910      * returns a column alias for a column name 
911      * if no alias can be found the column name is returned.
912      *
913      * @param string $columnName    column name
914      * @return string               column alias
915      */
916     public function getFieldName($columnName)
917     {
918         if (isset($this->_fieldNames[$columnName])) {
919             return $this->_fieldNames[$columnName];
920         }
921         return $columnName;
922     }
923
924     /**
925      * setColumn
926      *
927      * @param string $name
928      * @param string $type
929      * @param integer $length
930      * @param mixed $options
931      * @param boolean $prepend   Whether to prepend or append the new column to the column list.
932      *                           By default the column gets appended.
933      * @throws Doctrine_Table_Exception     if trying use wrongly typed parameter
934      * @return void
935      */
936     public function setColumn($name, $type, $length = null, $options = array(), $prepend = false)
937     {
938         if (is_string($options)) {
939             $options = explode('|', $options);
940         }
941
942         foreach ($options as $k => $option) {
943             if (is_numeric($k)) {
944                 if ( ! empty($option)) {
945                     $options[$option] = true;
946                 }
947                 unset($options[$k]);
948             }
949         }
950         
951         // extract column name & field name
952         $parts = explode(' as ', $name);
953         if (count($parts) > 1) {
954             $fieldName = $parts[1];
955         } else {
956             $fieldName = $parts[0];
957         }
958         $name = strtolower($parts[0]);
959         if ($prepend) {
960             $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames);
961             $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames);
962         } else {
963             $this->_columnNames[$fieldName] = $name;
964             $this->_fieldNames[$name] = $fieldName;
965         }
966
967         if ($length == null) {
968             switch ($type) {
969                 case 'string':
970                 case 'clob':
971                 case 'float':
972                 case 'integer':
973                 case 'array':
974                 case 'object':
975                 case 'blob':
976                 case 'gzip':
977                     // use php int max
978                     $length = 2147483647;
979                 break;
980                 case 'boolean':
981                     $length = 1;
982                 case 'date':
983                     // YYYY-MM-DD ISO 8601
984                     $length = 10;
985                 case 'time':
986                     // HH:NN:SS+00:00 ISO 8601
987                     $length = 14;
988                 case 'timestamp':
989                     // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601
990                     $length = 25;
991                 break;
992             }
993         }
994         
995         $options['type'] = $type;
996         $options['length'] = $length;
997         
998         if ($prepend) {
999             $this->_columns = array_merge(array($name => $options), $this->_columns);
1000         } else {
1001             $this->_columns[$name] = $options;
1002         }
1003
1004         if (isset($options['primary'])) {
1005             if (isset($this->_identifier)) {
1006                 $this->_identifier = (array) $this->_identifier; 
1007             }
1008             if ( ! in_array($fieldName, $this->_identifier)) {
1009                 $this->_identifier[] = $fieldName;
1010             }
1011         }
1012         if (isset($options['default'])) {
1013             $this->hasDefaultValues = true;
1014         }
1015     }
1016
1017     /**
1018      * hasDefaultValues
1019      * returns true if this table has default values, otherwise false
1020      *
1021      * @return boolean
1022      */
1023     public function hasDefaultValues()
1024     {
1025         return $this->hasDefaultValues;
1026     }
1027
1028     /**
1029      * getDefaultValueOf
1030      * returns the default value(if any) for given column
1031      *
1032      * @param string $fieldName
1033      * @return mixed
1034      */
1035     public function getDefaultValueOf($fieldName)
1036     {
1037         $columnName = $this->getColumnName($fieldName);
1038         if ( ! isset($this->_columns[$columnName])) {
1039             throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$columnName." doesn't exist.");
1040         }
1041         if (isset($this->_columns[$columnName]['default'])) {
1042             return $this->_columns[$columnName]['default'];
1043         } else {
1044             return null;
1045         }
1046     }
1047
1048     /**
1049      * @return mixed
1050      */
1051     public function getIdentifier()
1052     {
1053         return $this->_identifier;
1054     }
1055
1056     /**
1057      * @return integer
1058      */
1059     public function getIdentifierType()
1060     {
1061         return $this->_identifierType;
1062     }
1063
1064     /**
1065      * hasColumn
1066      * @return boolean
1067      */
1068     public function hasColumn($columnName)
1069     {
1070         return isset($this->_columns[$columnName]);
1071     }
1072
1073     /**
1074      * sets the connection for this class
1075      *
1076      * @params Doctrine_Connection      a connection object 
1077      * @return Doctrine_Table           this object
1078      */
1079     public function setConnection(Doctrine_Connection $conn)
1080     {
1081         $this->_conn = $conn;
1082
1083         $this->setParent($this->_conn);
1084         
1085         return $this;
1086     }
1087
1088     /**
1089      * returns the connection associated with this table (if any)
1090      *
1091      * @return Doctrine_Connection|null     the connection object
1092      */
1093     public function getConnection()
1094     {
1095         return $this->_conn;
1096     }
1097
1098     /**
1099      * creates a new record
1100      *
1101      * @param $array             an array where keys are field names and
1102      *                           values representing field values
1103      * @return Doctrine_Record   the created record object
1104      */
1105     public function create(array $array = array()) 
1106     {
1107         $record = new $this->_options['name']($this, true);
1108         $record->fromArray($array);
1109
1110         return $record;
1111     }
1112
1113     /**
1114      * finds a record by its identifier
1115      *
1116      * @param $id                       database row id
1117      * @param int $hydrationMode        Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD
1118      * @return mixed                    Array or Doctrine_Record or false if no result
1119      */
1120     public function find($id, $hydrationMode = null)
1121     {
1122         if (is_null($id)) {
1123             return false;
1124         }
1125
1126         $id = is_array($id) ? array_values($id) : array($id);
1127
1128         return $this->createQuery()
1129             ->where(implode(' = ? AND ', $this->getIdentifierColumnNames()) . ' = ?')
1130             ->fetchOne($id, $hydrationMode);
1131     }
1132
1133     /**
1134      * findAll
1135      * returns a collection of records
1136      *
1137      * @param int $hydrationMode        Doctrine::FETCH_ARRAY or Doctrine::FETCH_RECORD
1138      * @return Doctrine_Collection
1139      */
1140     public function findAll($hydrationMode = null)
1141     {
1142         return $this->createQuery()->execute(array(), $hydrationMode);
1143     }
1144
1145     /**
1146      * findByDql
1147      * finds records with given DQL where clause
1148      * returns a collection of records
1149      *
1150      * @param string $dql               DQL after WHERE clause
1151      * @param array $params             query parameters
1152      * @param int $hydrationMode        Doctrine::FETCH_ARRAY or Doctrine::FETCH_RECORD
1153      * @return Doctrine_Collection
1154      */
1155     public function findBySql($dql, array $params = array(), $hydrationMode = null)
1156     {
1157         return $this->createQuery()->where($dql)->execute($params, $hydrationMode);
1158     }
1159
1160     public function findByDql($dql, array $params = array(), $hydrationMode = null)
1161     {
1162         return $this->findBySql($dql, $params, $hydrationMode);
1163     }
1164
1165     /**
1166      * execute
1167      * fetches data using the provided queryKey and 
1168      * the associated query in the query registry
1169      *
1170      * if no query for given queryKey is being found a 
1171      * Doctrine_Query_Registry exception is being thrown
1172      *
1173      * @param string $queryKey      the query key
1174      * @param array $params         prepared statement params (if any)
1175      * @return mixed                the fetched data
1176      */
1177     public function execute($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
1178     {
1179         return Doctrine_Manager::getInstance()
1180                             ->getQueryRegistry()
1181                             ->get($queryKey, $this->getComponentName())
1182                             ->execute($params, $hydrationMode);
1183     }
1184
1185     /**
1186      * executeOne
1187      * fetches data using the provided queryKey and 
1188      * the associated query in the query registry
1189      *
1190      * if no query for given queryKey is being found a 
1191      * Doctrine_Query_Registry exception is being thrown
1192      *
1193      * @param string $queryKey      the query key
1194      * @param array $params         prepared statement params (if any)
1195      * @return mixed                the fetched data
1196      */
1197     public function executeOne($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
1198     {
1199         return Doctrine_Manager::getInstance()
1200                             ->getQueryRegistry()
1201                             ->get($queryKey, $this->getComponentName())
1202                             ->fetchOne($params, $hydrationMode);
1203     }
1204
1205     /**
1206      * clear
1207      * clears the first level cache (identityMap)
1208      *
1209      * @return void
1210      * @todo what about a more descriptive name? clearIdentityMap?
1211      */
1212     public function clear()
1213     {
1214         $this->_identityMap = array();
1215     }
1216
1217     /**
1218      * addRecord
1219      * adds a record to identity map
1220      *
1221      * @param Doctrine_Record $record       record to be added
1222      * @return boolean
1223      * @todo Better name? registerRecord?
1224      */
1225     public function addRecord(Doctrine_Record $record)
1226     {
1227         $id = implode(' ', $record->identifier());
1228
1229         if (isset($this->_identityMap[$id])) {
1230             return false;
1231         }
1232
1233         $this->_identityMap[$id] = $record;
1234
1235         return true;
1236     }
1237
1238     /**
1239      * removeRecord
1240      * removes a record from the identity map, returning true if the record
1241      * was found and removed and false if the record wasn't found.
1242      *
1243      * @param Doctrine_Record $record       record to be removed
1244      * @return boolean
1245      */
1246     public function removeRecord(Doctrine_Record $record)
1247     {
1248         $id = implode(' ', $record->identifier());
1249
1250         if (isset($this->_identityMap[$id])) {
1251             unset($this->_identityMap[$id]);
1252             return true;
1253         }
1254
1255         return false;
1256     }
1257
1258     /**
1259      * getRecord
1260      * first checks if record exists in identityMap, if not
1261      * returns a new record
1262      *
1263      * @return Doctrine_Record
1264      */
1265     public function getRecord()
1266     {
1267         if ( ! empty($this->_data)) {
1268
1269             $identifierFieldNames = $this->getIdentifier();
1270
1271             if ( ! is_array($identifierFieldNames)) {
1272                 $identifierFieldNames = array($identifierFieldNames);
1273             }
1274
1275             $found = false;
1276             foreach ($identifierFieldNames as $fieldName) {
1277                 if ( ! isset($this->_data[$fieldName])) {
1278                     // primary key column not found return new record
1279                     $found = true;
1280                     break;
1281                 }
1282                 $id[] = $this->_data[$fieldName];
1283             }
1284
1285             if ($found) {
1286                 $recordName = $this->getClassnameToReturn();
1287                 $record = new $recordName($this, true);
1288                 $this->_data = array();
1289                 return $record;
1290             }
1291
1292
1293             $id = implode(' ', $id);
1294
1295             if (isset($this->_identityMap[$id])) {
1296                 $record = $this->_identityMap[$id];
1297                 $record->hydrate($this->_data);
1298             } else {
1299                 $recordName = $this->getClassnameToReturn();
1300                 $record = new $recordName($this);
1301                 $this->_identityMap[$id] = $record;
1302             }
1303             $this->_data = array();
1304         } else {
1305             $recordName = $this->getClassnameToReturn();
1306             $record = new $recordName($this, true);
1307         }
1308
1309         return $record;
1310     }
1311
1312     /**
1313      * Get the classname to return. Most often this is just the options['name']
1314      *
1315      * Check the subclasses option and the inheritanceMap for each subclass to see
1316      * if all the maps in a subclass is met. If this is the case return that
1317      * subclass name. If no subclasses match or if there are no subclasses defined
1318      * return the name of the class for this tables record.
1319      *
1320      * @todo this function could use reflection to check the first time it runs
1321      * if the subclassing option is not set.
1322      *
1323      * @return string The name of the class to create
1324      *
1325      */
1326     public function getClassnameToReturn()
1327     {
1328         if ( ! isset($this->_options['subclasses'])) {
1329             return $this->_options['name'];
1330         }
1331         foreach ($this->_options['subclasses'] as $subclass) {
1332             $table = $this->_conn->getTable($subclass);
1333             $inheritanceMap = $table->getOption('inheritanceMap');
1334             $nomatch = false;
1335             foreach ($inheritanceMap as $key => $value) {
1336                 if ( ! isset($this->_data[$key]) || $this->_data[$key] != $value) {
1337                     $nomatch = true;
1338                     break;
1339                 }
1340             }
1341             if ( ! $nomatch) {
1342                 return $table->getComponentName();
1343             }
1344         }
1345         return $this->_options['name'];
1346     }
1347
1348     /**
1349      * @param $id                       database row id
1350      * @throws Doctrine_Find_Exception
1351      */
1352     final public function getProxy($id = null)
1353     {
1354         if ($id !== null) {
1355             $identifierColumnNames = $this->getIdentifierColumnNames();
1356             $query = 'SELECT ' . implode(', ', (array) $identifierColumnNames)
1357                 . ' FROM ' . $this->getTableName()
1358                 . ' WHERE ' . implode(' = ? && ', (array) $identifierColumnNames) . ' = ?';
1359             $query = $this->applyInheritance($query);
1360
1361             $params = array_merge(array($id), array_values($this->_options['inheritanceMap']));
1362
1363             $this->_data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC);
1364
1365             if ($this->_data === false)
1366                 return false;
1367         }
1368         return $this->getRecord();
1369     }
1370
1371     /**
1372      * applyInheritance
1373      * @param $where                    query where part to be modified
1374      * @return string                   query where part with column aggregation inheritance added
1375      */
1376     final public function applyInheritance($where)
1377     {
1378         if ( ! empty($this->_options['inheritanceMap'])) {
1379             $a = array();
1380             foreach ($this->_options['inheritanceMap'] as $field => $value) {
1381                 $a[] = $this->getColumnName($field) . ' = ?';
1382             }
1383             $i = implode(' AND ', $a);
1384             $where .= ' AND ' . $i;
1385         }
1386         return $where;
1387     }
1388
1389     /**
1390      * count
1391      *
1392      * @return integer
1393      */
1394     public function count()
1395     {
1396         $a = $this->_conn->execute('SELECT COUNT(1) FROM ' . $this->_options['tableName'])->fetch(Doctrine::FETCH_NUM);
1397         return current($a);
1398     }
1399
1400     /**
1401      * @return Doctrine_Query  a Doctrine_Query object
1402      */
1403     public function getQueryObject()
1404     {
1405         $graph = new Doctrine_Query($this->getConnection());
1406         $graph->load($this->getComponentName());
1407         return $graph;
1408     }
1409
1410     /**
1411      * @param string $fieldName
1412      * @return array
1413      */
1414     public function getEnumValues($fieldName)
1415     {
1416         $columnName = $this->getColumnName($fieldName);
1417         if (isset($this->_columns[$columnName]['values'])) {
1418             return $this->_columns[$columnName]['values'];
1419         } else {
1420             return array();
1421         }
1422     }
1423
1424     /**
1425      * enumValue
1426      *
1427      * @param string $field
1428      * @param integer $index
1429      * @return mixed
1430      */
1431     public function enumValue($fieldName, $index)
1432     {
1433         if ($index instanceof Doctrine_Null) {
1434             return $index;
1435         }
1436         
1437         $columnName = $this->getColumnName($fieldName);
1438         if ( ! $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)
1439             && isset($this->_columns[$columnName]['values'][$index])
1440         ) {
1441             return $this->_columns[$columnName]['values'][$index];
1442         }
1443
1444         return $index;
1445     }
1446
1447     /**
1448      * enumIndex
1449      *
1450      * @param string $field
1451      * @param mixed $value
1452      * @return mixed
1453      */
1454     public function enumIndex($fieldName, $value)
1455     {
1456         $values = $this->getEnumValues($fieldName);
1457
1458         $index = array_search($value, $values);
1459         if ($index === false || !$this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)) {
1460             return $index;
1461         }
1462         return $value;
1463     }
1464     
1465     /**
1466      * getColumnCount
1467      *
1468      * @return integer      the number of columns in this table
1469      */
1470     public function getColumnCount()
1471     {
1472         return $this->columnCount;
1473     }
1474
1475     /**
1476      * returns all columns and their definitions
1477      *
1478      * @return array
1479      */
1480     public function getColumns()
1481     {
1482         return $this->_columns;
1483     }
1484
1485     /**
1486      * removeColumn
1487      * removes given column
1488      *
1489      * @return boolean
1490      */
1491     public function removeColumn($fieldName)
1492     {
1493      $columnName = array_search($fieldName, $this->_fieldNames);
1494
1495         unset($this->_fieldNames[$columnName]);
1496
1497         if (isset($this->_columns[$columnName])) {
1498             unset($this->_columns[$columnName]);
1499             return true;
1500         }
1501         $this->columnCount--;
1502         
1503         return false;
1504     }
1505
1506     /**
1507      * returns an array containing all the column names.
1508      *
1509      * @return array
1510      */
1511     public function getColumnNames(array $fieldNames = null)
1512     {
1513         if ($fieldNames === null) {
1514             return array_keys($this->_columns);
1515         } else {
1516            $columnNames = array();
1517            foreach ($fieldNames as $fieldName) {
1518                $columnNames[] = $this->getColumnName($fieldName);
1519            }
1520            return $columnNames;
1521         }
1522     }
1523     
1524     /**
1525      * returns an array with all the identifier column names.
1526      *
1527      * @return array
1528      */
1529     public function getIdentifierColumnNames()
1530     {
1531         return $this->getColumnNames((array) $this->getIdentifier());
1532     }
1533     
1534     /**
1535      * returns an array containing all the field names.
1536      *
1537      * @return array
1538      */
1539     public function getFieldNames()
1540     {
1541         return array_values($this->_fieldNames);
1542     }
1543
1544     /**
1545      * getDefinitionOf
1546      *
1547      * @return mixed        array on success, false on failure
1548      */
1549     public function getDefinitionOf($fieldName)
1550     {
1551         $columnName = $this->getColumnName($fieldName);
1552         return $this->getColumnDefinition($columnName);
1553     }
1554
1555     /**
1556      * getTypeOf
1557      *
1558      * @return mixed        string on success, false on failure
1559      */
1560     public function getTypeOf($fieldName)
1561     {
1562         $columnName = $this->getColumnName($fieldName);
1563         if (isset($this->_columns[$columnName])) {
1564             return $this->_columns[$columnName]['type'];
1565         }
1566         return false;
1567     }
1568
1569     /**
1570      * setData
1571      * doctrine uses this function internally
1572      * users are strongly discouraged to use this function
1573      *
1574      * @param array $data               internal data
1575      * @return void
1576      */
1577     public function setData(array $data)
1578     {
1579         $this->_data = $data;
1580     }
1581
1582     /**
1583      * returns internal data, used by Doctrine_Record instances
1584      * when retrieving data from database
1585      *
1586      * @return array
1587      */
1588     public function getData()
1589     {
1590         return $this->_data;
1591     }
1592
1593     /**
1594      * prepareValue
1595      * this method performs special data preparation depending on
1596      * the type of the given column
1597      *
1598      * 1. It unserializes array and object typed columns
1599      * 2. Uncompresses gzip typed columns
1600      * 3. Gets the appropriate enum values for enum typed columns
1601      * 4. Initializes special null object pointer for null values (for fast column existence checking purposes)
1602      *
1603      * example:
1604      * <code type='php'>
1605      * $field = 'name';
1606      * $value = null;
1607      * $table->prepareValue($field, $value); // Doctrine_Null
1608      * </code>
1609      *
1610      * @throws Doctrine_Table_Exception     if unserialization of array/object typed column fails or
1611      * @throws Doctrine_Table_Exception     if uncompression of gzip typed column fails         *
1612      * @param string $field     the name of the field
1613      * @param string $value     field value
1614      * @return mixed            prepared value
1615      */
1616     public function prepareValue($fieldName, $value)
1617     {
1618         if ($value === self::$_null) {
1619             return self::$_null;
1620         } else if ($value === null) {
1621             return null;
1622         } else {
1623             $type = $this->getTypeOf($fieldName);
1624
1625             switch ($type) {
1626                 case 'array':
1627                 case 'object':
1628                     if (is_string($value)) {
1629                         $value = unserialize($value);
1630
1631                         if ($value === false) {
1632                             throw new Doctrine_Table_Exception('Unserialization of ' . $fieldName . ' failed.');
1633                         }
1634                         return $value;
1635                     }
1636                 break;
1637                 case 'gzip':
1638                     $value = gzuncompress($value);
1639
1640                     if ($value === false) {
1641                         throw new Doctrine_Table_Exception('Uncompressing of ' . $fieldName . ' failed.');
1642                     }
1643                     return $value;
1644                 break;
1645                 case 'enum':
1646                     return $this->enumValue($fieldName, $value);
1647                 break;
1648                 case 'boolean':
1649                     return (boolean) $value;
1650                 break;
1651                 case 'integer':
1652                     // don't do any casting here PHP INT_MAX is smaller than what the databases support
1653                 break;
1654             }
1655         }
1656         return $value;
1657     }
1658
1659     /**
1660      * getTree
1661      *
1662      * getter for associated tree
1663      *
1664      * @return mixed  if tree return instance of Doctrine_Tree, otherwise returns false
1665      */
1666     public function getTree()
1667     {
1668         if (isset($this->_options['treeImpl'])) {
1669             if ( ! $this->_tree) {
1670                 $options = isset($this->_options['treeOptions']) ? $this->_options['treeOptions'] : array();
1671                 $this->_tree = Doctrine_Tree::factory($this,
1672                     $this->_options['treeImpl'],
1673                     $options
1674                 );
1675             }
1676             return $this->_tree;
1677         }
1678         return false;
1679     }
1680
1681     /**
1682      * getComponentName
1683      *
1684      * @return void
1685      */
1686     public function getComponentName()
1687     {
1688         return $this->_options['name'];
1689     }
1690
1691     /**
1692      * getTableName
1693      *
1694      * @return void
1695      */
1696     public function getTableName()
1697     {
1698         return $this->_options['tableName'];
1699     }
1700
1701     /**
1702      * setTableName
1703      *
1704      * @param string $tableName 
1705      * @return void
1706      */
1707     public function setTableName($tableName)
1708     {
1709         $this->setOption('tableName', $this->_conn->formatter->getTableName($tableName));
1710     }
1711
1712     /**
1713      * isTree
1714      *
1715      * determine if table acts as tree
1716      *
1717      * @return mixed  if tree return true, otherwise returns false
1718      */
1719     public function isTree()
1720     {
1721         return ( ! is_null($this->_options['treeImpl'])) ? true : false;
1722     }
1723
1724     /**
1725      * getTemplate
1726      *
1727      * @param string $template 
1728      * @return void
1729      */
1730     public function getTemplate($template)
1731     {
1732         if ( ! isset($this->_templates[$template])) {
1733             throw new Doctrine_Table_Exception('Template ' . $template . ' not loaded');
1734         }
1735
1736         return $this->_templates[$template];
1737     }
1738     
1739     public function hasTemplate($template)
1740     {
1741         return isset($this->_templates[$template]);
1742     }
1743
1744     public function addTemplate($template, Doctrine_Template $impl)
1745     {
1746         $this->_templates[$template] = $impl;
1747
1748         return $this;
1749     }
1750
1751     /**
1752      * bindQueryParts
1753      * binds query parts to given component
1754      *
1755      * @param array $queryParts         an array of pre-bound query parts
1756      * @return Doctrine_Record          this object
1757      */
1758     public function bindQueryParts(array $queryParts)
1759     {
1760      $this->_options['queryParts'] = $queryParts;
1761
1762         return $this;
1763     }
1764
1765     /**
1766      * bindQueryPart
1767      * binds given value to given query part
1768      *
1769      * @param string $queryPart
1770      * @param mixed $value
1771      * @return Doctrine_Record          this object
1772      */
1773     public function bindQueryPart($queryPart, $value)
1774     {
1775      $this->_options['queryParts'][$queryPart] = $value;
1776
1777         return $this;
1778     }
1779     
1780     /**
1781      * getBoundQueryPart
1782      *
1783      * @param string $queryPart 
1784      * @return string $queryPart
1785      */
1786     public function getBoundQueryPart($queryPart)
1787     {
1788         if ( ! isset($this->_options['queryParts'][$queryPart])) {
1789             return null;
1790         }
1791
1792         return $this->_options['queryParts'][$queryPart];
1793     }
1794     
1795     /**
1796      * unshiftFilter
1797      *
1798      * @param  object Doctrine_Record_Filter $filter
1799      * @return object $this
1800      */
1801     public function unshiftFilter(Doctrine_Record_Filter $filter)
1802     {
1803         $filter->setTable($this);
1804
1805         $filter->init();
1806
1807         array_unshift($this->_filters, $filter);
1808
1809         return $this;
1810     }
1811     
1812     /**
1813      * getFilters
1814      *
1815      * @return array $filters
1816      */
1817     public function getFilters()
1818     {
1819         return $this->_filters;
1820     }
1821
1822     /**
1823      * returns a string representation of this object
1824      *
1825      * @return string
1826      */
1827     public function __toString()
1828     {
1829         return Doctrine_Lib::getTableAsString($this);
1830     }
1831     
1832     /**
1833      * findBy
1834      *
1835      * @param string $column 
1836      * @param string $value 
1837      * @param string $hydrationMode 
1838      * @return void
1839      */
1840     protected function findBy($fieldName, $value, $hydrationMode = null)
1841     {
1842         return $this->createQuery()->where($fieldName . ' = ?')->execute(array($value), $hydrationMode);
1843     }
1844     
1845     /**
1846      * findOneBy
1847      *
1848      * @param string $column 
1849      * @param string $value 
1850      * @param string $hydrationMode 
1851      * @return void
1852      */
1853     protected function findOneBy($fieldName, $value, $hydrationMode = null)
1854     {
1855         $results = $this->createQuery()->where($fieldName . ' = ?')->limit(1)->execute(array($value), $hydrationMode);
1856         
1857         return $hydrationMode === Doctrine::FETCH_ARRAY ? $results[0] : $results->getFirst();
1858     }
1859     
1860     /**
1861      * __call
1862      *
1863      * Adds support for magic finders.
1864      * findByColumnName, findByRelationAlias
1865      * findById, findByContactId, etc.
1866      *
1867      * @return void
1868      */
1869     public function __call($method, $arguments)
1870     {
1871         if (substr($method, 0, 6) == 'findBy') {
1872             $by = substr($method, 6, strlen($method));
1873             $method = 'findBy';
1874         } else if (substr($method, 0, 9) == 'findOneBy') {
1875             $by = substr($method, 9, strlen($method));
1876             $method = 'findOneBy';
1877         }
1878         
1879         if (isset($by)) {
1880             if ( ! isset($arguments[0])) {
1881                 throw new Doctrine_Table_Exception('You must specify the value to findBy');
1882             }
1883             
1884             $fieldName = Doctrine::tableize($by);
1885             $hydrationMode = isset($arguments[1]) ? $arguments[1]:null;
1886             
1887             if ($this->hasColumn($fieldName)) {
1888                 return $this->$method($fieldName, $arguments[0], $hydrationMode);
1889             } else if ($this->hasRelation($by)) {
1890                 $relation = $this->getRelation($by);
1891                 
1892                 if ($relation['type'] === Doctrine_Relation::MANY) {
1893                     throw new Doctrine_Table_Exception('Cannot findBy many relationship.');
1894                 }
1895                 
1896                 return $this->$method($relation['local'], $arguments[0], $hydrationMode);
1897             } else {
1898                 throw new Doctrine_Table_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.');
1899             }
1900         }
1901     }
1902 }