Coverage for Doctrine_Table

Back to coverage report

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