Record.php 48.7 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 21
 *  $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>.
 */

doctrine's avatar
doctrine committed
22 23
/**
 * Doctrine_Record
24 25 26 27 28
 * All record classes should inherit this super class
 *
 * @author      Konsta Vesterinen
 * @license     LGPL
 * @package     Doctrine
doctrine's avatar
doctrine committed
29
 */
30

doctrine's avatar
doctrine committed
31
abstract class Doctrine_Record extends Doctrine_Access implements Countable, IteratorAggregate, Serializable {
doctrine's avatar
doctrine committed
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 57 58 59 60 61 62 63 64 65 66 67 68 69
    /**
     * 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;

    /**
doctrine's avatar
doctrine committed
70
     * CALLBACK CONSTANTS
doctrine's avatar
doctrine committed
71 72
     */

doctrine's avatar
doctrine committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
    /**
     * RAW CALLBACK
     *
     * when using a raw callback and the property if a record is changed using this callback the
     * record state remains untouched
     */
    const CALLBACK_RAW       = 1;
    /**
     * STATE-WISE CALLBACK
     *
     * state-wise callback means that when callback is used and the property is changed the
     * record state is also updated
     */
    const CALLBACK_STATEWISE = 2;

doctrine's avatar
doctrine committed
88 89 90 91 92
    /**
     * @var object Doctrine_Table $table    the factory that created this data access object
     */
    protected $table;
    /**
93
     * @var integer $id                     the primary keys of this object
doctrine's avatar
doctrine committed
94
     */
95
    protected $id           = array();
doctrine's avatar
doctrine committed
96 97 98
    /**
     * @var array $data                     the record data
     */
99
    protected $data         = array();
doctrine's avatar
doctrine committed
100 101 102 103
    /**
     * @var integer $state                  the state of this record
     * @see STATE_* constants
     */
104 105 106 107
    protected $state;
    /**
     * @var array $modified                 an array containing properties that have been modified
     */
108
    protected $modified     = array();
doctrine's avatar
doctrine committed
109 110 111
    /**
     * @var array $collections              the collections this record is in
     */
112
    private $collections    = array();
doctrine's avatar
doctrine committed
113
    /**
114
     * @var array $references               an array containing all the references
doctrine's avatar
doctrine committed
115
     */
116
    private $references     = array();
doctrine's avatar
doctrine committed
117
    /**
118
     * @var array $originals                an array containing all the original references
doctrine's avatar
doctrine committed
119
     */
120 121 122 123 124
    private $originals      = array();
    /**
     * @var array $filters
     */
    private $filters        = array();
doctrine's avatar
doctrine committed
125 126 127
    /**
     * @var integer $index                  this index is used for creating object identifiers
     */
128
    private static $index   = 1;
doctrine's avatar
doctrine committed
129
    /**
130
     * @var Doctrine_Null $null             a Doctrine_Null object used for extremely fast
131
     *                                      null value testing
doctrine's avatar
doctrine committed
132 133 134 135 136 137 138 139 140 141
     */
    private static $null;
    /**
     * @var integer $oid                    object identifier
     */
    private $oid;

    /**
     * constructor
     * @param Doctrine_Table $table         a Doctrine_Table object
zYne's avatar
zYne committed
142 143
     * @throws Doctrine_Connection_Exception   if object is created using the new operator and there are no
     *                                      open connections
doctrine's avatar
doctrine committed
144 145 146 147 148 149
     */
    public function __construct($table = null) {
        if(isset($table) && $table instanceof Doctrine_Table) {
            $this->table = $table;
            $exists  = ( ! $this->table->isNewEntry());
        } else {
zYne's avatar
zYne committed
150
            $this->table = Doctrine_Manager::getInstance()->getCurrentConnection()->getTable(get_class($this));
doctrine's avatar
doctrine committed
151 152 153
            $exists  = false;
        }

zYne's avatar
zYne committed
154
        // Check if the current connection has the records table in its registry
doctrine's avatar
doctrine committed
155 156 157
        // If not this is record is only used for creating table definition and setting up
        // relations.

zYne's avatar
zYne committed
158
        if($this->table->getConnection()->hasTable($this->table->getComponentName())) {
doctrine's avatar
doctrine committed
159 160

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

doctrine's avatar
doctrine committed
162 163 164 165 166 167 168 169 170 171 172 173 174
            self::$index++;

            $keys = $this->table->getPrimaryKeys();

            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
175

doctrine's avatar
doctrine committed
176 177 178 179
            // get the column count
            $count = count($this->data);

            // clean data array
180
            $this->cleanData();
doctrine's avatar
doctrine committed
181 182 183 184

            $this->prepareIdentifiers($exists);

            if( ! $exists) {
185

186
                if($count > 0)
doctrine's avatar
doctrine committed
187 188 189 190
                    $this->state = Doctrine_Record::STATE_TDIRTY;
                else
                    $this->state = Doctrine_Record::STATE_TCLEAN;

191 192 193
                // set the default values for this record
                $this->setDefaultValues();

doctrine's avatar
doctrine committed
194 195 196 197 198 199 200 201 202 203 204 205 206
                // listen the onCreate event
                $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onCreate($this);

            } else {
                $this->state      = Doctrine_Record::STATE_CLEAN;

                if($count < $this->table->getColumnCount()) {
                    $this->state  = Doctrine_Record::STATE_PROXY;
                }

                // listen the onLoad event
                $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);
            }
doctrine's avatar
doctrine committed
207

doctrine's avatar
doctrine committed
208 209
            $repository = $this->table->getRepository();
            $repository->add($this);
doctrine's avatar
doctrine committed
210 211 212 213
        }
    }
    /**
     * initNullObject
214 215
     *
     * @param Doctrine_Null $null
doctrine's avatar
doctrine committed
216 217 218 219
     */
    public static function initNullObject(Doctrine_Null $null) {
        self::$null = $null;
    }
220 221 222 223 224 225
    /**
     * @return Doctrine_Null
     */
    public static function getNullObject() {
        return self::$null;
    }
226
    /**
doctrine's avatar
doctrine committed
227 228 229 230 231 232 233 234 235 236 237 238
     * setUp
     * implemented by child classes
     */
    public function setUp() { }
    /**
     * return the object identifier
     *
     * @return integer
     */
    public function getOID() {
        return $this->oid;
    }
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    /**
     * setDefaultValues
     * sets the default values
     *
     * @param boolean $overwrite        whether or not to overwrite the already set values
     * @return boolean
     */
    public function setDefaultValues($overwrite = false) {
        if( ! $this->table->hasDefaultValues())
            return false;
            
        foreach($this->data as $column => $value) {
            $default = $this->table->getDefaultValueOf($column);

            if($default === null)
                $default = self::$null;

256
            if($value === self::$null || $overwrite) {
257
                $this->data[$column] = $default;
258 259 260
                $this->modified[]    = $column;
                $this->state = Doctrine_Record::STATE_TDIRTY;
            }
261 262
        }
    }
doctrine's avatar
doctrine committed
263 264 265 266 267
    /**
     * cleanData
     * modifies data array
     * example:
     *
268 269
     * $data = array("name"=>"John","lastname"=> null, "id" => 1,"unknown" => "unknown");
     * $names = array("name", "lastname", "id");
doctrine's avatar
doctrine committed
270
     * $data after operation:
271 272
     * $data = array("name"=>"John","lastname" => Object(Doctrine_Null));
     *
273
     * here column 'id' is removed since its auto-incremented primary key (protected)
doctrine's avatar
doctrine committed
274 275
     *
     * @return integer
doctrine's avatar
doctrine committed
276
     */
zYne's avatar
zYne committed
277
    private function cleanData($debug = false) {
zYne's avatar
zYne committed
278
        $tmp = $this->data;
279

doctrine's avatar
doctrine committed
280
        $this->data = array();
281

282
        $count = 0;
doctrine's avatar
doctrine committed
283 284

        foreach($this->table->getColumnNames() as $name) {
285 286
            $type = $this->table->getTypeOf($name);

287 288
            if( ! isset($tmp[$name])) {
                $this->data[$name] = self::$null;
doctrine's avatar
doctrine committed
289
            } else {
290
                switch($type):
doctrine's avatar
doctrine committed
291 292
                    case "array":
                    case "object":
doctrine's avatar
doctrine committed
293

294 295 296
                        if($tmp[$name] !== self::$null) {
                            if(is_string($tmp[$name])) {
                                $value = unserialize($tmp[$name]);
doctrine's avatar
doctrine committed
297

zYne's avatar
zYne committed
298
                                if($value === false)
zYne's avatar
zYne committed
299
                                    throw new Doctrine_Record_Exception("Unserialization of $name failed. ".var_dump($tmp[$lower],true));
zYne's avatar
zYne committed
300
                            } else
301
                                $value = $tmp[$name];
zYne's avatar
zYne committed
302

doctrine's avatar
doctrine committed
303 304
                            $this->data[$name] = $value;
                        }
doctrine's avatar
doctrine committed
305
                    break;
zYne's avatar
zYne committed
306 307 308 309 310
                    case "gzip":

                        if($tmp[$name] !== self::$null) {
                            $value = gzuncompress($tmp[$name]);
                            
311

zYne's avatar
zYne committed
312 313 314 315 316 317 318
                            if($value === false)
                                throw new Doctrine_Record_Exception("Uncompressing of $name failed.");

                            $this->data[$name] = $value;
                        }
                    break;
                    case "enum":
319
                        $this->data[$name] = $this->table->enumValue($name, $tmp[$name]);
doctrine's avatar
doctrine committed
320 321
                    break;
                    default:
322
                        $this->data[$name] = $tmp[$name];
doctrine's avatar
doctrine committed
323
                endswitch;
doctrine's avatar
doctrine committed
324
                $count++;
doctrine's avatar
doctrine committed
325 326
            }
        }
doctrine's avatar
doctrine committed
327

zYne's avatar
zYne committed
328

329
        return $count;
doctrine's avatar
doctrine committed
330 331
    }
    /**
332
     * prepares identifiers for later use
doctrine's avatar
doctrine committed
333
     *
334
     * @param boolean $exists               whether or not this record exists in persistent data store
doctrine's avatar
doctrine committed
335 336 337 338 339 340
     * @return void
     */
    private function prepareIdentifiers($exists = true) {
        switch($this->table->getIdentifierType()):
            case Doctrine_Identifier::AUTO_INCREMENT:
            case Doctrine_Identifier::SEQUENCE:
doctrine's avatar
doctrine committed
341 342 343
                $name = $this->table->getIdentifier();

                if($exists) {
doctrine's avatar
doctrine committed
344
                    if(isset($this->data[$name]) && $this->data[$name] !== self::$null)
345
                        $this->id[$name] = $this->data[$name];
doctrine's avatar
doctrine committed
346
                }
doctrine's avatar
doctrine committed
347

doctrine's avatar
doctrine committed
348 349
                unset($this->data[$name]);

doctrine's avatar
doctrine committed
350
            break;
351 352 353
            case Doctrine_Identifier::NORMAL:
                 $this->id   = array();
                 $name       = $this->table->getIdentifier();
354

355 356 357
                 if(isset($this->data[$name]) && $this->data[$name] !== self::$null)
                    $this->id[$name] = $this->data[$name];
            break;
doctrine's avatar
doctrine committed
358 359
            case Doctrine_Identifier::COMPOSITE:
                $names      = $this->table->getIdentifier();
360

doctrine's avatar
doctrine committed
361 362 363 364

                foreach($names as $name) {
                    if($this->data[$name] === self::$null)
                        $this->id[$name] = null;
365
                    else
doctrine's avatar
doctrine committed
366 367 368 369 370 371 372 373 374 375
                        $this->id[$name] = $this->data[$name];
                }
            break;
        endswitch;
    }
    /**
     * this method is automatically called when this Doctrine_Record is serialized
     *
     * @return array
     */
doctrine's avatar
doctrine committed
376
    public function serialize() {
doctrine's avatar
doctrine committed
377 378
        $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onSleep($this);

doctrine's avatar
doctrine committed
379
        $vars = get_object_vars($this);
380

doctrine's avatar
doctrine committed
381 382 383 384 385
        unset($vars['references']);
        unset($vars['collections']);
        unset($vars['originals']);
        unset($vars['table']);

386 387 388

        $name = $this->table->getIdentifier();
        $this->data = array_merge($this->data, $this->id);
doctrine's avatar
doctrine committed
389

doctrine's avatar
doctrine committed
390
        foreach($this->data as $k => $v) {
doctrine's avatar
doctrine committed
391
            if($v instanceof Doctrine_Record)
doctrine's avatar
doctrine committed
392
                unset($vars['data'][$k]);
doctrine's avatar
doctrine committed
393
            elseif($v === self::$null) {
doctrine's avatar
doctrine committed
394
                unset($vars['data'][$k]);
doctrine's avatar
doctrine committed
395 396 397 398
            } else {
                switch($this->table->getTypeOf($k)):
                    case "array":
                    case "object":
doctrine's avatar
doctrine committed
399
                        $vars['data'][$k] = serialize($vars['data'][$k]);
doctrine's avatar
doctrine committed
400 401 402
                    break;
                endswitch;
            }
doctrine's avatar
doctrine committed
403 404
        }

doctrine's avatar
doctrine committed
405
        return serialize($vars);
doctrine's avatar
doctrine committed
406 407 408 409 410 411 412
    }
    /**
     * unseralize
     * this method is automatically called everytime a Doctrine_Record object is unserialized
     *
     * @return void
     */
doctrine's avatar
doctrine committed
413
    public function unserialize($serialized) {
doctrine's avatar
doctrine committed
414
        $manager    = Doctrine_Manager::getInstance();
zYne's avatar
zYne committed
415
        $connection    = $manager->getCurrentConnection();
doctrine's avatar
doctrine committed
416 417 418 419

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

zYne's avatar
zYne committed
420
        $this->table = $connection->getTable(get_class($this));
421

doctrine's avatar
doctrine committed
422 423 424 425 426 427

        $array = unserialize($serialized);

        foreach($array as $name => $values) {
            $this->$name = $values;
        }
doctrine's avatar
doctrine committed
428 429 430 431 432

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

        $this->cleanData();

doctrine's avatar
doctrine committed
433
        $exists = true;
doctrine's avatar
doctrine committed
434

doctrine's avatar
doctrine committed
435 436 437 438 439 440 441
        if($this->state == Doctrine_Record::STATE_TDIRTY ||
           $this->state == Doctrine_Record::STATE_TCLEAN)
            $exists = false;

        $this->prepareIdentifiers($exists);

        $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onWakeUp($this);
doctrine's avatar
doctrine committed
442
    }
doctrine's avatar
doctrine committed
443 444


doctrine's avatar
doctrine committed
445 446 447 448 449 450 451
    /**
     * addCollection
     * @param Doctrine_Collection $collection
     * @param mixed $key
     */
    final public function addCollection(Doctrine_Collection $collection,$key = null) {
        if($key !== null) {
452
            if(isset($this->collections[$key]))
doctrine's avatar
doctrine committed
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
                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
     * whether or not this record is part of a collection
     *
     * @return boolean
     */
    final public function hasCollections() {
        return (! empty($this->collections));
    }
    /**
     * getState
     * returns the current state of the object
     *
     * @see Doctrine_Record::STATE_* constants
     * @return integer
     */
    final public function getState() {
        return $this->state;
    }
    /**
488
     * refresh
doctrine's avatar
doctrine committed
489 490 491 492 493
     * refresh internal data from the database
     *
     * @return boolean
     */
    final public function refresh() {
494
        $id = $this->obtainIdentifier();
doctrine's avatar
doctrine committed
495 496 497 498 499 500 501 502
        if( ! is_array($id))
            $id = array($id);

        if(empty($id))
            return false;

        $id = array_values($id);

503
        $query          = $this->table->getQuery()." WHERE ".implode(" = ? AND ",$this->table->getPrimaryKeys())." = ?";
504 505 506 507
        $stmt           = $this->table->getConnection()->execute($query,$id);

        $this->data     = $stmt->fetch(PDO::FETCH_ASSOC);

doctrine's avatar
doctrine committed
508

zYne's avatar
zYne committed
509 510
        if( ! $this->data)
            throw new Doctrine_Record_Exception('Failed to refresh. Record does not exist anymore');
511

zYne's avatar
zYne committed
512 513
        $this->data     = array_change_key_case($this->data, CASE_LOWER);

doctrine's avatar
doctrine committed
514
        $this->modified = array();
zYne's avatar
zYne committed
515
        $this->cleanData(true);
doctrine's avatar
doctrine committed
516 517 518 519 520

        $this->prepareIdentifiers();

        $this->state    = Doctrine_Record::STATE_CLEAN;

521 522
        $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);

doctrine's avatar
doctrine committed
523 524 525 526
        return true;
    }
    /**
     * factoryRefresh
doctrine's avatar
doctrine committed
527 528
     * refreshes the data from outer source (Doctrine_Table)
     *
doctrine's avatar
doctrine committed
529 530 531 532
     * @throws Doctrine_Exception
     * @return void
     */
    final public function factoryRefresh() {
doctrine's avatar
doctrine committed
533
        $this->data = $this->table->getData();
534 535 536 537
        $old  = $this->id;

        $this->cleanData();

doctrine's avatar
doctrine committed
538 539
        $this->prepareIdentifiers();

540
        if($this->id != $old)
541
            throw new Doctrine_Record_Exception("The refreshed primary key doesn't match the one in the record memory.", Doctrine::ERR_REFRESH);
doctrine's avatar
doctrine committed
542 543 544

        $this->state    = Doctrine_Record::STATE_CLEAN;
        $this->modified = array();
545 546

        $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);
doctrine's avatar
doctrine committed
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
    }
    /**
     * 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;
    }
562 563 564 565 566 567 568 569 570 571 572
    /**
     * rawGet
     * returns the value of a property, if the property is not yet loaded
     * this method does NOT load it
     *
     * @param $name                     name of the property
     * @return mixed
     */
    public function rawGet($name) {
        if( ! isset($this->data[$name]))
            throw new InvalidKeyException();
573

574
        if($this->data[$name] === self::$null)
575 576 577 578
            return null;

        return $this->data[$name];
    }
579 580 581 582 583 584 585 586 587 588 589 590 591
    /**
     * load
     * loads all the unitialized properties from the database
     *
     * @return boolean
     */
    public function load() {
        // only load the data from database if the Doctrine_Record is in proxy state
        if($this->state == Doctrine_Record::STATE_PROXY) {
            if( ! empty($this->collections)) {
                // delegate the loading operation to collections in which this record resides
                foreach($this->collections as $collection) {
                    $collection->load($this);
592

593 594
                }
            } else {
595

596 597 598 599 600 601 602 603
                $this->refresh();
            }
            $this->state = Doctrine_Record::STATE_CLEAN;

            return true;
        }
        return false;
    }
doctrine's avatar
doctrine committed
604 605
    /**
     * get
606
     * returns a value of a property or a related component
doctrine's avatar
doctrine committed
607
     *
608 609 610
     * @param mixed $name                       name of the property or related component
     * @param boolean $invoke                   whether or not to invoke the onGetProperty listener
     * @throws Doctrine_Exception
doctrine's avatar
doctrine committed
611 612
     * @return mixed
     */
613 614 615
    public function get($name, $invoke = true) {
        $listener = $this->table->getAttribute(Doctrine::ATTR_LISTENER);
        $value    = self::$null;
616
        $lower    = strtolower($name);
617

618
        if(isset($this->data[$lower])) {
doctrine's avatar
doctrine committed
619 620

            // check if the property is null (= it is the Doctrine_Null object located in self::$null)
621
            if($this->data[$lower] === self::$null) {
622
                $this->load();
623 624
            }
            
625
            if($this->data[$lower] === self::$null)
626 627
                $value = null;
            else
628
                $value = $this->data[$lower];
doctrine's avatar
doctrine committed
629

630
        }
631

doctrine's avatar
doctrine committed
632

633
        if($value !== self::$null) {
634
            if($invoke && $name !== $this->table->getIdentifier()) {
635 636 637 638
                return $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onGetProperty($this, $name, $value);
            } else
                return $value;
        }
doctrine's avatar
doctrine committed
639

640

641 642
        if(isset($this->id[$lower]))
            return $this->id[$lower];
643 644 645 646

        if($name === $this->table->getIdentifier())
            return null;

doctrine's avatar
doctrine committed
647
        if( ! isset($this->references[$name]))
648
            $this->loadReference($name);
doctrine's avatar
doctrine committed
649 650 651 652


        return $this->references[$name];
    }
doctrine's avatar
doctrine committed
653 654
    /**
     * internalSet
655 656 657
     *
     * @param mixed $name
     * @param mixed $value
doctrine's avatar
doctrine committed
658 659
     */
    final public function internalSet($name, $value) {
zYne's avatar
zYne committed
660 661 662
        if($value === null)
            $value = self::$null;

doctrine's avatar
doctrine committed
663 664
        $this->data[$name] = $value;
    }
doctrine's avatar
doctrine committed
665 666 667 668
    /**
     * rawSet
     * doctrine uses this function internally, not recommended for developers
     *
669
     * rawSet() works in very same same way as set() with an exception that
zYne's avatar
zYne committed
670 671 672
     * 1. it cannot be used for setting references
     * 2. it cannot load uninitialized fields
     *
doctrine's avatar
doctrine committed
673 674 675 676 677
     * @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)
678
            $id = $value->getIncremented();
doctrine's avatar
doctrine committed
679

680
        if(isset($id))
doctrine's avatar
doctrine committed
681
            $value = $id;
682

doctrine's avatar
doctrine committed
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
        if(isset($this->data[$name])) {
            if($this->data[$name] === self::$null) {
                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;
                }
            }

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

zYne's avatar
zYne committed
699 700 701
            if($value === null)
                $value = self::$null;

doctrine's avatar
doctrine committed
702
            $this->data[$name] = $value;
doctrine's avatar
doctrine committed
703
            $this->modified[]  = $name;
doctrine's avatar
doctrine committed
704 705
        }
    }
706

doctrine's avatar
doctrine committed
707 708 709 710 711 712 713 714 715 716 717
    /**
     * 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) {
718 719 720
        $lower = strtolower($name);

        if(isset($this->data[$lower])) {
doctrine's avatar
doctrine committed
721 722

            if($value instanceof Doctrine_Record) {
723
                $id = $value->getIncremented();
724

725 726
                if($id !== null)
                    $value = $id;
doctrine's avatar
doctrine committed
727
            }
728

729
            $old = $this->get($lower, false);
doctrine's avatar
doctrine committed
730 731

            if($old !== $value) {
732 733 734 735
                
                // invoke the onPreSetProperty listener
                $value = $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreSetProperty($this, $name, $value);

zYne's avatar
zYne committed
736 737 738
                if($value === null)
                    $value = self::$null;

739 740
                $this->data[$lower] = $value;
                $this->modified[]   = $lower;
doctrine's avatar
doctrine committed
741 742 743 744 745 746 747 748 749 750 751 752 753
                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

754
            $fk = $this->table->getRelation($name);
doctrine's avatar
doctrine committed
755 756

            // one-to-many or one-to-one relation
doctrine's avatar
doctrine committed
757
            if($fk instanceof Doctrine_ForeignKey ||
doctrine's avatar
doctrine committed
758 759 760 761 762 763
               $fk instanceof Doctrine_LocalKey) {
                switch($fk->getType()):
                    case Doctrine_Relation::MANY_COMPOSITE:
                    case Doctrine_Relation::MANY_AGGREGATE:
                        // one-to-many relation found
                        if( ! ($value instanceof Doctrine_Collection))
764
                            throw new Doctrine_Record_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
765 766 767 768 769 770 771

                        $value->setReference($this,$fk);
                    break;
                    case Doctrine_Relation::ONE_COMPOSITE:
                    case Doctrine_Relation::ONE_AGGREGATE:
                        // one-to-one relation found
                        if( ! ($value instanceof Doctrine_Record))
772
                            throw new Doctrine_Record_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
773 774 775 776 777 778 779 780 781 782 783 784

                        if($fk->getLocal() == $this->table->getIdentifier()) {
                            $this->references[$name]->set($fk->getForeign(),$this);
                        } else {
                            $this->set($fk->getLocal(),$value);
                        }
                    break;
                endswitch;

            } elseif($fk instanceof Doctrine_Association) {
                // join table relation found
                if( ! ($value instanceof Doctrine_Collection))
785
                    throw new Doctrine_Record_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
786 787 788 789 790 791
            }

            $this->references[$name] = $value;
        }
    }
    /**
792
     * contains
doctrine's avatar
doctrine committed
793 794 795 796
     *
     * @param string $name
     * @return boolean
     */
797
    public function contains($name) {
doctrine's avatar
doctrine committed
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
        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 ?
    }
    /**
     * 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() {
zYne's avatar
zYne committed
826
        $this->table->getConnection()->beginTransaction();
doctrine's avatar
doctrine committed
827

zYne's avatar
zYne committed
828
        $saveLater = $this->table->getConnection()->saveRelated($this);
doctrine's avatar
doctrine committed
829

zYne's avatar
zYne committed
830
        $this->table->getConnection()->save($this);
doctrine's avatar
doctrine committed
831 832 833 834 835 836 837 838 839 840 841 842 843 844

        foreach($saveLater as $fk) {
            $table   = $fk->getTable();
            $alias   = $this->table->getAlias($table->getComponentName());

            if(isset($this->references[$alias])) {
                $obj = $this->references[$alias];
                $obj->save();
            }
        }

        // save the MANY-TO-MANY associations

        $this->saveAssociations();
845

zYne's avatar
zYne committed
846
        $this->table->getConnection()->commit();
doctrine's avatar
doctrine committed
847 848 849 850 851 852 853 854
    }
    /**
     * returns an array of modified fields and associated values
     * @return array
     */
    final public function getModified() {
        $a = array();

doctrine's avatar
doctrine committed
855
        foreach($this->modified as $k => $v) {
doctrine's avatar
doctrine committed
856 857 858 859 860 861 862 863 864 865
            $a[$v] = $this->data[$v];
        }
        return $a;
    }
    /**
     * returns an array of modified fields and values with data preparation
     * adds column aggregation inheritance and converts Records into primary key values
     *
     * @return array
     */
doctrine's avatar
doctrine committed
866
    final public function getPrepared(array $array = array()) {
doctrine's avatar
doctrine committed
867 868
        $a = array();

doctrine's avatar
doctrine committed
869 870 871 872
        if(empty($array))
            $array = $this->modified;

        foreach($array as $k => $v) {
doctrine's avatar
doctrine committed
873
            $type = $this->table->getTypeOf($v);
874

zYne's avatar
zYne committed
875 876 877 878
            switch($type) {
                case 'array':
                case 'object':
                    $a[$v] = serialize($this->data[$v]);
zYne's avatar
zYne committed
879 880 881 882
                break;
                case 'gzip':
                    $a[$v] = gzcompress($this->data[$v],5);
                break;
zYne's avatar
zYne committed
883
                case 'enum':
zYne's avatar
zYne committed
884 885 886 887 888
                    $a[$v] = $this->table->enumIndex($v,$this->data[$v]);
                break;
                default:
                    if($this->data[$v] instanceof Doctrine_Record)
                        $this->data[$v] = $this->data[$v]->getIncremented();
doctrine's avatar
doctrine committed
889

890

zYne's avatar
zYne committed
891 892 893 894 895
                    if($this->data[$v] === self::$null)
                        $a[$v] = null;
                    else
                        $a[$v] = $this->data[$v];
            }
doctrine's avatar
doctrine committed
896 897
        }

doctrine's avatar
doctrine committed
898 899 900 901 902 903 904 905 906
        foreach($this->table->getInheritanceMap() as $k => $v) {
            $old = $this->get($k);

            if((string) $old !== (string) $v || $old === null) {
                $a[$k] = $v;
                $this->data[$k] = $v;
            }
        }

doctrine's avatar
doctrine committed
907 908 909 910 911 912 913 914 915
        return $a;
    }
    /**
     * this class implements countable interface
     * @return integer                      the number of columns
     */
    public function count() {
        return count($this->data);
    }
916 917 918 919 920 921
    /**
     * alias for count()
     */
    public function getColumnCount() {
        return $this->count();
    }
zYne's avatar
zYne committed
922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
    /**
     * toArray
     * returns record as an array
     * 
     * @return array
     */
    public function toArray() {
        $a = array();

        foreach($this as $column => $value) {
            $a[$column] = $value;
        }
        if($this->table->getIdentifierType() == Doctrine_Identifier::AUTO_INCREMENT) {
            $i      = $this->table->getIdentifier();
            $a[$i]  = $this->getIncremented();
        }
        return $a;
    }
940 941 942 943
    /**
     * checks if record has data
     * @return boolean
     */
944
    public function exists() {
945
        return ($this->state !== Doctrine_Record::STATE_TCLEAN &&
946
                $this->state !== Doctrine_Record::STATE_TDIRTY);
947 948 949 950 951 952 953 954 955
    }
    /**
     * method for checking existence of properties and Doctrine_Record references
     * @param mixed $name               name of the property or reference
     * @return boolean
     */
    public function hasRelation($name) {
        if(isset($this->data[$name]) || isset($this->id[$name]))
            return true;
956
        return $this->table->hasRelation($name);
957
    }
doctrine's avatar
doctrine committed
958 959
    /**
     * getIterator
960
     * @return Doctrine_Record_Iterator     a Doctrine_Record_Iterator that iterates through the data
doctrine's avatar
doctrine committed
961 962
     */
    public function getIterator() {
963
        return new Doctrine_Record_Iterator($this);
doctrine's avatar
doctrine committed
964 965 966 967 968 969 970 971
    }
    /**
     * 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() {
972
        foreach($this->table->getRelations() as $fk):
doctrine's avatar
doctrine committed
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
            $table   = $fk->getTable();
            $name    = $table->getComponentName();
            $alias   = $this->table->getAlias($name);

            if($fk instanceof Doctrine_Association) {
                switch($fk->getType()):
                    case Doctrine_Relation::MANY_COMPOSITE:

                    break;
                    case Doctrine_Relation::MANY_AGGREGATE:
                        $asf     = $fk->getAssociationFactory();

                        if(isset($this->references[$alias])) {

                            $new = $this->references[$alias];

                            if( ! isset($this->originals[$alias])) {
                                $this->loadReference($alias);
                            }

993
                            $r = Doctrine_Relation::getDeleteOperations($this->originals[$alias],$new);
doctrine's avatar
doctrine committed
994

995
                            foreach($r as $record) {
doctrine's avatar
doctrine committed
996
                                $query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?"
997
                                                                            ." AND ".$fk->getLocal()." = ?";
zYne's avatar
zYne committed
998
                                $this->table->getConnection()->execute($query, array($record->getIncremented(),$this->getIncremented()));
doctrine's avatar
doctrine committed
999
                            }
1000

1001 1002
                            $r = Doctrine_Relation::getInsertOperations($this->originals[$alias],$new);
                            foreach($r as $record) {
doctrine's avatar
doctrine committed
1003 1004 1005 1006
                                $reldao = $asf->create();
                                $reldao->set($fk->getForeign(),$record);
                                $reldao->set($fk->getLocal(),$this);
                                $reldao->save();
1007

1008
                            }
doctrine's avatar
doctrine committed
1009 1010 1011 1012
                            $this->originals[$alias] = clone $this->references[$alias];
                        }
                    break;
                endswitch;
1013
            } elseif($fk instanceof Doctrine_ForeignKey ||
doctrine's avatar
doctrine committed
1014
                     $fk instanceof Doctrine_LocalKey) {
1015

doctrine's avatar
doctrine committed
1016 1017
                switch($fk->getType()):
                    case Doctrine_Relation::ONE_COMPOSITE:
1018
                        if(isset($this->originals[$alias]) && $this->originals[$alias]->obtainIdentifier() != $this->references[$alias]->obtainIdentifier())
doctrine's avatar
doctrine committed
1019
                            $this->originals[$alias]->delete();
1020

doctrine's avatar
doctrine committed
1021 1022 1023 1024 1025 1026 1027 1028
                    break;
                    case Doctrine_Relation::MANY_COMPOSITE:
                        if(isset($this->references[$alias])) {
                            $new = $this->references[$alias];

                            if( ! isset($this->originals[$alias]))
                                $this->loadReference($alias);

1029
                            $r = Doctrine_Relation::getDeleteOperations($this->originals[$alias], $new);
doctrine's avatar
doctrine committed
1030

1031
                            foreach($r as $record) {
doctrine's avatar
doctrine committed
1032 1033
                                $record->delete();
                            }
1034

doctrine's avatar
doctrine committed
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
                            $this->originals[$alias] = clone $this->references[$alias];
                        }
                    break;
                endswitch;
            }
        endforeach;
    }
    /**
     * 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
     */
    public function delete() {
zYne's avatar
zYne committed
1060
        return $this->table->getConnection()->delete($this);
doctrine's avatar
doctrine committed
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
    }
    /**
     * returns a copy of this object
     * @return DAO
     */
    public function copy() {
        return $this->table->create($this->data);
    }
    /**
     * @param integer $id
     * @return void
     */
1073
    final public function assignIdentifier($id = false) {
doctrine's avatar
doctrine committed
1074
        if($id === false) {
1075
            $this->id       = array();
doctrine's avatar
doctrine committed
1076 1077 1078 1079 1080 1081 1082 1083
            $this->cleanData();
            $this->state    = Doctrine_Record::STATE_TCLEAN;
            $this->modified = array();
        } elseif($id === true) {
            $this->prepareIdentifiers(false);
            $this->state    = Doctrine_Record::STATE_CLEAN;
            $this->modified = array();
        } else {
doctrine's avatar
doctrine committed
1084
            $name            = $this->table->getIdentifier();
1085

1086
            $this->id[$name] = $id;
doctrine's avatar
doctrine committed
1087 1088
            $this->state     = Doctrine_Record::STATE_CLEAN;
            $this->modified  = array();
doctrine's avatar
doctrine committed
1089 1090 1091
        }
    }
    /**
1092 1093 1094
     * returns the primary keys of this object
     *
     * @return array
doctrine's avatar
doctrine committed
1095
     */
1096
    final public function obtainIdentifier() {
doctrine's avatar
doctrine committed
1097 1098
        return $this->id;
    }
1099 1100 1101 1102 1103 1104 1105 1106 1107
    /**
     * returns the value of autoincremented primary key of this object (if any)
     *
     * @return integer
     */
    final public function getIncremented() {
        $id = current($this->id);
        if($id === false)
            return null;
1108

1109 1110
        return $id;
    }
doctrine's avatar
doctrine committed
1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
    /**
     * 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;
    }
    /**
     * hasRefence
     * @param string $name
     * @return boolean
     */
    public function hasReference($name) {
        return isset($this->references[$name]);
    }
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
    /**
     * obtainReference
     * 
     * @param string $name
     */
    public function obtainReference($name) {
        if(isset($this->references[$name]))
            return $this->references[$name];
    
        throw new Doctrine_Record_Exception("Unknown reference $name");
    }
doctrine's avatar
doctrine committed
1141 1142 1143 1144 1145 1146 1147
    /**
     * initalizes a one-to-one relation
     *
     * @param Doctrine_Record $record
     * @param Doctrine_Relation $connector
     * @return void
     */
1148 1149 1150 1151
    public function initSingleReference(Doctrine_Record $record, Doctrine_Relation $connector) {
        $alias = $connector->getAlias();

        $this->references[$alias] = $record;
doctrine's avatar
doctrine committed
1152 1153 1154 1155 1156 1157 1158 1159 1160
    }
    /**
     * initalizes a one-to-many / many-to-many relation
     *
     * @param Doctrine_Collection $coll
     * @param Doctrine_Relation $connector
     * @return void
     */
    public function initReference(Doctrine_Collection $coll, Doctrine_Relation $connector) {
doctrine's avatar
doctrine committed
1161
        $alias = $connector->getAlias();
1162

1163 1164 1165
        if( ! ($connector instanceof Doctrine_Association))
            $coll->setReference($this, $connector);

doctrine's avatar
doctrine committed
1166 1167
        $this->references[$alias] = $coll;
        $this->originals[$alias]  = clone $coll;
doctrine's avatar
doctrine committed
1168 1169 1170 1171 1172 1173 1174
    }
    /**
     * addReference
     * @param Doctrine_Record $record
     * @param mixed $key
     * @return void
     */
doctrine's avatar
doctrine committed
1175 1176
    public function addReference(Doctrine_Record $record, Doctrine_Relation $connector, $key = null) {
        $alias = $connector->getAlias();
doctrine's avatar
doctrine committed
1177

1178 1179
        $this->references[$alias]->internalAdd($record, $key);
        $this->originals[$alias]->internalAdd($record, $key);
doctrine's avatar
doctrine committed
1180 1181 1182 1183 1184 1185 1186 1187 1188
    }
    /**
     * getReferences
     * @return array    all references
     */
    public function getReferences() {
        return $this->references;
    }
    /**
1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
     * setRelated
     *
     * @param string $alias
     * @param Doctrine_Access $coll
     */
    final public function setRelated($alias, Doctrine_Access $coll) {
        $this->references[$alias] = $coll;
        $this->originals[$alias]  = $coll;
    }
    /**
     * loadReference
     * loads a related component
     *
doctrine's avatar
doctrine committed
1202
     * @throws InvalidKeyException
1203
     * @param string $name
doctrine's avatar
doctrine committed
1204 1205 1206
     * @return void
     */
    final public function loadReference($name) {
1207

1208
        $fk      = $this->table->getRelation($name);
doctrine's avatar
doctrine committed
1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
        $table   = $fk->getTable();

        $local   = $fk->getLocal();
        $foreign = $fk->getForeign();
        $graph   = $table->getQueryObject();
        $type    = $fk->getType();

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

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

                    // 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);
                    }
                    $this->originals[$name]  = new Doctrine_Collection($table);
                }
            break;
            case Doctrine_Record::STATE_DIRTY:
            case Doctrine_Record::STATE_CLEAN:
            case Doctrine_Record::STATE_PROXY:

                 switch($fk->getType()):
                    case Doctrine_Relation::ONE_COMPOSITE:
                    case Doctrine_Relation::ONE_AGGREGATE:

                        // ONE-TO-ONE
                        $id      = $this->get($local);

                        if($fk instanceof Doctrine_LocalKey) {

                            if(empty($id)) {
                                $this->references[$name] = $table->create();
                                $this->set($fk->getLocal(),$this->references[$name]);
                            } else {
1257 1258 1259

                                $record = $table->find($id);

1260
                                if($record !== false)
1261 1262
                                    $this->references[$name] = $record;
                                else
doctrine's avatar
doctrine committed
1263
                                    $this->references[$name] = $table->create();
1264

doctrine's avatar
doctrine committed
1265
                                    //$this->set($fk->getLocal(),$this->references[$name]);
1266

doctrine's avatar
doctrine committed
1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282
                            }

                        } elseif ($fk instanceof Doctrine_ForeignKey) {

                            if(empty($id)) {
                                $this->references[$name] = $table->create();
                                $this->references[$name]->set($fk->getForeign(), $this);
                            } else {
                                $dql  = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$fk->getForeign()." = ?";
                                $coll = $graph->query($dql, array($id));
                                $this->references[$name] = $coll[0];
                                $this->references[$name]->set($fk->getForeign(), $this);
                            }
                        }
                    break;
                    default:
pookey's avatar
pookey committed
1283 1284
                        $query   = $fk->getRelationDql(1);

doctrine's avatar
doctrine committed
1285 1286 1287
                        // ONE-TO-MANY
                        if($fk instanceof Doctrine_ForeignKey) {
                            $id      = $this->get($local);
pookey's avatar
pookey committed
1288 1289
                            $coll    = $graph->query($query,array($id));
                            $coll->setReference($this, $fk);
1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320
                        } elseif($fk instanceof Doctrine_Association_Self) {
                            $id      = $this->getIncremented();

                            $q = new Doctrine_RawSql();

                            $assocTable = $fk->getAssociationFactory()->getTableName();
                            $tableName  = $this->getTable()->getTableName();
                            $identifier = $this->getTable()->getIdentifier();

                            $sub     = "SELECT ".$fk->getForeign().
                                           " FROM ".$assocTable.
                                           " WHERE ".$fk->getLocal().
                                           " = ?";

                            $sub2   = "SELECT ".$fk->getLocal().
                                          " FROM ".$assocTable.
                                          " WHERE ".$fk->getForeign().
                                          " = ?";

                            $q->select('{'.$tableName.'.*}, {'.$assocTable.'.*}')
                                ->from($tableName.' INNER JOIN '.$assocTable.' ON '.
                                         $tableName.'.'.$identifier.' = '.$assocTable.'.'.$fk->getLocal().' OR '.
                                         $tableName.'.'.$identifier.' = '.$assocTable.'.'.$fk->getForeign()
                                         )
                                ->where($tableName.'.'.$identifier.' IN ('.$sub.') OR '.
                                          $tableName.'.'.$identifier.' IN ('.$sub2.')'
                                         );
                            $q->addComponent($tableName, $this->table->getComponentName());
                            $q->addComponent($assocTable, $this->table->getComponentName().'.'.$fk->getAssociationFactory()->getComponentName());

                            $coll    = $q->execute(array($id, $id));
1321
                        } elseif($fk instanceof Doctrine_Association) {
pookey's avatar
pookey committed
1322 1323
                            $id      = $this->getIncremented();
                            $coll    = $graph->query($query, array($id));
doctrine's avatar
doctrine committed
1324
                        }
pookey's avatar
pookey committed
1325 1326
                        $this->references[$name] = $coll;
                        $this->originals[$name]  = clone $coll;
doctrine's avatar
doctrine committed
1327 1328 1329 1330
                 endswitch;
            break;
        endswitch;
    }
1331 1332 1333 1334
    /**
     * filterRelated
     * lazy initializes a new filter instance for given related component
     *
1335
     * @param $componentAlias        alias of the related component
1336 1337
     * @return Doctrine_Filter
     */
1338 1339 1340
    final public function filterRelated($componentAlias) {
        if( ! isset($this->filters[$componentAlias])) {
            $this->filters[$componentAlias] = new Doctrine_Filter($componentAlias);
1341 1342
        }

1343
        return $this->filters[$componentAlias];
1344
    }
doctrine's avatar
doctrine committed
1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404
    /**
     * binds One-to-One composite relation
     *
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
    final public function ownsOne($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_COMPOSITE, $localKey);
    }
    /**
     * binds One-to-Many composite relation
     *
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
    final public function ownsMany($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::MANY_COMPOSITE, $localKey);
    }
    /**
     * binds One-to-One aggregate relation
     *
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
    final public function hasOne($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_AGGREGATE, $localKey);
    }
    /**
     * binds One-to-Many aggregate relation
     *
     * @param string $objTableName
     * @param string $fkField
     * @return void
     */
    final public function hasMany($componentName,$foreignKey, $localKey = null) {
        $this->table->bind($componentName,$foreignKey,Doctrine_Relation::MANY_AGGREGATE, $localKey);
    }
    /**
     * setPrimaryKey
     * @param mixed $key
     */
    final public function setPrimaryKey($key) {
        $this->table->setPrimaryKey($key);
    }
    /**
     * hasColumn
     * sets a column definition
     *
     * @param string $name
     * @param string $type
     * @param integer $length
     * @param mixed $options
     * @return void
     */
    final public function hasColumn($name, $type, $length = 20, $options = "") {
        $this->table->setColumn($name, $type, $length, $options);
    }
zYne's avatar
zYne committed
1405 1406
    /**
     * countRelated
1407
     *
zYne's avatar
zYne committed
1408
     * @param string $name      the name of the related component
1409
     * @return integer
zYne's avatar
zYne committed
1410 1411
     */
    public function countRelated($name) {
1412
        $rel            = $this->table->getRelation($name);
1413 1414 1415 1416 1417 1418
        $componentName  = $rel->getTable()->getComponentName();
        $alias          = $rel->getTable()->getAlias(get_class($this));
        $query          = new Doctrine_Query();
        $query->from($componentName. '(' . 'COUNT(1)' . ')')->where($componentName. '.' .$alias. '.' . $this->getTable()->getIdentifier(). ' = ?');
        $array = $query->execute(array($this->getIncremented()));
        return $array[0]['COUNT(1)'];
zYne's avatar
zYne committed
1419
    }
1420 1421
    /**
     * merge
1422
     * merges this record with an array of values
1423 1424 1425 1426 1427 1428
     *
     * @param array $values
     */
    public function merge(array $values) {
        foreach($this->table->getColumnNames() as $value) {
            try {
1429 1430
                if(isset($values[$value]))
                    $this->set($value, $values[$value]);
1431 1432 1433
            } catch(Exception $e) { }
        }
    }
doctrine's avatar
doctrine committed
1434 1435 1436 1437 1438 1439
    /**
     * __call
     * @param string $m
     * @param array $a
     */
    public function __call($m,$a) {
pookey's avatar
pookey committed
1440 1441 1442
        if(method_exists($this->table, $m))
            return call_user_func_array(array($this->table, $m), $a);

doctrine's avatar
doctrine committed
1443
        if( ! function_exists($m))
doctrine's avatar
doctrine committed
1444
            throw new Doctrine_Record_Exception("unknown callback '$m'");
1445

doctrine's avatar
doctrine committed
1446 1447
        if(isset($a[0])) {
            $column = $a[0];
doctrine's avatar
doctrine committed
1448
            $a[0] = $this->get($column);
doctrine's avatar
doctrine committed
1449 1450

            $newvalue = call_user_func_array($m, $a);
1451

pookey's avatar
pookey committed
1452
            $this->data[$column] = $newvalue;
doctrine's avatar
doctrine committed
1453 1454 1455
        }
        return $this;
    }
doctrine's avatar
doctrine committed
1456 1457 1458 1459 1460 1461 1462
    /**
     * returns a string representation of this object
     */
    public function __toString() {
        return Doctrine_Lib::getRecordAsString($this);
    }
}
1463