1<?php
2/*
3 *  $Id$
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.doctrine-project.org>.
20 */
21
22/**
23 * Doctrine_Table   represents a database table
24 *                  each Doctrine_Table holds the information of foreignKeys and associations
25 *
26 *
27 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
28 * @package     Doctrine
29 * @subpackage  Table
30 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
31 * @version     $Revision$
32 * @link        www.doctrine-project.org
33 * @since       1.0
34 * @method mixed findBy*(mixed $value) magic finders; @see __call()
35 * @method mixed findOneBy*(mixed $value) magic finders; @see __call()
36 */
37class Doctrine_Table extends Doctrine_Configurable implements Countable, Serializable
38{
39    /**
40     * @var array $data                                 temporary data which is then loaded into Doctrine_Record::$_data
41     */
42    protected $_data             = array();
43
44    /**
45     * @var mixed $identifier   The field names of all fields that are part of the identifier/primary key
46     */
47    protected $_identifier = array();
48
49    /**
50     * @see Doctrine_Identifier constants
51     * @var integer $identifierType                     the type of identifier this table uses
52     */
53    protected $_identifierType;
54
55    /**
56     * @var Doctrine_Connection $conn                   Doctrine_Connection object that created this table
57     */
58    protected $_conn;
59
60    /**
61     * @var array $identityMap                          first level cache
62     */
63    protected $_identityMap        = array();
64
65    /**
66     * @var Doctrine_Table_Repository $repository       record repository
67     */
68    protected $_repository;
69
70    /**
71     * @var array $columns                  an array of column definitions,
72     *                                      keys are column names and values are column definitions
73     *
74     *                                      the definition array has atleast the following values:
75     *
76     *                                      -- type         the column type, eg. 'integer'
77     *                                      -- length       the column length, eg. 11
78     *
79     *                                      additional keys:
80     *                                      -- notnull      whether or not the column is marked as notnull
81     *                                      -- values       enum values
82     *                                      -- notblank     notblank validator + notnull constraint
83     *                                      ... many more
84     */
85    protected $_columns          = array();
86
87    /**
88     * Array of unique sets of fields. These values are validated on save
89     *
90     * @var array $_uniques
91     */
92    protected $_uniques = array();
93
94    /**
95     * @var array $_fieldNames            an array of field names, used to look up field names
96     *                                    from column names. Keys are column
97     *                                    names and values are field names.
98     *                                    Alias for columns are here.
99     */
100    protected $_fieldNames    = array();
101
102    /**
103     *
104     * @var array $_columnNames             an array of column names
105     *                                      keys are field names and values column names.
106     *                                      used to look up column names from field names.
107     *                                      this is the reverse lookup map of $_fieldNames.
108     */
109    protected $_columnNames = array();
110
111    /**
112     * @var integer $columnCount            cached column count, Doctrine_Record uses this column count in when
113     *                                      determining its state
114     */
115    protected $columnCount;
116
117    /**
118     * @var boolean $hasDefaultValues       whether or not this table has default values
119     */
120    protected $hasDefaultValues;
121
122    /**
123     * @var array $options                  an array containing all options
124     *
125     *      -- name                         name of the component, for example component name of the GroupTable is 'Group'
126     *
127     *      -- parents                      the parent classes of this component
128     *
129     *      -- declaringClass               name of the table definition declaring class (when using inheritance the class
130     *                                      that defines the table structure can be any class in the inheritance hierarchy,
131     *                                      hence we need reflection to check out which class actually calls setTableDefinition)
132     *
133     *      -- tableName                    database table name, in most cases this is the same as component name but in some cases
134     *                                      where one-table-multi-class inheritance is used this will be the name of the inherited table
135     *
136     *      -- sequenceName                 Some databases need sequences instead of auto incrementation primary keys,
137     *                                      you can set specific sequence for your table by calling setOption('sequenceName', $seqName)
138     *                                      where $seqName is the name of the desired sequence
139     *
140     *      -- enumMap                      enum value arrays
141     *
142     *      -- inheritanceMap               inheritanceMap is used for inheritance mapping, keys representing columns and values
143     *                                      the column values that should correspond to child classes
144     *
145     *      -- type                         table type (mysql example: INNODB)
146     *
147     *      -- charset                      character set
148     *
149     *      -- foreignKeys                  the foreign keys of this table
150     *
151     *      -- checks                       the check constraints of this table, eg. 'price > dicounted_price'
152     *
153     *      -- collate                      collate attribute
154     *
155     *      -- indexes                      the index definitions of this table
156     *
157     *      -- treeImpl                     the tree implementation of this table (if any)
158     *
159     *      -- treeOptions                  the tree options
160     *
161     *      -- queryParts                   the bound query parts
162     *
163     *      -- versioning
164     */
165    protected $_options      = array('name'           => null,
166                                     'tableName'      => null,
167                                     'sequenceName'   => null,
168                                     'inheritanceMap' => array(),
169                                     'enumMap'        => array(),
170                                     'type'           => null,
171                                     'charset'        => null,
172                                     'collate'        => null,
173                                     'treeImpl'       => null,
174                                     'treeOptions'    => array(),
175                                     'indexes'        => array(),
176                                     'parents'        => array(),
177                                     'joinedParents'  => array(),
178                                     'queryParts'     => array(),
179                                     'versioning'     => null,
180                                     'subclasses'     => array(),
181                                     'orderBy'        => null
182                                     );
183
184    /**
185     * @var Doctrine_Tree $tree                 tree object associated with this table
186     */
187    protected $_tree;
188
189    /**
190     * @var Doctrine_Relation_Parser $_parser   relation parser object
191     */
192    protected $_parser;
193
194    /**
195     * @see Doctrine_Template
196     * @var array $_templates                   an array containing all templates attached to this table
197     */
198    protected $_templates   = array();
199
200    /**
201     * @see Doctrine_Record_Filter
202     * @var array $_filters                     an array containing all record filters attached to this table
203     */
204    protected $_filters     = array();
205
206    /**
207     * @see Doctrine_Record_Generator
208     * @var array $_generators                  an array containing all generators attached to this table
209     */
210    protected $_generators     = array();
211
212    /**
213     * Generator instance responsible for constructing this table
214     *
215     * @see Doctrine_Record_Generator
216     * @var Doctrine_Record_Generator $generator
217     */
218    protected $_generator;
219
220    /**
221     * @var array $_invokedMethods              method invoker cache
222     */
223    protected $_invokedMethods = array();
224
225    /**
226     * @var boolean $_useIdentityMap Use or not identyMap cache
227     */
228    protected $_useIdentityMap = true;
229
230    /**
231     * @var Doctrine_Record $record             empty instance of the given model
232     */
233    protected $record;
234
235    /**
236     * the constructor
237     *
238     * @throws Doctrine_Connection_Exception    if there are no opened connections
239     * @param string $name                      the name of the component
240     * @param Doctrine_Connection $conn         the connection associated with this table
241     * @param boolean $initDefinition           whether to init the in-memory schema
242     */
243    public function __construct($name, Doctrine_Connection $conn, $initDefinition = false)
244    {
245        $this->_conn = $conn;
246        $this->_options['name'] = $name;
247
248        $this->setParent($this->_conn);
249        $this->_conn->addTable($this);
250
251        $this->_parser = new Doctrine_Relation_Parser($this);
252
253        if ($charset = $this->getAttribute(Doctrine_Core::ATTR_DEFAULT_TABLE_CHARSET)) {
254            $this->_options['charset'] = $charset;
255        }
256        if ($collate = $this->getAttribute(Doctrine_Core::ATTR_DEFAULT_TABLE_COLLATE)) {
257            $this->_options['collate'] = $collate;
258        }
259
260        if ($initDefinition) {
261            $this->record = $this->initDefinition();
262
263            $this->initIdentifier();
264
265            $this->record->setUp();
266
267            // if tree, set up tree
268            if ($this->isTree()) {
269                $this->getTree()->setUp();
270            }
271        } else {
272            if ( ! isset($this->_options['tableName'])) {
273                $this->setTableName(Doctrine_Inflector::tableize($this->_options['name']));
274            }
275        }
276
277        $this->_filters[]  = new Doctrine_Record_Filter_Standard();
278        if ($this->getAttribute(Doctrine_Core::ATTR_USE_TABLE_REPOSITORY)) {
279            $this->_repository = new Doctrine_Table_Repository($this);
280        } else {
281            $this->_repository = new Doctrine_Table_Repository_None($this);
282        }
283
284        $this->_useIdentityMap = $this->getAttribute(Doctrine_Core::ATTR_USE_TABLE_IDENTITY_MAP);
285
286        $this->construct();
287    }
288
289    /**
290     * Construct template method.
291     *
292     * This method provides concrete Table classes with the possibility
293     * to hook into the constructor procedure. It is called after the
294     * Doctrine_Table construction process is finished.
295     *
296     * @return void
297     */
298    public function construct()
299    { }
300
301    /**
302     * Initializes the in-memory table definition.
303     *
304     * @param string $name
305     */
306    public function initDefinition()
307    {
308        $name = $this->_options['name'];
309        if ( ! class_exists($name) || empty($name)) {
310            throw new Doctrine_Exception("Couldn't find class " . $name);
311        }
312        $record = new $name($this);
313
314        $names = array();
315
316        $class = $name;
317
318        // get parent classes
319
320        do {
321            if ($class === 'Doctrine_Record') {
322                break;
323            }
324
325            $name = $class;
326            $names[] = $name;
327        } while ($class = get_parent_class($class));
328
329        if ($class === false) {
330            throw new Doctrine_Table_Exception('Class "' . $name . '" must be a child class of Doctrine_Record');
331        }
332
333        // reverse names
334        $names = array_reverse($names);
335        // save parents
336        array_pop($names);
337        $this->_options['parents'] = $names;
338
339        // create database table
340        if (method_exists($record, 'setTableDefinition')) {
341            $record->setTableDefinition();
342            // get the declaring class of setTableDefinition method
343            $method = new ReflectionMethod($this->_options['name'], 'setTableDefinition');
344            $class = $method->getDeclaringClass();
345
346        } else {
347            $class = new ReflectionClass($class);
348        }
349
350        $this->_options['joinedParents'] = array();
351
352        foreach (array_reverse($this->_options['parents']) as $parent) {
353
354            if ($parent === $class->getName()) {
355                continue;
356            }
357            $ref = new ReflectionClass($parent);
358
359            if ($ref->isAbstract() || ! $class->isSubclassOf($parent)) {
360                continue;
361            }
362            $parentTable = $this->_conn->getTable($parent);
363
364            $found = false;
365            $parentColumns = $parentTable->getColumns();
366
367            foreach ($parentColumns as $columnName => $definition) {
368                if ( ! isset($definition['primary']) || $definition['primary'] === false) {
369                    if (isset($this->_columns[$columnName])) {
370                        $found = true;
371                        break;
372                    } else {
373                        if ( ! isset($parentColumns[$columnName]['owner'])) {
374                            $parentColumns[$columnName]['owner'] = $parentTable->getComponentName();
375                        }
376
377                        $this->_options['joinedParents'][] = $parentColumns[$columnName]['owner'];
378                    }
379                } else {
380                    unset($parentColumns[$columnName]);
381                }
382            }
383
384            if ($found) {
385                continue;
386            }
387
388            foreach ($parentColumns as $columnName => $definition) {
389                $fullName = $columnName . ' as ' . $parentTable->getFieldName($columnName);
390                $this->setColumn($fullName, $definition['type'], $definition['length'], $definition, true);
391            }
392
393            break;
394        }
395
396        $this->_options['joinedParents'] = array_values(array_unique($this->_options['joinedParents']));
397
398        $this->_options['declaringClass'] = $class;
399
400        // set the table definition for the given tree implementation
401        if ($this->isTree()) {
402            $this->getTree()->setTableDefinition();
403        }
404
405        $this->columnCount = count($this->_columns);
406
407        if ( ! isset($this->_options['tableName'])) {
408            $this->setTableName(Doctrine_Inflector::tableize($class->getName()));
409        }
410
411        return $record;
412    }
413
414    /**
415     * Initializes the primary key.
416     *
417     * Called in the construction process, builds the identifier definition
418     * copying in the schema the list of the fields which constitutes
419     * the primary key.
420     *
421     * @return void
422     */
423    public function initIdentifier()
424    {
425        switch (count($this->_identifier)) {
426            case 0:
427                if ( ! empty($this->_options['joinedParents'])) {
428                    $root = current($this->_options['joinedParents']);
429
430                    $table = $this->_conn->getTable($root);
431
432                    $this->_identifier = $table->getIdentifier();
433
434                    $this->_identifierType = ($table->getIdentifierType() !== Doctrine_Core::IDENTIFIER_AUTOINC)
435                                            ? $table->getIdentifierType() : Doctrine_Core::IDENTIFIER_NATURAL;
436
437                    // add all inherited primary keys
438                    foreach ((array) $this->_identifier as $id) {
439                        $definition = $table->getDefinitionOf($id);
440
441                        // inherited primary keys shouldn't contain autoinc
442                        // and sequence definitions
443                        unset($definition['autoincrement']);
444                        unset($definition['sequence']);
445
446                        // add the inherited primary key column
447                        $fullName = $id . ' as ' . $table->getFieldName($id);
448                        $this->setColumn($fullName, $definition['type'], $definition['length'],
449                                $definition, true);
450                    }
451                } else {
452                    $identifierOptions = $this->getAttribute(Doctrine_Core::ATTR_DEFAULT_IDENTIFIER_OPTIONS);
453                    $name = (isset($identifierOptions['name']) && $identifierOptions['name']) ? $identifierOptions['name']:'id';
454                    $name = sprintf($name, $this->getTableName());
455
456                    $definition = array('type' => (isset($identifierOptions['type']) && $identifierOptions['type']) ? $identifierOptions['type']:'integer',
457                                        'length' => (isset($identifierOptions['length']) && $identifierOptions['length']) ? $identifierOptions['length']:8,
458                                        'autoincrement' => isset($identifierOptions['autoincrement']) ? $identifierOptions['autoincrement']:true,
459                                        'primary' => isset($identifierOptions['primary']) ? $identifierOptions['primary']:true);
460
461                    unset($identifierOptions['name'], $identifierOptions['type'], $identifierOptions['length']);
462                    foreach ($identifierOptions as $key => $value) {
463                        if ( ! isset($definition[$key]) || ! $definition[$key]) {
464                            $definition[$key] = $value;
465                        }
466                    }
467
468                    $this->setColumn($name, $definition['type'], $definition['length'], $definition, true);
469                    $this->_identifier = $name;
470                    $this->_identifierType = Doctrine_Core::IDENTIFIER_AUTOINC;
471                }
472                $this->columnCount++;
473                break;
474            case 1:
475                foreach ($this->_identifier as $pk) {
476                    $e = $this->getDefinitionOf($pk);
477
478                    $found = false;
479
480                    foreach ($e as $option => $value) {
481                        if ($found) {
482                            break;
483                        }
484
485                        $e2 = explode(':', $option);
486
487                        switch (strtolower($e2[0])) {
488                            case 'autoincrement':
489                            case 'autoinc':
490                                if ($value !== false) {
491                                    $this->_identifierType = Doctrine_Core::IDENTIFIER_AUTOINC;
492                                    $found = true;
493                                }
494                                break;
495                            case 'seq':
496                            case 'sequence':
497                                $this->_identifierType = Doctrine_Core::IDENTIFIER_SEQUENCE;
498                                $found = true;
499
500                                if (is_string($value)) {
501                                    $this->_options['sequenceName'] = $value;
502                                } else {
503                                    if (($sequence = $this->getAttribute(Doctrine_Core::ATTR_DEFAULT_SEQUENCE)) !== null) {
504                                        $this->_options['sequenceName'] = $sequence;
505                                    } else {
506                                        $this->_options['sequenceName'] = $this->_conn->formatter->getSequenceName($this->_options['tableName']);
507                                    }
508                                }
509                                break;
510                        }
511                    }
512                    if ( ! isset($this->_identifierType)) {
513                        $this->_identifierType = Doctrine_Core::IDENTIFIER_NATURAL;
514                    }
515                }
516
517                $this->_identifier = $pk;
518
519                break;
520            default:
521                $this->_identifierType = Doctrine_Core::IDENTIFIER_COMPOSITE;
522        }
523    }
524
525    /**
526     * Gets the owner of a column.
527     *
528     * The owner of a column is the name of the component in a hierarchy that
529     * defines the column.
530     *
531     * @param string $columnName   the column name
532     * @return string              the name of the owning/defining component
533     */
534    public function getColumnOwner($columnName)
535    {
536        if (isset($this->_columns[$columnName]['owner'])) {
537            return $this->_columns[$columnName]['owner'];
538        } else {
539            return $this->getComponentName();
540        }
541    }
542
543    /**
544     * Gets the record instance for this table.
545     *
546     * The Doctrine_Table instance always holds at least one
547     * instance of a model so that it can be reused for several things,
548     * but primarily it is first used to instantiate all the internal
549     * in memory schema definition.
550     *
551     * @return Doctrine_Record  Empty instance of the record
552     */
553    public function getRecordInstance()
554    {
555        if ( ! $this->record) {
556            $this->record = new $this->_options['name'];
557        }
558        return $this->record;
559    }
560
561    /**
562     * Checks whether a column is inherited from a component further up in the hierarchy.
563     *
564     * @param $columnName  The column name
565     * @return boolean     TRUE if column is inherited, FALSE otherwise.
566     */
567    public function isInheritedColumn($columnName)
568    {
569        return (isset($this->_columns[$columnName]['owner']));
570    }
571
572    /**
573     * Checks whether a field is in the primary key.
574     *
575     * Checks if $fieldName is part of the table identifier, which defines
576     * the one-column or multi-column primary key.
577     *
578     * @param string $fieldName  The field name
579     * @return boolean           TRUE if the field is part of the table identifier/primary key field(s),
580     */
581    public function isIdentifier($fieldName)
582    {
583        return ($fieldName === $this->getIdentifier() ||
584                in_array($fieldName, (array) $this->getIdentifier()));
585    }
586
587    /**
588     * Checks whether a field identifier is of type autoincrement.
589     *
590     * This method checks if the primary key is a AUTOINCREMENT column or
591     * if the table uses a natural key.
592     *
593     * @return boolean TRUE  if the identifier is autoincrement
594     *                 FALSE otherwise
595     */
596    public function isIdentifierAutoincrement()
597    {
598        return $this->getIdentifierType() === Doctrine_Core::IDENTIFIER_AUTOINC;
599    }
600
601    /**
602     * Checks whether a field identifier is a composite key.
603     *
604     * @return boolean TRUE  if the identifier is a composite key,
605     *                 FALSE otherwise
606     */
607    public function isIdentifierComposite()
608    {
609        return $this->getIdentifierType() === Doctrine_Core::IDENTIFIER_COMPOSITE;
610    }
611
612    /**
613     * getMethodOwner
614     *
615     * @param string $method
616     * @return void
617     */
618    public function getMethodOwner($method)
619    {
620        return (isset($this->_invokedMethods[$method])) ?
621                      $this->_invokedMethods[$method] : false;
622    }
623
624    /**
625     * setMethodOwner
626     *
627     * @param string $method
628     * @param string $class
629     */
630    public function setMethodOwner($method, $class)
631    {
632        $this->_invokedMethods[$method] = $class;
633    }
634
635    /**
636     * Exports this table to database based on the schema definition.
637     *
638     * This method create a physical table in the database, using the
639     * definition that comes from the component Doctrine_Record instance.
640     *
641     * @throws Doctrine_Connection_Exception    if some error other than Doctrine_Core::ERR_ALREADY_EXISTS
642     *                                          occurred during the create table operation
643     * @return boolean                          whether or not the export operation was successful
644     *                                          false if table already existed in the database
645     */
646    public function export()
647    {
648        $this->_conn->export->exportTable($this);
649    }
650
651    /**
652     * Returns an exportable representation of this object.
653     *
654     * This method produces a array representation of the table schema, where
655     * keys are tableName, columns (@see $_columns) and options.
656     * The options subarray contains 'primary' and 'foreignKeys'.
657     *
658     * @param boolean $parseForeignKeys     whether to include foreign keys definition in the options
659     * @return array
660     */
661    public function getExportableFormat($parseForeignKeys = true)
662    {
663        $columns = array();
664        $primary = array();
665
666        foreach ($this->getColumns() as $name => $definition) {
667
668            if (isset($definition['owner'])) {
669                continue;
670            }
671
672            switch ($definition['type']) {
673                case 'boolean':
674                    if (isset($definition['default'])) {
675                        $definition['default'] = $this->getConnection()->convertBooleans($definition['default']);
676                    }
677                    break;
678            }
679            $columns[$name] = $definition;
680
681            if (isset($definition['primary']) && $definition['primary']) {
682                $primary[] = $name;
683            }
684        }
685
686        $options['foreignKeys'] = isset($this->_options['foreignKeys']) ?
687                $this->_options['foreignKeys'] : array();
688
689        if ($parseForeignKeys && $this->getAttribute(Doctrine_Core::ATTR_EXPORT) & Doctrine_Core::EXPORT_CONSTRAINTS) {
690
691            $constraints = array();
692
693            $emptyIntegrity = array('onUpdate' => null,
694                                    'onDelete' => null);
695
696            foreach ($this->getRelations() as $name => $relation) {
697                $fk = $relation->toArray();
698                $fk['foreignTable'] = $relation->getTable()->getTableName();
699
700                // do not touch tables that have EXPORT_NONE attribute
701                if ($relation->getTable()->getAttribute(Doctrine_Core::ATTR_EXPORT) === Doctrine_Core::EXPORT_NONE) {
702                    continue;
703                }
704
705                if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) {
706                    if ($relation->hasConstraint()) {
707                        throw new Doctrine_Table_Exception("Badly constructed integrity constraints. Cannot define constraint of different fields in the same table.");
708                    }
709                    continue;
710                }
711
712                $integrity = array('onUpdate' => $fk['onUpdate'],
713                                   'onDelete' => $fk['onDelete']);
714
715                $fkName = $relation->getForeignKeyName();
716
717                if ($relation instanceof Doctrine_Relation_LocalKey) {
718                    $def = array('name'         => $fkName,
719                                 'local'        => $relation->getLocalColumnName(),
720                                 'foreign'      => $relation->getForeignColumnName(),
721                                 'foreignTable' => $relation->getTable()->getTableName());
722
723                    if ($integrity !== $emptyIntegrity) {
724                        $def = array_merge($def, $integrity);
725                    }
726                    if (($key = $this->_checkForeignKeyExists($def, $options['foreignKeys'])) === false) {
727                        $options['foreignKeys'][$fkName] = $def;
728                    } else {
729                        unset($def['name']);
730                        $options['foreignKeys'][$key] = array_merge($options['foreignKeys'][$key], $def);
731                    }
732                }
733            }
734        }
735
736        $options['primary'] = $primary;
737
738        return array('tableName' => $this->getOption('tableName'),
739                     'columns'   => $columns,
740                     'options'   => array_merge($this->getOptions(), $options));
741    }
742
743    /**
744     * Check if a foreign definition already exists in the fks array for a
745     * foreign table, local and foreign key
746     *
747     * @param  array $def          Foreign key definition to check for
748     * @param  array $foreignKeys  Array of existing foreign key definitions to check in
749     * @return boolean $result     Whether or not the foreign key was found
750     */
751    protected function _checkForeignKeyExists($def, $foreignKeys)
752    {
753        foreach ($foreignKeys as $key => $foreignKey) {
754            if ($def['local'] == $foreignKey['local'] && $def['foreign'] == $foreignKey['foreign'] && $def['foreignTable'] == $foreignKey['foreignTable']) {
755                return $key;
756            }
757        }
758        return false;
759    }
760
761    /**
762     * Retrieves the relation parser associated with this table.
763     *
764     * @return Doctrine_Relation_Parser     relation parser object
765     */
766    public function getRelationParser()
767    {
768        return $this->_parser;
769    }
770
771    /**
772     * Magic method for accessing to object properties.
773     *
774     * This method is an alias for getOption.
775     * <code>
776     * foreach ($table->indexes as $name => $definition) {
777     *     // ...
778     * }
779     * </code>
780     *
781     * @param string $option
782     * @return mixed
783     */
784    public function __get($option)
785    {
786        if (isset($this->_options[$option])) {
787            return $this->_options[$option];
788        }
789        return null;
790    }
791
792    /**
793     * Magic method for testing object properties existence.
794     *
795     * This method tests if an option exists.
796     * <code>
797     * if (isset($table->tableName)) {
798     *     // ...
799     * }
800     * </code>
801     *
802     * @param string $option
803     */
804    public function __isset($option)
805    {
806        return isset($this->_options[$option]);
807    }
808
809    /**
810     * Retrieves all options of this table and the associated values.
811     *
812     * @return array    all options and their values
813     */
814    public function getOptions()
815    {
816        return $this->_options;
817    }
818
819    /**
820     * Sets all the options.
821     *
822     * This method sets options of the table that are specified in the argument.
823     * It has no effect on other options.
824     *
825     * @param array $options    keys are option names
826     * @return void
827     */
828    public function setOptions($options)
829    {
830        foreach ($options as $key => $value) {
831            $this->setOption($key, $value);
832        }
833    }
834
835    /**
836     * Adds a foreignKey to the table in-memory definition.
837     *
838     * This method adds a foreign key to the schema definition.
839     * It does not add the key to the physical table in the db; @see export().
840     *
841     * @param array $definition     definition of the foreign key
842     * @return void
843     */
844    public function addForeignKey(array $definition)
845    {
846        $this->_options['foreignKeys'][] = $definition;
847    }
848
849    /**
850     * Adds a check constraint to the table in-memory definition.
851     *
852     * This method adds a CHECK constraint to the schema definition.
853     * It does not add the constraint to the physical table in the
854     * db; @see export().
855     *
856     * @param $definition
857     * @param mixed $name   if string used as name for the constraint.
858     *                      Otherwise it is indexed numerically.
859     * @return void
860     */
861    public function addCheckConstraint($definition, $name)
862    {
863        if (is_string($name)) {
864            $this->_options['checks'][$name] = $definition;
865        } else {
866            $this->_options['checks'][] = $definition;
867        }
868
869        return $this;
870    }
871
872    /**
873     * Adds an index to this table in-memory definition.
874     *
875     * This method adds an INDEX to the schema definition.
876     * It does not add the index to the physical table in the db; @see export().
877     *
878     * @param string $index         index name
879     * @param array $definition     keys are type, fields
880     * @return void
881     */
882    public function addIndex($index, array $definition)
883    {
884        if (isset($definition['fields'])) {
885            foreach ((array) $definition['fields'] as $key => $field) {
886                if (is_numeric($key)) {
887                    $definition['fields'][$key] = $this->getColumnName($field);
888                } else {
889                    $columnName = $this->getColumnName($key);
890
891                    unset($definition['fields'][$key]);
892
893                    $definition['fields'][$columnName] = $field;
894                }
895            }
896        }
897
898        $this->_options['indexes'][$index] = $definition;
899    }
900
901    /**
902     * Retrieves an index definition.
903     *
904     * This method returns a given index definition: @see addIndex().
905     *
906     * @param string $index         index name; @see addIndex()
907     * @return array|boolean        array on success, FALSE on failure
908     */
909    public function getIndex($index)
910    {
911        if (isset($this->_options['indexes'][$index])) {
912            return $this->_options['indexes'][$index];
913        }
914
915        return false;
916    }
917
918    /**
919     * Defines a n-uple of fields that must be unique for every record.
920     *
921     * This method Will automatically add UNIQUE index definition
922     * and validate the values on save. The UNIQUE index is not created in the
923     * database until you use @see export().
924     *
925     * @param array $fields     values are fieldnames
926     * @param array $options    array of options for unique validator
927     * @param bool $createUniqueIndex  Whether or not to create a unique index in the database
928     * @return void
929     */
930    public function unique($fields, $options = array(), $createdUniqueIndex = true)
931    {
932        if ($createdUniqueIndex) {
933            $name = implode('_', $fields) . '_unqidx';
934            $definition = array('type' => 'unique', 'fields' => $fields);
935            $this->addIndex($name, $definition);
936        }
937
938        $this->_uniques[] = array($fields, $options);
939    }
940
941    /**
942     * Adds a relation to the table.
943     *
944     * This method defines a relation on this table, that will be present on
945     * every record belonging to this component.
946     *
947     * @param array $args       first value is a string, name of related component;
948     *                          second value is array, options for the relation.
949     *                          @see Doctrine_Relation::_$definition
950     * @param integer $type     Doctrine_Relation::ONE or Doctrine_Relation::MANY
951     * @return void
952     * @todo Name proposal: addRelation
953     */
954    public function bind($args, $type)
955    {
956        $options = ( ! isset($args[1])) ? array() : $args[1];
957        $options['type'] = $type;
958
959        $this->_parser->bind($args[0], $options);
960    }
961
962    /**
963     * Binds One-to-One aggregate relation
964     *
965     * @param string $componentName     the name of the related component
966     * @param string $options           relation options
967     * @see Doctrine_Relation::_$definition
968     * @return Doctrine_Record          this object
969     */
970    public function hasOne()
971    {
972        $this->bind(func_get_args(), Doctrine_Relation::ONE);
973    }
974
975    /**
976     * Binds One-to-Many / Many-to-Many aggregate relation
977     *
978     * @param string $componentName     the name of the related component
979     * @param string $options           relation options
980     * @see Doctrine_Relation::_$definition
981     * @return Doctrine_Record          this object
982     */
983    public function hasMany()
984    {
985        $this->bind(func_get_args(), Doctrine_Relation::MANY);
986    }
987
988    /**
989     * Tests if a relation exists.
990     *
991     * This method queries the table definition to find out if a relation
992     * is defined for this component. Alias defined with foreignAlias are not
993     * recognized as there's only one Doctrine_Relation object on the owning
994     * side.
995     *
996     * @param string $alias      the relation alias to search for.
997     * @return boolean           true if the relation exists. Otherwise false.
998     */
999    public function hasRelation($alias)
1000    {
1001        return $this->_parser->hasRelation($alias);
1002    }
1003
1004    /**
1005     * Retrieves a relation object for this component.
1006     *
1007     * @param string $alias      relation alias; @see hasRelation()
1008     * @return Doctrine_Relation
1009     */
1010    public function getRelation($alias, $recursive = true)
1011    {
1012        return $this->_parser->getRelation($alias, $recursive);
1013    }
1014
1015    /**
1016     * Retrieves all relation objects defined on this table.
1017     *
1018     * @return array
1019     */
1020    public function getRelations()
1021    {
1022        return $this->_parser->getRelations();
1023    }
1024
1025    /**
1026     * Creates a query on this table.
1027     *
1028     * This method returns a new Doctrine_Query object and adds the component
1029     * name of this table as the query 'from' part.
1030     * <code>
1031     * $table = Doctrine_Core::getTable('User');
1032     * $table->createQuery('myuser')
1033     *       ->where('myuser.Phonenumber = ?', '5551234');
1034     * </code>
1035     *
1036     * @param string $alias     name for component aliasing
1037     * @return Doctrine_Query
1038     */
1039    public function createQuery($alias = '')
1040    {
1041        if ( ! empty($alias)) {
1042            $alias = ' ' . trim($alias);
1043        }
1044
1045        $class = $this->getAttribute(Doctrine_Core::ATTR_QUERY_CLASS);
1046
1047        return Doctrine_Query::create(null, $class)
1048            ->from($this->getComponentName() . $alias);
1049    }
1050
1051    /**
1052     * Gets the internal record repository.
1053     *
1054     * @return Doctrine_Table_Repository
1055     */
1056    public function getRepository()
1057    {
1058        return $this->_repository;
1059    }
1060
1061    /**
1062     * Sets an option for the table.
1063     *
1064     * This method sets an option and returns this object in order to
1065     * allow flexible method chaining.
1066     *
1067     * @see Doctrine_Table::$_options   for available options
1068     * @param string $name              the name of the option to set
1069     * @param mixed $value              the value of the option
1070     * @return Doctrine_Table           this object
1071     */
1072    public function setOption($name, $value)
1073    {
1074        switch ($name) {
1075            case 'name':
1076            case 'tableName':
1077                break;
1078            case 'enumMap':
1079            case 'inheritanceMap':
1080            case 'index':
1081            case 'treeOptions':
1082                if ( ! is_array($value)) {
1083                throw new Doctrine_Table_Exception($name . ' should be an array.');
1084                }
1085                break;
1086        }
1087        $this->_options[$name] = $value;
1088    }
1089
1090    /**
1091     * Returns the value of a given option.
1092     *
1093     * @see Doctrine_Table::$_options   for available options
1094     * @param string $name  the name of the option
1095     * @return mixed        the value of given option
1096     */
1097    public function getOption($name)
1098    {
1099        if (isset($this->_options[$name])) {
1100            return $this->_options[$name];
1101        }
1102        return null;
1103    }
1104
1105
1106    /**
1107     * Get the table orderby statement
1108     *
1109     * @param string $alias        The alias to use
1110     * @param boolean $columnNames Whether or not to use column names instead of field names
1111     * @return string $orderByStatement
1112     */
1113    public function getOrderByStatement($alias = null, $columnNames = false)
1114    {
1115        if (isset($this->_options['orderBy'])) {
1116            return $this->processOrderBy($alias, $this->_options['orderBy']);
1117        }
1118    }
1119
1120    /**
1121     * Process an order by statement to be prefixed with the passed alias and
1122     * field names converted to column names if the 3rd argument is true.
1123     *
1124     * @param string $alias        The alias to prefix columns with
1125     * @param string $orderBy      The order by to process
1126     * @param string $columnNames  Whether or not to convert field names to column names
1127     * @return string $orderBy
1128     */
1129    public function processOrderBy($alias, $orderBy, $columnNames = false)
1130    {
1131        if ( ! $alias) {
1132           $alias = $this->getComponentName();
1133        }
1134
1135        if ( ! is_array($orderBy)) {
1136            $e1 = explode(',', $orderBy);
1137        } else {
1138            $e1 = $orderBy;
1139        }
1140
1141        foreach ($e1 as $k => $v) {
1142            $v = trim($v);
1143            $e2 = explode(' ', $v);
1144            if ($columnNames) {
1145                $e2[0] = $this->getColumnName($e2[0]);
1146            }
1147            if ($this->hasField($this->getFieldName($e2[0]))) {
1148                $e1[$k] = $alias . '.' . $e2[0];
1149            } else {
1150                $e1[$k] = $e2[0];
1151            }
1152            if (isset($e2[1])) {
1153                $e1[$k] .=  ' ' . $e2[1];
1154            }
1155        }
1156
1157        return implode(', ', $e1);
1158    }
1159
1160    /**
1161     * Returns a column name for a column alias.
1162     *
1163     * If the actual name for the alias cannot be found
1164     * this method returns the given alias.
1165     *
1166     * @param string $alias         column alias
1167     * @return string               column name
1168     */
1169    public function getColumnName($fieldName)
1170    {
1171        // FIX ME: This is being used in places where an array is passed, but it should not be an array
1172        // For example in places where Doctrine should support composite foreign/primary keys
1173        $fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName;
1174
1175        if (isset($this->_columnNames[$fieldName])) {
1176            return $this->_columnNames[$fieldName];
1177        }
1178
1179        return strtolower($fieldName);
1180    }
1181
1182    /**
1183     * Retrieves a column definition from this table schema.
1184     *
1185     * @param string $columnName
1186     * @return array              column definition; @see $_columns
1187     */
1188    public function getColumnDefinition($columnName)
1189    {
1190        if ( ! isset($this->_columns[$columnName])) {
1191            return false;
1192        }
1193        return $this->_columns[$columnName];
1194    }
1195
1196    /**
1197     * Returns a column alias for a column name.
1198     *
1199     * If no alias can be found the column name is returned.
1200     *
1201     * @param string $columnName    column name
1202     * @return string               column alias
1203     */
1204    public function getFieldName($columnName)
1205    {
1206        if (isset($this->_fieldNames[$columnName])) {
1207            return $this->_fieldNames[$columnName];
1208        }
1209        return $columnName;
1210    }
1211
1212    /**
1213     * Customize the array of options for a column or multiple columns. First
1214     * argument can be a single field/column name or an array of them. The second
1215     * argument is an array of options.
1216     *
1217     *     [php]
1218     *     public function setTableDefinition()
1219     *     {
1220     *         parent::setTableDefinition();
1221     *         $this->setColumnOptions('username', array(
1222     *             'unique' => true
1223     *         ));
1224     *     }
1225     *
1226     * @param string $columnName
1227     * @param array $validators
1228     * @return void
1229     */
1230    public function setColumnOptions($columnName, array $options)
1231    {
1232        if (is_array($columnName)) {
1233            foreach ($columnName as $name) {
1234                $this->setColumnOptions($name, $options);
1235            }
1236        } else {
1237            foreach ($options as $option => $value) {
1238                $this->setColumnOption($columnName, $option, $value);
1239            }
1240        }
1241    }
1242
1243    /**
1244     * Set an individual column option
1245     *
1246     * @param string $columnName
1247     * @param string $option
1248     * @param string $value
1249     * @return void
1250     */
1251    public function setColumnOption($columnName, $option, $value)
1252    {
1253        if ($option == 'primary') {
1254            if (isset($this->_identifier)) {
1255                $this->_identifier = (array) $this->_identifier;
1256            }
1257
1258            if ($value &&  ! in_array($columnName, $this->_identifier)) {
1259                $this->_identifier[] = $columnName;
1260            } else if (!$value && in_array($columnName, $this->_identifier)) {
1261                $key = array_search($columnName, $this->_identifier);
1262                unset($this->_identifier[$key]);
1263            }
1264        }
1265
1266        $columnName = $this->getColumnName($columnName);
1267        $this->_columns[$columnName][$option] = $value;
1268    }
1269
1270    /**
1271     * Set multiple column definitions at once
1272     *
1273     * @param array $definitions
1274     * @return void
1275     */
1276    public function setColumns(array $definitions)
1277    {
1278        foreach ($definitions as $name => $options) {
1279            $this->setColumn($name, $options['type'], $options['length'], $options);
1280        }
1281    }
1282
1283    /**
1284     * Adds a column to the schema.
1285     *
1286     * This method does not alter the database table; @see export();
1287     *
1288     * @see $_columns;
1289     * @param string $name      column physical name
1290     * @param string $type      type of data
1291     * @param integer $length   maximum length
1292     * @param mixed $options
1293     * @param boolean $prepend  Whether to prepend or append the new column to the column list.
1294     *                          By default the column gets appended.
1295     * @throws Doctrine_Table_Exception     if trying use wrongly typed parameter
1296     * @return void
1297     */
1298    public function setColumn($name, $type = null, $length = null, $options = array(), $prepend = false)
1299    {
1300        if (is_string($options)) {
1301            $options = explode('|', $options);
1302        }
1303
1304        foreach ($options as $k => $option) {
1305            if (is_numeric($k)) {
1306                if ( ! empty($option)) {
1307                    $options[$option] = true;
1308                }
1309                unset($options[$k]);
1310            }
1311        }
1312
1313        // extract column name & field name
1314        if (stripos($name, ' as '))
1315        {
1316            if (strpos($name, ' as ')) {
1317                $parts = explode(' as ', $name);
1318            } else {
1319                $parts = explode(' AS ', $name);
1320            }
1321
1322            if (count($parts) > 1) {
1323                $fieldName = $parts[1];
1324            } else {
1325                $fieldName = $parts[0];
1326            }
1327
1328            $name = strtolower($parts[0]);
1329        } else {
1330            $fieldName = $name;
1331            $name = strtolower($name);
1332        }
1333
1334        $name = trim($name);
1335        $fieldName = trim($fieldName);
1336
1337        if ($prepend) {
1338            $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames);
1339            $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames);
1340        } else {
1341            $this->_columnNames[$fieldName] = $name;
1342            $this->_fieldNames[$name] = $fieldName;
1343        }
1344
1345        $defaultOptions = $this->getAttribute(Doctrine_Core::ATTR_DEFAULT_COLUMN_OPTIONS);
1346
1347        if (isset($defaultOptions['length']) && $defaultOptions['length'] && $length == null) {
1348            $length = $defaultOptions['length'];
1349        }
1350
1351        if ($length == null) {
1352            switch ($type) {
1353                case 'integer':
1354                    $length = 8;
1355                break;
1356                case 'decimal':
1357                    $length = 18;
1358                break;
1359                case 'string':
1360                case 'clob':
1361                case 'float':
1362                case 'integer':
1363                case 'array':
1364                case 'object':
1365                case 'blob':
1366                case 'gzip':
1367                    //$length = 2147483647;
1368
1369                    //All the DataDict driver classes have work-arounds to deal
1370                    //with unset lengths.
1371                    $length = null;
1372                break;
1373                case 'boolean':
1374                    $length = 1;
1375                case 'date':
1376                    // YYYY-MM-DD ISO 8601
1377                    $length = 10;
1378                case 'time':
1379                    // HH:NN:SS+00:00 ISO 8601
1380                    $length = 14;
1381                case 'timestamp':
1382                    // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601
1383                    $length = 25;
1384            }
1385        }
1386
1387        $options['type'] = $type;
1388        $options['length'] = $length;
1389
1390        if (strtolower($fieldName) != $name) {
1391            $options['alias'] = $fieldName;
1392        }
1393
1394        foreach ($defaultOptions as $key => $value) {
1395            if ( ! array_key_exists($key, $options) || is_null($options[$key])) {
1396                $options[$key] = $value;
1397            }
1398        }
1399
1400        if ($prepend) {
1401            $this->_columns = array_merge(array($name => $options), $this->_columns);
1402        } else {
1403            $this->_columns[$name] = $options;
1404        }
1405
1406        if (isset($options['primary']) && $options['primary']) {
1407            if (isset($this->_identifier)) {
1408                $this->_identifier = (array) $this->_identifier;
1409            }
1410            if ( ! in_array($fieldName, $this->_identifier)) {
1411                $this->_identifier[] = $fieldName;
1412            }
1413        }
1414        if (isset($options['default'])) {
1415            $this->hasDefaultValues = true;
1416        }
1417    }
1418
1419    /**
1420     * Finds out whether this table has default values for columns.
1421     *
1422     * @return boolean
1423     */
1424    public function hasDefaultValues()
1425    {
1426        return $this->hasDefaultValues;
1427    }
1428
1429    /**
1430     * Retrieves the default value (if any) for a given column.
1431     *
1432     * @param string $fieldName     column name
1433     * @return mixed                default value as set in definition
1434     */
1435    public function getDefaultValueOf($fieldName)
1436    {
1437        $columnName = $this->getColumnName($fieldName);
1438        if ( ! isset($this->_columns[$columnName])) {
1439            throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$columnName." doesn't exist.");
1440        }
1441        if (isset($this->_columns[$columnName]['default'])) {
1442            return $this->_columns[$columnName]['default'];
1443        } else {
1444            return null;
1445        }
1446    }
1447
1448    /**
1449     * Returns the definition of the identifier key.
1450     * @return string    can be array if a multi-column primary key is used.
1451     */
1452    public function getIdentifier()
1453    {
1454        return $this->_identifier;
1455    }
1456
1457    /**
1458     * Retrieves the type of primary key.
1459     *
1460     * This method finds out if the primary key is multifield.
1461     * @see Doctrine_Identifier constants
1462     * @return integer
1463     */
1464    public function getIdentifierType()
1465    {
1466        return $this->_identifierType;
1467    }
1468
1469    /**
1470     * Finds out whether the table definition contains a given column.
1471     * @param string $columnName
1472     * @return boolean
1473     */
1474    public function hasColumn($columnName)
1475    {
1476        return isset($this->_columns[strtolower($columnName)]);
1477    }
1478
1479    /**
1480     * Finds out whether the table definition has a given field.
1481     *
1482     * This method returns true if @see hasColumn() returns true or if an alias
1483     * named $fieldName exists.
1484     * @param string $fieldName
1485     * @return boolean
1486     */
1487    public function hasField($fieldName)
1488    {
1489        return isset($this->_columnNames[$fieldName]);
1490    }
1491
1492    /**
1493     * Sets the default connection for this table.
1494     *
1495     * This method assign the connection which this table will use
1496     * to create queries.
1497     *
1498     * @param Doctrine_Connection $conn a connection object
1499     *
1500     * @return Doctrine_Table this object; fluent interface
1501     */
1502    public function setConnection(Doctrine_Connection $conn)
1503    {
1504        $this->_conn = $conn;
1505
1506        $this->setParent($this->_conn);
1507
1508        return $this;
1509    }
1510
1511    /**
1512     * Returns the connection associated with this table (if any).
1513     *
1514     * @return Doctrine_Connection|null     the connection object
1515     */
1516    public function getConnection()
1517    {
1518        return $this->_conn;
1519    }
1520
1521    /**
1522     * Creates a new record.
1523     *
1524     * This method create a new instance of the model defined by this table.
1525     * The class of this record is the subclass of Doctrine_Record defined by
1526     * this component. The record is not created in the database until you
1527     * call Doctrine_Record::save().
1528     *
1529     * @param array $array an array where keys are field names and
1530     *                     values representing field values. Can contain
1531     *                     also related components;
1532     *                     @see Doctrine_Record::fromArray()
1533     *
1534     * @return Doctrine_Record the created record object
1535     */
1536    public function create(array $array = array())
1537    {
1538        $record = new $this->_options['name']($this, true);
1539        $record->fromArray($array);
1540
1541        return $record;
1542    }
1543
1544    /**
1545     * Adds a named query in the query registry.
1546     *
1547     * This methods register a query object with a name to use in the future.
1548     *
1549     * @see createNamedQuery()
1550     *
1551     * @param string                $queryKey query key name to use for storage
1552     * @param string|Doctrine_Query $query    DQL string or object
1553     *
1554     * @return void
1555     */
1556    public function addNamedQuery($queryKey, $query)
1557    {
1558        $registry = Doctrine_Manager::getInstance()->getQueryRegistry();
1559        $registry->add($this->getComponentName() . '/' . $queryKey, $query);
1560    }
1561
1562    /**
1563     * Creates a named query from one in the query registry.
1564     *
1565     * This method clones a new query object from a previously registered one.
1566     *
1567     * @see addNamedQuery()
1568     * @param string $queryKey  query key name
1569     * @return Doctrine_Query
1570     */
1571    public function createNamedQuery($queryKey)
1572    {
1573        $queryRegistry = Doctrine_Manager::getInstance()->getQueryRegistry();
1574
1575        if (strpos($queryKey, '/') !== false) {
1576            $e = explode('/', $queryKey);
1577
1578            return $queryRegistry->get($e[1], $e[0]);
1579        }
1580
1581        return $queryRegistry->get($queryKey, $this->getComponentName());
1582    }
1583
1584    /**
1585     * Finds a record by its identifier.
1586     *
1587     * <code>
1588     * $table->find(11);
1589     * $table->find(11, Doctrine_Core::HYDRATE_RECORD);
1590     * $table->find('namedQueryForYearArchive', array(2009), Doctrine_Core::HYDRATE_ARRAY);
1591     * </code>
1592     *
1593     * @param mixed $name         Database Row ID or Query Name defined previously as a NamedQuery
1594     * @param mixed $params       This argument is the hydration mode (Doctrine_Core::HYDRATE_ARRAY or
1595     *                            Doctrine_Core::HYDRATE_RECORD) if first param is a Database Row ID.
1596     *                            Otherwise this argument expect an array of query params.
1597     * @param int $hydrationMode  Optional Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD if
1598     *                            first argument is a NamedQuery
1599     *
1600     * @return Doctrine_Collection|array|Doctrine_Record|false Doctrine_Collection, array, Doctrine_Record or false if no result
1601     */
1602    public function find()
1603    {
1604        $num_args = func_num_args();
1605
1606        // Named Query or IDs
1607        $name = func_get_arg(0);
1608
1609        if (is_null($name)) {
1610            return false;
1611        }
1612
1613        $ns = $this->getComponentName();
1614        $m = $name;
1615
1616        // Check for possible cross-access
1617        if ( ! is_array($name) && strpos($name, '/') !== false) {
1618            list($ns, $m) = explode('/', $name);
1619        }
1620
1621        // Define query to be used
1622        if (
1623            ! is_array($name) &&
1624            Doctrine_Manager::getInstance()->getQueryRegistry()->has($m, $ns)
1625        ) {
1626            // We're dealing with a named query
1627            $q = $this->createNamedQuery($name);
1628
1629            // Parameters construction
1630            $params = ($num_args >= 2) ? func_get_arg(1) : array();
1631
1632            // Hydration mode
1633            $hydrationMode = ($num_args == 3) ? func_get_arg(2) : null;
1634
1635            // Executing query
1636            $res = $q->execute($params, $hydrationMode);
1637        } else {
1638            // We're passing a single ID or an array of IDs
1639            $q = $this->createQuery('dctrn_find')
1640                ->where('dctrn_find.' . implode(' = ? AND dctrn_find.', (array) $this->getIdentifier()) . ' = ?')
1641                ->limit(1);
1642
1643            // Parameters construction
1644            $params = is_array($name) ? array_values($name) : array($name);
1645
1646            // Hydration mode
1647            $hydrationMode = ($num_args == 2) ? func_get_arg(1) : null;
1648
1649            // Executing query
1650            $res = $q->fetchOne($params, $hydrationMode);
1651        }
1652
1653        return $res;
1654    }
1655
1656    /**
1657     * Retrieves all the records stored in this table.
1658     *
1659     * @param int $hydrationMode        Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
1660     * @return Doctrine_Collection|array
1661     */
1662    public function findAll($hydrationMode = null)
1663    {
1664        return $this->createQuery('dctrn_find')->execute(array(), $hydrationMode);
1665    }
1666
1667    /**
1668     * Finds records in this table with a given SQL where clause.
1669     *
1670     * @param string $dql               DQL WHERE clause to use
1671     * @param array $params             query parameters (a la PDO)
1672     * @param int $hydrationMode        Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
1673     * @return Doctrine_Collection|array
1674     *
1675     * @todo This actually takes DQL, not SQL, but it requires column names
1676     *       instead of field names. This should be fixed to use raw SQL instead.
1677     */
1678    public function findBySql($dql, $params = array(), $hydrationMode = null)
1679    {
1680        return $this->createQuery('dctrn_find')
1681            ->where($dql)->execute($params, $hydrationMode);
1682    }
1683
1684    /**
1685     * Finds records in this table with a given DQL where clause.
1686     *
1687     * @param string $dql               DQL WHERE clause
1688     * @param array $params             preparated statement parameters
1689     * @param int $hydrationMode        Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
1690     * @return Doctrine_Collection|array
1691     */
1692    public function findByDql($dql, $params = array(), $hydrationMode = null)
1693    {
1694        $parser = $this->createQuery();
1695        $query = 'FROM ' . $this->getComponentName() . ' dctrn_find WHERE ' . $dql;
1696
1697        return $parser->query($query, $params, $hydrationMode);
1698    }
1699
1700    /**
1701     * Find records basing on a field.
1702     *
1703     * @param string $column            field for the WHERE clause
1704     * @param string $value             prepared statement parameter
1705     * @param int $hydrationMode        Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
1706     * @return Doctrine_Collection|array
1707     */
1708    public function findBy($fieldName, $value, $hydrationMode = null)
1709    {
1710        return $this->createQuery('dctrn_find')
1711            ->where($this->buildFindByWhere($fieldName), (array) $value)
1712            ->execute(array(), $hydrationMode);
1713    }
1714
1715    /**
1716     * Finds the first record that satisfy the clause.
1717     *
1718     * @param string $column            field for the WHERE clause
1719     * @param string $value             prepared statement parameter
1720     * @param int $hydrationMode        Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
1721     * @return Doctrine_Record
1722     */
1723    public function findOneBy($fieldName, $value, $hydrationMode = null)
1724    {
1725        return $this->createQuery('dctrn_find')
1726            ->where($this->buildFindByWhere($fieldName), (array) $value)
1727            ->limit(1)
1728            ->fetchOne(array(), $hydrationMode);
1729    }
1730
1731    /**
1732     * Finds result of a named query.
1733     *
1734     * This method fetches data using the provided $queryKey to choose a named
1735     * query in the query registry.
1736     *
1737     * @param string $queryKey      the query key
1738     * @param array $params         prepared statement params (if any)
1739     * @param int $hydrationMode    Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
1740     * @throws Doctrine_Query_Registry if no query for given queryKey is found
1741     * @return Doctrine_Collection|array
1742     */
1743    public function execute($queryKey, $params = array(), $hydrationMode = Doctrine_Core::HYDRATE_RECORD)
1744    {
1745        return $this->createNamedQuery($queryKey)->execute($params, $hydrationMode);
1746    }
1747
1748    /**
1749     * Fetches one record with a named query.
1750     *
1751     * This method uses the provided $queryKey to clone and execute
1752     * the associated named query in the query registry.
1753     *
1754     * @param string $queryKey      the query key
1755     * @param array $params         prepared statement params (if any)
1756     * @param int $hydrationMode    Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
1757     * @throws Doctrine_Query_Registry if no query for given queryKey is found
1758     * @return Doctrine_Record|array
1759     */
1760    public function executeOne($queryKey, $params = array(), $hydrationMode = Doctrine_Core::HYDRATE_RECORD)
1761    {
1762        return $this->createNamedQuery($queryKey)->fetchOne($params, $hydrationMode);
1763    }
1764
1765    /**
1766     * Clears the first level cache (identityMap).
1767     *
1768     * This method ensures that records are reloaded from the db.
1769     *
1770     * @return void
1771     * @todo what about a more descriptive name? clearIdentityMap?
1772     */
1773    public function clear()
1774    {
1775        $this->_identityMap = array();
1776    }
1777
1778    /**
1779     * Adds a record to the first level cache (identity map).
1780     *
1781     * This method is used internally to cache records, ensuring that only one
1782     * object that represents a sql record exists in all scopes.
1783     *
1784     * @param Doctrine_Record $record       record to be added
1785     * @return boolean                      true if record was not present in the map
1786     * @todo Better name? registerRecord?
1787     */
1788    public function addRecord(Doctrine_Record $record)
1789    {
1790        if (!$this->_useIdentityMap) {
1791            return false;
1792        }
1793
1794        $id = implode(' ', $record->identifier());
1795
1796        if (isset($this->_identityMap[$id])) {
1797            return false;
1798        }
1799
1800        $this->_identityMap[$id] = $record;
1801
1802        return true;
1803    }
1804
1805    /**
1806     * Removes a record from the identity map.
1807     *
1808     * This method deletes from the cache the given record; can be used to
1809     * force reloading of an object from database.
1810     *
1811     * @param Doctrine_Record $record   record to remove from cache
1812     * @return boolean                  true if the record was found and removed,
1813     *                                  false if the record wasn't found.
1814     */
1815    public function removeRecord(Doctrine_Record $record)
1816    {
1817        if (!$this->_useIdentityMap) {
1818            return false;
1819        }
1820
1821        $id = implode(' ', $record->identifier());
1822
1823        if (isset($this->_identityMap[$id])) {
1824            unset($this->_identityMap[$id]);
1825            return true;
1826        }
1827
1828        return false;
1829    }
1830
1831    /**
1832     * Returns a new record.
1833     *
1834     * This method checks if a internal record exists in identityMap, if does
1835     * not exist it creates a new one.
1836     *
1837     * @return Doctrine_Record
1838     */
1839    public function getRecord()
1840    {
1841        if ( ! empty($this->_data)) {
1842            $identifierFieldNames = $this->getIdentifier();
1843
1844            if ( ! is_array($identifierFieldNames)) {
1845                $identifierFieldNames = array($identifierFieldNames);
1846            }
1847
1848            $found = false;
1849            foreach ($identifierFieldNames as $fieldName) {
1850                if ( ! isset($this->_data[$fieldName])) {
1851                    // primary key column not found return new record
1852                    $found = true;
1853                    break;
1854                }
1855                $id[] = $this->_data[$fieldName];
1856            }
1857
1858            if ($found) {
1859                $recordName = $this->getComponentName();
1860                $record = new $recordName($this, true);
1861                $this->_data = array();
1862                return $record;
1863            }
1864
1865            $id = implode(' ', $id);
1866
1867            if ($this->_useIdentityMap && isset($this->_identityMap[$id])) {
1868                $record = $this->_identityMap[$id];
1869                if ($record->getTable()->getAttribute(Doctrine_Core::ATTR_HYDRATE_OVERWRITE)) {
1870                    $record->hydrate($this->_data);
1871                    if ($record->state() == Doctrine_Record::STATE_PROXY) {
1872                        if (!$record->isInProxyState()) {
1873                            $record->state(Doctrine_Record::STATE_CLEAN);
1874                        }
1875                    }
1876                } else {
1877                    $record->hydrate($this->_data, false);
1878                }
1879            } else {
1880                $recordName = $this->getComponentName();
1881                $record = new $recordName($this);
1882
1883                if ($this->_useIdentityMap) {
1884                    $this->_identityMap[$id] = $record;
1885                }
1886            }
1887            $this->_data = array();
1888        } else {
1889            $recordName = $this->getComponentName();
1890            $record = new $recordName($this, true);
1891        }
1892
1893        return $record;
1894    }
1895
1896    /**
1897     * Get the classname to return. Most often this is just the options['name'].
1898     *
1899     * Check the subclasses option and the inheritanceMap for each subclass to see
1900     * if all the maps in a subclass is met. If this is the case return that
1901     * subclass name. If no subclasses match or if there are no subclasses defined
1902     * return the name of the class for this tables record.
1903     *
1904     * @todo this function could use reflection to check the first time it runs
1905     * if the subclassing option is not set.
1906     *
1907     * @return string The name of the class to create
1908     * @deprecated
1909     */
1910    public function getClassnameToReturn()
1911    {
1912        if ( ! isset($this->_options['subclasses'])) {
1913            return $this->_options['name'];
1914        }
1915        foreach ($this->_options['subclasses'] as $subclass) {
1916            $table = $this->_conn->getTable($subclass);
1917            $inheritanceMap = $table->getOption('inheritanceMap');
1918            $nomatch = false;
1919            foreach ($inheritanceMap as $key => $value) {
1920                if ( ! isset($this->_data[$key]) || $this->_data[$key] != $value) {
1921                    $nomatch = true;
1922                    break;
1923                }
1924            }
1925            if ( ! $nomatch) {
1926                return $table->getComponentName();
1927            }
1928        }
1929        return $this->_options['name'];
1930    }
1931
1932    /**
1933     * @param $id                       database row id
1934     * @throws Doctrine_Find_Exception
1935     * @return Doctrine_Record
1936     */
1937    final public function getProxy($id = null)
1938    {
1939        if ($id !== null) {
1940            $identifierColumnNames = $this->getIdentifierColumnNames();
1941            $query = 'SELECT ' . implode(', ', (array) $identifierColumnNames)
1942                . ' FROM ' . $this->getTableName()
1943                . ' WHERE ' . implode(' = ? && ', (array) $identifierColumnNames) . ' = ?';
1944            $query = $this->applyInheritance($query);
1945
1946            $params = array_merge(array($id), array_values($this->_options['inheritanceMap']));
1947
1948            $this->_data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC);
1949
1950            if ($this->_data === false)
1951                return false;
1952        }
1953        return $this->getRecord();
1954    }
1955
1956    /**
1957     * applyInheritance
1958     * @param $where                    query where part to be modified
1959     * @return string                   query where part with column aggregation inheritance added
1960     */
1961    final public function applyInheritance($where)
1962    {
1963        if ( ! empty($this->_options['inheritanceMap'])) {
1964            $a = array();
1965            foreach ($this->_options['inheritanceMap'] as $field => $value) {
1966                $a[] = $this->getColumnName($field) . ' = ?';
1967            }
1968            $i = implode(' AND ', $a);
1969            $where .= ' AND ' . $i;
1970        }
1971
1972        return $where;
1973    }
1974
1975    /**
1976     * Implements Countable interface.
1977     *
1978     * @return integer number of records in the table
1979     */
1980    public function count()
1981    {
1982        return $this->createQuery()->count();
1983    }
1984
1985    /**
1986     * @return Doctrine_Query  a Doctrine_Query object
1987     */
1988    public function getQueryObject()
1989    {
1990        $graph = $this->createQuery();
1991        $graph->load($this->getComponentName());
1992
1993        return $graph;
1994    }
1995
1996    /**
1997     * Retrieves the enum values for a given field.
1998     *
1999     * @param string $fieldName
2000     * @return array
2001     */
2002    public function getEnumValues($fieldName)
2003    {
2004        $columnName = $this->getColumnName($fieldName);
2005        if (isset($this->_columns[$columnName]['values'])) {
2006            return $this->_columns[$columnName]['values'];
2007        } else {
2008            return array();
2009        }
2010    }
2011
2012    /**
2013     * Retrieves an enum value.
2014     *
2015     * This method finds a enum string value. If ATTR_USE_NATIVE_ENUM is set
2016     * on the connection, index and value are the same thing.
2017     *
2018     * @param string $fieldName
2019     * @param integer $index        numeric index of the enum
2020     * @return mixed
2021     */
2022    public function enumValue($fieldName, $index)
2023    {
2024        if ($index instanceof Doctrine_Null) {
2025            return false;
2026        }
2027
2028        if ($this->_conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM)) {
2029            return $index;
2030        }
2031
2032        $columnName = $this->getColumnName($fieldName);
2033
2034        return isset($this->_columns[$columnName]['values'][$index]) ? $this->_columns[$columnName]['values'][$index] : false;
2035    }
2036
2037    /**
2038     * Retrieves an enum index.
2039     * @see enumValue()
2040     *
2041     * @param string $fieldName
2042     * @param mixed $value          value of the enum considered
2043     * @return integer              can be string if native enums are used.
2044     */
2045    public function enumIndex($fieldName, $value)
2046    {
2047        $values = $this->getEnumValues($fieldName);
2048
2049        if ($this->_conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM)) {
2050            return $value;
2051        }
2052        return array_search($value, $values);
2053    }
2054
2055    /**
2056     * Validates a given field using table ATTR_VALIDATE rules.
2057     * @see Doctrine_Core::ATTR_VALIDATE
2058     *
2059     * @param string $fieldName
2060     * @param string $value
2061     * @param Doctrine_Record $record   record to consider; if it does not exists, it is created
2062     * @return Doctrine_Validator_ErrorStack $errorStack
2063     */
2064    public function validateField($fieldName, $value, Doctrine_Record $record = null)
2065    {
2066        if ($record instanceof Doctrine_Record) {
2067            $errorStack = $record->getErrorStack();
2068        } else {
2069            $record  = $this->create();
2070            $errorStack = new Doctrine_Validator_ErrorStack($this->getOption('name'));
2071        }
2072
2073        if ($value === self::$_null) {
2074            $value = null;
2075        } else if ($value instanceof Doctrine_Record && $value->exists()) {
2076            $value = $value->getIncremented();
2077        } else if ($value instanceof Doctrine_Record && ! $value->exists()) {
2078            foreach($this->getRelations() as $relation) {
2079                if ($fieldName == $relation->getLocalFieldName() && (get_class($value) == $relation->getClass() || is_subclass_of($value, $relation->getClass()))) {
2080                    return $errorStack;
2081                }
2082            }
2083        }
2084
2085        $dataType = $this->getTypeOf($fieldName);
2086
2087        // Validate field type, if type validation is enabled
2088        if ($this->getAttribute(Doctrine_Core::ATTR_VALIDATE) & Doctrine_Core::VALIDATE_TYPES) {
2089            if ( ! Doctrine_Validator::isValidType($value, $dataType)) {
2090                $errorStack->add($fieldName, 'type');
2091            }
2092            if ($dataType == 'enum') {
2093                $enumIndex = $this->enumIndex($fieldName, $value);
2094                if ($enumIndex === false && $value !== null) {
2095                    $errorStack->add($fieldName, 'enum');
2096                }
2097            }
2098            if ($dataType == 'set') {
2099                $values = $this->_columns[$fieldName]['values'];
2100                // Convert string to array
2101                if (is_string($value)) {
2102                    $value = explode(',', $value);
2103                    foreach ($value as &$v) {
2104                        $v = trim($v);
2105                    }
2106                    $record->set($fieldName, $value);
2107                }
2108                // Make sure each set value is valid
2109                foreach ($value as $k => $v) {
2110                    if ( ! in_array($v, $values)) {
2111                        $errorStack->add($fieldName, 'set');
2112                    }
2113                }
2114            }
2115        }
2116
2117        // Validate field length, if length validation is enabled
2118        if ($this->getAttribute(Doctrine_Core::ATTR_VALIDATE) & Doctrine_Core::VALIDATE_LENGTHS) {
2119            if ( ! Doctrine_Validator::validateLength($value, $dataType, $this->getFieldLength($fieldName))) {
2120                $errorStack->add($fieldName, 'length');
2121            }
2122        }
2123
2124        // Run all custom validators
2125        foreach ($this->getFieldValidators($fieldName) as $validatorName => $args) {
2126            if ( ! is_string($validatorName)) {
2127                $validatorName = $args;
2128                $args = array();
2129            }
2130
2131            $validator = Doctrine_Validator::getValidator($validatorName);
2132            $validator->invoker = $record;
2133            $validator->field = $fieldName;
2134            $validator->args = $args;
2135            if ( ! $validator->validate($value)) {
2136                $errorStack->add($fieldName, $validator);
2137            }
2138        }
2139
2140        return $errorStack;
2141    }
2142
2143    /**
2144     * Validates all the unique indexes.
2145     *
2146     * This methods validates 'unique' sets of fields for the given Doctrine_Record instance.
2147     * Pushes error to the record error stack if they are generated.
2148     *
2149     * @param Doctrine_Record $record
2150     */
2151    public function validateUniques(Doctrine_Record $record)
2152    {
2153        $errorStack = $record->getErrorStack();
2154        $validator = Doctrine_Validator::getValidator('unique');
2155        $validator->invoker = $record;
2156
2157        foreach ($this->_uniques as $unique)
2158        {
2159            list($fields, $options) = $unique;
2160            $validator->args = $options;
2161            $validator->field = $fields;
2162            $values = array();
2163            foreach ($fields as $field) {
2164                $values[] = $record->$field;
2165            }
2166            if ( ! $validator->validate($values)) {
2167                foreach ($fields as $field) {
2168                    $errorStack->add($field, $validator);
2169                }
2170            }
2171        }
2172    }
2173
2174    /**
2175     * @return integer      the number of columns in this table
2176     */
2177    public function getColumnCount()
2178    {
2179        return $this->columnCount;
2180    }
2181
2182    /**
2183     * Retrieves all columns of the table.
2184     *
2185     * @see $_columns;
2186     * @return array    keys are column names and values are definition
2187     */
2188    public function getColumns()
2189    {
2190        return $this->_columns;
2191    }
2192
2193    /**
2194     * Removes a field name from the table schema information.
2195     *
2196     * @param string $fieldName
2197     * @return boolean      true if the field is found and removed.
2198     *                      False otherwise.
2199     */
2200    public function removeColumn($fieldName)
2201    {
2202        if ( ! $this->hasField($fieldName)) {
2203          return false;
2204        }
2205
2206        $columnName = $this->getColumnName($fieldName);
2207        unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]);
2208        $this->columnCount = count($this->_columns);
2209        return true;
2210    }
2211
2212    /**
2213     * Returns an array containing all the column names.
2214     *
2215     * @return array numeric array
2216     */
2217    public function getColumnNames(array $fieldNames = null)
2218    {
2219        if ($fieldNames === null) {
2220            return array_keys($this->_columns);
2221        } else {
2222           $columnNames = array();
2223           foreach ($fieldNames as $fieldName) {
2224               $columnNames[] = $this->getColumnName($fieldName);
2225           }
2226           return $columnNames;
2227        }
2228    }
2229
2230    /**
2231     * Returns an array with all the identifier column names.
2232     *
2233     * @return array numeric array
2234     */
2235    public function getIdentifierColumnNames()
2236    {
2237        return $this->getColumnNames((array) $this->getIdentifier());
2238    }
2239
2240    /**
2241     * Gets the array of unique fields sets.
2242     * @see $_uniques;
2243     *
2244     * @return array numeric array
2245     */
2246    public function getUniques()
2247    {
2248        return $this->_uniques;
2249    }
2250
2251    /**
2252     * Returns an array containing all the field names.
2253     *
2254     * @return array numeric array
2255     */
2256    public function getFieldNames()
2257    {
2258        return array_values($this->_fieldNames);
2259    }
2260
2261    /**
2262     * Retrieves the definition of a field.
2263     *
2264     * This method retrieves the definition of the column, basing of $fieldName
2265     * which can be a column name or a field name (alias).
2266     *
2267     * @param string $fieldName
2268     * @return array        false on failure
2269     */
2270    public function getDefinitionOf($fieldName)
2271    {
2272        $columnName = $this->getColumnName($fieldName);
2273        return $this->getColumnDefinition($columnName);
2274    }
2275
2276    /**
2277     * Retrieves the type of a field.
2278     *
2279     * @param string $fieldName
2280     * @return string        false on failure
2281     */
2282    public function getTypeOf($fieldName)
2283    {
2284        return $this->getTypeOfColumn($this->getColumnName($fieldName));
2285    }
2286
2287    /**
2288     * Retrieves the type of a column.
2289     *
2290     * @param string $columnName
2291     * @return string        false if column is not found
2292     */
2293    public function getTypeOfColumn($columnName)
2294    {
2295        return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false;
2296    }
2297
2298    /**
2299     * Doctrine uses this function internally.
2300     * Users are strongly discouraged to use this function.
2301     *
2302     * @access private
2303     * @param array $data               internal data
2304     * @return void
2305     */
2306    public function setData(array $data)
2307    {
2308        $this->_data = $data;
2309    }
2310
2311    /**
2312     * Returns internal data.
2313     *
2314     * This method is used by Doctrine_Record instances
2315     * when retrieving data from database.
2316     *
2317     * @return array
2318     */
2319    public function getData()
2320    {
2321        return $this->_data;
2322    }
2323
2324    /**
2325     * Performs special data preparation.
2326     *
2327     * This method returns a representation of a field data, depending on
2328     * the type of the given column.
2329     *
2330     * 1. It unserializes array and object typed columns
2331     * 2. Uncompresses gzip typed columns
2332     * 3. Initializes special null object pointer for null values (for fast column existence checking purposes)
2333     *
2334     * example:
2335     * <code type='php'>
2336     * $field = 'name';
2337     * $value = null;
2338     * $table->prepareValue($field, $value); // Doctrine_Null
2339     * </code>
2340     *
2341     * @throws Doctrine_Table_Exception     if unserialization of array/object typed column fails or
2342     * @throws Doctrine_Table_Exception     if uncompression of gzip typed column fails         *
2343     * @param string $field     the name of the field
2344     * @param string $value     field value
2345     * @param string $typeHint  Type hint used to pass in the type of the value to prepare
2346     *                          if it is already known. This enables the method to skip
2347     *                          the type determination. Used i.e. during hydration.
2348     * @return mixed            prepared value
2349     */
2350    public function prepareValue($fieldName, $value, $typeHint = null)
2351    {
2352        if ($value === self::$_null) {
2353            return self::$_null;
2354        } else if ($value === null) {
2355            return null;
2356        } else {
2357            $type = is_null($typeHint) ? $this->getTypeOf($fieldName) : $typeHint;
2358
2359            switch ($type) {
2360                case 'enum':
2361                case 'integer':
2362                case 'string';
2363                    // don't do any casting here PHP INT_MAX is smaller than what the databases support
2364                break;
2365                case 'set':
2366                    return explode(',', $value);
2367                break;
2368                case 'boolean':
2369                    return (boolean) $value;
2370                break;
2371                case 'array':
2372                case 'object':
2373                    if (is_string($value)) {
2374                        $value = empty($value) ? null:unserialize($value);
2375
2376                        if ($value === false) {
2377                            throw new Doctrine_Table_Exception('Unserialization of ' . $fieldName . ' failed.');
2378                        }
2379                        return $value;
2380                    }
2381                break;
2382                case 'gzip':
2383                    $value = gzuncompress($value);
2384
2385                    if ($value === false) {
2386                        throw new Doctrine_Table_Exception('Uncompressing of ' . $fieldName . ' failed.');
2387                    }
2388                    return $value;
2389                break;
2390            }
2391        }
2392        return $value;
2393    }
2394
2395    /**
2396     * Gets associated tree.
2397     * This method returns the associated Tree object (if any exists).
2398     * Normally implemented by NestedSet behavior.
2399     *
2400     * @return Doctrine_Tree false if not a tree
2401     */
2402    public function getTree()
2403    {
2404        if (isset($this->_options['treeImpl'])) {
2405            if ( ! $this->_tree) {
2406                $options = isset($this->_options['treeOptions']) ? $this->_options['treeOptions'] : array();
2407                $this->_tree = Doctrine_Tree::factory($this,
2408                    $this->_options['treeImpl'],
2409                    $options
2410                );
2411            }
2412            return $this->_tree;
2413        }
2414        return false;
2415    }
2416
2417    /**
2418     * Gets the subclass of Doctrine_Record that belongs to this table.
2419     *
2420     * @return string
2421     */
2422    public function getComponentName()
2423    {
2424        return $this->_options['name'];
2425    }
2426
2427    /**
2428     * Gets the table name in the db.
2429     *
2430     * @return string
2431     */
2432    public function getTableName()
2433    {
2434        return $this->_options['tableName'];
2435    }
2436
2437    /**
2438     * sets the table name in the schema definition.
2439     *
2440     * @param string $tableName
2441     * @return void
2442     */
2443    public function setTableName($tableName)
2444    {
2445        $this->setOption('tableName', $this->_conn->formatter->getTableName($tableName));
2446    }
2447
2448    /**
2449     * Determines if table acts as tree.
2450     *
2451     * @return boolean  if tree return true, otherwise returns false
2452     */
2453    public function isTree()
2454    {
2455        return ( ! is_null($this->_options['treeImpl'])) ? true : false;
2456    }
2457
2458    /**
2459     * Retrieves all templates (behaviors) attached to this table.
2460     *
2461     * @return array     an array containing all templates
2462     */
2463    public function getTemplates()
2464    {
2465        return $this->_templates;
2466    }
2467
2468    /**
2469     * Retrieves a particular template by class name.
2470     *
2471     * This method retrieves a behavior/template object attached to the table.
2472     * For Doctrine_Template_* classes, the base name can be used.
2473     *
2474     * @param string $template              name of the behavior
2475     * @throws Doctrine_Table_Exception     if the given template is
2476     *                                      not set on this table
2477     * @return Doctrine_Template
2478     */
2479    public function getTemplate($template)
2480    {
2481        if (isset($this->_templates['Doctrine_Template_' . $template])) {
2482            return $this->_templates['Doctrine_Template_' . $template];
2483        } else if (isset($this->_templates[$template])) {
2484            return $this->_templates[$template];
2485        }
2486
2487        throw new Doctrine_Table_Exception('Template ' . $template . ' not loaded');
2488    }
2489
2490    /**
2491     * Checks if the table has a given template.
2492     *
2493     * @param string $template  name of template; @see getTemplate()
2494     * @return boolean
2495     */
2496    public function hasTemplate($template)
2497    {
2498        return isset($this->_templates[$template]) || isset($this->_templates['Doctrine_Template_' . $template]);
2499    }
2500
2501    /**
2502     * Adds a template to this table.
2503     *
2504     * @param string $template          template name
2505     * @param Doctrine_Template $impl   behavior to attach
2506     * @return Doctrine_Table
2507     */
2508    public function addTemplate($template, Doctrine_Template $impl)
2509    {
2510        $this->_templates[$template] = $impl;
2511
2512        return $this;
2513    }
2514
2515    /**
2516     * Gets all the generators for this table.
2517     *
2518     * @return array $generators
2519     */
2520    public function getGenerators()
2521    {
2522        return $this->_generators;
2523    }
2524
2525    /**
2526     * Gets generator instance for a passed name.
2527     *
2528     * @param string $generator
2529     * @return Doctrine_Record_Generator $generator
2530     */
2531    public function getGenerator($generator)
2532    {
2533        if ( ! isset($this->_generators[$generator])) {
2534            throw new Doctrine_Table_Exception('Generator ' . $generator . ' not loaded');
2535        }
2536
2537        return $this->_generators[$generator];
2538    }
2539
2540    /**
2541     * Checks if a generator name exists.
2542     *
2543     * @param string $generator
2544     * @return void
2545     */
2546    public function hasGenerator($generator)
2547    {
2548        return isset($this->_generators[$generator]);
2549    }
2550
2551    /**
2552     * Adds a generate to the table instance.
2553     *
2554     * @param Doctrine_Record_Generator $generator
2555     * @param string $name
2556     * @return Doctrine_Table
2557     */
2558    public function addGenerator(Doctrine_Record_Generator $generator, $name = null)
2559    {
2560        if ($name === null) {
2561            $this->_generators[] = $generator;
2562        } else {
2563            $this->_generators[$name] = $generator;
2564        }
2565        return $this;
2566    }
2567
2568    /**
2569     * Set the generator responsible for creating this table
2570     *
2571     * @param Doctrine_Record_Generator $generator
2572     * @return void
2573     */
2574    public function setGenerator(Doctrine_Record_Generator $generator)
2575    {
2576        $this->_generator = $generator;
2577    }
2578
2579    /**
2580     * Check whether this table was created by a record generator or not
2581     *
2582     * @return boolean
2583     */
2584    public function isGenerator()
2585    {
2586        return isset($this->_generator) ? true : false;
2587    }
2588
2589    /**
2590     * Get the parent generator responsible for this table instance
2591     *
2592     * @return Doctrine_Record_Generator
2593     */
2594    public function getParentGenerator()
2595    {
2596        return $this->_generator;
2597    }
2598
2599    /**
2600     * Binds query parts to this component.
2601     * @see bindQueryPart()
2602     *
2603     * @param array $queryParts         an array of pre-bound query parts
2604     * @return Doctrine_Table           this object
2605     */
2606    public function bindQueryParts(array $queryParts)
2607    {
2608        $this->_options['queryParts'] = $queryParts;
2609
2610        return $this;
2611    }
2612
2613    /**
2614     * Adds default query parts to the selects executed on this table.
2615     *
2616     * This method binds given value to given query part.
2617     * Every query created by this table will have this part set by default.
2618     *
2619     * @param string $queryPart
2620     * @param mixed $value
2621     * @return Doctrine_Record          this object
2622     */
2623    public function bindQueryPart($queryPart, $value)
2624    {
2625        $this->_options['queryParts'][$queryPart] = $value;
2626
2627        return $this;
2628    }
2629
2630    /**
2631     * Gets the names of all validators being applied on a field.
2632     *
2633     * @param string $fieldName
2634     * @return array                names of validators
2635     */
2636    public function getFieldValidators($fieldName)
2637    {
2638        $validators = array();
2639        $columnName = $this->getColumnName($fieldName);
2640        // this loop is a dirty workaround to get the validators filtered out of
2641        // the options, since everything is squeezed together currently
2642        foreach ($this->_columns[$columnName] as $name => $args) {
2643             if (empty($name)
2644                    || $name == 'primary'
2645                    || $name == 'protected'
2646                    || $name == 'autoincrement'
2647                    || $name == 'default'
2648                    || $name == 'values'
2649                    || $name == 'sequence'
2650                    || $name == 'zerofill'
2651                    || $name == 'owner'
2652                    || $name == 'scale'
2653                    || $name == 'type'
2654                    || $name == 'length'
2655                    || $name == 'fixed'
2656                    || $name == 'comment'
2657                    || $name == 'alias'
2658                    || $name == 'charset'
2659                    || $name == 'collation'
2660                    || $name == 'extra') {
2661                continue;
2662            }
2663            if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement'])
2664                    && $this->_columns[$columnName]['autoincrement'] === true) {
2665                continue;
2666            }
2667            // skip it if it's explicitly set to FALSE (i.e. notnull => false)
2668            if ($args === false) {
2669                continue;
2670            }
2671            $validators[$name] = $args;
2672        }
2673
2674        return $validators;
2675    }
2676
2677    /**
2678     * Gets the maximum length of a field.
2679     * For integer fields, length is bytes occupied.
2680     * For decimal fields, it is the total number of cyphers
2681     *
2682     * @param string $fieldName
2683     * @return integer
2684     */
2685    public function getFieldLength($fieldName)
2686    {
2687        return $this->_columns[$this->getColumnName($fieldName)]['length'];
2688    }
2689
2690    /**
2691     * Retrieves a bound query part.
2692     * @see bindQueryPart()
2693     *
2694     * @param string $queryPart     field interested
2695     * @return string               value of the bind
2696     */
2697    public function getBoundQueryPart($queryPart)
2698    {
2699        if ( ! isset($this->_options['queryParts'][$queryPart])) {
2700            return null;
2701        }
2702
2703        return $this->_options['queryParts'][$queryPart];
2704    }
2705
2706    /**
2707     * unshiftFilter
2708     *
2709     * @param  Doctrine_Record_Filter $filter
2710     * @return Doctrine_Table                           this object (provides a fluent interface)
2711     */
2712    public function unshiftFilter(Doctrine_Record_Filter $filter)
2713    {
2714        $filter->setTable($this);
2715
2716        $filter->init();
2717
2718        array_unshift($this->_filters, $filter);
2719
2720        return $this;
2721    }
2722
2723    /**
2724     * getFilters
2725     *
2726     * @return array $filters
2727     */
2728    public function getFilters()
2729    {
2730        return $this->_filters;
2731    }
2732
2733    /**
2734     * Generates a string representation of this object.
2735     *
2736     * This method is useful for debugging purposes, or it can be overriden in
2737     * Doctrine_Record to provide a value when Record is casted to (string).
2738     *
2739     * @return string
2740     */
2741    public function __toString()
2742    {
2743        return Doctrine_Lib::getTableAsString($this);
2744    }
2745
2746    /**
2747     * Helper method for buildFindByWhere to decide if a string is greater than another
2748     *
2749     * @param string $a
2750     * @param string $b
2751     */
2752    private function isGreaterThan($a, $b)
2753    {
2754        if (strlen($a) == strlen($b)) return 0;
2755        return (strlen($a) > strlen($b)) ? 1 : -1;
2756    }
2757
2758    public function buildFindByWhere($fieldName)
2759    {
2760        // Get all variations of possible field names
2761        $fields = array_merge($this->getFieldNames(), $this->getColumnNames());
2762        $classifyFields = array();
2763        foreach ($fields as $k => $v) {
2764            $classifyFields[$k] = Doctrine_Inflector::classify($v);
2765        }
2766        $fields = array_merge($fields, $classifyFields);
2767        $ucfirstFields = array();
2768        foreach ($fields as $k => $v) {
2769            $ucfirstFields[$k] = ucfirst($v);
2770        }
2771        $fields = array_merge($fields, $ucfirstFields);
2772
2773        // Sort field names by length - smallest first
2774        // and then reverse so that largest is first
2775        usort($fields, array($this, 'isGreaterThan'));
2776        $fields = array_reverse(array_unique($fields));
2777
2778        // Identify fields and operators
2779        preg_match_all('/(' . implode('|', $fields) . ')(Or|And)?/', $fieldName, $matches);
2780        $fieldsFound = $matches[1];
2781        $operatorFound = $matches[2];
2782        foreach ($operatorFound as &$v) {
2783            $v = strtoupper($v);
2784        }
2785
2786        // Check if $fieldName has unidentified parts left
2787        if (strlen(implode('', $fieldsFound) . implode('', $operatorFound)) !== strlen($fieldName)) {
2788            $expression = preg_replace('/(' . implode('|', $fields) . ')(Or|And)?/', '($1)$2', $fieldName);
2789            throw new Doctrine_Table_Exception('Invalid expression found: ' . $expression);
2790        }
2791
2792        // Build result
2793        $where = $lastOperator = '';
2794        $bracketOpen = false;
2795        foreach ($fieldsFound as $index => $field) {
2796            $field = $this->_resolveFindByFieldName($field);
2797            if (!$field) {
2798                throw new Doctrine_Table_Exception('Invalid field name to find by: ' . $field);
2799            }
2800
2801            if ($operatorFound[$index] == 'OR' && !$bracketOpen) {
2802                $where .= '(';
2803                $bracketOpen = true;
2804            }
2805
2806            $where .= 'dctrn_find.' . $field . ' = ?';
2807
2808            if ($operatorFound[$index] != 'OR' && $lastOperator == 'OR') {
2809                $where .= ')';
2810                $bracketOpen = false;
2811            }
2812
2813            $where .= ' ' . strtoupper($operatorFound[$index]) . ' ';
2814
2815            $lastOperator = $operatorFound[$index];
2816        }
2817
2818        return trim($where);
2819    }
2820
2821    /**
2822     * Resolves the passed find by field name inflecting the parameter.
2823     *
2824     * This method resolves the appropriate field name
2825     * regardless of whether the user passes a column name, field name, or a Doctrine_Inflector::classified()
2826     * version of their column name. It will be inflected with Doctrine_Inflector::tableize()
2827     * to get the column or field name.
2828     *
2829     * @param string $name
2830     * @return string $fieldName
2831     */
2832    protected function _resolveFindByFieldName($name)
2833    {
2834        $fieldName = Doctrine_Inflector::tableize($name);
2835        if ($this->hasColumn($name) || $this->hasField($name)) {
2836            return $this->getFieldName($this->getColumnName($name));
2837        } else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) {
2838            return $this->getFieldName($this->getColumnName($fieldName));
2839        } else {
2840            return false;
2841        }
2842    }
2843
2844    /**
2845     * deletes table row(s) matching the specified identifier
2846     *
2847     * @throws Doctrine_Connection_Exception    if something went wrong at the database level
2848     * @param mixed $identifier         An associateve array containing identifier column-value pairs.
2849     * @return integer                  the number of affected rows. Boolean false if empty value array was given,
2850     */
2851    public function delete($identifier)
2852    {
2853        return $this->getConnection()->delete($this, (array) $identifier);
2854    }
2855
2856    /**
2857     * Inserts a table row with specified data.
2858     *
2859     * @param array $fields             An associative array containing column-value pairs.
2860     *                                  Values can be strings or Doctrine_Expression instances.
2861     * @return integer                  the number of affected rows. Boolean false if empty value array was given,
2862     */
2863    public function insert(array $fields)
2864    {
2865        return $this->getConnection()->insert($this, $fields);
2866    }
2867
2868    /**
2869     * Updates table row(s) with specified data.
2870     *
2871     * @throws Doctrine_Connection_Exception    if something went wrong at the database level
2872     * @param array $fields             An associative array containing column-value pairs.
2873     *                                  Values can be strings or Doctrine_Expression instances.
2874     * @param mixed $identifier         An associateve array containing identifier column-value pairs.
2875     * @return integer                  the number of affected rows. Boolean false if empty value array was given,
2876     */
2877    public function update(array $fields, $identifier)
2878    {
2879        return $this->getConnection()->update($this, $fields, (array) $identifier);
2880    }
2881
2882    /**
2883     * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
2884     * query, except that if there is already a row in the table with the same
2885     * key field values, the REPLACE query just updates its values instead of
2886     * inserting a new row.
2887     *
2888     * The REPLACE type of query does not make part of the SQL standards. Since
2889     * practically only MySQL and SQLIte implement it natively, this type of
2890     * query isemulated through this method for other DBMS using standard types
2891     * of queries inside a transaction to assure the atomicity of the operation.
2892     *
2893     *
2894     * @param array $fields     an associative array that describes the fields and the
2895     *                          values that will be inserted or updated in the specified table. The
2896     *                          indexes of the array are the names of all the fields of the table.
2897     *
2898     *                          The values of the array are values to be assigned to the specified field.
2899     *
2900     * @param mixed $keys       an array containing all key fields (primary key fields
2901     *                          or unique index fields) for this table
2902     *
2903     *                          the uniqueness of a row will be determined according to
2904     *                          the provided key fields
2905     *
2906     *                          this method will fail if no key fields are specified
2907     *
2908     * @throws Doctrine_Connection_Exception        if this driver doesn't support replace
2909     * @throws Doctrine_Connection_Exception        if some of the key values was null
2910     * @throws Doctrine_Connection_Exception        if there were no key fields
2911     * @throws PDOException                         if something fails at PDO level
2912     * @ return integer                              number of rows affected
2913     */
2914    public function replace(array $fields, $keys)
2915    {
2916        return $this->getConnection()->replace($this, $fields, (array) $keys);
2917    }
2918
2919    /**
2920     * Adds support for magic finders.
2921     *
2922     * This method add support for calling methods not defined in code, such as:
2923     * findByColumnName, findByRelationAlias
2924     * findById, findByContactId, etc.
2925     *
2926     * @return the result of the finder
2927     */
2928    public function __call($method, $arguments)
2929    {
2930        $lcMethod = strtolower($method);
2931
2932        if (substr($lcMethod, 0, 6) == 'findby') {
2933            $by = substr($method, 6, strlen($method));
2934            $method = 'findBy';
2935        } else if (substr($lcMethod, 0, 9) == 'findoneby') {
2936            $by = substr($method, 9, strlen($method));
2937            $method = 'findOneBy';
2938        }
2939
2940        if (isset($by)) {
2941            if ( ! isset($arguments[0])) {
2942                throw new Doctrine_Table_Exception('You must specify the value to ' . $method);
2943            }
2944
2945            $fieldName = $this->_resolveFindByFieldName($by);
2946            $count = count(explode('Or', $by)) + (count(explode('And', $by)) - 1);
2947            if (count($arguments) > $count)
2948            {
2949                $hydrationMode = end($arguments);
2950                unset($arguments[count($arguments) - 1]);
2951            } else {
2952                $hydrationMode = null;
2953            }
2954            if ($this->hasField($fieldName)) {
2955                return $this->$method($fieldName, $arguments[0], $hydrationMode);
2956            } else if ($this->hasRelation($by)) {
2957                $relation = $this->getRelation($by);
2958
2959                if ($relation['type'] === Doctrine_Relation::MANY) {
2960                    throw new Doctrine_Table_Exception('Cannot findBy many relationship.');
2961                }
2962
2963                return $this->$method($relation['local'], $arguments[0], $hydrationMode);
2964            } else {
2965                return $this->$method($by, $arguments, $hydrationMode);
2966            }
2967        }
2968
2969        // Forward the method on to the record instance and see if it has anything or one of its behaviors
2970        try {
2971            return call_user_func_array(array($this->getRecordInstance(), $method . 'TableProxy'), $arguments);
2972        } catch (Doctrine_Record_UnknownPropertyException $e) {}
2973
2974        throw new Doctrine_Table_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));
2975    }
2976
2977    public function serialize()
2978    {
2979        $options = $this->_options;
2980        unset($options['declaringClass']);
2981
2982        return serialize(array(
2983            $this->_identifier,
2984            $this->_identifierType,
2985            $this->_columns,
2986            $this->_uniques,
2987            $this->_fieldNames,
2988            $this->_columnNames,
2989            $this->columnCount,
2990            $this->hasDefaultValues,
2991            $options,
2992            $this->_invokedMethods,
2993            $this->_useIdentityMap,
2994        ));
2995    }
2996
2997    public function unserialize($data)
2998    {
2999        $all = unserialize($data);
3000
3001        $this->_identifier = $all[0];
3002        $this->_identifierType = $all[1];
3003        $this->_columns = $all[2];
3004        $this->_uniques = $all[3];
3005        $this->_fieldNames = $all[4];
3006        $this->_columnNames = $all[5];
3007        $this->columnCount = $all[6];
3008        $this->hasDefaultValues = $all[7];
3009        $this->_options = $all[8];
3010        $this->_invokedMethods = $all[9];
3011        $this->_useIdentityMap = $all[10];
3012    }
3013
3014    public function initializeFromCache(Doctrine_Connection $conn)
3015    {
3016        $this->_conn = $conn;
3017        $this->setParent($this->_conn);
3018
3019        $this->_parser = new Doctrine_Relation_Parser($this);
3020
3021        $name = $this->_options['name'];
3022        if ( ! class_exists($name) || empty($name)) {
3023            throw new Doctrine_Exception("Couldn't find class " . $name);
3024        }
3025        $record = new $name($this);
3026
3027        $class = $name;
3028
3029        // get parent classes
3030        do {
3031            if ($class === 'Doctrine_Record') {
3032                break;
3033            }
3034        } while ($class = get_parent_class($class));
3035
3036        if ($class === false) {
3037            throw new Doctrine_Table_Exception('Class "' . $name . '" must be a child class of Doctrine_Record');
3038        }
3039
3040        if (method_exists($record, 'setTableDefinition')) {
3041            // get the declaring class of setTableDefinition method
3042            $method = new ReflectionMethod($this->_options['name'], 'setTableDefinition');
3043            $class = $method->getDeclaringClass();
3044
3045        } else {
3046            $class = new ReflectionClass($class);
3047        }
3048
3049        $this->record = $record;
3050
3051        $this->_options['declaringClass'] = $class;
3052
3053        $this->record->setUp();
3054
3055        // if tree, set up tree
3056        if ($this->isTree()) {
3057            $this->getTree()->setUp();
3058        }
3059
3060        $this->_filters[]  = new Doctrine_Record_Filter_Standard();
3061        if ($this->getAttribute(Doctrine_Core::ATTR_USE_TABLE_REPOSITORY)) {
3062            $this->_repository = new Doctrine_Table_Repository($this);
3063        } else {
3064            $this->_repository = new Doctrine_Table_Repository_None($this);
3065        }
3066
3067        $this->construct();
3068    }
3069}
3070