Entity.php 30.6 KB
Newer Older
1 2
<?php
/*
3
 *  $Id: Record.php 4342 2008-05-08 14:17:35Z romanb $
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * 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
19
 * <http://www.phpdoctrine.org>.
20
 */
21

22 23
#namespace Doctrine::ORM;

24
/**
25 26
 * Base class for all Entities (objects with persistent state in a RDBMS that are
 * managed by Doctrine).
27 28 29 30
 * 
 * NOTE: Methods that are intended for internal use only but must be public
 * are marked INTERNAL: and begin with an underscore "_" to indicate that they
 * ideally would not be public and to minimize naming collisions.
31 32 33 34
 * 
 * The "final" modifiers on most methods prevent accidental overrides.
 * It is not desirable that subclasses can override these methods.
 * The persistence layer should stay in the background as much as possible.
35 36
 *
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
37
 * @author      Roman Borschel <roman@code-factory.org>
38
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
39
 * @link        www.phpdoctrine.org
40 41
 * @since       2.0
 * @version     $Revision: 4342 $
42
 */
43
abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
44 45
{
    /**
46 47 48
     * MANAGED
     * An Entity is in managed state when it has a primary key/identifier and is
     * managed by an EntityManager (registered in the identity map).
49
     */
50
    const STATE_MANAGED = 1;
51

52
    /**
53 54 55
     * NEW
     * An Entity is new if it does not yet have an identifier/primary key
     * and is not (yet) managed by an EntityManager.
56
     */
57
    const STATE_NEW = 2;
58

59
    /**
60
     * LOCKED STATE
61
     * An Entity is temporarily locked during deletes and saves.
zYne's avatar
zYne committed
62 63
     *
     * This state is used internally to ensure that circular deletes
64
     * and saves will not cause infinite loops.
65 66
     * @todo Not sure this is a good idea. It is a problematic solution because
     * it hides the original state while the locked state is active.
67
     */
68
    const STATE_LOCKED = 6;
69
    
70 71 72 73 74
    /**
     * A detached Entity is an instance with a persistent identity that is not
     * (or no longer) associated with an EntityManager (and a UnitOfWork).
     * This means its no longer in the identity map.
     */
75
    const STATE_DETACHED = 3;
76 77 78
    
    /**
     * A removed Entity instance is an instance with a persistent identity,
romanb's avatar
romanb committed
79 80
     * associated with an EntityManager, whose persistent state has been
     * deleted (or is scheduled for deletion).
81
     */
82
    const STATE_DELETED = 4;
83
    
84 85 86
    /**
     * Index used for creating object identifiers (oid's).
     *
87
     * @var integer
88 89 90 91
     */
    private static $_index = 1;
    
    /**
92
     * Boolean flag that indicates whether automatic accessor overriding is enabled.
romanb's avatar
romanb committed
93 94
     *
     * @var boolean
95 96 97 98
     */
    private static $_useAutoAccessorOverride;
    
    /**
99
     * The accessor cache is used as a memory for the existance of custom accessors.
romanb's avatar
romanb committed
100 101
     *
     * @var array
102 103 104 105
     */
    private static $_accessorCache = array();
    
    /**
106
     * The mutator cache is used as a memory for the existance of custom mutators.
romanb's avatar
romanb committed
107 108
     *
     * @var array
109 110
     */
    private static $_mutatorCache = array();
romanb's avatar
romanb committed
111 112
    
    /**
113
     * The class descriptor.
romanb's avatar
romanb committed
114
     *
romanb's avatar
romanb committed
115
     * @var Doctrine::ORM::ClassMetadata
romanb's avatar
romanb committed
116
     */
117
    private $_class;
romanb's avatar
romanb committed
118
    
119
    /**
120 121 122
     * The name of the Entity.
     * 
     * @var string
123
     */
124
    private $_entityName;
125

126
    /**
127
     * The values that make up the ID/primary key of the entity.
128 129
     *
     * @var array                   
130
     */
131
    private $_id = array();
132

133
    /**
134
     * The entity data.
135 136
     *
     * @var array                  
137
     */
138
    private $_data = array();
139

140
    /**
141 142
     * The state of the object.
     *
143
     * @var integer
144
     */
145
    private $_state;
146

147
    /**
148
     * The names of fields that have been modified but not yet persisted.
149
     * Keys are field names, values oldValue => newValue tuples.
150
     *
151 152
     * @var array
     * @todo Rename to $_changeSet
153
     */
154
    private $_modified = array();
155

156
    /**
romanb's avatar
romanb committed
157
     * The references for all associations of the entity to other entities.
158
     *
romanb's avatar
romanb committed
159
     * @var array
160
     */
161
    private $_references = array();
162 163 164 165
    
    /**
     * The EntityManager that is responsible for the persistence of the entity.
     *
romanb's avatar
romanb committed
166
     * @var Doctrine::ORM::EntityManager
167
     */
168
    private $_em;
169

170
    /**
171 172
     * The object identifier of the object. Each object has a unique identifier
     * during script execution.
173
     * 
romanb's avatar
romanb committed
174
     * @var integer                  
175
     */
zYne's avatar
zYne committed
176
    private $_oid;
177 178

    /**
179
     * Constructor.
180
     * Creates a new Entity instance.
181
     */
182
    public function __construct()
183
    {
184
        $this->_entityName = get_class($this);
185
        $this->_em = Doctrine_EntityManagerFactory::getManager($this->_entityName);
186 187
        $this->_class = $this->_em->getClassMetadata($this->_entityName);
        $this->_oid = self::$_index++;
188 189 190
        $this->_data = $this->_em->_getTmpEntityData();
        if ($this->_data) {
            $this->_extractIdentifier();
191
            $this->_state = self::STATE_MANAGED;
192
        } else {
193
            $this->_state = self::STATE_NEW;
194
        }
195
        
196 197
        // @todo read from attribute the first time and move this initialization elsewhere.
        self::$_useAutoAccessorOverride = true; 
198
    }
199
    
200
    /**
201
     * Returns the object identifier.
202 203 204
     *
     * @return integer
     */
205
    final public function getOid()
206
    {
zYne's avatar
zYne committed
207
        return $this->_oid;
208
    }
209

210
    /**
211
     * Copies the identifier names and values from _data into _id.
212
     */
213
    private function _extractIdentifier()
214
    {
215 216 217 218 219 220 221 222 223 224 225 226 227 228
        if ( ! $this->_class->isIdentifierComposite()) {
            // Single field identifier
            $name = $this->_class->getIdentifier();
            $name = $name[0];
            if (isset($this->_data[$name]) && $this->_data[$name] !== Doctrine_Null::$INSTANCE) {
                $this->_id[$name] = $this->_data[$name];
            }
        } else {
            // Composite identifier
            $names = $this->_class->getIdentifier();
            foreach ($names as $name) {
                if ($this->_data[$name] === Doctrine_Null::$INSTANCE) {
                    $this->_id[$name] = null;
                } else {
229
                    $this->_id[$name] = $this->_data[$name];
230
                }
231
            }
zYne's avatar
zYne committed
232
        }
233
    }
234

235
    /**
236 237 238 239
     * Serializes the entity.
     * This method is automatically called when the entity is serialized.
     *
     * Part of the implementation of the Serializable interface.
240 241 242 243 244
     *
     * @return array
     */
    public function serialize()
    {
245 246
        //$this->_em->getEventManager()->dispatchEvent(Event::preSerialize);
        //$this->_class->dispatchLifecycleEvent(Event::preSerialize, $this);
247 248 249

        $vars = get_object_vars($this);

zYne's avatar
zYne committed
250
        unset($vars['_references']);
251
        unset($vars['_em']);
252

253
        //$name = (array)$this->_table->getIdentifier();
254 255 256
        $this->_data = array_merge($this->_data, $this->_id);

        foreach ($this->_data as $k => $v) {
romanb's avatar
romanb committed
257
            if ($v instanceof Doctrine_Entity && $this->_class->getTypeOfField($k) != 'object') {
258
                unset($vars['_data'][$k]);
259
            } else if ($v === Doctrine_Null::$INSTANCE) {
260 261
                unset($vars['_data'][$k]);
            } else {
romanb's avatar
romanb committed
262
                switch ($this->_class->getTypeOfField($k)) {
263 264
                    case 'array':
                    case 'object':
265 266
                        $vars['_data'][$k] = serialize($vars['_data'][$k]);
                        break;
267 268 269 270
                    case 'gzip':
                        $vars['_data'][$k] = gzcompress($vars['_data'][$k]);
                        break;
                    case 'enum':
romanb's avatar
romanb committed
271
                        $vars['_data'][$k] = $this->_class->enumIndex($k, $vars['_data'][$k]);
272
                        break;
zYne's avatar
zYne committed
273
                }
274 275
            }
        }
276
        
277
        $str = serialize($vars);
278

279
        //$this->postSerialize($event);
280 281

        return $str;
282
    }
283

284
    /**
285 286
     * Reconstructs the entity from it's serialized form.
     * This method is automatically called everytime the entity is unserialized.
287
     *
288
     * @param string $serialized                Doctrine_Entity as serialized string
289 290 291 292 293
     * @throws Doctrine_Record_Exception        if the cleanData operation fails somehow
     * @return void
     */
    public function unserialize($serialized)
    {
294 295
        //$event = new Doctrine_Event($this, Doctrine_Event::RECORD_UNSERIALIZE);
        //$this->preUnserialize($event);
zYne's avatar
zYne committed
296

romanb's avatar
romanb committed
297
        $this->_entityName = get_class($this);
298
        $manager = Doctrine_EntityManagerFactory::getManager($this->_entityName);
romanb's avatar
romanb committed
299
        $connection = $manager->getConnection();
300

zYne's avatar
zYne committed
301 302
        $this->_oid = self::$_index;
        self::$_index++;
303

romanb's avatar
romanb committed
304
        $this->_em = $manager;  
305 306 307

        $array = unserialize($serialized);

zYne's avatar
zYne committed
308 309
        foreach($array as $k => $v) {
            $this->$k = $v;
310
        }
311
        
312
        $this->_class = $this->_em->getClassMetadata($this->_entityName);
zYne's avatar
zYne committed
313

314
        foreach ($this->_data as $k => $v) {
romanb's avatar
romanb committed
315
            switch ($this->_class->getTypeOfField($k)) {
316 317 318 319 320 321 322 323
                case 'array':
                case 'object':
                    $this->_data[$k] = unserialize($this->_data[$k]);
                    break;
                case 'gzip':
                   $this->_data[$k] = gzuncompress($this->_data[$k]);
                    break;
                case 'enum':
romanb's avatar
romanb committed
324
                    $this->_data[$k] = $this->_class->enumValue($k, $this->_data[$k]);
325
                    break;
326

327 328
            }
        }
329

330
        $this->_extractIdentifier(!$this->isNew());
331
        
332
        //$this->postUnserialize($event);
333
    }
334

335
    /**
336
     * INTERNAL:
337
     * Gets or sets the state of this Entity.
338 339
     *
     * @param integer|string $state                 if set, this method tries to set the record state to $state
340
     * @see Doctrine_Entity::STATE_* constants
341 342 343 344
     *
     * @throws Doctrine_Record_State_Exception      if trying to set an unknown state
     * @return null|integer
     */
345
    final public function _state($state = null)
346 347 348 349
    {
        if ($state == null) {
            return $this->_state;
        }
350 351 352
        
        /* TODO: Do we really need this check? This is only for internal use after all. */
        switch ($state) {
353 354 355 356
            case self::STATE_MANAGED:
            case self::STATE_DELETED:
            case self::STATE_DETACHED:
            case self::STATE_NEW:
357
            case self::STATE_LOCKED:
358
                $this->_state = $state;
359 360 361
                break;
            default:
                throw Doctrine_Entity_Exception::invalidState($state);
362 363
        }
    }
364

365
    /**
366
     * Gets the current field values.
367
     *
368
     * @return array  The fields and their values.                     
369
     */
370
    final public function getData()
371 372 373
    {
        return $this->_data;
    }
374

375
    /**
376
     * Gets the value of a field (regular field or reference).
377
     * If the field is not yet loaded this method does NOT load it.
378 379
     *
     * @param $name                         name of the property
380
     * @throws Doctrine_Entity_Exception    if trying to get an unknown field
381 382
     * @return mixed
     */
romanb's avatar
romanb committed
383
    final protected function _get($fieldName)
384
    {
385
        if (isset($this->_data[$fieldName])) {
romanb's avatar
romanb committed
386
            return $this->_internalGetField($fieldName);
387
        } else if (isset($this->_references[$fieldName])) {
romanb's avatar
romanb committed
388
            return $this->_internalGetReference($fieldName);
389 390
        } else {
            throw Doctrine_Entity_Exception::unknownField($fieldName);
391
        }
392 393 394
    }
    
    /**
395 396 397 398 399 400 401
     * Sets the value of a field (regular field or reference).
     * If the field is not yet loaded this method does NOT load it.
     *
     * @param $name                         name of the field
     * @throws Doctrine_Entity_Exception    if trying to get an unknown field
     * @return mixed
     */
romanb's avatar
romanb committed
402
    final protected function _set($fieldName, $value)
403
    {
romanb's avatar
romanb committed
404 405 406 407
        if ($this->_class->hasField($fieldName)) {
            return $this->_internalSetField($fieldName, $value);
        } else if ($this->_class->hasRelation($fieldName)) {
            return $this->_internalSetReference($fieldName, $value);
408 409 410 411 412 413 414
        } else {
            throw Doctrine_Entity_Exception::unknownField($fieldName);
        }
    }
    
    /**
     * INTERNAL:
415 416
     * Gets the value of a field.
     * 
romanb's avatar
romanb committed
417 418 419
     * NOTE: Use of this method in userland code is strongly discouraged.
     * This method does NOT check whether the field exists.
     * _get() in extending classes should be preferred.
420 421 422
     *
     * @param string $fieldName
     * @return mixed
423
     * @todo Rename to _unsafeGetField()
424
     */
romanb's avatar
romanb committed
425
    final public function _internalGetField($fieldName)
426
    {
427
        if ($this->_data[$fieldName] === Doctrine_Null::$INSTANCE) {
428
            return null;
429 430
        }
        return $this->_data[$fieldName];
431
    }
432 433
    
    /**
434
     * INTERNAL:
435 436
     * Sets the value of a field.
     * 
romanb's avatar
romanb committed
437 438 439
     * NOTE: Use of this method in userland code is strongly discouraged.
     * This method does NOT check whether the field exists.
     * _set() in extending classes should be preferred.
440 441 442 443
     *
     * @param string $fieldName
     * @param mixed $value
     */
romanb's avatar
romanb committed
444
    final public function _internalSetField($fieldName, $value)
445 446 447 448 449 450 451
    {
        $this->_data[$fieldName] = $value;
    }
    
    /**
     * Gets a reference to another Entity.
     * 
romanb's avatar
romanb committed
452 453
     * NOTE: Use of this method in userland code is strongly discouraged.
     * This method does NOT check whether the reference exists.
454
     *
romanb's avatar
romanb committed
455
     * @param string $fieldName
456
     */
romanb's avatar
romanb committed
457
    final public function _internalGetReference($fieldName)
458 459 460 461 462 463 464 465
    {
        if ($this->_references[$fieldName] === Doctrine_Null::$INSTANCE) {
            return null;
        }
        return $this->_references[$fieldName];
    }
    
    /**
466
     * INTERNAL:
467 468
     * Sets a reference to another Entity.
     * 
romanb's avatar
romanb committed
469
     * NOTE: Use of this method in userland code is strongly discouraged.
470
     *
471 472
     * @param string $fieldName
     * @param mixed $value
473
     * @todo Refactor. What about composite keys?
474
     * @todo Rename to _unsafeSetReference()
475
     */
romanb's avatar
romanb committed
476
    final public function _internalSetReference($name, $value)
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
    {
        if ($value === Doctrine_Null::$INSTANCE) {
            $this->_references[$name] = $value;
            return;
        }
        
        $rel = $this->_class->getRelation($name);

        // one-to-many or one-to-one relation
        if ($rel instanceof Doctrine_Relation_ForeignKey ||
                $rel instanceof Doctrine_Relation_LocalKey) {
            if ( ! $rel->isOneToOne()) {
                // one-to-many relation found
                if ( ! $value instanceof Doctrine_Collection) {
                    throw Doctrine_Entity_Exception::invalidValueForOneToManyReference();
                }
                if (isset($this->_references[$name])) {
                    $this->_references[$name]->setData($value->getData());
495
                    return;
496 497 498 499 500 501 502 503 504 505 506 507 508
                }
            } else {
                $relatedTable = $value->getTable();
                $foreignFieldName = $rel->getForeignFieldName();
                $localFieldName = $rel->getLocalFieldName();

                // one-to-one relation found
                if ( ! ($value instanceof Doctrine_Entity)) {
                    throw Doctrine_Entity_Exception::invalidValueForOneToOneReference();
                }
                if ($rel instanceof Doctrine_Relation_LocalKey) {
                    $idFieldNames = $value->getTable()->getIdentifier();
                    if ( ! empty($foreignFieldName) && $foreignFieldName != $idFieldNames[0]) {
romanb's avatar
romanb committed
509
                        $this->set($localFieldName, $value->_get($foreignFieldName));
510
                    } else {
511
                        $this->set($localFieldName, $value);
512 513
                    }
                } else {
514
                    $value->set($foreignFieldName, $this);
515 516 517 518 519 520 521 522 523 524
                }
            }
        } else if ($rel instanceof Doctrine_Relation_Association) {
            if ( ! ($value instanceof Doctrine_Collection)) {
                throw Doctrine_Entity_Exception::invalidValueForManyToManyReference();
            }
        }

        $this->_references[$name] = $value;
    }
525 526

    /**
romanb's avatar
romanb committed
527
     * Generic getter for all persistent fields.
528
     *
romanb's avatar
romanb committed
529
     * @param string $fieldName  Name of the field.
530 531
     * @return mixed
     */
532
    final public function get($fieldName)
533
    {
romanb's avatar
romanb committed
534 535 536
        if ($getter = $this->_getCustomAccessor($fieldName)) {
            return $this->$getter();
        }
537
        
romanb's avatar
romanb committed
538
        // Use built-in accessor functionality        
539
        $nullObj = Doctrine_Null::$INSTANCE;
540
        if (isset($this->_data[$fieldName])) {
romanb's avatar
romanb committed
541 542 543 544 545 546
            return $this->_data[$fieldName] !== $nullObj ?
                    $this->_data[$fieldName] : null;
        } else if (isset($this->_references[$fieldName])) {
            return $this->_references[$fieldName] !== $nullObj ?
                    $this->_references[$fieldName] : null;
        } else {
547 548 549 550 551 552 553
            $class = $this->_class;
            if ($class->hasField($fieldName)) {
                return null;
            } else if ($class->hasRelation($fieldName)) {
                $rel = $class->getRelation($fieldName);
                if ($rel->isLazilyLoaded()) {
                    $this->_references[$fieldName] = $rel->lazyLoadFor($this);
romanb's avatar
romanb committed
554 555 556 557 558 559 560
                    return $this->_references[$fieldName] !== $nullObj ?
                            $this->_references[$fieldName] : null;
                } else {
                    return null;
                }
            } else {
                throw Doctrine_Entity_Exception::invalidField($fieldName);
zYne's avatar
zYne committed
561
            }
562 563
        }
    }
564
    
565 566 567 568 569 570 571
    /**
     * Gets the custom mutator method for a field, if it exists.
     *
     * @param string $fieldName  The field name.
     * @return mixed  The name of the custom mutator or FALSE, if the field does
     *                not have a custom mutator.
     */
romanb's avatar
romanb committed
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
    private function _getCustomMutator($fieldName)
    {
        if ( ! isset(self::$_mutatorCache[$this->_entityName][$fieldName])) {
            if (self::$_useAutoAccessorOverride) {
                $setterMethod = 'set' . Doctrine::classify($fieldName);
                if (method_exists($this, $setterMethod)) {
                    self::$_mutatorCache[$this->_entityName][$fieldName] = $setterMethod;
                } else {
                    self::$_mutatorCache[$this->_entityName][$fieldName] = false;
                }
            }
            
            if ($setter = $this->_class->getCustomMutator($fieldName)) {
                self::$_mutatorCache[$this->_entityName][$fieldName] = $setter;
            } else if ( ! isset(self::$_mutatorCache[$this->_entityName][$fieldName])) {
                self::$_mutatorCache[$this->_entityName][$fieldName] = false;
            }
        }
        
        return self::$_mutatorCache[$this->_entityName][$fieldName];
    }
    
594 595 596 597 598 599 600
    /**
     * Gets the custom accessor method of a field, if it exists.
     *
     * @param string $fieldName  The field name.
     * @return mixed  The name of the custom accessor method, or FALSE if the 
     *                field does not have a custom accessor.
     */
romanb's avatar
romanb committed
601
    private function _getCustomAccessor($fieldName)
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
    {
        if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) {
            if (self::$_useAutoAccessorOverride) {
                $getterMethod = 'get' . Doctrine::classify($fieldName);
                if (method_exists($this, $getterMethod)) {
                    self::$_accessorCache[$this->_entityName][$fieldName] = $getterMethod;
                } else {
                    self::$_accessorCache[$this->_entityName][$fieldName] = false;
                }
            }
            if ($getter = $this->_class->getCustomAccessor($fieldName)) {
                self::$_accessorCache[$this->_entityName][$fieldName] = $getter;
            } else if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) {
                self::$_accessorCache[$this->_entityName][$fieldName] = false;
            }
        }
romanb's avatar
romanb committed
618 619
        
        return self::$_accessorCache[$this->_entityName][$fieldName];
620 621
    }
    
622 623 624 625 626 627
    /**
     * Gets the entity class name.
     *
     * @return string
     */
    final public function getClassName()
628 629 630
    {
        return $this->_entityName;
    }
631

632
    /**
romanb's avatar
romanb committed
633
     * Generic setter for persistent fields.
634
     *
romanb's avatar
romanb committed
635 636
     * @param string $name  The name of the field to set.
     * @param mixed $value  The value of the field.
637
     */
638
    final public function set($fieldName, $value)
romanb's avatar
romanb committed
639 640 641 642 643
    {
        if ($setter = $this->_getCustomMutator($fieldName)) {
            return $this->$setter($value);
        }
        
644 645
        if ($this->_class->hasField($fieldName)) {
            /*if ($value instanceof Doctrine_Entity) {
646
                $type = $class->getTypeOf($fieldName);
647 648 649
                // FIXME: composite key support
                $ids = $value->identifier();
                $id = count($ids) > 0 ? array_pop($ids) : null;
zYne's avatar
zYne committed
650
                if ($id !== null && $type !== 'object') {
651
                    $value = $id;
zYne's avatar
zYne committed
652
                }
653
            }*/
654

655
            $old = isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null;
656
            //FIXME: null == 0 => true
657
            if ($old != $value) {
658
                $this->_data[$fieldName] = $value;
659 660
                $this->_modified[$fieldName] = array($old => $value);
                if ($this->isNew() && $this->_class->isIdentifier($fieldName)) {
661 662
                    $this->_id[$fieldName] = $value;
                }
663
            }
664
        } else if ($this->_class->hasRelation($fieldName)) {
romanb's avatar
romanb committed
665
            $this->_internalSetReference($fieldName, $value);
666
        } else {
romanb's avatar
romanb committed
667
            throw Doctrine_Entity_Exception::invalidField($fieldName);
668 669
        }
    }
670

671
    /**
672 673 674
     * Checks whether a field is set (not null).
     * 
     * NOTE: Invoked by Doctrine::ORM::Access#__isset().
675 676 677 678
     *
     * @param string $name
     * @return boolean
     */
679
    final public function contains($fieldName)
680
    {
681
        if (isset($this->_data[$fieldName])) {
682 683 684
            if ($this->_data[$fieldName] === Doctrine_Null::$INSTANCE) {
                return false;
            }
685 686
            return true;
        }
687
        if (isset($this->_id[$fieldName])) {
688 689
            return true;
        }
690
        if (isset($this->_references[$fieldName]) &&
691
                $this->_references[$fieldName] !== Doctrine_Null::$INSTANCE) {
692 693 694 695
            return true;
        }
        return false;
    }
696

697
    /**
698 699 700 701
     * Clears the value of a field.
     * 
     * NOTE: Invoked by Doctrine::ORM::Access#__unset().
     * 
702 703 704
     * @param string $name
     * @return void
     */
705
    final public function remove($fieldName)
706
    {
707 708
        if (isset($this->_data[$fieldName])) {
            $this->_data[$fieldName] = array();
709
        } else if (isset($this->_references[$fieldName])) {
710
            if ($this->_references[$fieldName] instanceof Doctrine_Entity) {
711
                // todo: delete related record when saving $this
712
                $this->_references[$fieldName] = Doctrine_Null::$INSTANCE;
713
            } else if ($this->_references[$fieldName] instanceof Doctrine_Collection) {
714 715 716
                $this->_references[$fieldName]->setData(array());
            }
        }
717
    }
718 719
    
    /**
romanb's avatar
romanb committed
720 721
     * INTERNAL:
     * Gets the changeset of the entities persistent state.
722 723 724
     *
     * @return array
     */
romanb's avatar
romanb committed
725
    final public function _getChangeSet()
726
    {
romanb's avatar
romanb committed
727
        //return $this->_changeSet;
728
    }
729

730
    /**
731
     * Returns an array of modified fields and values with data preparation
732 733 734 735
     * adds column aggregation inheritance and converts Records into primary key values
     *
     * @param array $array
     * @return array
736
     * @todo What about a little bit more expressive name? getPreparedData?
737
     * @todo Does not look like the best place here ...
romanb's avatar
romanb committed
738
     * @todo Prop: Move to EntityPersister. There call _getChangeSet() and apply this logic.
739
     */
740
    final public function getPrepared(array $array = array())
zYne's avatar
zYne committed
741
    {
742
        $dataSet = array();
743 744

        if (empty($array)) {
745
            $modifiedFields = $this->_modified;
746
        }
zYne's avatar
zYne committed
747

748
        foreach ($modifiedFields as $field) {
romanb's avatar
romanb committed
749
            $type = $this->_class->getTypeOfField($field);
750

751
            if ($this->_data[$field] === Doctrine_Null::$INSTANCE) {
752
                $dataSet[$field] = null;
753 754 755 756 757 758
                continue;
            }

            switch ($type) {
                case 'array':
                case 'object':
759
                    $dataSet[$field] = serialize($this->_data[$field]);
760 761
                    break;
                case 'gzip':
762
                    $dataSet[$field] = gzcompress($this->_data[$field],5);
763 764
                    break;
                case 'boolean':
765 766
                    $dataSet[$field] = $this->_em->getConnection()
                            ->convertBooleans($this->_data[$field]);
767 768
                break;
                case 'enum':
romanb's avatar
romanb committed
769
                    $dataSet[$field] = $this->_class->enumIndex($field, $this->_data[$field]);
770 771
                    break;
                default:
772
                    $dataSet[$field] = $this->_data[$field];
773 774
            }
        }
775 776
        
        // @todo cleanup
romanb's avatar
romanb committed
777
        // populates the discriminator field in Single & Class Table Inheritance
778 779 780 781
        if ($this->_class->getInheritanceType() == Doctrine::INHERITANCE_TYPE_JOINED ||
                $this->_class->getInheritanceType() == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE) {
            $discCol = $this->_class->getInheritanceOption('discriminatorColumn');
            $discMap = $this->_class->getInheritanceOption('discriminatorMap');
782
            $old = $this->get($discCol, false);
783 784 785 786
            $discValue = array_search($this->_entityName, $discMap);
            if ((string) $old !== (string) $discValue || $old === null) {
                $dataSet[$discCol] = $discValue;
                $this->_data[$discCol] = $discValue;
787 788 789
            }
        }

790
        return $dataSet;
791
    }
romanb's avatar
romanb committed
792 793 794 795 796 797
    
    /**
     * Checks whether the entity already has a persistent state.
     *
     * @return boolean  TRUE if the object is new, FALSE otherwise.
     */
798
    final public function isNew()
romanb's avatar
romanb committed
799
    {
800
        return $this->_state == self::STATE_NEW;
romanb's avatar
romanb committed
801
    }
802

803
    /**
romanb's avatar
romanb committed
804 805
     * Checks whether the entity has been modified since it was last synchronized
     * with the database.
806
     *
romanb's avatar
romanb committed
807
     * @return boolean  TRUE if the object has been modified, FALSE otherwise.
808
     */
809
    final public function isModified()
810
    {
811
        return count($this->_modified) > 0;
812
    }
813

814
    /**
815
     * INTERNAL:
816 817
     * Assigns an identifier to the entity. This is only intended for use by
     * the EntityPersisters or the UnitOfWork.
818
     *
819
     * @param mixed $id
820
     */
821
    final public function _assignIdentifier($id)
822
    {
823 824 825 826
        if (is_array($id)) {
            foreach ($id as $fieldName => $value) {
                $this->_id[$fieldName] = $value;
                $this->_data[$fieldName] = $value;
romanb's avatar
romanb committed
827
            }
828 829 830 831
        } else {
            $name = $this->_class->getSingleIdentifierFieldName();
            $this->_id[$name] = $id;
            $this->_data[$name] = $id;
832
        }
833
        $this->_modified = array();
834
    }
835

836
    /**
837 838
     * INTERNAL:
     * Returns the primary keys of the entity (key => value pairs).
839 840 841
     *
     * @return array
     */
842
    final public function _identifier()
843 844 845
    {
        return $this->_id;
    }
846

847 848 849 850
    /**
     * hasRefence
     * @param string $name
     * @return boolean
romanb's avatar
romanb committed
851
     * @todo Better name? hasAssociation() ? Remove?
852
     */
853
    final public function hasReference($name)
854
    {
zYne's avatar
zYne committed
855
        return isset($this->_references[$name]);
856
    }
857

858 859 860 861 862
    /**
     * obtainReference
     *
     * @param string $name
     * @throws Doctrine_Record_Exception        if trying to get an unknown related component
romanb's avatar
romanb committed
863
     * @todo Better name? Remove?
864
     */
865
    final public function obtainReference($name)
866
    {
zYne's avatar
zYne committed
867 868
        if (isset($this->_references[$name])) {
            return $this->_references[$name];
869
        }
romanb's avatar
romanb committed
870
        throw new Doctrine_Record_Exception("Unknown reference $name.");
871
    }
872

873
    /**
874 875
     * INTERNAL:
     * 
876 877 878
     * getReferences
     * @return array    all references
     */
879
    final public function _getReferences()
880
    {
zYne's avatar
zYne committed
881
        return $this->_references;
882
    }
883

884
    /**
885
     * INTERNAL:
886 887 888 889
     * setRelated
     *
     * @param string $alias
     * @param Doctrine_Access $coll
romanb's avatar
romanb committed
890
     * @todo Name? Remove?
891
     */
892
    final public function _setRelated($alias, Doctrine_Access $coll)
893
    {
zYne's avatar
zYne committed
894
        $this->_references[$alias] = $coll;
895
    }
896
    
romanb's avatar
romanb committed
897 898
    /**
     * Gets the ClassMetadata object that describes the entity class.
899 900
     * 
     * @return Doctrine::ORM::Mapping::ClassMetadata
romanb's avatar
romanb committed
901
     */
902
    final public function getClass()
romanb's avatar
romanb committed
903 904 905
    {
        return $this->_class;
    }
906
    
907
    /**
908
     * Gets the EntityManager that is responsible for the persistence of 
romanb's avatar
romanb committed
909
     * the entity.
910
     *
911
     * @return Doctrine::ORM::EntityManager
912
     */
913
    final public function getEntityManager()
914 915 916 917
    {
        return $this->_em;
    }
    
918
    /**
919
     * Gets the EntityRepository of the Entity.
920
     *
921
     * @return Doctrine::ORM::EntityRepository
922 923
     */
    final public function getRepository()
romanb's avatar
romanb committed
924
    {
925
        return $this->_em->getRepository($this->_entityName);
romanb's avatar
romanb committed
926
    }
927
    
romanb's avatar
romanb committed
928 929 930
    /**
     * @todo Why toString() and __toString() ?
     */
zYne's avatar
zYne committed
931 932 933 934
    public function toString()
    {
        return Doctrine::dump(get_object_vars($this));
    }
935

936 937
    /**
     * returns a string representation of this object
romanb's avatar
romanb committed
938
     * @todo Why toString() and __toString() ?
939 940 941
     */
    public function __toString()
    {
romanb's avatar
romanb committed
942
        return (string)$this->_oid;
943
    }
944
    
945 946 947 948 949 950 951
    /**
     * Helps freeing the memory occupied by the entity.
     * Cuts all references the entity has to other entities and removes the entity
     * from the instance pool.
     * Note: The entity is no longer useable after free() has been called. Any operations
     * done with the entity afterwards can lead to unpredictable results.
     */
952
    public function free($deep = false)
953
    {
954
        if ($this->_state != self::STATE_LOCKED) {
955
            $this->_em->detach($this);
956 957 958 959 960 961 962 963 964 965 966 967 968
            $this->_data = array();
            $this->_id = array();

            if ($deep) {
                foreach ($this->_references as $name => $reference) {
                    if ( ! ($reference instanceof Doctrine_Null)) {
                        $reference->free($deep);
                    }
                }
            }

            $this->_references = array();
        }
969
    }
970
}