Table.php 33.8 KB
Newer Older
doctrine's avatar
doctrine committed
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.phpdoctrine.com>.
 */
21

doctrine's avatar
doctrine committed
22 23 24
/**
 * Doctrine_Table   represents a database table
 *                  each Doctrine_Table holds the information of foreignKeys and associations
25
 *
doctrine's avatar
doctrine committed
26 27 28 29 30 31 32
 *
 * @author      Konsta Vesterinen
 * @package     Doctrine ORM
 * @url         www.phpdoctrine.com
 * @license     LGPL
 * @version     1.0 alpha
 */
33
class Doctrine_Table extends Doctrine_Configurable implements Countable {
doctrine's avatar
doctrine committed
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
    /**
     * @var boolean $isNewEntry                         whether ot not this table created a new record or not, used only internally
     */
    private $isNewEntry       = false;
    /**
     * @var array $data                                 temporary data which is then loaded into Doctrine_Record::$data
     */
    private $data             = array();
    /**
     * @var array $relations                            an array containing all the Doctrine_Relation objects for this table
     */
    private $relations        = array();
    /**
     * @var array $primaryKeys                          an array containing all primary key column names
     */
    private $primaryKeys      = array();
    /**
     * @var mixed $identifier
     */
    private $identifier;
    /**
55 56
     * @see Doctrine_Identifier constants
     * @var integer $identifierType                     the type of identifier this table uses
doctrine's avatar
doctrine committed
57 58 59 60 61 62 63
     */
    private $identifierType;
    /**
     * @var string $query                               cached simple query
     */
    private $query;
    /**
zYne's avatar
zYne committed
64
     * @var Doctrine_Connection $connection             Doctrine_Connection object that created this table
doctrine's avatar
doctrine committed
65
     */
zYne's avatar
zYne committed
66
    private $connection;
doctrine's avatar
doctrine committed
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
    /**
     * @var string $name                                name of the component, for example component name of the GroupTable is 'Group'
     */
    private $name;
    /**
     * @var string $tableName                           database table name, in most cases this is the same as component name but in some cases
     *                                                  where one-table-multi-class inheritance is used this will be the name of the inherited table
     */
    private $tableName;
    /**
     * @var string $sequenceName                        Some databases need sequences instead of auto incrementation primary keys, you can set specific
     *                                                  sequence for your table by calling setSequenceName()
     */
    private $sequenceName;
    /**
     * @var array $identityMap                          first level cache
     */
    private $identityMap        = array();
85 86 87
    /**
     * @var Doctrine_Repository $repository             record repository
     */
doctrine's avatar
doctrine committed
88
    private $repository;
89

doctrine's avatar
doctrine committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    /**
     * @var Doctrine_Cache $cache                       second level cache
     */
    private $cache;
    /**
     * @var array $columns                              an array of column definitions
     */
    private $columns;
    /**
     * @var array $bound                                bound relations
     */
    private $bound              = array();
    /**
     * @var array $boundAliases                         bound relation aliases
     */
    private $boundAliases       = array();
    /**
107 108
     * @var integer $columnCount                        cached column count, Doctrine_Record uses this column count in when
     *                                                  determining its state
doctrine's avatar
doctrine committed
109 110 111 112 113 114 115 116 117 118 119 120 121
     */
    private $columnCount;


    /**
     * @var array $inheritanceMap                       inheritanceMap is used for inheritance mapping, keys representing columns and values
     *                                                  the column values that should correspond to child classes
     */
    private $inheritanceMap     = array();
    /**
     * @var array $parents                              the parent classes of this component
     */
    private $parents            = array();
doctrine's avatar
doctrine committed
122 123 124 125
    /**
     * @var array $enum                                 enum value arrays
     */
    private $enum               = array();
126 127 128 129
    /**
     * @var boolean $hasDefaultValues                   whether or not this table has default values
     */
    private $hasDefaultValues;
doctrine's avatar
doctrine committed
130 131


doctrine's avatar
doctrine committed
132

zYne's avatar
zYne committed
133

doctrine's avatar
doctrine committed
134 135
    /**
     * the constructor
zYne's avatar
zYne committed
136 137
     * @throws Doctrine_Connection_Exception    if there are no opened connections
     * @throws Doctrine_Table_Exception         if there is already an instance of this table
doctrine's avatar
doctrine committed
138 139 140
     * @return void
     */
    public function __construct($name) {
zYne's avatar
zYne committed
141
        $this->connection = Doctrine_Manager::getInstance()->getCurrentConnection();
doctrine's avatar
doctrine committed
142

zYne's avatar
zYne committed
143
        $this->setParent($this->connection);
doctrine's avatar
doctrine committed
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158

        $this->name = $name;

        if( ! class_exists($name) || empty($name))
            throw new Doctrine_Exception("Couldn't find class $name");

        $record = new $name($this);

        $names = array();

        $class = $name;

        // get parent classes

        do {
zYne's avatar
zYne committed
159 160
            if($class == "Doctrine_Record") 
                break;
doctrine's avatar
doctrine committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175

           	$name  = $class;
            $names[] = $name;
        } while($class = get_parent_class($class));

        // reverse names
        $names = array_reverse($names);

        // create database table
        if(method_exists($record,"setTableDefinition")) {
            $record->setTableDefinition();

            $this->columnCount = count($this->columns);

            if(isset($this->columns)) {
zYne's avatar
zYne committed
176 177
                                      	
                // get the declaring class of setTableDefinition method
doctrine's avatar
doctrine committed
178 179 180 181
                $method    = new ReflectionMethod($this->name,"setTableDefinition");
                $class     = $method->getDeclaringClass();

                if( ! isset($this->tableName))
182
                    $this->tableName = Doctrine::tableize($class->getName());
doctrine's avatar
doctrine committed
183 184 185

                switch(count($this->primaryKeys)):
                    case 0:
186
                        $this->columns = array_merge(array("id" => array("integer",11, array("autoincrement" => true, "primary" => true))), $this->columns);
doctrine's avatar
doctrine committed
187 188 189
                        $this->primaryKeys[] = "id";
                        $this->identifier = "id";
                        $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;
190
                        $this->columnCount++;
doctrine's avatar
doctrine committed
191 192 193 194 195
                    break;
                    default:
                        if(count($this->primaryKeys) > 1) {
                            $this->identifier = $this->primaryKeys;
                            $this->identifierType = Doctrine_Identifier::COMPOSITE;
196

doctrine's avatar
doctrine committed
197 198
                        } else {
                            foreach($this->primaryKeys as $pk) {
199
                                $e = $this->columns[$pk][2];
200

201
                                $found = false;
doctrine's avatar
doctrine committed
202

203
                                foreach($e as $option => $value) {
doctrine's avatar
doctrine committed
204 205
                                    if($found)
                                        break;
206

doctrine's avatar
doctrine committed
207
                                    $e2 = explode(":",$option);
208

doctrine's avatar
doctrine committed
209 210 211 212 213 214 215 216 217 218 219 220 221
                                    switch(strtolower($e2[0])):
                                        case "autoincrement":
                                            $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;
                                            $found = true;
                                        break;
                                        case "seq":
                                            $this->identifierType = Doctrine_Identifier::SEQUENCE;
                                            $found = true;
                                        break;
                                    endswitch;
                                }
                                if( ! isset($this->identifierType))
                                    $this->identifierType = Doctrine_Identifier::NORMAL;
222

doctrine's avatar
doctrine committed
223 224 225 226 227
                                $this->identifier = $pk;
                            }
                        }
                endswitch;

zYne's avatar
zYne committed
228 229 230 231 232
                 if($this->getAttribute(Doctrine::ATTR_CREATE_TABLES)) {
                    if(Doctrine_DataDict::isValidClassname($class->getName())) {
                        $dict      = new Doctrine_DataDict($this->getConnection()->getDBH());
                        $dict->createTable($this->tableName, $this->columns);
                    }
doctrine's avatar
doctrine committed
233 234 235 236 237 238
                }

            }
        } else {
            throw new Doctrine_Exception("Class '$name' has no table definition.");
        }
239

zYne's avatar
zYne committed
240

doctrine's avatar
doctrine committed
241 242 243 244 245 246 247 248 249
        $record->setUp();

        // save parents
        array_pop($names);
        $this->parents   = $names;

        $this->query     = "SELECT ".implode(", ",array_keys($this->columns))." FROM ".$this->getTableName();

        // check if an instance of this table is already initialized
zYne's avatar
zYne committed
250
        if( ! $this->connection->addTable($this))
doctrine's avatar
doctrine committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
            throw new Doctrine_Table_Exception();

        $this->initComponents();
    }
    /**
     * initializes components this table uses
     *
     * @return void
     */
    final public function initComponents() {
        $this->repository = new Doctrine_Repository($this);
        switch($this->getAttribute(Doctrine::ATTR_CACHE)):
            case Doctrine::CACHE_SQLITE:
                $this->cache       = new Doctrine_Cache_Sqlite($this);
            break;
            case Doctrine::CACHE_NONE:
                $this->cache       = new Doctrine_Cache($this);
            break;
        endswitch;
    }
    /**
     * @return Doctrine_Repository
     */
    public function getRepository() {
        return $this->repository;
    }
    /**
     * setColumn
     * @param string $name
     * @param string $type
     * @param integer $length
     * @param mixed $options
     * @return void
     */
285 286 287 288
    final public function setColumn($name, $type, $length, $options = array()) {
        if(is_string($options)) 
            $options = explode('|', $options);

289 290 291 292 293 294 295 296
        foreach($options as $k => $option) {
            if(is_numeric($k)) {
                if( ! empty($option))
                    $options[$option] = true;

                unset($options[$k]);
            }
        }
297
        $name = strtolower($name);
doctrine's avatar
doctrine committed
298
        $this->columns[$name] = array($type,$length,$options);
299

300
        if(isset($options['primary'])) {
doctrine's avatar
doctrine committed
301 302
            $this->primaryKeys[] = $name;
        }
303 304 305 306 307 308 309 310 311 312 313 314
        if(isset($options['default'])) {
            $this->hasDefaultValues = true;
        }
    }
    /**
     * hasDefaultValues
     * returns true if this table has default values, otherwise false
     *
     * @return boolean
     */
    public function hasDefaultValues() {
        return $this->hasDefaultValues;
doctrine's avatar
doctrine committed
315
    }
316 317 318 319 320 321 322 323
    /**
     * getDefaultValueOf
     * returns the default value(if any) for given column
     *
     * @param string $column
     * @return mixed
     */
    public function getDefaultValueOf($column) {
324
        $column = strtolower($column);
325 326
        if( ! isset($this->columns[$column]))
            throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$column." doesn't exist.");
zYne's avatar
zYne committed
327

328 329 330 331 332 333
        if(isset($this->columns[$column][2]['default'])) {

            return $this->columns[$column][2]['default'];
        } else
            return null;
    }
doctrine's avatar
doctrine committed
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
    /**
     * @return mixed
     */
    final public function getIdentifier() {
        return $this->identifier;
    }
    /**
     * @return integer
     */
    final public function getIdentifierType() {
        return $this->identifierType;
    }
    /**
     * hasColumn
     * @return boolean
     */
    final public function hasColumn($name) {
        return isset($this->columns[$name]);
    }
    /**
     * @param mixed $key
     * @return void
     */
    final public function setPrimaryKey($key) {
        switch(gettype($key)):
            case "array":
                $this->primaryKeys = array_values($key);
            break;
            case "string":
                $this->primaryKeys[] = $key;
            break;
        endswitch;
    }
    /**
     * returns all primary keys
     * @return array
     */
    final public function getPrimaryKeys() {
        return $this->primaryKeys;
    }
    /**
     * @return boolean
     */
    final public function hasPrimaryKey($key) {
        return in_array($key,$this->primaryKeys);
    }
    /**
     * @param $sequence
     * @return void
     */
    final public function setSequenceName($sequence) {
        $this->sequenceName = $sequence;
    }
    /**
     * @return string   sequence name
     */
    final public function getSequenceName() {
        return $this->sequenceName;
    }
393 394 395 396
    /**
     * getParents
     */
    final public function getParents() {
doctrine's avatar
doctrine committed
397
        return $this->parents;
398 399 400 401 402 403 404
    }
    /**
     * @return boolean
     */
    final public function hasInheritanceMap() {
        return (empty($this->inheritanceMap));
    }
doctrine's avatar
doctrine committed
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    /**
     * setInheritanceMap
     * @param array $inheritanceMap
     * @return void
     */
    final public function setInheritanceMap(array $inheritanceMap) {
        $this->inheritanceMap = $inheritanceMap;
    }
    /**
     * @return array        inheritance map (array keys as fields)
     */
    final public function getInheritanceMap() {
        return $this->inheritanceMap;
    }
    /**
     * return all composite paths in the form [component1].[component2]. . .[componentN]
     * @return array
     */
    final public function getCompositePaths() {
        $array = array();
        $name  = $this->getComponentName();
        foreach($this->bound as $k=>$a) {
            try {
428
            $fk = $this->getRelation($k);
doctrine's avatar
doctrine committed
429 430 431 432 433 434 435 436 437 438 439 440 441 442
            switch($fk->getType()):
                case Doctrine_Relation::ONE_COMPOSITE:
                case Doctrine_Relation::MANY_COMPOSITE:
                    $n = $fk->getTable()->getComponentName();
                    $array[] = $name.".".$n;
                    $e = $fk->getTable()->getCompositePaths();
                    if( ! empty($e)) {
                        foreach($e as $name) {
                            $array[] = $name.".".$n.".".$name;
                        }
                    }
                break;
            endswitch;
            } catch(InvalidKeyException $e) {
443

doctrine's avatar
doctrine committed
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
            }
        }
        return $array;
    }
    /**
     * returns all bound relations
     *
     * @return array
     */
    final public function getBounds() {
        return $this->bound;
    }
    /**
     * returns a bound relation array
     *
     * @param string $name
     * @return array
     */
    final public function getBound($name) {
463
        if( ! isset($this->bound[$name]))
464
            throw new InvalidKeyException('Unknown bound '.$name);
doctrine's avatar
doctrine committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479

        return $this->bound[$name];
    }
    /**
     * returns a bound relation array
     *
     * @param string $name
     * @return array
     */
    final public function getBoundForName($name) {
        foreach($this->bound as $k => $bound) {
            if($bound[3] == $name) {
                return $this->bound[$k];
            }
        }
480
        throw new InvalidKeyException('Unknown bound '.$name);
doctrine's avatar
doctrine committed
481 482 483 484 485 486 487 488 489 490
    }
    /**
     * returns the alias for given component name
     *
     * @param string $name
     * @return string
     */
    final public function getAlias($name) {
        if(isset($this->boundAliases[$name]))
            return $this->boundAliases[$name];
491

doctrine's avatar
doctrine committed
492 493 494 495
        return $name;
    }
    /**
     * returns component name for given alias
496
     *
doctrine's avatar
doctrine committed
497 498 499 500 501 502
     * @param string $alias
     * @return string
     */
    final public function getAliasName($alias) {
        if($name = array_search($this->boundAliases,$alias))
            return $name;
503

504
        throw new InvalidKeyException('Unknown alias '.$alias);
doctrine's avatar
doctrine committed
505 506 507
    }
    /**
     * unbinds all relations
508
     *
doctrine's avatar
doctrine committed
509 510 511 512 513 514 515 516 517 518 519 520 521 522
     * @return void
     */
    final public function unbindAll() {
        $this->bound        = array();
        $this->relations    = array();
        $this->boundAliases = array();
    }
    /**
     * unbinds a relation
     * returns true on success, false on failure
     *
     * @param $name
     * @return boolean
     */
523
    final public function unbind($name) {
doctrine's avatar
doctrine committed
524 525
        if( ! isset($this->bound[$name]))
            return false;
526

doctrine's avatar
doctrine committed
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
        unset($this->bound[$name]);

        if(isset($this->relations[$name]))
            unset($this->relations[$name]);

        if(isset($this->boundAliases[$name]))
            unset($this->boundAliases[$name]);

        return true;
    }
    /**
     * binds a relation
     *
     * @param string $name
     * @param string $field
     * @return void
     */
    final public function bind($name,$field,$type,$localKey) {
        if(isset($this->relations[$name]))
546
            throw new InvalidKeyException('Relation already set for '.$name);
doctrine's avatar
doctrine committed
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567

        $e          = explode(" as ",$name);
        $name       = $e[0];

        if(isset($e[1])) {
            $alias = $e[1];
            $this->boundAliases[$name] = $alias;
        } else
            $alias = $name;


        $this->bound[$alias] = array($field,$type,$localKey,$name);
    }
    /**
     * getComponentName
     * @return string                   the component name
     */
    final public function getComponentName() {
        return $this->name;
    }
    /**
zYne's avatar
zYne committed
568
     * @return Doctrine_Connection
doctrine's avatar
doctrine committed
569
     */
zYne's avatar
zYne committed
570 571
    final public function getConnection() {
        return $this->connection;
doctrine's avatar
doctrine committed
572 573 574 575 576 577 578
    }
    /**
     * @return Doctrine_Cache
     */
    final public function getCache() {
        return $this->cache;
    }
579 580 581 582 583 584 585 586 587 588 589
    /**
     * hasRelatedComponent
     * @return boolean
     */
    final public function hasRelatedComponent($name, $component) {
         return (strpos($this->bound[$name][0], $component.'.') !== false);
    }
    /**
     * @param string $name              component name of which a foreign key object is bound
     * @return boolean
     */
590
    final public function hasRelation($name) {
591 592 593 594 595 596 597 598 599 600
        if(isset($this->bound[$name]))
            return true;

        foreach($this->bound as $k=>$v)
        {
            if($this->hasRelatedComponent($k, $name))
                return true;
        }
        return false;
    }
doctrine's avatar
doctrine committed
601 602 603 604
    /**
     * @param string $name              component name of which a foreign key object is bound
     * @return Doctrine_Relation
     */
605
    final public function getRelation($name) {
606 607
        $original = $name;

doctrine's avatar
doctrine committed
608 609 610 611 612 613 614 615 616 617
        if(isset($this->relations[$name]))
            return $this->relations[$name];

        if(isset($this->bound[$name])) {
            $type       = $this->bound[$name][1];
            $local      = $this->bound[$name][2];
            list($component, $foreign) = explode(".",$this->bound[$name][0]);
            $alias      = $name;
            $name       = $this->bound[$alias][3];

zYne's avatar
zYne committed
618
            $table      = $this->connection->getTable($name);
doctrine's avatar
doctrine committed
619 620 621 622 623 624 625 626 627

            if($component == $this->name || in_array($component, $this->parents)) {

                // ONE-TO-ONE
                if($type == Doctrine_Relation::ONE_COMPOSITE ||
                   $type == Doctrine_Relation::ONE_AGGREGATE) {
                    if( ! isset($local))
                        $local = $table->getIdentifier();

doctrine's avatar
doctrine committed
628
                    $relation = new Doctrine_LocalKey($table,$foreign,$local,$type, $alias);
doctrine's avatar
doctrine committed
629
                } else
630
                    throw new Doctrine_Table_Exception("Only one-to-one relations are possible when local reference key is used.");
doctrine's avatar
doctrine committed
631

zYne's avatar
zYne committed
632 633 634
            } elseif($component == $name || 
                    ($component == $alias && ($name == $this->name || in_array($name,$this->parents)))) {

doctrine's avatar
doctrine committed
635 636 637 638
                if( ! isset($local))
                    $local = $this->identifier;

                // ONE-TO-MANY or ONE-TO-ONE
doctrine's avatar
doctrine committed
639
                $relation = new Doctrine_ForeignKey($table, $local, $foreign, $type, $alias);
doctrine's avatar
doctrine committed
640 641 642 643 644

            } else {
                // MANY-TO-MANY
                // only aggregate relations allowed

645
                if($type != Doctrine_Relation::MANY_AGGREGATE)
646
                    throw new Doctrine_Table_Exception("Only aggregate relations are allowed for many-to-many relations");
doctrine's avatar
doctrine committed
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663

                $classes = array_merge($this->parents, array($this->name));

                foreach(array_reverse($classes) as $class) {
                    try {
                        $bound = $table->getBoundForName($class);
                        break;
                    } catch(InvalidKeyException $exc) { }

                }
                if( ! isset($local))
                    $local = $this->identifier;

                $e2    = explode(".",$bound[0]);
                $fields = explode("-",$e2[1]);

                if($e2[0] != $component)
664
                    throw new Doctrine_Table_Exception($e2[0]." doesn't match ".$component);
doctrine's avatar
doctrine committed
665

zYne's avatar
zYne committed
666
                $associationTable = $this->connection->getTable($e2[0]);
doctrine's avatar
doctrine committed
667 668 669

                if(count($fields) > 1) {
                    // SELF-REFERENCING THROUGH JOIN TABLE
doctrine's avatar
doctrine committed
670
                    $this->relations[$e2[0]] = new Doctrine_ForeignKey($associationTable,$local,$fields[0],Doctrine_Relation::MANY_COMPOSITE, $e2[0]);
671

672
                    $relation = new Doctrine_Association_Self($table,$associationTable,$fields[0],$fields[1], $type, $alias);
doctrine's avatar
doctrine committed
673
                } else {
674 675

                    // auto initialize a new one-to-one relationship for association table
zYne's avatar
zYne committed
676 677
                    $associationTable->bind($this->getComponentName(),  $associationTable->getComponentName(). '.' .$e2[1], Doctrine_Relation::ONE_AGGREGATE, 'id');
                    $associationTable->bind($table->getComponentName(), $associationTable->getComponentName(). '.' .$foreign, Doctrine_Relation::ONE_AGGREGATE, 'id');
678

doctrine's avatar
doctrine committed
679
                    // NORMAL MANY-TO-MANY RELATIONSHIP
doctrine's avatar
doctrine committed
680
                    $this->relations[$e2[0]] = new Doctrine_ForeignKey($associationTable,$local,$e2[1],Doctrine_Relation::MANY_COMPOSITE, $e2[0]);
doctrine's avatar
doctrine committed
681

doctrine's avatar
doctrine committed
682
                    $relation = new Doctrine_Association($table, $associationTable, $e2[1], $foreign, $type, $alias);
doctrine's avatar
doctrine committed
683 684 685 686 687 688
                }

            }
            $this->relations[$alias] = $relation;
            return $this->relations[$alias];
        }
689 690 691 692 693
        try {
            throw new Doctrine_Table_Exception($this->name . " doesn't have a relation to " . $original);
        } catch(Exception $e) {
            print $e;
        }
doctrine's avatar
doctrine committed
694 695 696 697 698 699
    }
    /**
     * returns an array containing all foreign key objects
     *
     * @return array
     */
700
    final public function getRelations() {
doctrine's avatar
doctrine committed
701 702
        $a = array();
        foreach($this->bound as $k=>$v) {
703
            $this->getRelation($k);
doctrine's avatar
doctrine committed
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
        }

        return $this->relations;
    }
    /**
     * sets the database table name
     *
     * @param string $name              database table name
     * @return void
     */
    final public function setTableName($name) {
        $this->tableName = $name;
    }

    /**
     * returns the database table name
     *
     * @return string
     */
    final public function getTableName() {
        return $this->tableName;
    }
    /**
     * create
     * creates a new record
     *
     * @param $array                    an array where keys are field names and values representing field values
     * @return Doctrine_Record
     */
    public function create(array $array = array()) {
734
        $this->data         = $array;
doctrine's avatar
doctrine committed
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
        $this->isNewEntry   = true;
        $record = new $this->name($this);
        $this->isNewEntry   = false;
        $this->data         = array();
        return $record;
    }
    /**
     * finds a record by its identifier
     *
     * @param $id                       database row id
     * @throws Doctrine_Find_Exception
     * @return Doctrine_Record          a record for given database identifier
     */
    public function find($id) {
        if($id !== null) {
            if( ! is_array($id))
                $id = array($id);
752
            else
doctrine's avatar
doctrine committed
753 754 755 756
                $id = array_values($id);

            $query  = $this->query." WHERE ".implode(" = ? AND ",$this->primaryKeys)." = ?";
            $query  = $this->applyInheritance($query);
doctrine's avatar
doctrine committed
757 758


doctrine's avatar
doctrine committed
759 760
            $params = array_merge($id, array_values($this->inheritanceMap));

zYne's avatar
zYne committed
761
            $stmt  = $this->connection->execute($query,$params);
doctrine's avatar
doctrine committed
762 763

            $this->data = $stmt->fetch(PDO::FETCH_ASSOC);
doctrine's avatar
doctrine committed
764 765

            if($this->data === false)
766
                return false;
doctrine's avatar
doctrine committed
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
        }
        return $this->getRecord();
    }
    /**
     * applyInheritance
     * @param $where                    query where part to be modified
     * @return string                   query where part with column aggregation inheritance added
     */
    final public function applyInheritance($where) {
        if( ! empty($this->inheritanceMap)) {
            $a = array();
            foreach($this->inheritanceMap as $field => $value) {
                $a[] = $field." = ?";
            }
            $i = implode(" AND ",$a);
            $where .= " AND $i";
        }
        return $where;
    }
    /**
     * findAll
     * returns a collection of records
     *
     * @return Doctrine_Collection
     */
    public function findAll() {
zYne's avatar
zYne committed
793
        $graph = new Doctrine_Query($this->connection);
doctrine's avatar
doctrine committed
794 795 796 797
        $users = $graph->query("FROM ".$this->name);
        return $users;
    }
    /**
798 799
     * findByDql
     * finds records with given DQL where clause
doctrine's avatar
doctrine committed
800 801
     * returns a collection of records
     *
802
     * @param string $dql               DQL after WHERE clause
doctrine's avatar
doctrine committed
803 804 805
     * @param array $params             query parameters
     * @return Doctrine_Collection
     */
806
    public function findBySql($dql, array $params = array()) {
zYne's avatar
zYne committed
807
        $q = new Doctrine_Query($this->connection);
808
        $users = $q->query("FROM ".$this->name." WHERE ".$dql, $params);
doctrine's avatar
doctrine committed
809 810
        return $users;
    }
811

812 813 814
    public function findByDql($dql, array $params = array()) {
        return $this->findBySql($dql, $params);
    }
doctrine's avatar
doctrine committed
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
    /**
     * clear
     * clears the first level cache (identityMap)
     *
     * @return void
     */
    public function clear() {
        $this->identityMap = array();
    }
    /**
     * getRecord
     * first checks if record exists in identityMap, if not
     * returns a new record
     *
     * @return Doctrine_Record
     */
    public function getRecord() {
zYne's avatar
zYne committed
832 833
        $this->data = array_change_key_case($this->data, CASE_LOWER);

doctrine's avatar
doctrine committed
834 835 836 837 838 839 840
        $key = $this->getIdentifier();

        if( ! is_array($key))
            $key = array($key);

        foreach($key as $k) {
            if( ! isset($this->data[$k]))
841
                throw new Doctrine_Exception("Primary key value for $k wasn't found");
842

doctrine's avatar
doctrine committed
843 844
            $id[] = $this->data[$k];
        }
845

doctrine's avatar
doctrine committed
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
        $id = implode(' ', $id);

        if(isset($this->identityMap[$id]))
            $record = $this->identityMap[$id];
        else {
            $record = new $this->name($this);
            $this->identityMap[$id] = $record;
        }
        $this->data = array();

        return $record;
    }
    /**
     * @param $id                       database row id
     * @throws Doctrine_Find_Exception
     * @return DAOProxy                 a proxy for given identifier
     */
    final public function getProxy($id = null) {
        if($id !== null) {
            $query = "SELECT ".implode(", ",$this->primaryKeys)." FROM ".$this->getTableName()." WHERE ".implode(" = ? && ",$this->primaryKeys)." = ?";
            $query = $this->applyInheritance($query);
867

doctrine's avatar
doctrine committed
868 869
            $params = array_merge(array($id), array_values($this->inheritanceMap));

zYne's avatar
zYne committed
870
            $this->data = $this->connection->execute($query,$params)->fetch(PDO::FETCH_ASSOC);
doctrine's avatar
doctrine committed
871 872

            if($this->data === false)
873
                return false;
doctrine's avatar
doctrine committed
874 875 876 877 878
        }
        return $this->getRecord();
    }
    /**
     * getTableDescription
879
     * @return Doctrine_Table_Description
doctrine's avatar
doctrine committed
880 881 882 883
     */
    final public function getTableDescription() {
        return $this->columns;
    }
884 885
    /**
     * count
886
     *
887 888 889
     * @return integer
     */
    public function count() {
zYne's avatar
zYne committed
890
        $a = $this->connection->getDBH()->query("SELECT COUNT(1) FROM ".$this->tableName)->fetch(PDO::FETCH_NUM);
891 892
        return current($a);
    }
doctrine's avatar
doctrine committed
893 894 895 896
    /**
     * @return Doctrine_Query                           a Doctrine_Query object
     */
    public function getQueryObject() {
zYne's avatar
zYne committed
897
        $graph = new Doctrine_Query($this->getConnection());
doctrine's avatar
doctrine committed
898 899 900 901 902 903 904 905 906 907 908 909
        $graph->load($this->getComponentName());
        return $graph;
    }
    /**
     * execute
     * @param string $query
     * @param array $array
     * @param integer $limit
     * @param integer $offset
     */
    public function execute($query, array $array = array(), $limit = null, $offset = null) {
        $coll  = new Doctrine_Collection($this);
zYne's avatar
zYne committed
910
        $query = $this->connection->modifyLimitQuery($query,$limit,$offset);
doctrine's avatar
doctrine committed
911
        if( ! empty($array)) {
zYne's avatar
zYne committed
912
            $stmt = $this->connection->getDBH()->prepare($query);
doctrine's avatar
doctrine committed
913 914
            $stmt->execute($array);
        } else {
zYne's avatar
zYne committed
915
            $stmt = $this->connection->getDBH()->query($query);
doctrine's avatar
doctrine committed
916 917 918 919 920 921 922 923 924 925 926
        }
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $stmt->closeCursor();

        foreach($data as $row) {
            $this->data = $row;
            $record = $this->getRecord();
            $coll->add($record);
        }
        return $coll;
    }
doctrine's avatar
doctrine committed
927 928 929 930 931 932 933 934
    /**
     * sets enumerated value array for given field
     *
     * @param string $field
     * @param array $values
     * @return void
     */
    final public function setEnumValues($field, array $values) {
935
        $this->enum[strtolower($field)] = $values;
doctrine's avatar
doctrine committed
936
    }
doctrine's avatar
doctrine committed
937 938 939 940 941
    /**
     * @param string $field
     * @return array
     */
    final public function getEnumValues($field) {
942
        if(isset($this->enum[$field]))
doctrine's avatar
doctrine committed
943 944 945 946
            return $this->enum[$field];
        else
            return array();
    }
doctrine's avatar
doctrine committed
947 948
    /**
     * enumValue
949 950 951 952
     *
     * @param string $field
     * @param integer $index
     * @return mixed
doctrine's avatar
doctrine committed
953 954 955 956 957 958
     */
    final public function enumValue($field, $index) {
        return isset($this->enum[$field][$index])?$this->enum[$field][$index]:$index;
    }
    /**
     * enumIndex
959 960 961 962
     *
     * @param string $field
     * @param mixed $value
     * @return mixed
doctrine's avatar
doctrine committed
963 964
     */
    final public function enumIndex($field, $value) {
965 966 967 968 969 970
        if( ! isset($this->enum[$field])) 
            $values = array();
        else
            $values = $this->enum[$field];

        return array_search($value, $values);
doctrine's avatar
doctrine committed
971
    }
doctrine's avatar
doctrine committed
972 973 974 975
    /**
     * @return integer
     */
    final public function getColumnCount() {
976
        return $this->columnCount;
doctrine's avatar
doctrine committed
977
    }
doctrine's avatar
doctrine committed
978

doctrine's avatar
doctrine committed
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994
    /**
     * returns all columns and their definitions
     *
     * @return array
     */
    final public function getColumns() {
        return $this->columns;
    }
    /**
     * returns an array containing all the column names
     *
     * @return array
     */
    public function getColumnNames() {
        return array_keys($this->columns);
    }
doctrine's avatar
doctrine committed
995 996
    /**
     * getDefinitionOf
997 998
     *
     * @return mixed        array on success, false on failure
doctrine's avatar
doctrine committed
999 1000 1001 1002
     */
    public function getDefinitionOf($column) {
        if(isset($this->columns[$column]))
            return $this->columns[$column];
1003 1004

        return false;
doctrine's avatar
doctrine committed
1005
    }
doctrine's avatar
doctrine committed
1006 1007
    /**
     * getTypeOf
1008 1009
     *
     * @return mixed        string on success, false on failure
doctrine's avatar
doctrine committed
1010 1011 1012
     */
    public function getTypeOf($column) {
        if(isset($this->columns[$column]))
1013
            return $this->columns[$column][0];
1014 1015

        return false;
doctrine's avatar
doctrine committed
1016
    }
doctrine's avatar
doctrine committed
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
    /**
     * setData
     * doctrine uses this function internally
     * users are strongly discouraged to use this function
     *
     * @param array $data               internal data
     * @return void
     */
    public function setData(array $data) {
        $this->data = $data;
    }
    /**
     * returns the maximum primary key value
     *
     * @return integer
     */
    final public function getMaxIdentifier() {
        $sql  = "SELECT MAX(".$this->getIdentifier().") FROM ".$this->getTableName();
zYne's avatar
zYne committed
1035
        $stmt = $this->connection->getDBH()->query($sql);
doctrine's avatar
doctrine committed
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
        $data = $stmt->fetch(PDO::FETCH_NUM);
        return isset($data[0])?$data[0]:1;
    }
    /**
     * return whether or not a newly created object is new or not
     *
     * @return boolean
     */
    final public function isNewEntry() {
        return $this->isNewEntry;
    }
    /**
     * returns simple cached query
     *
     * @return string
     */
    final public function getQuery() {
        return $this->query;
    }
    /**
1056
     * returns internal data, used by Doctrine_Record instances
doctrine's avatar
doctrine committed
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
     * when retrieving data from database
     *
     * @return array
     */
    final public function getData() {
        return $this->data;
    }
    /**
     * returns a string representation of this object
     *
     * @return string
     */
    public function __toString() {
        return Doctrine_Lib::getTableAsString($this);
    }
}
1073