Record.php 36.8 KB
Newer Older
doctrine's avatar
doctrine committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
<?php
require_once("Access.class.php");
/**
 * Doctrine_Record
 */
abstract class Doctrine_Record extends Doctrine_Access implements Countable, IteratorAggregate {
    /**
     * STATE CONSTANTS
     */

    /**
     * DIRTY STATE
     * a Doctrine_Record is in dirty state when its properties are changed
     */
    const STATE_DIRTY       = 1;
    /**
     * TDIRTY STATE
     * a Doctrine_Record is in transient dirty state when it is created and some of its fields are modified
     * but it is NOT yet persisted into database
     */
    const STATE_TDIRTY      = 2;
    /**
     * CLEAN STATE
     * a Doctrine_Record is in clean state when all of its properties are loaded from the database
     * and none of its properties are changed
     */
    const STATE_CLEAN       = 3;
    /**
     * PROXY STATE
     * a Doctrine_Record is in proxy state when its properties are not fully loaded
     */
    const STATE_PROXY       = 4;
    /**
     * NEW TCLEAN
     * a Doctrine_Record is in transient clean state when it is created and none of its fields are modified
     */
    const STATE_TCLEAN      = 5;
    /**
     * DELETED STATE
     * a Doctrine_Record turns into deleted state when it is deleted
     */
    const STATE_DELETED     = 6;

    /**
     * FETCHMODE CONSTANTS
     */

    /**
     * @var object Doctrine_Table $table    the factory that created this data access object
     */
    protected $table;
    /**
     * @var integer $id                     the primary key of this object
     */
    protected $id;
    /**
57
     * @var array $data                     the record data
doctrine's avatar
doctrine committed
58 59 60 61 62 63 64 65
     */
    protected $data       = array();

    /**
     * @var array $modified                 an array containing properties that have been modified
     */
    private $modified   = array();
    /**
66
     * @var integer $state                  the state of this record
doctrine's avatar
doctrine committed
67 68 69 70
     * @see STATE_* constants
     */
    private $state;
    /**
71
     * @var array $collections              the collections this record is in
doctrine's avatar
doctrine committed
72 73 74 75 76 77 78 79 80 81 82 83 84 85
     */
    private $collections = array();
    /**
     * @var mixed $references               an array containing all the references
     */
    private $references  = array();
    /**
     * @var mixed $originals                an array containing all the original references
     */
    private $originals   = array();
    /**
     * @var integer $index                  this index is used for creating object identifiers
     */
    private static $index = 1;
86
    /**
87 88
     * @var Doctrine_Null $nullObject       a Doctrine_Null object used for extremely fast 
     *                                      SQL null value testing
89
     */
90
    private static $null;
doctrine's avatar
doctrine committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
    /**
     * @var integer $oid                    object identifier
     */
    private $oid;

    /**
     * constructor
     * @param Doctrine_Table $table         a Doctrine_Table object
     * @throws Doctrine_Session_Exception   if object is created using the new operator and there are no
     *                                      open sessions
     */
    public function __construct($table = null) {
        if(isset($table) && $table instanceof Doctrine_Table) {
            $this->table = $table;
            $exists  = ( ! $this->table->isNewEntry());
        } else {
            $this->table = Doctrine_Manager::getInstance()->getCurrentSession()->getTable(get_class($this));
            $exists  = false;
        }

        // Check if the current session has the records table in its registry
        // If not this is record is only used for creating table definition and setting up
        // relations.

        if($this->table->getSession()->hasTable($this->table->getComponentName())) {

            $this->oid = self::$index;
    
            self::$index++;

121
            $keys = $this->table->getPrimaryKeys();
doctrine's avatar
doctrine committed
122 123 124 125 126 127 128 129 130 131 132

            if( ! $exists) {
                // listen the onPreCreate event
                $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreCreate($this);
            } else {
                // listen the onPreLoad event
                $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreLoad($this);
            }
            // get the data array
            $this->data = $this->table->getData();
    
doctrine's avatar
doctrine committed
133 134 135
            // get the column count
            $count = count($this->data);

doctrine's avatar
doctrine committed
136 137
            // clean data array
            $cols = $this->cleanData();
138 139 140

            $this->prepareIdentifiers($exists);

doctrine's avatar
doctrine committed
141 142 143 144 145 146
            if( ! $exists) {
    
                if($cols > 0)
                    $this->state = Doctrine_Record::STATE_TDIRTY;
                else
                    $this->state = Doctrine_Record::STATE_TCLEAN;
147

doctrine's avatar
doctrine committed
148 149
                // listen the onCreate event
                $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onCreate($this);
150

doctrine's avatar
doctrine committed
151
            } else {
152
                $this->state      = Doctrine_Record::STATE_CLEAN;
doctrine's avatar
doctrine committed
153

doctrine's avatar
doctrine committed
154
                if($count < $this->table->getColumnCount()) {
doctrine's avatar
doctrine committed
155
                    $this->state  = Doctrine_Record::STATE_PROXY;
doctrine's avatar
doctrine committed
156
                }
doctrine's avatar
doctrine committed
157

158

doctrine's avatar
doctrine committed
159 160 161 162 163 164
                // listen the onLoad event
                $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);
            }
            $this->table->getRepository()->add($this);
        }
    }
165 166 167
    /**
     * initNullObject
     */
168 169
    public static function initNullObject(Doctrine_Null $null) {
        self::$null = $null;
170
    }
doctrine's avatar
doctrine committed
171 172 173 174 175 176 177
    /** 
     * setUp
     * implemented by child classes
     */
    public function setUp() { }
    /**
     * return the object identifier
178
     *
doctrine's avatar
doctrine committed
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
     * @return integer
     */
    public function getOID() {
        return $this->oid;
    }
    /**
     * cleanData
     * modifies data array
     * example:
     *
     * $data = array("name"=>"John","lastname"=> null,"id"=>1,"unknown"=>"unknown");
     * $names = array("name","lastname","id");
     * $data after operation:
     * $data = array("name"=>"John","lastname" => array(),"id"=>1);
     */
    private function cleanData() {
        $cols = 0;
        $tmp  = $this->data;
        
        $this->data = array();

        foreach($this->table->getColumnNames() as $name) {
            if( ! isset($tmp[$name])) {
202
                $this->data[$name] = self::$null;
doctrine's avatar
doctrine committed
203 204 205 206 207 208 209 210 211

            } else {
                $cols++;
                $this->data[$name] = $tmp[$name];
            }
        }

        return $cols;
    }
212
    /**
213 214
     * prepares identifiers
     *
215 216
     * @return void
     */
217
    private function prepareIdentifiers($exists = true) {
218 219 220
        switch($this->table->getIdentifierType()):
            case Doctrine_Identifier::AUTO_INCREMENT:
            case Doctrine_Identifier::SEQUENCE:
221 222
                if($exists) {
                    $name = $this->table->getIdentifier();
doctrine's avatar
doctrine committed
223

224 225 226 227 228 229 230 231 232
                    if(isset($this->data[$name]))
                        $this->id = $this->data[$name];
    
                    unset($this->data[$name]);
                }
            break;
            case Doctrine_Identifier::COMPOSITE:
                $names      = $this->table->getIdentifier();
                $this->id   = array();
233

234
                foreach($names as $name) {
235 236 237 238
                    if($this->data[$name] === self::$null)
                        $this->id[$name] = null;
                    else 
                        $this->id[$name] = $this->data[$name];
239
                }
240 241 242
            break;
        endswitch;
    }
doctrine's avatar
doctrine committed
243 244
    /**
     * this method is automatically called when this Doctrine_Record is serialized
245 246
     *
     * @return array
doctrine's avatar
doctrine committed
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
     */
    public function __sleep() {
        $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onSleep($this);

        $this->table = $this->table->getComponentName();
        // unset all vars that won't need to be serialized

        unset($this->modified);
        unset($this->associations);
        unset($this->state);
        unset($this->collections);
        unset($this->references);    
        unset($this->originals);
        unset($this->oid);

        foreach($this->data as $k=>$v) {
            if($v instanceof Doctrine_Record)
                $this->data[$k] = array();
        }
        return array_keys(get_object_vars($this));
doctrine's avatar
doctrine committed
267

doctrine's avatar
doctrine committed
268 269
    }
    /**
doctrine's avatar
doctrine committed
270
     * unseralize
doctrine's avatar
doctrine committed
271
     * this method is automatically called everytime a Doctrine_Record object is unserialized
272 273
     *
     * @return void
doctrine's avatar
doctrine committed
274 275
     */
    public function __wakeup() {
doctrine's avatar
doctrine committed
276

doctrine's avatar
doctrine committed
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
        $this->modified = array();
        $this->state    = Doctrine_Record::STATE_CLEAN;

        $name       = $this->table;

        $manager    = Doctrine_Manager::getInstance();
        $sess       = $manager->getCurrentSession();

        $this->oid  = self::$index;
        self::$index++;

        $this->table = $sess->getTable($name);

        $this->table->getRepository()->add($this);

        $this->cleanData();

294 295
        //unset($this->data['id']);
        
doctrine's avatar
doctrine committed
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
        $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onWakeUp($this);

    }
    /**
     * addCollection
     * @param Doctrine_Collection $collection
     * @param mixed $key
     */
    final public function addCollection(Doctrine_Collection $collection,$key = null) {
        if($key !== null) {
            if(isset($this->collections[$key])) 
                throw InvalidKeyException();

            $this->collections[$key] = $collection;
        } else {
            $this->collections[] = $collection;
        }
    }
    /**
     * getCollection
     * @param integer $key
     * @return Doctrine_Collection
     */
    final public function getCollection($key) {
        return $this->collections[$key];
    }
    /**
     * hasCollections
doctrine's avatar
doctrine committed
324 325 326
     * whether or not this record is part of a collection
     *
     * @return boolean
doctrine's avatar
doctrine committed
327 328 329 330 331 332
     */
    final public function hasCollections() {
        return (! empty($this->collections));
    }
    /**
     * getState
doctrine's avatar
doctrine committed
333 334
     * returns the current state of the object
     *
doctrine's avatar
doctrine committed
335
     * @see Doctrine_Record::STATE_* constants
doctrine's avatar
doctrine committed
336
     * @return integer
doctrine's avatar
doctrine committed
337 338 339 340 341
     */
    final public function getState() {
        return $this->state;
    }
    /**
doctrine's avatar
doctrine committed
342 343 344
     * refresh   
     * refresh internal data from the database
     *
doctrine's avatar
doctrine committed
345 346 347
     * @return boolean
     */
    final public function refresh() {
348 349 350 351 352 353 354 355
        $id = $this->getID();
        if( ! is_array($id))
            $id = array($id);

        if(empty($id))
            return false;

        $id = array_values($id);
doctrine's avatar
doctrine committed
356 357

        $query          = $this->table->getQuery()." WHERE ".implode(" = ? && ",$this->table->getPrimaryKeys())." = ?";
358
        $this->data     = $this->table->getSession()->execute($query,$id)->fetch(PDO::FETCH_ASSOC);
359

doctrine's avatar
doctrine committed
360 361
        $this->modified = array();
        $this->cleanData();
doctrine's avatar
doctrine committed
362

363 364
        $this->prepareIdentifiers();

doctrine's avatar
doctrine committed
365 366 367 368 369 370 371 372 373 374 375
        $this->state    = Doctrine_Record::STATE_CLEAN;

        return true;
    }
    /**
     * factoryRefresh
     * @throws Doctrine_Exception
     * @return void
     */
    final public function factoryRefresh() {
        $data = $this->table->getData();
376 377 378
        $id   = $this->id;
        
        $this->prepareIdentifiers();
doctrine's avatar
doctrine committed
379

380
        if($this->id != $id)
doctrine's avatar
doctrine committed
381 382
            throw new Doctrine_Refresh_Exception();

doctrine's avatar
doctrine committed
383
        $this->data     = $data;
doctrine's avatar
doctrine committed
384

doctrine's avatar
doctrine committed
385
        $this->cleanData();
doctrine's avatar
doctrine committed
386

doctrine's avatar
doctrine committed
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
        $this->state    = Doctrine_Record::STATE_CLEAN;
        $this->modified = array();
    }
    /**
     * return the factory that created this data access object
     * @return object Doctrine_Table        a Doctrine_Table object
     */
    final public function getTable() {
        return $this->table;
    }
    /**
     * return all the internal data
     * @return array                    an array containing all the properties
     */
    final public function getData() {
        return $this->data;
    }
    /**
     * get
doctrine's avatar
doctrine committed
406
     * returns a value of a property or a related component 
doctrine's avatar
doctrine committed
407 408 409 410 411 412 413 414
     *
     * @param $name                     name of the property or related component
     * @throws InvalidKeyException
     * @return mixed
     */
    public function get($name) {
        if(isset($this->data[$name])) {

415 416
            // check if the property is null (= it is the Doctrine_Null object located in self::$null)
            if($this->data[$name] === self::$null) {
doctrine's avatar
doctrine committed
417 418

                // no use trying to load the data from database if the Doctrine_Record is not a proxy
419
                if($this->state == Doctrine_Record::STATE_PROXY) {
doctrine's avatar
doctrine committed
420 421 422
                    if( ! empty($this->collections)) {
                        foreach($this->collections as $collection) {
                            $collection->load($this);
doctrine's avatar
doctrine committed
423
                        }
doctrine's avatar
doctrine committed
424 425
                    } else {
                        $this->refresh();
doctrine's avatar
doctrine committed
426
                    }
doctrine's avatar
doctrine committed
427
                    $this->state = Doctrine_Record::STATE_CLEAN;
doctrine's avatar
doctrine committed
428
                }
doctrine's avatar
doctrine committed
429

430
                if($this->data[$name] === self::$null)
doctrine's avatar
doctrine committed
431 432 433 434
                    return null;
            }
            return $this->data[$name];
        }
435

436
        if($name == $this->table->getIdentifier())
doctrine's avatar
doctrine committed
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
            return $this->id;

        if( ! isset($this->references[$name]))
                $this->loadReference($name);


        return $this->references[$name];
    }
    /**
     * rawSet
     * doctrine uses this function internally, not recommended for developers
     *
     * @param mixed $name               name of the property or reference
     * @param mixed $value              value of the property or reference
     */
    final public function rawSet($name,$value) {
        if($value instanceof Doctrine_Record)
            $id = $value->getID();

        if( ! empty($id))
            $value = $id;
doctrine's avatar
doctrine committed
458 459
            
        if(isset($this->data[$name])) {
460
            if($this->data[$name] === self::$null) {
doctrine's avatar
doctrine committed
461 462 463 464 465 466 467 468 469 470
                if($this->data[$name] !== $value) {
                    switch($this->state):
                        case Doctrine_Record::STATE_CLEAN:
                            $this->state = Doctrine_Record::STATE_DIRTY;
                        break;
                        case Doctrine_Record::STATE_TCLEAN:
                            $this->state = Doctrine_Record::STATE_TDIRTY;
                    endswitch;
                }
            }
doctrine's avatar
doctrine committed
471 472 473 474

            if($this->state == Doctrine_Record::STATE_TCLEAN)
                $this->state = Doctrine_Record::STATE_TDIRTY;

doctrine's avatar
doctrine committed
475 476 477
            $this->data[$name] = $value;
            $this->modified[]  = $name;
        }
doctrine's avatar
doctrine committed
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    }
    /**
     * set
     * method for altering properties and Doctrine_Record references
     *
     * @param mixed $name               name of the property or reference
     * @param mixed $value              value of the property or reference
     * @throws InvalidKeyException
     * @throws InvalidTypeException
     * @return void
     */
    public function set($name,$value) {
        if(isset($this->data[$name])) {

            if($value instanceof Doctrine_Record) {
                $id = $value->getID();
                
                if( ! empty($id)) 
                    $value = $value->getID();
            }
doctrine's avatar
doctrine committed
498 499
            
            $old = $this->get($name);
doctrine's avatar
doctrine committed
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522

            if($old !== $value) {
                $this->data[$name] = $value;
                $this->modified[]  = $name;
                switch($this->state):
                    case Doctrine_Record::STATE_CLEAN:
                    case Doctrine_Record::STATE_PROXY:
                        $this->state = Doctrine_Record::STATE_DIRTY;
                    break;
                    case Doctrine_Record::STATE_TCLEAN:
                        $this->state = Doctrine_Record::STATE_TDIRTY;
                    break;
                endswitch;
            }
        } else {
            // if not found, throws InvalidKeyException

            $fk = $this->table->getForeignKey($name);

            // one-to-many or one-to-one relation
            if($fk instanceof Doctrine_ForeignKey || 
               $fk instanceof Doctrine_LocalKey) {
                switch($fk->getType()):
523 524
                    case Doctrine_Relation::MANY_COMPOSITE:
                    case Doctrine_Relation::MANY_AGGREGATE:
doctrine's avatar
doctrine committed
525 526
                        // one-to-many relation found
                        if( ! ($value instanceof Doctrine_Collection))
527
                            throw new Doctrine_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references.");
doctrine's avatar
doctrine committed
528 529 530

                        $value->setReference($this,$fk);
                    break;
531 532
                    case Doctrine_Relation::ONE_COMPOSITE:
                    case Doctrine_Relation::ONE_AGGREGATE:
doctrine's avatar
doctrine committed
533 534
                        // one-to-one relation found
                        if( ! ($value instanceof Doctrine_Record))
535
                            throw new Doctrine_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Record when setting one-to-one references.");
doctrine's avatar
doctrine committed
536

537
                        if($fk->getLocal() == $this->table->getIdentifier()) {
doctrine's avatar
doctrine committed
538 539 540 541 542 543 544 545
                            $this->references[$name]->set($fk->getForeign(),$this);
                        } else {
                            $this->set($fk->getLocal(),$value);
                        }
                    break;
                endswitch;

            } elseif($fk instanceof Doctrine_Association) {
546
                // join table relation found
doctrine's avatar
doctrine committed
547
                if( ! ($value instanceof Doctrine_Collection))
548
                    throw new Doctrine_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references.");
doctrine's avatar
doctrine committed
549 550 551 552 553
            }

            $this->references[$name] = $value;
        }
    }
doctrine's avatar
doctrine committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
    /**
     * __isset
     *
     * @param string $name
     * @return boolean
     */
    public function __isset($name) {
        if(isset($this->data[$name]))
            return true;

        if(isset($this->references[$name]))
            return true;

        return false;
    }
    /**
     * @param string $name
     * @return void
     */
    public function __unset($name) {
        if(isset($this->data[$name]))
            $this->data[$name] = array();

        // todo: what to do with references ?
    }
doctrine's avatar
doctrine committed
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
    /**
     * applies the changes made to this object into database
     * this method is smart enough to know if any changes are made
     * and whether to use INSERT or UPDATE statement
     *
     * this method also saves the related composites
     *
     * @return void
     */
    final public function save() {
        $this->table->getSession()->beginTransaction();

        // listen the onPreSave event
        $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($this);

        $saveLater = $this->table->getSession()->saveRelated($this);

        $this->table->getSession()->save($this);

        foreach($saveLater as $fk) {
599
            $table   = $fk->getTable();
doctrine's avatar
doctrine committed
600 601 602 603
            $alias   = $this->table->getAlias($table->getComponentName());

            if(isset($this->references[$alias])) {
                $obj = $this->references[$alias];
doctrine's avatar
doctrine committed
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
                $obj->save();
            }
        }

        // save the MANY-TO-MANY associations

        $this->saveAssociations();
            
        $this->table->getSession()->commit();
    }
    /**
     * returns an array of modified fields and associated values
     * @return array
     */
    final public function getModified() {
        $a = array();

        foreach($this->modified as $k=>$v) {
            $a[$v] = $this->data[$v];
        }
        return $a;
    }
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
    /**
     * returns an array of modified fields and values with data preparation
     * adds column aggregation inheritance and converts Records into primary key values
     *
     * @return array
     */
    final public function getPrepared() {
        $a = array();

        foreach($this->table->getInheritanceMap() as $k => $v) {
            $this->set($k,$v);                                                       	
        }

        foreach($this->modified as $k => $v) {
            if($this->data[$v] instanceof Doctrine_Record) {
                $this->data[$v] = $this->data[$v]->getID();
            }
            $a[$v] = $this->data[$v];
        }

        return $a;
    }
doctrine's avatar
doctrine committed
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
    /**
     * this class implements countable interface
     * @return integer                      the number of columns
     */
    public function count() {
        return count($this->data);
    }
    /**
     * getIterator
     * @return ArrayIterator                an ArrayIterator that iterates through the data
     */
    public function getIterator() {
        return new ArrayIterator($this->data);
    }
    /**
     * saveAssociations
     * save the associations of many-to-many relations
     * this method also deletes associations that do not exist anymore
     * @return void
     */
    final public function saveAssociations() {
        foreach($this->table->getForeignKeys() as $fk):
670
            $table   = $fk->getTable();
doctrine's avatar
doctrine committed
671
            $name    = $table->getComponentName();
672
            $alias   = $this->table->getAlias($name);
doctrine's avatar
doctrine committed
673

doctrine's avatar
doctrine committed
674 675
            if($fk instanceof Doctrine_Association) {
                switch($fk->getType()):
676
                    case Doctrine_Relation::MANY_COMPOSITE:
doctrine's avatar
doctrine committed
677 678

                    break;
679
                    case Doctrine_Relation::MANY_AGGREGATE:
doctrine's avatar
doctrine committed
680 681
                        $asf     = $fk->getAssociationFactory();

682
                        if(isset($this->references[$alias])) {
doctrine's avatar
doctrine committed
683

684 685 686 687
                            $new = $this->references[$alias];

                            if( ! isset($this->originals[$alias])) {
                                $this->loadReference($alias);
doctrine's avatar
doctrine committed
688 689
                            }

690
                            $r = $this->getRelationOperations($alias,$new);
doctrine's avatar
doctrine committed
691 692 693 694 695 696 697 698 699 700 701 702

                            foreach($r["delete"] as $record) {
                                $query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?"
                                                                            ." && ".$fk->getLocal()." = ?";
                                $this->table->getSession()->execute($query, array($record->getID(),$this->getID()));
                            }
                            foreach($r["add"] as $record) {
                                $reldao = $asf->create();
                                $reldao->set($fk->getForeign(),$record);
                                $reldao->set($fk->getLocal(),$this);
                                $reldao->save();
                            }  
703
                            $this->originals[$alias] = clone $this->references[$alias];
doctrine's avatar
doctrine committed
704 705 706 707 708 709 710
                        }
                    break;
                endswitch;
            } elseif($fk instanceof Doctrine_ForeignKey || 
                     $fk instanceof Doctrine_LocalKey) {
                
                switch($fk->getType()):
711
                    case Doctrine_Relation::ONE_COMPOSITE:
712 713
                        if(isset($this->originals[$alias]) && $this->originals[$alias]->getID() != $this->references[$alias]->getID())
                            $this->originals[$alias]->delete();
doctrine's avatar
doctrine committed
714 715
                    
                    break;
716
                    case Doctrine_Relation::MANY_COMPOSITE:
717 718
                        if(isset($this->references[$alias])) {
                            $new = $this->references[$alias];
doctrine's avatar
doctrine committed
719

720 721
                            if( ! isset($this->originals[$alias]))
                                $this->loadReference($alias);
doctrine's avatar
doctrine committed
722

723
                            $r = $this->getRelationOperations($alias,$new);
doctrine's avatar
doctrine committed
724 725 726 727 728

                            foreach($r["delete"] as $record) {
                                $record->delete();
                            }
                            
729
                            $this->originals[$alias] = clone $this->references[$alias];
doctrine's avatar
doctrine committed
730 731 732 733 734 735 736 737 738 739 740 741 742
                        }
                    break;
                endswitch;
            }
        endforeach;
    }
    /**
     * get the records that need to be added
     * and/or deleted in order to change the old collection
     * to the new one
     *
     * The algorithm here is very simple and definitely not
     * the fastest one, since we have to iterate through the collections twice.
743
     * the complexity of this algorithm is O(n^2)
doctrine's avatar
doctrine committed
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 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 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
     *
     * First we iterate through the new collection and get the
     * records that do not exist in the old collection (Doctrine_Records that need to be added).
     *
     * Then we iterate through the old collection and get the records
     * that do not exists in the new collection (Doctrine_Records that need to be deleted).
     */
    final public function getRelationOperations($name, Doctrine_Collection $new) {
        $r["add"]    = array();
        $r["delete"] = array();



        foreach($new as $k=>$record) {

            $found = false;

            if($record->getID() !== null) {
                foreach($this->originals[$name] as $k2 => $record2) {
                    if($record2->getID() == $record->getID()) {
                        $found = true;
                        break;
                    }
                }
            }
            if( ! $found) {
                $this->originals[$name][] = $record;
                $r["add"][] = $record;
            }
        }

        foreach($this->originals[$name] as $k => $record) {
            if($record->getID() === null)
                continue;

            $found = false;
            foreach($new as $k2=>$record2) {
                if($record2->getID() == $record->getID()) {
                    $found = true;
                    break;
                }
            }

            if( ! $found)  {
                $r["delete"][] = $record;
                unset($this->originals[$name][$k]);
            }
        }

        return $r;
    }
    /**
     * getOriginals
     */
    final public function getOriginals($name) {
        if( ! isset($this->originals[$name]))
            throw new InvalidKeyException();

        return $this->originals[$name];
    }
    /**
     * deletes this data access object and all the related composites
     * this operation is isolated by a transaction
     *
     * this event can be listened by the onPreDelete and onDelete listeners
     *
     * @return boolean      true on success, false on failure
     */
812
    public function delete() {
doctrine's avatar
doctrine committed
813 814 815 816 817 818
        $this->table->getSession()->delete($this);
    }
    /**
     * returns a copy of this object
     * @return DAO
     */
doctrine's avatar
doctrine committed
819
    public function copy() {
doctrine's avatar
doctrine committed
820 821 822 823 824 825
        return $this->table->create($this->data);
    }
    /**
     * @param integer $id
     * @return void
     */
826 827 828
    final public function setID($id = false) {
        if($id === false) {
            $this->id       = false;
doctrine's avatar
doctrine committed
829
            $this->cleanData();
830
            $this->state    = Doctrine_Record::STATE_TCLEAN;
doctrine's avatar
doctrine committed
831
            $this->modified = array();
832 833 834 835
        } elseif($id === true) {
            $this->prepareIdentifiers(false);
            $this->state    = Doctrine_Record::STATE_CLEAN;
            $this->modified = array();
doctrine's avatar
doctrine committed
836 837 838 839 840 841 842
        } else {
            $this->id       = $id;
            $this->state    = Doctrine_Record::STATE_CLEAN;
            $this->modified = array();
        }
    }
    /**
843 844
     * return the primary key(s) this object is pointing at
     * @return mixed id
doctrine's avatar
doctrine committed
845 846 847 848
     */
    final public function getID() {
        return $this->id;
    }
849 850 851 852 853 854 855 856 857 858 859
    /**
     * getLast
     * this method is used internally be Doctrine_Query
     * it is needed to provide compatibility between
     * records and collections
     *
     * @return Doctrine_Record
     */
    public function getLast() {
        return $this;
    }
doctrine's avatar
doctrine committed
860 861 862
    /**
     * hasRefence
     * @param string $name
doctrine's avatar
doctrine committed
863
     * @return boolean
doctrine's avatar
doctrine committed
864 865 866 867 868
     */
    public function hasReference($name) {
        return isset($this->references[$name]);
    }
    /**
869 870 871 872 873 874 875 876 877 878 879 880 881
     * initalizes a one-to-one relation
     *
     * @param Doctrine_Record $record
     * @param Doctrine_Relation $connector
     * @return void
     */
    public function initSingleReference(Doctrine_Record $record) {
        $name = $this->table->getAlias($record->getTable()->getComponentName());
        $this->references[$name] = $record;
    }
    /**
     * initalizes a one-to-many / many-to-many relation
     *
doctrine's avatar
doctrine committed
882
     * @param Doctrine_Collection $coll
883 884
     * @param Doctrine_Relation $connector
     * @return void
doctrine's avatar
doctrine committed
885 886
     */
    public function initReference(Doctrine_Collection $coll, Doctrine_Relation $connector) {
doctrine's avatar
doctrine committed
887
        $name = $this->table->getAlias($coll->getTable()->getComponentName());
doctrine's avatar
doctrine committed
888 889 890 891 892 893
        $coll->setReference($this, $connector);
        $this->references[$name] = $coll;
        $this->originals[$name]  = clone $coll;
    }
    /**
     * addReference
doctrine's avatar
doctrine committed
894 895 896
     * @param Doctrine_Record $record
     * @param mixed $key
     * @return void
doctrine's avatar
doctrine committed
897
     */
doctrine's avatar
doctrine committed
898
    public function addReference(Doctrine_Record $record, $key = null) {
doctrine's avatar
doctrine committed
899 900
        $name = $this->table->getAlias($record->getTable()->getComponentName());

doctrine's avatar
doctrine committed
901 902
        $this->references[$name]->add($record, $key);
        $this->originals[$name]->add($record, $key);
doctrine's avatar
doctrine committed
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
    }
    /**
     * getReferences
     * @return array    all references
     */
    public function getReferences() {
        return $this->references;
    }

    /**
     * @throws InvalidKeyException
     * @param name
     * @return void
     */
    final public function loadReference($name) {
        $fk      = $this->table->getForeignKey($name);
        $table   = $fk->getTable();
920

doctrine's avatar
doctrine committed
921 922
        $local   = $fk->getLocal();
        $foreign = $fk->getForeign();
923
        $graph   = $table->getQueryObject();
924
        $type    = $fk->getType();
doctrine's avatar
doctrine committed
925 926 927 928 929

        switch($this->getState()):
            case Doctrine_Record::STATE_TDIRTY:
            case Doctrine_Record::STATE_TCLEAN:

930
                if($type == Doctrine_Relation::ONE_COMPOSITE ||
931
                   $type == Doctrine_Relation::ONE_AGGREGATE) {
932

doctrine's avatar
doctrine committed
933 934 935 936 937 938 939 940 941 942 943 944 945 946
                    // ONE-TO-ONE
                    $this->references[$name] = $table->create();

                    if($fk instanceof Doctrine_ForeignKey) {
                        $this->references[$name]->set($fk->getForeign(),$this);
                    } else {
                        $this->set($fk->getLocal(),$this->references[$name]);
                    }
                } else {
                    $this->references[$name] = new Doctrine_Collection($table);
                    if($fk instanceof Doctrine_ForeignKey) {
                        // ONE-TO-MANY
                        $this->references[$name]->setReference($this,$fk);
                    }
doctrine's avatar
doctrine committed
947
                    $this->originals[$name]  = new Doctrine_Collection($table);
doctrine's avatar
doctrine committed
948 949 950 951 952
                }
            break;
            case Doctrine_Record::STATE_DIRTY:
            case Doctrine_Record::STATE_CLEAN:
            case Doctrine_Record::STATE_PROXY:
953

doctrine's avatar
doctrine committed
954
                 switch($fk->getType()):
955 956
                    case Doctrine_Relation::ONE_COMPOSITE:
                    case Doctrine_Relation::ONE_AGGREGATE:
957

doctrine's avatar
doctrine committed
958 959
                        // ONE-TO-ONE
                        $id      = $this->get($local);
doctrine's avatar
doctrine committed
960

doctrine's avatar
doctrine committed
961
                        if($fk instanceof Doctrine_LocalKey) {
962

doctrine's avatar
doctrine committed
963 964 965 966 967 968 969 970 971 972 973 974
                            if(empty($id)) {
                                $this->references[$name] = $table->create();
                                $this->set($fk->getLocal(),$this->references[$name]);
                            } else {
                                try {
                                    $this->references[$name] = $table->find($id);
                                } catch(Doctrine_Find_Exception $e) {

                                }
                            }

                        } elseif ($fk instanceof Doctrine_ForeignKey) {
975

976 977 978 979
                            if(empty($id)) {
                                $this->references[$name] = $table->create();
                                $this->references[$name]->set($fk->getForeign(), $this);
                            } else {
980
                                $dql  = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$fk->getForeign()." = ?";
981
                                $coll = $graph->query($dql, array($id));
982 983 984
                                $this->references[$name] = $coll[0];
                                $this->references[$name]->set($fk->getForeign(), $this);
                            }
doctrine's avatar
doctrine committed
985 986 987 988 989
                        }
                    break;
                    default:
                        // ONE-TO-MANY
                        if($fk instanceof Doctrine_ForeignKey) {
990
                            $id      = $this->get($local);
991
                            $query = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$fk->getForeign()." = ?";
doctrine's avatar
doctrine committed
992
                            $coll = $graph->query($query,array($id));
doctrine's avatar
doctrine committed
993

doctrine's avatar
doctrine committed
994
                            $this->references[$name] = $coll;
doctrine's avatar
doctrine committed
995
                            $this->references[$name]->setReference($this, $fk);
doctrine's avatar
doctrine committed
996 997 998
    
                            $this->originals[$name]  = clone $coll;

999
                        } elseif($fk instanceof Doctrine_Association) {            
doctrine's avatar
doctrine committed
1000 1001 1002
                            $asf     = $fk->getAssociationFactory();
                            $query   = "SELECT ".$foreign." FROM ".$asf->getTableName()." WHERE ".$local." = ?";
        
1003
                            $graph   = new Doctrine_Query($table->getSession());
1004
                            $query   = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$table->getIdentifier()." IN ($query)";
doctrine's avatar
doctrine committed
1005 1006 1007 1008

                            $coll    = $graph->query($query, array($this->getID()));
        
                            $this->references[$name] = $coll;
doctrine's avatar
doctrine committed
1009
                            $this->originals[$name]  = clone $coll;
1010

doctrine's avatar
doctrine committed
1011 1012
                        }
                 endswitch;
doctrine's avatar
doctrine committed
1013 1014 1015 1016 1017
            break;
        endswitch;
    }

    /**
1018 1019
     * binds One-to-One composite relation
     *
doctrine's avatar
doctrine committed
1020 1021 1022 1023
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
1024 1025
    final public function ownsOne($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_COMPOSITE, $localKey);
doctrine's avatar
doctrine committed
1026 1027
    }
    /**
1028 1029
     * binds One-to-Many composite relation
     *
doctrine's avatar
doctrine committed
1030 1031 1032 1033
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
1034 1035
    final public function ownsMany($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::MANY_COMPOSITE, $localKey);
doctrine's avatar
doctrine committed
1036 1037
    }
    /**
1038 1039
     * binds One-to-One aggregate relation
     *
doctrine's avatar
doctrine committed
1040 1041 1042 1043
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
1044 1045
    final public function hasOne($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_AGGREGATE, $localKey);
doctrine's avatar
doctrine committed
1046 1047
    }
    /**
1048 1049
     * binds One-to-Many aggregate relation
     *
doctrine's avatar
doctrine committed
1050 1051 1052 1053
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
1054 1055
    final public function hasMany($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::MANY_AGGREGATE, $localKey);
doctrine's avatar
doctrine committed
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
    }
    /**
     * setInheritanceMap
     * @param array $inheritanceMap
     * @return void
     */
    final public function setInheritanceMap(array $inheritanceMap) {
        $this->table->setInheritanceMap($inheritanceMap);
    }
    /**
     * setPrimaryKey
1067
     * @param mixed $key
doctrine's avatar
doctrine committed
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
     */
    final public function setPrimaryKey($key) {
        $this->table->setPrimaryKey($key);
    }
    /**
     * setTableName
     * @param string $name              table name
     * @return void
     */
    final public function setTableName($name) {
        $this->table->setTableName($name);
    }
    /**
     * setAttribute
     * @param integer $attribute
     * @param mixed $value
     * @see Doctrine::ATTR_* constants
     * @return void
     */
    final public function setAttribute($attribute, $value) {
        $this->table->setAttribute($attribute,$value);
    }
    /**
     * hasColumn
1092 1093
     * sets a column definition
     *
doctrine's avatar
doctrine committed
1094 1095 1096 1097 1098 1099 1100
     * @param string $name
     * @param string $type
     * @param integer $length
     * @param mixed $options
     * @return void
     */
    final public function hasColumn($name, $type, $length = 20, $options = "") {
doctrine's avatar
doctrine committed
1101
        $this->table->setColumn($name, $type, $length, $options);
doctrine's avatar
doctrine committed
1102 1103 1104 1105 1106 1107 1108 1109 1110
    }
    /**
     * returns a string representation of this object
     */
    public function __toString() {
        return Doctrine_Lib::getRecordAsString($this);
    }
}
?>