Coverage for Doctrine_Table

Back to coverage report

1 <?php
2 /*
3  *  $Id: Table.php 2821 2007-10-12 21:32:26Z 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  * Doctrine_Table   represents a database table
23  *                  each Doctrine_Table holds the information of foreignKeys and associations
24  *
25  *
26  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
27  * @package     Doctrine
28  * @subpackage  Table
29  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30  * @version     $Revision: 2821 $
31  * @link        www.phpdoctrine.com
32  * @since       1.0
33  */
34 class Doctrine_Table extends Doctrine_Configurable implements Countable
35 {
36     /**
37      * @var array $data                                 temporary data which is then loaded into Doctrine_Record::$data
38      */
39     private $_data             = array();
40     /**
41      * @var mixed $identifier
42      */
43     private $_identifier;
44     /**
45      * @see Doctrine_Identifier constants
46      * @var integer $identifierType                     the type of identifier this table uses
47      */
48     private $_identifierType;
49     /**
50      * @var Doctrine_Connection $conn                   Doctrine_Connection object that created this table
51      */
52     private $_conn;
53     /**
54      * @var array $identityMap                          first level cache
55      */
56     private $_identityMap        = array();
57     /**
58      * @var Doctrine_Table_Repository $repository       record repository
59      */
60     private $_repository;
61     /**
62      * @var array $columns                  an array of column definitions,
63      *                                      keys as column names and values as column definitions
64      *
65      *                                      the definition array has atleast the following values:
66      *
67      *                                      -- type         the column type, eg. 'integer'
68      *                                      -- length       the column length, eg. 11
69      *
70      *                                      additional keys:
71      *                                      -- notnull      whether or not the column is marked as notnull
72      *                                      -- values       enum values
73      *                                      -- notblank     notblank validator + notnull constraint
74      *                                      ... many more
75      */
76     protected $_columns          = array();
77     /**
78      * @var array $columnAliases            an array of column aliases
79      *                                      keys as column aliases and values as column names
80      */
81     protected $_columnAliases    = array();
82     /**
83      * @var integer $columnCount            cached column count, Doctrine_Record uses this column count in when
84      *                                      determining its state
85      */
86     private $columnCount;
87     /**
88      * @var boolean $hasDefaultValues       whether or not this table has default values
89      */
90     private $hasDefaultValues;
91     /**
92      * @var array $options                  an array containing all options
93      *
94      *      -- name                         name of the component, for example component name of the GroupTable is 'Group'
95      *
96      *      -- parents                      the parent classes of this component
97      *
98      *      -- declaringClass               name of the table definition declaring class (when using inheritance the class
99      *                                      that defines the table structure can be any class in the inheritance hierarchy,
100      *                                      hence we need reflection to check out which class actually calls setTableDefinition)
101      *
102      *      -- tableName                    database table name, in most cases this is the same as component name but in some cases
103      *                                      where one-table-multi-class inheritance is used this will be the name of the inherited table
104      *
105      *      -- sequenceName                 Some databases need sequences instead of auto incrementation primary keys,
106      *                                      you can set specific sequence for your table by calling setOption('sequenceName', $seqName)
107      *                                      where $seqName is the name of the desired sequence
108      *
109      *      -- enumMap                      enum value arrays
110      *
111      *      -- inheritanceMap               inheritanceMap is used for inheritance mapping, keys representing columns and values
112      *                                      the column values that should correspond to child classes
113      *
114      *      -- type                         table type (mysql example: INNODB)
115      *
116      *      -- charset                      character set
117      *
118      *      -- foreignKeys                  the foreign keys of this table
119      *
120      *      -- checks                       the check constraints of this table, eg. 'price > dicounted_price'
121      *
122      *      -- collation                    collation attribute
123      *
124      *      -- indexes                      the index definitions of this table
125      *
126      *      -- treeImpl                     the tree implementation of this table (if any)
127      *
128      *      -- treeOptions                  the tree options
129      *
130      *      -- queryParts                   the bound query parts
131      *
132      *      -- versioning
133      */
134     protected $_options      = array('name'           => null,
135                                      'tableName'      => null,
136                                      'sequenceName'   => null,
137                                      'inheritanceMap' => array(),
138                                      'enumMap'        => array(),
139                                      'type'           => null,
140                                      'charset'        => null,
141                                      'collation'      => null,
142                                      'treeImpl'       => null,
143                                      'treeOptions'    => null,
144                                      'indexes'        => array(),
145                                      'parents'        => array(),
146                                      'queryParts'     => array(),
147                                      'versioning'     => null,
148                                      );
149     /**
150      * @var Doctrine_Tree $tree                 tree object associated with this table
151      */
152     protected $_tree;
153     /**
154      * @var Doctrine_Relation_Parser $_parser   relation parser object
155      */
156     protected $_parser;
157     /**
158      * @var array $_templates                   an array containing all templates attached to this table
159      */
160     protected $_templates   = array();
161     /**
162      * @var array $_filters                     an array containing all record filters attached to this table
163      */
164     protected $_filters     = array();
165     
166     protected $_invokedMethods = array();
167
168
169
170     /**
171      * the constructor
172      * @throws Doctrine_Connection_Exception    if there are no opened connections
173      * @throws Doctrine_Table_Exception         if there is already an instance of this table
174      * @return void
175      */
176     public function __construct($name, Doctrine_Connection $conn)
177     {
178         $this->_conn = $conn;
179
180         $this->setParent($this->_conn);
181
182         $this->_options['name'] = $name;
183         $this->_parser = new Doctrine_Relation_Parser($this);
184
185         if ( ! class_exists($name) || empty($name)) {
186             throw new Doctrine_Exception("Couldn't find class " . $name);
187         }
188         $record = new $name($this);
189
190         $names = array();
191
192         $class = $name;
193
194         // get parent classes
195
196         do {
197             if ($class === 'Doctrine_Record') {
198                 break;
199             }
200
201             $name = $class;
202             $names[] = $name;
203         } while ($class = get_parent_class($class));
204
205         if ($class === false) {
206             throw new Doctrine_Table_Exception('Unknown component.');
207         }
208
209         // reverse names
210         $names = array_reverse($names);
211         // save parents
212         array_pop($names);
213         $this->_options['parents'] = $names;
214
215         // create database table
216         if (method_exists($record, 'setTableDefinition')) {
217             $record->setTableDefinition();
218             // get the declaring class of setTableDefinition method
219             $method = new ReflectionMethod($this->_options['name'], 'setTableDefinition');
220             $class  = $method->getDeclaringClass();
221         } else {
222             $class = new ReflectionClass($class);
223         }
224         $this->_options['declaringClass'] = $class;
225
226         // set the table definition for the given tree implementation
227         if ($this->isTree()) {
228             $this->getTree()->setTableDefinition();
229         }
230
231         $this->columnCount = count($this->_columns);
232
233         if ( ! isset($this->_options['tableName'])) {
234             $this->_options['tableName'] = Doctrine::tableize($class->getName());
235         }
236
237         switch (count($this->_identifier)) {
238             case 0:
239                 $this->_columns = array_merge(array('id' =>
240                                               array('type'          => 'integer',
241                                                     'length'        => 20,
242                                                     'autoincrement' => true,
243                                                     'primary'       => true)), $this->_columns);
244                 $this->_identifier = 'id';
245                 $this->_identifierType = Doctrine::IDENTIFIER_AUTOINC;
246                 $this->columnCount++;
247                 break;
248             case 1:
249                 foreach ($this->_identifier as $pk) {
250                     $e = $this->_columns[$pk];
251
252                     $found = false;
253
254                     foreach ($e as $option => $value) {
255                         if ($found)
256                                 break;
257
258                         $e2 = explode(':', $option);
259
260                         switch (strtolower($e2[0])) {
261                             case 'autoincrement':
262                             case 'autoinc':
263                                 $this->_identifierType = Doctrine::IDENTIFIER_AUTOINC;
264                                 $found = true;
265                                 break;
266                             case 'seq':
267                             case 'sequence':
268                                 $this->_identifierType = Doctrine::IDENTIFIER_SEQUENCE;
269                                 $found = true;
270
271                                 if ($value) {
272                                     $this->_options['sequenceName'] = $value;
273                                 } else {
274                                     if (($sequence = $this->getAttribute(Doctrine::ATTR_DEFAULT_SEQUENCE)) !== null) {
275                                         $this->_options['sequenceName'] = $sequence;
276                                     } else {
277                                         $this->_options['sequenceName'] = $this->_conn->getSequenceName($this->_options['tableName']);
278                                     }
279                                 }
280                                 break;
281                         }
282                     }
283                     if ( ! isset($this->_identifierType)) {
284                         $this->_identifierType = Doctrine::IDENTIFIER_NATURAL;
285                     }
286                 }
287
288                 $this->_identifier = $pk;
289
290                 break;
291             default:
292                 $this->_identifierType = Doctrine::IDENTIFIER_COMPOSITE;
293         }
294
295         $record->setUp();
296
297         // if tree, set up tree
298         if ($this->isTree()) {
299             $this->getTree()->setUp();
300         }
301         $this->_filters[]  = new Doctrine_Record_Filter_Standard();
302         $this->_repository = new Doctrine_Table_Repository($this);
303     }
304     public function getMethodOwner($method) 
305     {
306         return (isset($this->_invokedMethods[$method])) ?
307                       $this->_invokedMethods[$method] : false;
308     }
309     
310     public function setMethodOwner($method, $class)
311     {
312         $this->_invokedMethods[$method] = $class;
313     }
314     /**
315      * getTemplates
316      * returns all templates attached to this table
317      *
318      * @return array     an array containing all templates
319      */
320     public function getTemplates()
321     {
322         return $this->_templates;
323     }
324     /**
325      * export
326      * exports this table to database based on column and option definitions
327      *
328      * @throws Doctrine_Connection_Exception    if some error other than Doctrine::ERR_ALREADY_EXISTS
329      *                                          occurred during the create table operation
330      * @return boolean                          whether or not the export operation was successful
331      *                                          false if table already existed in the database
332      */
333     public function export()
334     {
335         $this->_conn->export->exportTable($this);
336     }
337     /**
338      * getExportableFormat
339      * returns exportable presentation of this object
340      *
341      * @return array
342      */
343     public function getExportableFormat($parseForeignKeys = true)
344     {
345         $columns = array();
346         $primary = array();
347
348         foreach ($this->getColumns() as $name => $column) {
349             $definition = $column;
350
351             switch ($definition['type']) {
352                 case 'enum':
353                     if (isset($definition['default'])) {
354                         $definition['default'] = $this->enumIndex($name, $definition['default']);
355                     }
356                     break;
357                 case 'boolean':
358                     if (isset($definition['default'])) {
359                         $definition['default'] = $this->getConnection()->convertBooleans($definition['default']);
360                     }
361                     break;
362             }
363             $columns[$name] = $definition;
364
365             if (isset($definition['primary']) && $definition['primary']) {
366                 $primary[] = $name;
367             }
368         }
369         $options['foreignKeys'] = array();
370
371         if ($parseForeignKeys) {
372             if ($this->getAttribute(Doctrine::ATTR_EXPORT) & Doctrine::EXPORT_CONSTRAINTS) {
373
374                 $constraints = array();
375
376                 $emptyIntegrity = array('onUpdate' => null,
377                                         'onDelete' => null);
378
379                 foreach ($this->getRelations() as $name => $relation) {
380                     $fk = $relation->toArray();
381                     $fk['foreignTable'] = $relation->getTable()->getTableName();
382
383                     if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) {
384                         if ($relation->hasConstraint()) {
385                             throw new Doctrine_Table_Exception("Badly constructed integrity constraints.");
386                         }
387
388                         continue;
389                     }
390
391                     $integrity = array('onUpdate' => $fk['onUpdate'],
392                                        'onDelete' => $fk['onDelete']);
393
394                     if ($relation instanceof Doctrine_Relation_LocalKey) {
395                         $def = array('local'        => $relation->getLocal(),
396                                      'foreign'      => $relation->getForeign(),
397                                      'foreignTable' => $relation->getTable()->getTableName());
398
399                         if (($key = array_search($def, $options['foreignKeys'])) === false) {
400                             $options['foreignKeys'][] = $def;
401
402                             $constraints[] = $integrity;
403                         } else {
404                             if ($integrity !== $emptyIntegrity) {
405                                 $constraints[$key] = $integrity;
406                             }
407                         }
408                     }
409                 }
410
411                 foreach ($constraints as $k => $def) {
412                     $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def);
413                 }
414
415             }
416         }
417         $options['primary'] = $primary;
418
419         return array('tableName' => $this->getOption('tableName'),
420                      'columns'   => $columns,
421                      'options'   => array_merge($this->getOptions(), $options));
422     }
423     /**
424      * exportConstraints
425      * exports the constraints of this table into database based on option definitions
426      *
427      * @throws Doctrine_Connection_Exception    if something went wrong on db level
428      * @return void
429      */
430     public function exportConstraints()
431     {
432         try {
433             $this->_conn->beginTransaction();
434
435             foreach ($this->_options['index'] as $index => $definition) {
436                 $this->_conn->export->createIndex($this->_options['tableName'], $index, $definition);
437             }
438             $this->_conn->commit();
439         } catch(Doctrine_Connection_Exception $e) {
440             $this->_conn->rollback();
441
442             throw $e;
443         }
444     }
445     /**
446      * getRelationParser
447      * return the relation parser associated with this table
448      *
449      * @return Doctrine_Relation_Parser     relation parser object
450      */
451     public function getRelationParser()
452     {
453         return $this->_parser;
454     }
455     /**
456      * __get
457      * an alias for getOption
458      *
459      * @param string $option
460      */
461     public function __get($option)
462     {
463         if (isset($this->_options[$option])) {
464             return $this->_options[$option];
465         }
466         return null;
467     }
468     /**
469      * __isset
470      *
471      * @param string $option
472      */
473     public function __isset($option)
474     {
475         return isset($this->_options[$option]);
476     }
477     /**
478      * getOptions
479      * returns all options of this table and the associated values
480      *
481      * @return array    all options and their values
482      */
483     public function getOptions()
484     {
485         return $this->_options;
486     }
487     /**
488      * addForeignKey
489      *
490      * adds a foreignKey to this table
491      *
492      * @return void
493      */
494     public function addForeignKey(array $definition)
495     {
496         $this->_options['foreignKeys'][] = $definition;
497     }
498     /**
499      * addCheckConstraint
500      *
501      * adds a check constraint to this table
502      *
503      * @return void
504      */
505     public function addCheckConstraint($definition, $name)
506     {
507         if (is_string($name)) {
508             $this->_options['checks'][$name] = $definition;
509         } else {
510             $this->_options['checks'][] = $definition;
511         }
512
513         return $this;
514     }
515     /**
516      * addIndex
517      *
518      * adds an index to this table
519      *
520      * @return void
521      */
522     public function addIndex($index, array $definition)
523     {
524         $this->_options['indexes'][$index] = $definition;
525     }
526     /**
527      * getIndex
528      *
529      * @return array|boolean        array on success, FALSE on failure
530      */
531     public function getIndex($index)
532     {
533         if (isset($this->_options['indexes'][$index])) {
534             return $this->_options['indexes'][$index];
535         }
536
537         return false;
538     }
539     public function bind($args, $type)
540     {
541         $options = array();
542         $options['type'] = $type;
543
544         if ( ! isset($args[1])) {
545             $args[1] = array();
546         }
547
548         // the following is needed for backwards compatibility
549         if (is_string($args[1])) {
550             if ( ! isset($args[2])) {
551                 $args[2] = array();
552             } elseif (is_string($args[2])) {
553                 $args[2] = (array) $args[2];
554             }
555
556             $classes = array_merge($this->_options['parents'], array($this->getComponentName()));
557
558
559             $e = explode('.', $args[1]);
560             if (in_array($e[0], $classes)) {
561                 if ($options['type'] >= Doctrine_Relation::MANY) {
562                     $options['foreign'] = $e[1];
563                 } else {
564                     $options['local'] = $e[1];
565                 }
566             } else {
567                 $e2 = explode(' as ', $args[0]);
568                 if ($e[0] !== $e2[0] && ( ! isset($e2[1]) || $e[0] !== $e2[1])) {
569                     $options['refClass'] = $e[0];
570                 }
571
572                 $options['foreign'] = $e[1];
573             }
574
575             $options = array_merge($args[2], $options);
576
577             $this->_parser->bind($args[0], $options);
578         } else {
579             $options = array_merge($args[1], $options);
580             $this->_parser->bind($args[0], $options);
581         }
582     }
583
584     /**
585      * hasRelation
586      *
587      * @param string $alias      the relation to check if exists
588      * @return boolean           true if the relation exists otherwise false
589      */
590     public function hasRelation($alias)
591     {
592         return $this->_parser->hasRelation($alias);
593     }
594
595     /**
596      * getRelation
597      *
598      * @param string $alias      relation alias
599      */
600     public function getRelation($alias, $recursive = true)
601     {
602         return $this->_parser->getRelation($alias, $recursive);
603     }
604     /**
605      * getRelations
606      * returns an array containing all relation objects
607      *
608      * @return array        an array of Doctrine_Relation objects
609      */
610     public function getRelations()
611     {
612         return $this->_parser->getRelations();
613     }
614     /**
615      * createQuery
616      * creates a new Doctrine_Query object and adds the component name
617      * of this table as the query 'from' part
618      *
619      * @param string Optional alias name for component aliasing.
620      *
621      * @return Doctrine_Query
622      */
623     public function createQuery($alias = '')
624     {
625         if (!empty($alias)) {
626             $alias = ' ' . trim($alias);
627         }
628         return Doctrine_Query::create($this->_conn)->from($this->getComponentName() . $alias);
629     }
630     /**
631      * getRepository
632      *
633      * @return Doctrine_Table_Repository
634      */
635     public function getRepository()
636     {
637         return $this->_repository;
638     }
639     /**
640      * setOption
641      * sets an option and returns this object in order to
642      * allow flexible method chaining
643      *
644      * @see Doctrine_Table::$_options   for available options
645      * @param string $name              the name of the option to set
646      * @param mixed $value              the value of the option
647      * @return Doctrine_Table           this object
648      */
649     public function setOption($name, $value)
650     {
651         switch ($name) {
652         case 'name':
653         case 'tableName':
654             break;
655         case 'enumMap':
656         case 'inheritanceMap':
657         case 'index':
658         case 'treeOptions':
659             if ( ! is_array($value)) {
660                 throw new Doctrine_Table_Exception($name . ' should be an array.');
661             }
662             break;
663         }
664         $this->_options[$name] = $value;
665     }
666     /**
667      * getOption
668      * returns the value of given option
669      *
670      * @param string $name  the name of the option
671      * @return mixed        the value of given option
672      */
673     public function getOption($name)
674     {
675         if (isset($this->_options[$name])) {
676             return $this->_options[$name];
677         }
678         return null;
679     }
680     /**
681      * getColumnName
682      *
683      * returns a column name for column alias
684      * if the actual name for the alias cannot be found
685      * this method returns the given alias
686      *
687      * @param string $alias         column alias
688      * @return string               column name
689      */
690     public function getColumnName($alias)
691     {
692         $alias = strtolower($alias);
693         if (isset($this->_columnAliases[$alias])) {
694             return $this->_columnAliases[$alias];
695         }
696
697         return $alias;
698     }
699     /**
700      * setColumn
701      *
702      * @param string $name
703      * @param string $type
704      * @param integer $length
705      * @param mixed $options
706      * @throws Doctrine_Table_Exception     if trying use wrongly typed parameter
707      * @return void
708      */
709     public function setColumn($name, $type, $length = null, $options = array())
710     {
711         if (is_string($options)) {
712             $options = explode('|', $options);
713         }
714
715         foreach ($options as $k => $option) {
716             if (is_numeric($k)) {
717                 if ( ! empty($option)) {
718                     $options[$option] = true;
719                 }
720                 unset($options[$k]);
721             }
722         }
723
724         $name  = strtolower($name);
725         $parts = explode(' as ', $name);
726
727         if (count($parts) > 1) {
728             $this->_columnAliases[$parts[1]] = $parts[0];
729             $name = $parts[0];
730         }
731
732
733
734         if ($length == null) {
735             switch ($type) {
736                 case 'string':
737                 case 'clob':
738                 case 'float':
739                 case 'integer':
740                 case 'array':
741                 case 'object':
742                 case 'blob':
743                 case 'gzip':
744                     // use php int max
745                     $length = 2147483647;
746                 break;
747                 case 'boolean':
748                     $length = 1;
749                 case 'date':
750                     // YYYY-MM-DD ISO 8601
751                     $length = 10;
752                 case 'time':
753                     // HH:NN:SS+00:00 ISO 8601
754                     $length = 14;
755                 case 'timestamp':
756                     // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601
757                     $length = 25;
758                 break;
759             }
760         }
761
762         $this->_columns[$name] = $options;
763         $this->_columns[$name]['type'] = $type;
764         $this->_columns[$name]['length'] = $length;
765
766         if (isset($options['primary'])) {
767             $this->_identifier[] = $name;
768         }
769         if (isset($options['default'])) {
770             $this->hasDefaultValues = true;
771         }
772     }
773     /**
774      * hasDefaultValues
775      * returns true if this table has default values, otherwise false
776      *
777      * @return boolean
778      */
779     public function hasDefaultValues()
780     {
781         return $this->hasDefaultValues;
782     }
783     /**
784      * getDefaultValueOf
785      * returns the default value(if any) for given column
786      *
787      * @param string $column
788      * @return mixed
789      */
790     public function getDefaultValueOf($column)
791     {
792         $column = strtolower($column);
793         if ( ! isset($this->_columns[$column])) {
794             throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$column." doesn't exist.");
795         }
796         if (isset($this->_columns[$column]['default'])) {
797             return $this->_columns[$column]['default'];
798         } else {
799             return null;
800         }
801     }
802     /**
803      * @return mixed
804      */
805     public function getIdentifier()
806     {
807         return $this->_identifier;
808     }
809     /**
810      * @return integer
811      */
812     public function getIdentifierType()
813     {
814         return $this->_identifierType;
815     }
816     /**
817      * hasColumn
818      * @return boolean
819      */
820     public function hasColumn($name)
821     {
822         return isset($this->_columns[$name]);
823     }
824     /**
825      * @return Doctrine_Connection
826      */
827     public function getConnection()
828     {
829         return $this->_conn;
830     }
831     /**
832      * create
833      * creates a new record
834      *
835      * @param $array                    an array where keys are field names and values representing field values
836      * @return Doctrine_Record
837      */
838     public function create(array $array = array()) {
839         $this->_data         = $array;
840         $record = new $this->_options['name']($this, true);
841         $this->_data         = array();
842         return $record;
843     }
844     /**
845      * finds a record by its identifier
846      *
847      * @param $id                       database row id
848      * @param int $hydrationMode        Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD
849      * @return mixed                    Array or Doctrine_Record or false if no result
850      */
851     public function find($id, $hydrationMode = null)
852     {
853         if (is_null($id)) {
854             return false;
855         }
856
857         $id = is_array($id) ? array_values($id) : array($id);
858
859         return $this->createQuery()
860             ->where(implode(' = ? AND ', (array) $this->_identifier) . ' = ?')
861             ->fetchOne($id, $hydrationMode);
862     }
863     /**
864      * findAll
865      * returns a collection of records
866      *
867      * @param int $hydrationMode        Doctrine::FETCH_ARRAY or Doctrine::FETCH_RECORD
868      * @return Doctrine_Collection
869      */
870     public function findAll($hydrationMode = null)
871     {
872         return $this->createQuery()->execute(array(), $hydrationMode);
873     }
874     /**
875      * findByDql
876      * finds records with given DQL where clause
877      * returns a collection of records
878      *
879      * @param string $dql               DQL after WHERE clause
880      * @param array $params             query parameters
881      * @param int $hydrationMode        Doctrine::FETCH_ARRAY or Doctrine::FETCH_RECORD
882      * @return Doctrine_Collection
883      */
884     public function findBySql($dql, array $params = array(), $hydrationMode = null)
885     {
886         return $this->createQuery()->where($dql)->execute($params, $hydrationMode);
887     }
888
889     public function findByDql($dql, array $params = array(), $hydrationMode = null)
890     {
891         return $this->findBySql($dql, $params, $hydrationMode);
892     }
893     
894     /**
895      * execute
896      * fetches data using the provided queryKey and 
897      * the associated query in the query registry
898      *
899      * if no query for given queryKey is being found a 
900      * Doctrine_Query_Registry exception is being thrown
901      *
902      * @param string $queryKey      the query key
903      * @param array $params         prepared statement params (if any)
904      * @return mixed                the fetched data
905      */
906     public function execute($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
907     {
908         return Doctrine_Manager::getInstance()
909                             ->getQueryRegistry()
910                             ->get($queryKey, $this->getComponentName())
911                             ->execute($params, $hydrationMode);
912     }
913     
914     /**
915      * executeOne
916      * fetches data using the provided queryKey and 
917      * the associated query in the query registry
918      *
919      * if no query for given queryKey is being found a 
920      * Doctrine_Query_Registry exception is being thrown
921      *
922      * @param string $queryKey      the query key
923      * @param array $params         prepared statement params (if any)
924      * @return mixed                the fetched data
925      */
926     public function executeOne($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
927     {
928         return Doctrine_Manager::getInstance()
929                             ->getQueryRegistry()
930                             ->get($queryKey, $this->getComponentName())
931                             ->fetchOne($params, $hydrationMode);
932     }
933
934     /**
935      * clear
936      * clears the first level cache (identityMap)
937      *
938      * @return void
939      */
940     public function clear()
941     {
942         $this->_identityMap = array();
943     }
944     /**
945      * addRecord
946      * adds a record to identity map
947      *
948      * @param Doctrine_Record $record       record to be added
949      * @return boolean
950      */
951     public function addRecord(Doctrine_Record $record)
952     {
953         $id = implode(' ', $record->identifier());
954
955         if (isset($this->_identityMap[$id])) {
956             return false;
957         }
958
959         $this->_identityMap[$id] = $record;
960
961         return true;
962     }
963     /**
964      * getRecord
965      * first checks if record exists in identityMap, if not
966      * returns a new record
967      *
968      * @return Doctrine_Record
969      */
970     public function getRecord()
971     {
972         if ( ! empty($this->_data)) {
973             $this->_data = array_change_key_case($this->_data, CASE_LOWER);
974
975             $key = $this->getIdentifier();
976
977             if ( ! is_array($key)) {
978                 $key = array($key);
979             }
980
981             $found = false;
982             foreach ($key as $k) {
983                 if ( ! isset($this->_data[$k])) {
984                     // primary key column not found return new record
985                     $found = true;
986                     break;
987                 }
988                 $id[] = $this->_data[$k];
989             }
990
991             if ($found) {
992                 $recordName = $this->getClassnameToReturn();
993                 $record = new $recordName($this, true);
994                 $this->_data = array();
995
996                 return $record;
997             }
998
999
1000             $id = implode(' ', $id);
1001
1002             if (isset($this->_identityMap[$id])) {
1003                 $record = $this->_identityMap[$id];
1004                 $record->hydrate($this->_data);
1005             } else {
1006                 $recordName = $this->getClassnameToReturn();
1007                 $record = new $recordName($this);
1008                 $this->_identityMap[$id] = $record;
1009             }
1010             $this->_data = array();
1011         } else {
1012             $recordName = $this->getClassnameToReturn();
1013             $record = new $recordName($this, true);
1014         }
1015
1016
1017         return $record;
1018     }
1019
1020     /**
1021      * Get the classname to return. Most often this is just the options['name']
1022      *
1023      * Check the subclasses option and the inheritanceMap for each subclass to see
1024      * if all the maps in a subclass is met. If this is the case return that
1025      * subclass name. If no subclasses match or if there are no subclasses defined
1026      * return the name of the class for this tables record.
1027      *
1028      * @todo this function could use reflection to check the first time it runs
1029      * if the subclassing option is not set.
1030      *
1031      * @return string The name of the class to create
1032      *
1033      */
1034     public function getClassnameToReturn()
1035     {
1036         if ( ! isset($this->_options['subclasses'])) {
1037             return $this->_options['name'];
1038         }
1039         foreach ($this->_options['subclasses'] as $subclass) {
1040             $table = $this->_conn->getTable($subclass);
1041             $inheritanceMap = $table->getOption('inheritanceMap');
1042             $nomatch = false;
1043             foreach ($inheritanceMap as $key => $value) {
1044                 if ( ! isset($this->_data[$key]) || $this->_data[$key] != $value) {
1045                     $nomatch = true;
1046                     break;
1047                 }
1048             }
1049             if ( ! $nomatch) {
1050                 return $table->getComponentName();
1051             }
1052         }
1053         return $this->_options['name'];
1054     }
1055
1056     /**
1057      * @param $id                       database row id
1058      * @throws Doctrine_Find_Exception
1059      */
1060     final public function getProxy($id = null)
1061     {
1062         if ($id !== null) {
1063             $query = 'SELECT ' . implode(', ', (array) $this->_identifier)
1064                 . ' FROM ' . $this->getTableName()
1065                 . ' WHERE ' . implode(' = ? && ', (array) $this->_identifier) . ' = ?';
1066             $query = $this->applyInheritance($query);
1067
1068             $params = array_merge(array($id), array_values($this->_options['inheritanceMap']));
1069
1070             $this->_data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC);
1071
1072             if ($this->_data === false)
1073                 return false;
1074         }
1075         return $this->getRecord();
1076     }
1077     /**
1078      * applyInheritance
1079      * @param $where                    query where part to be modified
1080      * @return string                   query where part with column aggregation inheritance added
1081      */
1082     final public function applyInheritance($where)
1083     {
1084         if ( ! empty($this->_options['inheritanceMap'])) {
1085             $a = array();
1086             foreach ($this->_options['inheritanceMap'] as $field => $value) {
1087                 $a[] = $field . ' = ?';
1088             }
1089             $i = implode(' AND ', $a);
1090             $where .= ' AND ' . $i;
1091         }
1092         return $where;
1093     }
1094     /**
1095      * count
1096      *
1097      * @return integer
1098      */
1099     public function count()
1100     {
1101         $a = $this->_conn->execute('SELECT COUNT(1) FROM ' . $this->_options['tableName'])->fetch(Doctrine::FETCH_NUM);
1102         return current($a);
1103     }
1104     /**
1105      * @return Doctrine_Query                           a Doctrine_Query object
1106      */
1107     public function getQueryObject()
1108     {
1109         $graph = new Doctrine_Query($this->getConnection());
1110         $graph->load($this->getComponentName());
1111         return $graph;
1112     }
1113     /**
1114      * @param string $field
1115      * @return array
1116      */
1117     public function getEnumValues($field)
1118     {
1119         if (isset($this->_columns[$field]['values'])) {
1120             return $this->_columns[$field]['values'];
1121         } else {
1122             return array();
1123         }
1124     }
1125     /**
1126      * enumValue
1127      *
1128      * @param string $field
1129      * @param integer $index
1130      * @return mixed
1131      */
1132     public function enumValue($field, $index)
1133     {
1134         if ($index instanceof Doctrine_Null) {
1135             return $index;
1136         }
1137
1138         if (!$this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)
1139             && isset($this->_columns[$field]['values'][$index])
1140         ) {
1141             return $this->_columns[$field]['values'][$index];
1142         }
1143
1144         return $index;
1145     }
1146     /**
1147      * enumIndex
1148      *
1149      * @param string $field
1150      * @param mixed $value
1151      * @return mixed
1152      */
1153     public function enumIndex($field, $value)
1154     {
1155         $values = $this->getEnumValues($field);
1156
1157         $index = array_search($value, $values);
1158         if ($index === false || !$this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)) {
1159             return $index;
1160         }
1161         return $value;
1162     }
1163     /* getColumnCount
1164      *
1165      * @return integer      the number of columns in this table
1166      */
1167     public function getColumnCount()
1168     {
1169         return $this->columnCount;
1170     }
1171
1172     /**
1173      * returns all columns and their definitions
1174      *
1175      * @return array
1176      */
1177     public function getColumns()
1178     {
1179         return $this->_columns;
1180     }
1181     /**
1182      * removeColumn
1183      * removes given column
1184      *
1185      * @return boolean
1186      */
1187     public function removeColumn($column)
1188     {
1189         if (isset($this->_columns[$column])) {
1190             unset($this->_columns[$column]);
1191
1192             return true;
1193         }
1194         
1195         return false;
1196     }
1197     /**
1198      * returns an array containing all the column names
1199      *
1200      * @return array
1201      */
1202     public function getColumnNames()
1203     {
1204         return array_keys($this->_columns);
1205     }
1206     /**
1207      * getDefinitionOf
1208      *
1209      * @return mixed        array on success, false on failure
1210      */
1211     public function getDefinitionOf($column)
1212     {
1213         if (isset($this->_columns[$column])) {
1214             return $this->_columns[$column];
1215         }
1216         return false;
1217     }
1218     /**
1219      * getTypeOf
1220      *
1221      * @return mixed        string on success, false on failure
1222      */
1223     public function getTypeOf($column)
1224     {
1225         if (isset($this->_columns[$column])) {
1226             return $this->_columns[$column]['type'];
1227         }
1228         return false;
1229     }
1230     /**
1231      * setData
1232      * doctrine uses this function internally
1233      * users are strongly discouraged to use this function
1234      *
1235      * @param array $data               internal data
1236      * @return void
1237      */
1238     public function setData(array $data)
1239     {
1240         $this->_data = $data;
1241     }
1242     /**
1243      * returns internal data, used by Doctrine_Record instances
1244      * when retrieving data from database
1245      *
1246      * @return array
1247      */
1248     public function getData()
1249     {
1250         return $this->_data;
1251     }
1252     /**
1253      * prepareValue
1254      * this method performs special data preparation depending on
1255      * the type of the given column
1256      *
1257      * 1. It unserializes array and object typed columns
1258      * 2. Uncompresses gzip typed columns
1259      * 3. Gets the appropriate enum values for enum typed columns
1260      * 4. Initializes special null object pointer for null values (for fast column existence checking purposes)
1261      *
1262      * example:
1263      * <code type='php'>
1264      * $field = 'name';
1265      * $value = null;
1266      * $table->prepareValue($field, $value); // Doctrine_Null
1267      * </code>
1268      *
1269      * @throws Doctrine_Table_Exception     if unserialization of array/object typed column fails or
1270      * @throws Doctrine_Table_Exception     if uncompression of gzip typed column fails         *
1271      * @param string $field     the name of the field
1272      * @param string $value     field value
1273      * @return mixed            prepared value
1274      */
1275     public function prepareValue($field, $value)
1276     {
1277         if ($value === self::$_null) {
1278             return self::$_null;
1279         } else if ($value === null) {
1280             return null;
1281         } else {
1282             $type = $this->getTypeOf($field);
1283
1284             switch ($type) {
1285                 case 'array':
1286                 case 'object':
1287                     if (is_string($value)) {
1288                         $value = unserialize($value);
1289
1290                         if ($value === false) {
1291                             throw new Doctrine_Table_Exception('Unserialization of ' . $field . ' failed.');
1292                         }
1293                         return $value;
1294                     }
1295                 break;
1296                 case 'gzip':
1297                     $value = gzuncompress($value);
1298
1299                     if ($value === false) {
1300                         throw new Doctrine_Table_Exception('Uncompressing of ' . $field . ' failed.');
1301                     }
1302                     return $value;
1303                 break;
1304                 case 'enum':
1305                     return $this->enumValue($field, $value);
1306                 break;
1307                 case 'boolean':
1308                     return (boolean) $value;
1309                 break;
1310                 case 'integer':
1311                     // don't do any casting here PHP INT_MAX is smaller than what the databases support
1312                 break;
1313             }
1314         }
1315         return $value;
1316     }
1317     /**
1318      * getter for associated tree
1319      *
1320      * @return mixed  if tree return instance of Doctrine_Tree, otherwise returns false
1321      */
1322     public function getTree() {
1323         if (isset($this->_options['treeImpl'])) {
1324             if ( ! $this->_tree) {
1325                 $options = isset($this->_options['treeOptions']) ? $this->_options['treeOptions'] : array();
1326                 $this->_tree = Doctrine_Tree::factory($this,
1327                     $this->_options['treeImpl'],
1328                     $options
1329                 );
1330             }
1331             return $this->_tree;
1332         }
1333         return false;
1334     }
1335     public function getComponentName()
1336     {
1337         return $this->_options['name'];
1338     }
1339     public function getTableName()
1340     {
1341         return $this->_options['tableName'];
1342     }
1343     public function setTableName($tableName)
1344     {
1345         $this->_options['tableName'] = $tableName;
1346     }
1347     /**
1348      * determine if table acts as tree
1349      *
1350      * @return mixed  if tree return true, otherwise returns false
1351      */
1352     public function isTree() {
1353         return ( ! is_null($this->_options['treeImpl'])) ? true : false;
1354     }
1355
1356     public function getTemplate($template)
1357     {
1358         if ( ! isset($this->_templates[$template])) {
1359             throw new Doctrine_Table_Exception('Template ' . $template . ' not loaded');
1360         }
1361
1362         return $this->_templates[$template];
1363     }
1364     
1365     public function hasTemplate($template)
1366     {
1367         return isset($this->_templates[$template]);
1368     }
1369
1370     public function addTemplate($template, Doctrine_Template $impl)
1371     {
1372         $this->_templates[$template] = $impl;
1373
1374         return $this;
1375     }
1376     /**
1377      * bindQueryParts
1378      * binds query parts to given component
1379      *
1380      * @param array $queryParts         an array of pre-bound query parts
1381      * @return Doctrine_Record          this object
1382      */
1383     public function bindQueryParts(array $queryParts)
1384     {
1385      $this->_options['queryParts'] = $queryParts;
1386
1387         return $this;
1388     }
1389     /**
1390      * bindQueryPart
1391      * binds given value to given query part
1392      *
1393      * @param string $queryPart
1394      * @param mixed $value
1395      * @return Doctrine_Record          this object
1396      */
1397     public function bindQueryPart($queryPart, $value)
1398     {
1399      $this->_options['queryParts'][$queryPart] = $value;
1400
1401         return $this;
1402     }
1403     public function getBoundQueryPart($queryPart)
1404     {
1405         if ( ! isset($this->_options['queryParts'][$queryPart])) {
1406             return null;
1407         }
1408
1409         return $this->_options['queryParts'][$queryPart];
1410     }
1411     public function unshiftFilter(Doctrine_Record_Filter $filter)
1412     {
1413         $filter->setTable($this);
1414
1415         $filter->init();
1416
1417         array_unshift($this->_filters, $filter);
1418
1419         return $this;
1420     }
1421     public function getFilters()
1422     {
1423         return $this->_filters;
1424     }
1425     /**
1426      * returns a string representation of this object
1427      *
1428      * @return string
1429      */
1430     public function __toString()
1431     {
1432         return Doctrine_Lib::getTableAsString($this);
1433     }
1434 }