Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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