Inheritances
Files
Overview
FRAMES
NO FRAMES

Record.php source code

Contents of file lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Record.php
1 <?php
2
/*
3  *  $Id: Record.php 5877 2009-06-11 12:06:07Z jwage $
4  *
5  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16  *
17  * This software consists of voluntary contributions made by many individuals
18  * and is licensed under the LGPL. For more information, see
19  * <http://www.phpdoctrine.org>.
20  */
21
22 /**
23  * Doctrine_Record
24  * All record classes should inherit this super class
25  *
26  * @package     Doctrine
27  * @subpackage  Record
28  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
29  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30  * @link        www.phpdoctrine.org
31  * @since       1.0
32  * @version     $Revision: 5877 $
33  */
34
abstract class Doctrine_Record extends Doctrine_Record_Abstract implements CountableIteratorAggregateSerializable
35
{
36     
/**
37      * STATE CONSTANTS
38      */
39
40     /**
41      * DIRTY STATE
42      * a Doctrine_Record is in dirty state when its properties are changed
43      */
44     
const STATE_DIRTY       1;
45
46     
/**
47      * TDIRTY STATE
48      * a Doctrine_Record is in transient dirty state when it is created
49      * and some of its fields are modified but it is NOT yet persisted into database
50      */
51     
const STATE_TDIRTY      2;
52
53     
/**
54      * CLEAN STATE
55      * a Doctrine_Record is in clean state when all of its properties are loaded from the database
56      * and none of its properties are changed
57      */
58     
const STATE_CLEAN       3;
59
60     
/**
61      * PROXY STATE
62      * a Doctrine_Record is in proxy state when its properties are not fully loaded
63      */
64     
const STATE_PROXY       4;
65
66     
/**
67      * NEW TCLEAN
68      * a Doctrine_Record is in transient clean state when it is created and none of its fields are modified
69      */
70     
const STATE_TCLEAN      5;
71
72     
/**
73      * LOCKED STATE
74      * a Doctrine_Record is temporarily locked during deletes and saves
75      *
76      * This state is used internally to ensure that circular deletes
77      * and saves will not cause infinite loops
78      */
79     
const STATE_LOCKED     6;
80
81      
/**
82       * TLOCKED STATE
83       * a Doctrine_Record is temporarily locked (and transient) during deletes and saves
84       *
85       * This state is used internally to ensure that circular deletes
86       * and saves will not cause infinite loops
87       */
88      
const STATE_TLOCKED     7;
89
90
91     
/**
92      * @var Doctrine_Node_<TreeImpl>        node object
93      */
94     
protected $_node;
95
96     
/**
97      * @var integer $_id                    the primary keys of this object
98      */
99     
protected $_id           = array();
100
101     
/**
102      * @var array $_data                    the record data
103      */
104     
protected $_data         = array();
105
106     
/**
107      * @var array $_values                  the values array, aggregate values and such are mapped into this array
108      */
109     
protected $_values       = array();
110
111     
/**
112      * @var integer $_state                 the state of this record
113      * @see STATE_* constants
114      */
115     
protected $_state;
116
117     
/**
118      * @var array $_modified                an array containing field names that have been modified
119      * @todo Better name? $_modifiedFields?
120      */
121     
protected $_modified     = array();
122
123     
/**
124      * @var Doctrine_Validator_ErrorStack   error stack object
125      */
126     
protected $_errorStack;
127
128     
/**
129      * @var array $_references              an array containing all the references
130      */
131     
protected $_references     = array();
132
133     
/**
134      * Doctrine_Collection of objects needing to be deleted on save
135      *
136      * @var string
137      */
138     
protected $_pendingDeletes = array();
139
140     
/**
141      * Array of custom accessors for cache
142      *
143      * @var array
144      */
145     
protected static $_customAccessors = array();
146
147     
/**
148      * Array of custom mutators for cache
149      *
150      * @var array
151      */
152     
protected static $_customMutators = array();
153
154     
/**
155      * Whether or not to serialize references when a Doctrine_Record is serialized
156      *
157      * @var boolean
158      */
159     
protected $_serializeReferences false;
160
161     
/**
162      * @var integer $index                  this index is used for creating object identifiers
163      */
164     
private static $_index 1;
165
166     
/**
167      * @var integer $oid                    object identifier, each Record object has a unique object identifier
168      */
169     
private $_oid;
170
171     
/**
172      * constructor
173      * @param Doctrine_Table|null $table       a Doctrine_Table object or null,
174      *                                         if null the table object is retrieved from current connection
175      *
176      * @param boolean $isNewEntry              whether or not this record is transient
177      *
178      * @throws Doctrine_Connection_Exception   if object is created using the new operator and there are no
179      *                                         open connections
180      * @throws Doctrine_Record_Exception       if the cleanData operation fails somehow
181      */
182     
public function __construct($table null$isNewEntry false)
183     {
184         if (isset(
$table) && $table instanceof Doctrine_Table) {
185             
$this->_table $table;
186             
$exists = ( ! $isNewEntry);
187         } else {
188             
// get the table of this class
189             
$class get_class($this);
190             
$this->_table Doctrine::getTable($class);
191             
$exists false;
192         }
193
194         
// Check if the current connection has the records table in its registry
195         // If not this record is only used for creating table definition and setting up
196         // relations.
197         
if ( ! $this->_table->getConnection()->hasTable($this->_table->getComponentName())) {
198             return;
199         }
200
201         
$this->_oid self::$_index;
202
203         
self::$_index++;
204
205         
// get the data array
206         
$this->_data $this->_table->getData();
207
208         
// get the column count
209         
$count count($this->_data);
210
211         
$this->_values $this->cleanData($this->_data);
212
213         
$this->prepareIdentifiers($exists);
214
215         if ( ! 
$exists) {
216             if (
$count count($this->_values)) {
217                 
$this->_state Doctrine_Record::STATE_TDIRTY;
218             } else {
219                 
$this->_state Doctrine_Record::STATE_TCLEAN;
220             }
221
222             
// set the default values for this record
223             
$this->assignDefaultValues();
224         } else {
225             
$this->_state Doctrine_Record::STATE_CLEAN;
226
227             if (
$count $this->_table->getColumnCount()) {
228                 
$this->_state  Doctrine_Record::STATE_PROXY;
229             }
230         }
231
232         
$repository $this->_table->getRepository();
233         
$repository->add($this);
234
235         
$this->construct();
236     }
237
238     
/**
239      * Set whether or not to serialize references.
240      * This is used by caching since we want to serialize references when caching
241      * but not when just normally serializing a instance
242      *
243      * @param boolean $bool
244      * @return boolean $bool
245      */
246     
public function serializeReferences($bool null)
247     {
248         if ( ! 
is_null($bool)) {
249             
$this->_serializeReferences $bool;
250         }
251         return 
$this->_serializeReferences;
252     }
253
254     
/**
255      * _index
256      *
257      * @return integer
258      */
259     
public static function _index()
260     {
261         return 
self::$_index;
262     }
263
264     
/**
265      * setUp
266      * this method is used for setting up relations and attributes
267      * it should be implemented by child classes
268      *
269      * @return void
270      */
271     
public function setUp()
272     { }
273     
/**
274      * construct
275      * Empty template method to provide concrete Record classes with the possibility
276      * to hook into the constructor procedure
277      *
278      * @return void
279      */
280     
public function construct()
281     { }
282     
/**
283      * getOid
284      * returns the object identifier
285      *
286      * @return integer
287      */
288     
public function getOid()
289     {
290         return 
$this->_oid;
291     }
292     public function 
oid()
293     {
294         return 
$this->_oid;
295     }
296
297     
/**
298      * isValid
299      *
300      * @return boolean  whether or not this record is valid
301      */
302     
public function isValid()
303     {
304         if ( ! 
$this->_table->getAttribute(Doctrine::ATTR_VALIDATE)) {
305             return 
true;
306         }
307         
// Clear the stack from any previous errors.
308         
$this->getErrorStack()->clear();
309
310         
// Run validation process
311         
$event = new Doctrine_Event($thisDoctrine_Event::RECORD_VALIDATE);
312         
$this->preValidate($event);
313         
$this->getTable()->getRecordListener()->preValidate($event);
314         
315         if ( ! 
$event->skipOperation) {
316         
317             
$validator = new Doctrine_Validator();
318             
$validator->validateRecord($this);
319             
$this->validate();
320             if (
$this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) {
321                 
$this->validateOnInsert();
322             } else {
323                 
$this->validateOnUpdate();
324             }
325         }
326
327         
$this->getTable()->getRecordListener()->postValidate($event);
328         
$this->postValidate($event);
329
330         return 
$this->getErrorStack()->count() == true false;
331     }
332
333     
/**
334      * Empty template method to provide concrete Record classes with the possibility
335      * to hook into the validation procedure, doing any custom / specialized
336      * validations that are neccessary.
337      */
338     
protected function validate()
339     { }
340
341     
/**
342      * Empty template method to provide concrete Record classes with the possibility
343      * to hook into the validation procedure only when the record is going to be
344      * updated.
345      */
346     
protected function validateOnUpdate()
347     { }
348
349     
/**
350      * Empty template method to provide concrete Record classes with the possibility
351      * to hook into the validation procedure only when the record is going to be
352      * inserted into the data store the first time.
353      */
354     
protected function validateOnInsert()
355     { }
356
357     
/**
358      * Empty template method to provide concrete Record classes with the possibility
359      * to hook into the serializing procedure.
360      */
361     
public function preSerialize($event)
362     { }
363
364     
/**
365      * Empty template method to provide concrete Record classes with the possibility
366      * to hook into the serializing procedure.
367      */
368     
public function postSerialize($event)
369     { }
370
371     
/**
372      * Empty template method to provide concrete Record classes with the possibility
373      * to hook into the serializing procedure.
374      */
375     
public function preUnserialize($event)
376     { }
377
378     
/**
379      * Empty template method to provide concrete Record classes with the possibility
380      * to hook into the serializing procedure.
381      */
382     
public function postUnserialize($event)
383     { }
384
385     
/**
386      * Empty template method to provide concrete Record classes with the possibility
387      * to hook into the saving procedure.
388      */
389     
public function preSave($event)
390     { }
391
392     
/**
393      * Empty template method to provide concrete Record classes with the possibility
394      * to hook into the saving procedure.
395      */
396     
public function postSave($event)
397     { }
398
399     
/**
400      * Empty template method to provide concrete Record classes with the possibility
401      * to hook into the deletion procedure.
402      */
403     
public function preDelete($event)
404     { }
405
406     
/**
407      * Empty template method to provide concrete Record classes with the possibility
408      * to hook into the deletion procedure.
409      */
410     
public function postDelete($event)
411     { }
412
413     
/**
414      * Empty template method to provide concrete Record classes with the possibility
415      * to hook into the saving procedure only when the record is going to be
416      * updated.
417      */
418     
public function preUpdate($event)
419     { }
420
421     
/**
422      * Empty template method to provide concrete Record classes with the possibility
423      * to hook into the saving procedure only when the record is going to be
424      * updated.
425      */
426     
public function postUpdate($event)
427     { }
428
429     
/**
430      * Empty template method to provide concrete Record classes with the possibility
431      * to hook into the saving procedure only when the record is going to be
432      * inserted into the data store the first time.
433      */
434     
public function preInsert($event)
435     { }
436
437     
/**
438      * Empty template method to provide concrete Record classes with the possibility
439      * to hook into the saving procedure only when the record is going to be
440      * inserted into the data store the first time.
441      */
442     
public function postInsert($event)
443     { }
444
445     
/**
446      * Empty template method to provide concrete Record classes with the possibility
447      * to hook into the validation procedure. Useful for cleaning up data before 
448      * validating it.
449      */
450     
public function preValidate($event)
451     { }
452     
/**
453      * Empty template method to provide concrete Record classes with the possibility
454      * to hook into the validation procedure.
455      */
456     
public function postValidate($event)
457     { }
458
459     
/**
460      * Empty template method to provide Record classes with the ability to alter DQL select
461      * queries at runtime
462      */
463     
public function preDqlSelect($event)
464     { }
465
466     
/**
467      * Empty template method to provide Record classes with the ability to alter DQL update
468      * queries at runtime
469      */
470     
public function preDqlUpdate($event)
471     { }
472
473     
/**
474      * Empty template method to provide Record classes with the ability to alter DQL delete
475      * queries at runtime
476      */
477     
public function preDqlDelete($event)
478     { }
479
480     
/**
481      * Get the record error stack as a human readable string.
482      * Useful for outputting errors to user via web browser
483      *
484      * @return string $message
485      */
486     
public function getErrorStackAsString()
487     {
488         
$errorStack $this->getErrorStack();
489
490         if (
count($errorStack)) {
491             
$message sprintf("Validation failed in class %s\n\n"get_class($this));
492
493             
$message .= "  " count($errorStack) . " field" . (count($errorStack) > ?  's' null) . " had validation error" . (count($errorStack) > ?  's' null) . ":\n\n";
494             foreach (
$errorStack as $field => $errors) {
495                 
$message .= "    * " count($errors) . " validator" . (count($errors) > ?  's' null) . " failed on $field (" implode(", "$errors) . ")\n";
496             }
497             return 
$message;
498         } else {
499             return 
false;
500         }
501     }
502
503     
/**
504      * getErrorStack
505      *
506      * @return Doctrine_Validator_ErrorStack    returns the errorStack associated with this record
507      */
508     
public function getErrorStack()
509     {
510         if ( ! 
$this->_errorStack) {
511             
$this->_errorStack = new Doctrine_Validator_ErrorStack(get_class($this));
512         }
513         
514         return 
$this->_errorStack;
515     }
516
517     
/**
518      * errorStack
519      * assigns / returns record errorStack
520      *
521      * @param Doctrine_Validator_ErrorStack          errorStack to be assigned for this record
522      * @return void|Doctrine_Validator_ErrorStack    returns the errorStack associated with this record
523      */
524     
public function errorStack($stack null)
525     {
526         if (
$stack !== null) {
527             if ( ! (
$stack instanceof Doctrine_Validator_ErrorStack)) {
528                throw new 
Doctrine_Record_Exception('Argument should be an instance of Doctrine_Validator_ErrorStack.');
529             }
530             
$this->_errorStack $stack;
531         } else {
532             return 
$this->getErrorStack();
533         }
534     }
535
536     
/**
537      * Assign the inheritance column values
538      *
539      * @return void
540      */
541     
public function assignInheritanceValues()
542     {
543         
$map $this->_table->inheritanceMap;
544         foreach (
$map as $k => $v) {
545             
$k $this->_table->getFieldName($k);
546             
$old $this->get($kfalse);
547
548             if (((string) 
$old !== (string) $v || $old === null) && !in_array($k$this->_modified)) {
549                 
$this->set($k$v);
550             }
551         }
552     }
553
554     
/**
555      * setDefaultValues
556      * sets the default values for records internal data
557      *
558      * @param boolean $overwrite                whether or not to overwrite the already set values
559      * @return boolean
560      */
561     
public function assignDefaultValues($overwrite false)
562     {
563         if ( ! 
$this->_table->hasDefaultValues()) {
564             return 
false;
565         }
566         foreach (
$this->_data as $column => $value) {
567             
$default $this->_table->getDefaultValueOf($column);
568
569             if (
$default === null) {
570                 continue;
571             }
572
573             if (
$value === self::$_null || $overwrite) {
574                 
$this->_data[$column] = $default;
575                 
$this->_modified[]    = $column;
576                 
$this->_state Doctrine_Record::STATE_TDIRTY;
577             }
578         }
579     }
580
581     
/**
582      * cleanData
583      * leaves the $data array only with values whose key is a field inside this
584      * record and returns the values that were removed from $data.  Also converts
585      * any values of 'null' to objects of type Doctrine_Null.
586      *
587      * @param array $data       data array to be cleaned
588      * @return array $tmp       values cleaned from data
589      */
590     
public function cleanData(&$data)
591     {
592         
$tmp $data;
593         
$data = array();
594
595         foreach (
$this->getTable()->getFieldNames() as $fieldName) {
596             if (isset(
$tmp[$fieldName])) {
597                 
$data[$fieldName] = $tmp[$fieldName];
598             } else if (
array_key_exists($fieldName$tmp)) {
599                 
$data[$fieldName] = self::$_null;
600             } else if (!isset(
$this->_data[$fieldName])) {
601                 
$data[$fieldName] = self::$_null;
602             }
603             unset(
$tmp[$fieldName]);
604         }
605
606         return 
$tmp;
607     }
608
609     
/**
610      * hydrate
611      * hydrates this object from given array
612      *
613      * @param array $data
614      * @return boolean
615      */
616     
public function hydrate(array $data$overwriteLocalChanges true)
617     {
618         if (
$overwriteLocalChanges) {
619             
$this->_values array_merge($this->_values$this->cleanData($data));
620             
$this->_data array_merge($this->_data$data);
621         } else {
622             
$this->_values array_merge($this->cleanData($data), $this->_values);
623             
$this->_data array_merge($data$this->_data);
624         }
625
626         if ( ! 
$this->isModified() && count($this->_values) < $this->_table->getColumnCount()) {
627             
$this->_state self::STATE_PROXY;
628         }
629     }
630
631     
/**
632      * prepareIdentifiers
633      * prepares identifiers for later use
634      *
635      * @param boolean $exists               whether or not this record exists in persistent data store
636      * @return void
637      */
638     
private function prepareIdentifiers($exists true)
639     {
640         switch (
$this->_table->getIdentifierType()) {
641             case 
Doctrine::IDENTIFIER_AUTOINC:
642             case 
Doctrine::IDENTIFIER_SEQUENCE:
643             case 
Doctrine::IDENTIFIER_NATURAL:
644                 
$name $this->_table->getIdentifier();
645                 if (
is_array($name)) {
646                     
$name $name[0];
647                 }
648                 if (
$exists) {
649                     if (isset(
$this->_data[$name]) && $this->_data[$name] !== self::$_null) {
650                         
$this->_id[$name] = $this->_data[$name];
651                     }
652                 }
653                 break;
654             case 
Doctrine::IDENTIFIER_COMPOSITE:
655                 
$names $this->_table->getIdentifier();
656
657                 foreach (
$names as $name) {
658                     if (
$this->_data[$name] === self::$_null) {
659                         
$this->_id[$name] = null;
660                     } else {
661                         
$this->_id[$name] = $this->_data[$name];
662                     }
663                 }
664                 break;
665         }
666     }
667
668     
/**
669      * serialize
670      * this method is automatically called when this Doctrine_Record is serialized
671      *
672      * @return array
673      */
674     
public function serialize()
675     {
676         
$event = new Doctrine_Event($thisDoctrine_Event::RECORD_SERIALIZE);
677
678         
$this->preSerialize($event);
679         
$this->getTable()->getRecordListener()->preSerialize($event);
680
681         
$vars get_object_vars($this);
682
683         if ( ! 
$this->serializeReferences()) {
684             unset(
$vars['_references']);
685         }
686         unset(
$vars['_table']);
687         unset(
$vars['_errorStack']);
688         unset(
$vars['_filter']);
689         unset(
$vars['_node']);
690
691         
$data $this->_data;
692         if (
$this->exists()) {
693             
$data array_merge($data$this->_id);
694         }
695
696         foreach (
$data as $k => $v) {
697             if (
$v instanceof Doctrine_Record && $this->_table->getTypeOf($k) != 'object') {
698                 unset(
$vars['_data'][$k]);
699             } elseif (
$v === self::$_null) {
700                 unset(
$vars['_data'][$k]);
701             } else {
702                 switch (
$this->_table->getTypeOf($k)) {
703                     case 
'array':
704                     case 
'object':
705                         
$vars['_data'][$k] = serialize($vars['_data'][$k]);
706                         break;
707                     case 
'gzip':
708                         
$vars['_data'][$k] = gzcompress($vars['_data'][$k]);
709                         break;
710                 }
711             }
712         }
713
714         
$str serialize($vars);
715
716         
$this->postSerialize($event);
717         
$this->getTable()->getRecordListener()->postSerialize($event);
718
719         return 
$str;
720     }
721
722     
/**
723      * unseralize
724      * this method is automatically called everytime a Doctrine_Record object is unserialized
725      *
726      * @param string $serialized                Doctrine_Record as serialized string
727      * @throws Doctrine_Record_Exception        if the cleanData operation fails somehow
728      * @return void
729      */
730     
public function unserialize($serialized)
731     {
732         
$event = new Doctrine_Event($thisDoctrine_Event::RECORD_UNSERIALIZE);
733         
734         
$manager    Doctrine_Manager::getInstance();
735         
$connection $manager->getConnectionForComponent(get_class($this));
736
737         
$this->_oid self::$_index;
738         
self::$_index++;
739
740         
$this->_table $connection->getTable(get_class($this));
741         
742         
$this->preUnserialize($event);
743         
$this->getTable()->getRecordListener()->preUnserialize($event);
744
745         
$array unserialize($serialized);
746
747         foreach(
$array as $k => $v) {
748             
$this->$k $v;
749         }
750
751         foreach (
$this->_data as $k => $v) {
752             switch (
$this->_table->getTypeOf($k)) {
753                 case 
'array':
754                 case 
'object':
755                     
$this->_data[$k] = unserialize($this->_data[$k]);
756                     break;
757                 case 
'gzip':
758                    
$this->_data[$k] = gzuncompress($this->_data[$k]);
759                     break;
760                 case 
'enum':
761                     
$this->_data[$k] = $this->_table->enumValue($k$this->_data[$k]);
762                     break;
763
764             }
765         }
766
767         
$this->_table->getRepository()->add($this);
768
769         
$this->cleanData($this->_data);
770
771         
$this->prepareIdentifiers($this->exists());
772
773         
$this->postUnserialize($event);
774         
$this->getTable()->getRecordListener()->postUnserialize($event);
775     }
776
777     
/**
778      * state
779      * returns / assigns the state of this record
780      *
781      * @param integer|string $state                 if set, this method tries to set the record state to $state
782      * @see Doctrine_Record::STATE_* constants
783      *
784      * @throws Doctrine_Record_State_Exception      if trying to set an unknown state
785      * @return null|integer
786      */
787     
public function state($state null)
788     {
789         if (
$state == null) {
790             return 
$this->_state;
791         }
792         
793         
$err false;
794         if (
is_integer($state)) {
795             if (
$state >= && $state <= 7) {
796                 
$this->_state $state;
797             } else {
798                 
$err true;
799             }
800         } else if (
is_string($state)) {
801             
$upper strtoupper($state);
802
803             
$const 'Doctrine_Record::STATE_' $upper;
804             if (
defined($const)) {
805                 
$this->_state constant($const);
806             } else {
807                 
$err true;
808             }
809         }
810
811         if (
$this->_state === Doctrine_Record::STATE_TCLEAN ||
812                 
$this->_state === Doctrine_Record::STATE_CLEAN) {
813             
$this->_modified = array();
814         }
815
816         if (
$err) {
817             throw new 
Doctrine_Record_State_Exception('Unknown record state ' $state);
818         }
819     }
820
821     
/**
822      * refresh
823      * refresh internal data from the database
824      *
825      * @param bool $deep                        If true, fetch also current relations. Caution: this deletes
826      *                                          any aggregated values you may have queried beforee
827      *
828      * @throws Doctrine_Record_Exception        When the refresh operation fails (when the database row
829      *                                          this record represents does not exist anymore)
830      * @return boolean
831      */
832     
public function refresh($deep false)
833     {
834         
$id $this->identifier();
835         if ( ! 
is_array($id)) {
836             
$id = array($id);
837         }
838         if (empty(
$id)) {
839             return 
false;
840         }
841         
$id array_values($id);
842
843         
$overwrite $this->getTable()->getAttribute(Doctrine::ATTR_HYDRATE_OVERWRITE);
844         
$this->getTable()->setAttribute(Doctrine::ATTR_HYDRATE_OVERWRITEtrue);
845
846         if (
$deep) {
847             
$query $this->getTable()->createQuery();
848             foreach (
array_keys($this->_references) as $name) {
849                 
$query->leftJoin(get_class($this) . '.' $name);
850             }
851             
$query->where(implode(' = ? AND ', (array)$this->getTable()->getIdentifier()) . ' = ?');
852             
$this->clearRelated();
853             
$record $query->fetchOne($id);
854         } else {
855             
// Use HYDRATE_ARRAY to avoid clearing object relations
856             
$record $this->getTable()->find($idDoctrine::HYDRATE_ARRAY);
857             if (
$record) {
858                 
$this->hydrate($record);
859             }
860         }
861
862         
$this->getTable()->setAttribute(Doctrine::ATTR_HYDRATE_OVERWRITE$overwrite);
863
864         if (
$record === false) {
865             throw new 
Doctrine_Record_Exception('Failed to refresh. Record does not exist.');
866         }
867
868         
$this->_modified = array();
869
870         
$this->prepareIdentifiers();
871
872         
$this->_state Doctrine_Record::STATE_CLEAN;
873
874         return 
$this;
875     }
876
877     
/**
878      * refresh
879      * refresh data of related objects from the database
880      *
881      * @param string $name              name of a related component.
882      *                                  if set, this method only refreshes the specified related component
883      *
884      * @return Doctrine_Record          this object
885      */
886     
public function refreshRelated($name null)
887     {
888         if (
is_null($name)) {
889             foreach (
$this->_table->getRelations() as $rel) {
890                 
$this->_references[$rel->getAlias()] = $rel->fetchRelatedFor($this);
891             }
892         } else {
893             
$rel $this->_table->getRelation($name);
894             
$this->_references[$name] = $rel->fetchRelatedFor($this);
895         }
896     }
897
898     
/**
899      * clearRelated
900      * unsets all the relationships this object has
901      *
902      * (references to related objects still remain on Table objects)
903      */
904     
public function clearRelated()
905     {
906         
$this->_references = array();
907     }
908
909     
/**
910      * getTable
911      * returns the table object for this record
912      *
913      * @return Doctrine_Table        a Doctrine_Table object
914      */
915     
public function getTable()
916     {
917         return 
$this->_table;
918     }
919
920     
/**
921      * getData
922      * return all the internal data
923      *
924      * @return array                        an array containing all the properties
925      */
926     
public function getData()
927     {
928         return 
$this->_data;
929     }
930
931     
/**
932      * rawGet
933      * returns the value of a property, if the property is not yet loaded
934      * this method does NOT load it
935      *
936      * @param $name                         name of the property
937      * @throws Doctrine_Record_Exception    if trying to get an unknown property
938      * @return mixed
939      */
940     
public function rawGet($fieldName)
941     {
942         if ( ! isset(
$this->_data[$fieldName])) {
943             throw new 
Doctrine_Record_Exception('Unknown property '$fieldName);
944         }
945         if (
$this->_data[$fieldName] === self::$_null) {
946             return 
null;
947         }
948
949         return 
$this->_data[$fieldName];
950     }
951
952     
/**
953      * load
954      * Loads all the uninitialized properties from the database.
955      * Used to move a record from PROXY to CLEAN/DIRTY state.
956      *
957      * @return boolean
958      */
959     
public function load(array $data = array())
960     {
961         
// only load the data from database if the Doctrine_Record is in proxy state
962         
if ($this->_state == Doctrine_Record::STATE_PROXY) {
963             
$id $this->identifier();
964             
965             if ( ! 
is_array($id)) {
966                 
$id = array($id);
967             }
968             
969             if (empty(
$id)) {
970                 return 
false;
971             }
972
973             
$data = empty($data) ? $this->getTable()->find($idDoctrine::HYDRATE_ARRAY) : $data;
974             
975             foreach (
$data as $field => $value) {
976                if ( ! isset(
$this->_data[$field]) || $this->_data[$field] === self::$_null) {
977                    
// Ticket #2031: null value was causing removal of field during load
978                    
if ($value === null) { 
979                        
$value self::$_null
980                    }
981
982                    
$this->_data[$field] = $value;
983                }
984             }
985             
986             if (
$this->isModified()) {
987                
$this->_state Doctrine_Record::STATE_DIRTY;
988             } else if (
count($data) >= $this->_table->getColumnCount()) {
989                 
$this->_state Doctrine_Record::STATE_CLEAN;
990             }
991
992             return 
true;
993         }
994
995         return 
false;
996     }
997
998     
/**
999      * get
1000      * returns a value of a property or a related component
1001      *
1002      * @param mixed $name                       name of the property or related component
1003      * @param boolean $load                     whether or not to invoke the loading procedure
1004      * @throws Doctrine_Record_Exception        if trying to get a value of unknown property / related component
1005      * @return mixed
1006      */
1007     
public function get($fieldName$load true)
1008     {
1009         if (
$this->_table->getAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE)) {
1010             
$componentName $this->_table->getComponentName();
1011
1012             
$accessor = isset(self::$_customAccessors[$componentName][$fieldName])
1013                 ? 
self::$_customAccessors[$componentName][$fieldName]
1014                 : 
'get' Doctrine_Inflector::classify($fieldName);
1015
1016             if (isset(
self::$_customAccessors[$componentName][$fieldName]) || method_exists($this$accessor)) {
1017                 
self::$_customAccessors[$componentName][$fieldName] = $accessor;
1018                 return 
$this->$accessor($load);
1019             }
1020         }
1021
1022         return 
$this->_get($fieldName$load);
1023     }
1024
1025     protected function 
_get($fieldName$load true)
1026     {
1027         
$value self::$_null;
1028
1029         if (
array_key_exists($fieldName$this->_data)) {
1030             
// check if the value is the Doctrine_Null object located in self::$_null)
1031             
if ($this->_data[$fieldName] === self::$_null && $load) {
1032                 
$this->load();
1033             }
1034
1035             if (
$this->_data[$fieldName] === self::$_null) {
1036                 
$value null;
1037             } else {
1038                 
$value $this->_data[$fieldName];
1039             }
1040             return 
$value;
1041         }
1042
1043         if (
array_key_exists($fieldName$this->_values)) {
1044             return 
$this->_values[$fieldName];
1045         }
1046         
1047         try {
1048             if ( ! isset(
$this->_references[$fieldName]) && $load) {
1049                 
$rel $this->_table->getRelation($fieldName);
1050                 
$this->_references[$fieldName] = $rel->fetchRelatedFor($this);
1051             }
1052
1053             if (
$this->_references[$fieldName] === self::$_null) {
1054                 return 
null;
1055             }
1056
1057             return 
$this->_references[$fieldName];
1058         } catch (
Doctrine_Table_Exception $e) {
1059             
$success false;
1060             foreach (
$this->_table->getFilters() as $filter) {
1061                 try {
1062                     
$value $filter->filterGet($this$fieldName);
1063                     
$success true;
1064                 } catch (
Doctrine_Exception $e) {}
1065             }
1066             if (
$success) {
1067                 return 
$value;
1068             } else {
1069                 throw 
$e;
1070             }
1071         }
1072     }
1073
1074     
/**
1075      * mapValue
1076      * This simple method is used for mapping values to $values property.
1077      * Usually this method is used internally by Doctrine for the mapping of
1078      * aggregate values.
1079      *
1080      * @param string $name                  the name of the mapped value
1081      * @param mixed $value                  mixed value to be mapped
1082      * @return void
1083      */
1084     
public function mapValue($name$value)
1085     {
1086         
$this->_values[$name] = $value;
1087     }
1088
1089     
/**
1090      * set
1091      * method for altering properties and Doctrine_Record references
1092      * if the load parameter is set to false this method will not try to load uninitialized record data
1093      *
1094      * @param mixed $name                   name of the property or reference
1095      * @param mixed $value                  value of the property or reference
1096      * @param boolean $load                 whether or not to refresh / load the uninitialized record data
1097      *
1098      * @throws Doctrine_Record_Exception    if trying to set a value for unknown property / related component
1099      * @throws Doctrine_Record_Exception    if trying to set a value of wrong type for related component
1100      *
1101      * @return Doctrine_Record
1102      */
1103     
public function set($fieldName$value$load true)
1104     {
1105         if (
$this->_table->getAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE)) {
1106             
$componentName $this->_table->getComponentName();
1107             
$mutator = isset(self::$_customMutators[$componentName][$fieldName]) ? self::$_customMutators[$componentName][$fieldName]:'set' Doctrine_Inflector::classify($fieldName);
1108             if (isset(
self::$_customMutators[$componentName][$fieldName]) || method_exists($this$mutator)) {
1109                 
self::$_customMutators[$componentName][$fieldName] = $mutator;
1110                 return 
$this->$mutator($value$load);
1111             }
1112         }
1113         return 
$this->_set($fieldName$value$load);
1114     }
1115
1116     protected function 
_set($fieldName$value$load true)
1117     {
1118         if (isset(
$this->_data[$fieldName])) {
1119             
$type $this->_table->getTypeOf($fieldName);
1120             if (
$value instanceof Doctrine_Record) {
1121                 
$id $value->getIncremented();
1122
1123                 if (
$id !== null && $type !== 'object') {
1124                     
$value $id;
1125                 }
1126             }
1127
1128             if (
$load) {
1129                 
$old $this->get($fieldName$load);
1130             } else {
1131                 
$old $this->_data[$fieldName];
1132             }
1133             
1134             if (
$this->_isValueModified($type$old$value)) {
1135                 if (
$value === null) {
1136                     
$default $this->_table->getDefaultValueOf($fieldName); 
1137                     
$value = ($default === null) ? self::$_null $default;
1138                 }
1139                 
$this->_data[$fieldName] = $value;
1140                 
$this->_modified[] = $fieldName;
1141
1142                 switch (
$this->_state) {
1143                     case 
Doctrine_Record::STATE_CLEAN:
1144                     case 
Doctrine_Record::STATE_PROXY:
1145                         
$this->_state Doctrine_Record::STATE_DIRTY;
1146                         break;
1147                     case 
Doctrine_Record::STATE_TCLEAN:
1148                         
$this->_state Doctrine_Record::STATE_TDIRTY;
1149                         break;
1150                 }
1151             }
1152         } else {
1153             try {
1154                 
$this->coreSetRelated($fieldName$value);
1155             } catch (
Doctrine_Table_Exception $e) {
1156                 
$success false;
1157                 foreach (
$this->_table->getFilters() as $filter) {
1158                     try {
1159                         
$value $filter->filterSet($this$fieldName$value);
1160                         
$success true;
1161                     } catch (
Doctrine_Exception $e) {}
1162                 }
1163                 if (
$success) {
1164                     return 
$value;
1165                 } else {
1166                     throw 
$e;
1167                 }
1168             }
1169         }
1170
1171         return 
$this;
1172     }
1173
1174     
/**
1175      * Check if a value has changed according to Doctrine
1176      * Doctrine is loose with type checking in the same ways PHP is for consistancy of behavior
1177      *
1178      * This function basically says if what is being set is of Doctrine type boolean and something
1179      * like current_value == 1 && new_value = true would not be considered modified
1180      *
1181      * Simply doing $old !== $new will return false for boolean columns would mark the field as modified
1182      * and change it in the database when it is not necessary
1183      *
1184      * @param string $type  Doctrine type of the column
1185      * @param string $old   Old value
1186      * @param string $new   New value
1187      * @return boolean $modified  Whether or not Doctrine considers the value modified
1188      */
1189     
protected function _isValueModified($type$old$new)
1190     {
1191         if (
$type == 'boolean' && (is_bool($old) || is_numeric($old)) && (is_bool($new) || is_numeric($new)) && $old == $new) {
1192             return 
false;
1193         } else if (
in_array($type, array('decimal''float')) && is_numeric($old) && is_numeric($new)) {
1194             return 
$old 100 != $new 100;
1195         } else if (
in_array($type, array('integer''int')) && is_numeric($old) && is_numeric($new)) {
1196             return (int) 
$old !== (int) $new;
1197         } else {
1198             return 
$old !== $new;
1199         }
1200     }
1201
1202     
/**
1203      * DESCRIBE WHAT THIS METHOD DOES, PLEASE!
1204      * @todo Refactor. What about composite keys?
1205      */
1206     
public function coreSetRelated($name$value)
1207     {
1208         
$rel $this->_table->getRelation($name);
1209         
1210         if (
$value === null) {
1211             
$value self::$_null;
1212         }
1213         
1214         
// one-to-many or one-to-one relation
1215         
if ($rel instanceof Doctrine_Relation_ForeignKey || $rel instanceof Doctrine_Relation_LocalKey) {
1216             if ( ! 
$rel->isOneToOne()) {
1217                 
// one-to-many relation found
1218                 
if ( ! ($value instanceof Doctrine_Collection)) {
1219                     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.");
1220                 }
1221
1222                 if (isset(
$this->_references[$name])) {
1223                     
$this->_references[$name]->setData($value->getData());
1224
1225                     return 
$this;
1226                 }
1227             } else {
1228                 
$localFieldName $this->_table->getFieldName($rel->getLocal());
1229
1230                 if (
$value !== self::$_null) {
1231                     
$relatedTable $value->getTable();
1232                     
$foreignFieldName $relatedTable->getFieldName($rel->getForeign());
1233                 }
1234
1235                 
// one-to-one relation found
1236                 
if ( ! ($value instanceof Doctrine_Record) && ! ($value instanceof Doctrine_Null)) {
1237                     throw new 
Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Record or Doctrine_Null when setting one-to-one references.");
1238                 }
1239
1240                 if (
$rel instanceof Doctrine_Relation_LocalKey) {
1241                     if (
$value !== self::$_null &&  ! empty($foreignFieldName) && $foreignFieldName != $value->getTable()->getIdentifier()) {
1242                         
$this->set($localFieldName$value->rawGet($foreignFieldName), false);
1243                     } else {
1244                         
// FIX: Ticket #1280 fits in this situation
1245                         
$this->set($localFieldName$valuefalse);
1246                     }
1247                 } elseif (
$value !== self::$_null) {
1248                     
// We should only be able to reach $foreignFieldName if we have a Doctrine_Record on hands
1249                     
$value->set($foreignFieldName$thisfalse);
1250                 }
1251             }
1252         } else if (
$rel instanceof Doctrine_Relation_Association) {
1253             
// join table relation found
1254             
if ( ! ($value instanceof Doctrine_Collection)) {
1255                 throw new 
Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting many-to-many references.");
1256             }
1257         }
1258
1259         
$this->_references[$name] = $value;
1260     }
1261
1262     
/**
1263      * contains
1264      *
1265      * @param string $name
1266      * @return boolean
1267      */
1268     
public function contains($fieldName)
1269     {
1270         if (isset(
$this->_data[$fieldName])) {
1271             
// this also returns true if the field is a Doctrine_Null.
1272             // imho this is not correct behavior.
1273             
return true;
1274         }
1275         if (isset(
$this->_id[$fieldName])) {
1276             return 
true;
1277         }
1278         if (isset(
$this->_values[$fieldName])) {
1279             return 
true;
1280         }
1281         if (isset(
$this->_references[$fieldName]) &&
1282             
$this->_references[$fieldName] !== self::$_null) {
1283
1284             return 
true;
1285         }
1286         return 
false;
1287     }
1288
1289     
/**
1290      * @param string $name
1291      * @return void
1292      */
1293     
public function __unset($name)
1294     {
1295         if (isset(
$this->_data[$name])) {
1296             
$this->_data[$name] = array();
1297         } else if (isset(
$this->_references[$name])) {
1298             if (
$this->_references[$name] instanceof Doctrine_Record) {
1299                 
$this->_pendingDeletes[] = $this->$name;
1300                 
$this->_references[$name] = self::$_null;
1301             } elseif (
$this->_references[$name] instanceof Doctrine_Collection) {
1302                 
$this->_pendingDeletes[] = $this->$name;
1303                 
$this->_references[$name]->setData(array());
1304             }
1305         }
1306     }
1307
1308     
/**
1309      * getPendingDeletes
1310      *
1311      * @return array Array of Doctrine_Records instances which need to be deleted on save
1312      */
1313     
public function getPendingDeletes()
1314     {
1315         return 
$this->_pendingDeletes;
1316     }
1317
1318     
/**
1319      * applies the changes made to this object into database
1320      * this method is smart enough to know if any changes are made
1321      * and whether to use INSERT or UPDATE statement
1322      *
1323      * this method also saves the related components
1324      *
1325      * @param Doctrine_Connection $conn                 optional connection parameter
1326      * @return void
1327      */
1328     
public function save(Doctrine_Connection $conn null)
1329     {
1330         if (
$conn === null) {
1331             
$conn $this->_table->getConnection();
1332         }
1333         
$conn->unitOfWork->saveGraph($this);
1334     }
1335
1336     
/**
1337      * Tries to save the object and all its related components.
1338      * In contrast to Doctrine_Record::save(), this method does not
1339      * throw an exception when validation fails but returns TRUE on
1340      * success or FALSE on failure.
1341      *
1342      * @param Doctrine_Connection $conn                 optional connection parameter
1343      * @return TRUE if the record was saved sucessfully without errors, FALSE otherwise.
1344      */
1345     
public function trySave(Doctrine_Connection $conn null) {
1346         try {
1347             
$this->save($conn);
1348             return 
true;
1349         } catch (
Doctrine_Validator_Exception $ignored) {
1350             return 
false;
1351         }
1352     }
1353
1354     
/**
1355      * replace
1356      * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
1357      * query, except that if there is already a row in the table with the same
1358      * key field values, the REPLACE query just updates its values instead of
1359      * inserting a new row.
1360      *
1361      * The REPLACE type of query does not make part of the SQL standards. Since
1362      * practically only MySQL and SQLIte implement it natively, this type of
1363      * query isemulated through this method for other DBMS using standard types
1364      * of queries inside a transaction to assure the atomicity of the operation.
1365      *
1366      * @param Doctrine_Connection $conn             optional connection parameter
1367      * @throws Doctrine_Connection_Exception        if some of the key values was null
1368      * @throws Doctrine_Connection_Exception        if there were no key fields
1369      * @throws Doctrine_Connection_Exception        if something fails at database level
1370      * @return integer                              number of rows affected
1371      */
1372     
public function replace(Doctrine_Connection $conn null)
1373     {
1374         if (
$conn === null) {
1375             
$conn $this->_table->getConnection();
1376         }
1377
1378         if (
$this->exists()) {
1379             return 
$this->save();
1380         } else {
1381             if (
$this->isValid()) {
1382                 
$identifier = (array) $this->getTable()->getIdentifier();
1383                 
$data $this->getPrepared();
1384                 return 
$conn->replace($this->_table$data$identifier);
1385             } else {
1386                 return 
false;
1387             }
1388         }
1389     }
1390
1391     
/**
1392      * returns an array of modified fields and associated values
1393      * @return array
1394      * @todo What about a better name? getModifiedFields?
1395      */
1396     
public function getModified()
1397     {
1398         
$a = array();
1399
1400         foreach (
$this->_modified as $k => $v) {
1401             
$a[$v] = $this->_data[$v];
1402         }
1403         return 
$a;
1404     }
1405
1406     
/**
1407      * REDUNDANT?
1408      */
1409     
public function modifiedFields()
1410     {
1411         
$a = array();
1412
1413         foreach (
$this->_modified as $k => $v) {
1414             
$a[$v] = $this->_data[$v];
1415         }
1416         return 
$a;
1417     }
1418
1419     
/**
1420      * getPrepared
1421      *
1422      * returns an array of modified fields and values with data preparation
1423      * adds column aggregation inheritance and converts Records into primary key values
1424      *
1425      * @param array $array
1426      * @return array
1427      * @todo What about a little bit more expressive name? getPreparedData?
1428      */
1429     
public function getPrepared(array $array = array())
1430     {
1431         
$a = array();
1432
1433         if (empty(
$array)) {
1434             
$modifiedFields $this->_modified;
1435         }
1436
1437         foreach (
$modifiedFields as $field) {
1438             
$type $this->_table->getTypeOf($field);
1439
1440             if (
$this->_data[$field] === self::$_null) {
1441                 
$a[$field] = null;
1442                 continue;
1443             }
1444
1445             switch (
$type) {
1446                 case 
'array':
1447                 case 
'object':
1448                     
$a[$field] = serialize($this->_data[$field]);
1449                     break;
1450                 case 
'gzip':
1451                     
$a[$field] = gzcompress($this->_data[$field],5);
1452                     break;
1453                 case 
'boolean':
1454                     
$a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]);
1455                 break;
1456                 default:
1457                     if (
$this->_data[$field] instanceof Doctrine_Record) {
1458                         
$a[$field] = $this->_data[$field]->getIncremented();
1459                         if (
$a[$field] !== null) {
1460                             
$this->_data[$field] = $a[$field];
1461                         }
1462                     } else {
1463                         
$a[$field] = $this->_data[$field];
1464                     }
1465                     
/** TODO:
1466                     if ($this->_data[$v] === null) {
1467                         throw new Doctrine_Record_Exception('Unexpected null value.');
1468                     }
1469                     */
1470             
}
1471         }
1472
1473         return 
$a;
1474     }
1475
1476     
/**
1477      * count
1478      * this class implements countable interface
1479      *
1480      * @return integer          the number of columns in this record
1481      */
1482     
public function count()
1483     {
1484         return 
count($this->_data);
1485     }
1486
1487     
/**
1488      * alias for count()
1489      *
1490      * @return integer          the number of columns in this record
1491      */
1492     
public function columnCount()
1493     {
1494         return 
$this->count();
1495     }
1496
1497     
/**
1498      * toArray
1499      * returns the record as an array
1500      *
1501      * @param boolean $deep - Return also the relations
1502      * @return array
1503      */
1504     
public function toArray($deep true$prefixKey false)
1505     {
1506         if (
$this->_state == self::STATE_LOCKED || $this->_state == self::STATE_TLOCKED) {
1507             return 
false;
1508         }
1509         
1510         
$stateBeforeLock $this->_state;
1511         
$this->_state $this->exists() ? self::STATE_LOCKED self::STATE_TLOCKED;
1512         
1513         
$a = array();
1514
1515         foreach (
$this as $column => $value) {
1516             if (
$value === self::$_null || is_object($value)) {
1517                 
$value null;
1518             }
1519
1520             
$columnValue $this->get($column);
1521             
$a[$column] = ($columnValue instanceof Doctrine_Record
1522                 ? 
$columnValue->toArray($deep$prefixKey) : $columnValue;
1523         }
1524
1525         if (
$this->_table->getIdentifierType() ==  Doctrine::IDENTIFIER_AUTOINC) {
1526             
$i      $this->_table->getIdentifier();
1527             
$a[$i]  = $this->getIncremented();
1528         }
1529         
1530         if (
$deep) {
1531             foreach (
$this->_references as $key => $relation) {
1532                 if ( ! 
$relation instanceof Doctrine_Null) {
1533                     
$a[$key] = $relation->toArray($deep$prefixKey);
1534                 }
1535             }
1536         }
1537
1538         
// [FIX] Prevent mapped Doctrine_Records from being displayed fully
1539         
foreach ($this->_values as $key => $value) {
1540             
$a[$key] = ($value instanceof Doctrine_Record)  
1541                 ? 
$value->toArray($deep$prefixKey) : $value;
1542         }
1543         
1544         
$this->_state $stateBeforeLock;
1545
1546         return 
$a;
1547     }
1548
1549     
/**
1550      * Merges this record with an array of values
1551      * or with another existing instance of this object
1552      *
1553      * @param  mixed $data Data to merge. Either another instance of this model or an array
1554      * @param  bool  $deep Bool value for whether or not to merge the data deep
1555      * @return void
1556      */
1557     
public function merge($data$deep true)
1558     {
1559         if (
$data instanceof $this) {
1560             
$array $data->toArray($deep);
1561         } else if (
is_array($data)) {
1562             
$array $data;
1563         }
1564
1565         return 
$this->fromArray($array$deep);
1566     }
1567
1568     
/**
1569      * Import data from a php array
1570      *
1571      * @param   string $array Php array of data
1572      * @param   bool   $deep  Bool value for whether or not to merge the data deep
1573      * @return  void
1574      */
1575     
public function fromArray(array $array$deep true)
1576     {
1577         
$refresh false;
1578         foreach (
$array as $key => $value) {
1579             if (
$key == '_identifier') {
1580                 
$refresh true;
1581                 
$this->assignIdentifier($value);
1582                 continue;
1583             }
1584
1585             if (
$deep && $this->getTable()->hasRelation($key)) {
1586                 if ( ! 
$this->$key) {
1587                     
$this->refreshRelated($key);
1588                 }
1589
1590                 if (
is_array($value)) {
1591                     
$this->$key->fromArray($value$deep);
1592                 }
1593             } else if (
$this->getTable()->hasField($key)) {
1594                 
$this->set($key$value);
1595             } else {
1596                 
$method 'set' Doctrine_Inflector::classify($key);
1597
1598                 try {
1599                     if (
is_callable(array($this$method))) {
1600                         
$this->$method($value);
1601                     }
1602                 } catch (
Doctrine_Record_Exception $e) {}
1603             }
1604         }
1605
1606         if (
$refresh) {
1607             
$this->refresh();
1608         }
1609     }
1610
1611     
/**
1612      * Synchronizes a Doctrine_Record and its relations with data from an array
1613      *
1614      * it expects an array representation of a Doctrine_Record similar to the return
1615      * value of the toArray() method. If the array contains relations it will create
1616      * those that don't exist, update the ones that do, and delete the ones missing
1617      * on the array but available on the Doctrine_Record
1618      *
1619      * @param array $array representation of a Doctrine_Record
1620      */
1621     
public function synchronizeWithArray(array $array$deep true)
1622     {
1623         
$refresh false;
1624         foreach (
$array as $key => $value) {
1625             if (
$key == '_identifier') {
1626                 
$refresh true;
1627                 
$this->assignIdentifier($value);
1628                 continue;
1629             }
1630
1631             if (
$deep && $this->getTable()->hasRelation($key)) {
1632                 if ( ! 
$this->$key) {
1633                     
$this->refreshRelated($key);
1634                 }
1635
1636                 if (
is_array($value)) {
1637                     
$this->get($key)->synchronizeWithArray($value);
1638                 }
1639             } else if (
$this->getTable()->hasField($key)) {
1640                 
$this->set($key$value);
1641             }
1642         }
1643
1644         
// eliminate relationships missing in the $array
1645         
foreach ($this->_references as $name => $obj) {
1646             if ( ! isset(
$array[$name])) {
1647                 unset(
$this->$name);
1648             }
1649         }
1650
1651         if (
$refresh) {
1652             
$this->refresh();
1653         }
1654     }
1655
1656     
/**
1657      * exportTo
1658      *
1659      * @param string $type Format type: xml, yml, json
1660      * @param string $deep Whether or not to export deep in to all relationships
1661      * @return mixed $exported
1662      */
1663     
public function exportTo($type$deep true)
1664     {
1665         if (
$type == 'array') {
1666             return 
$this->toArray($deep);
1667         } else {
1668             return 
Doctrine_Parser::dump($this->toArray($deeptrue), $type);
1669         }
1670     }
1671
1672     
/**
1673      * importFrom
1674      *
1675      * Import data from an external data source
1676      *
1677      * @param string $type  Format type: xml, yml, json
1678      * @param string $data  Data to be parsed and imported
1679      * @return void
1680      */
1681     
public function importFrom($type$data)
1682     {
1683         if (
$type == 'array') {
1684             return 
$this->fromArray($data);
1685         } else {
1686             return 
$this->fromArray(Doctrine_Parser::load($data$type));
1687         }
1688     }
1689
1690     
/**
1691      * exists
1692      * returns true if this record is persistent, otherwise false
1693      *
1694      * @return boolean
1695      */
1696     
public function exists()
1697     {
1698         return (
$this->_state !== Doctrine_Record::STATE_TCLEAN  &&
1699                 
$this->_state !== Doctrine_Record::STATE_TDIRTY  &&
1700                 
$this->_state !== Doctrine_Record::STATE_TLOCKED &&
1701                 
$this->_state !== null);
1702     }
1703
1704     
/**
1705      * isModified
1706      * returns true if this record was modified, otherwise false
1707      *
1708      * @return boolean
1709      */
1710     
public function isModified()
1711     {
1712         return (
$this->_state === Doctrine_Record::STATE_DIRTY ||
1713                 
$this->_state === Doctrine_Record::STATE_TDIRTY);
1714     }
1715
1716     
/**
1717      * method for checking existence of properties and Doctrine_Record references
1718      * @param mixed $name               name of the property or reference
1719      * @return boolean
1720      */
1721     
public function hasRelation($fieldName)
1722     {
1723         if (isset(
$this->_data[$fieldName]) || isset($this->_id[$fieldName])) {
1724             return 
true;
1725         }
1726         return 
$this->_table->hasRelation($fieldName);
1727     }
1728
1729     
/**
1730      * getIterator
1731      * @return Doctrine_Record_Iterator     a Doctrine_Record_Iterator that iterates through the data
1732      */
1733     
public function getIterator()
1734     {
1735         return new 
Doctrine_Record_Iterator($this);
1736     }
1737
1738     
/**
1739      * deletes this data access object and all the related composites
1740      * this operation is isolated by a transaction
1741      *
1742      * this event can be listened by the onPreDelete and onDelete listeners
1743      *
1744      * @return boolean      true on success, false on failure
1745      */
1746     
public function delete(Doctrine_Connection $conn null)
1747     {
1748         if (
$conn == null) {
1749             
$conn $this->_table->getConnection();
1750         }
1751         return 
$conn->unitOfWork->delete($this);
1752     }
1753
1754     
/**
1755      * copy
1756      * returns a copy of this object
1757      *
1758      * @return Doctrine_Record
1759      */
1760     
public function copy($deep false)
1761     {
1762         
$data $this->_data;
1763
1764         if (
$this->_table->getIdentifierType() === Doctrine::IDENTIFIER_AUTOINC) {
1765             
$id $this->_table->getIdentifier();
1766
1767             unset(
$data[$id]);
1768         }
1769
1770         
$ret $this->_table->create($data);
1771         
$modified = array();
1772
1773         foreach (
$data as $key => $val) {
1774             if ( ! (
$val instanceof Doctrine_Null)) {
1775                 
$ret->_modified[] = $key;
1776             }
1777         }
1778
1779         if (
$deep) {
1780             foreach (
$this->_references as $key => $value) {
1781                 if (
$value instanceof Doctrine_Collection) {
1782                     foreach (
$value as $record) {
1783                         
$ret->{$key}[] = $record->copy($deep);
1784                     }
1785                 } else if(
$value instanceof Doctrine_Record) {
1786                     
$ret->set($key$value->copy($deep));
1787                 }
1788             }
1789         }
1790
1791         return 
$ret;
1792     }
1793
1794     
/**
1795      * assignIdentifier
1796      *
1797      * @param integer $id
1798      * @return void
1799      */
1800     
public function assignIdentifier($id false)
1801     {
1802         if (
$id === false) {
1803             
$this->_id       = array();
1804             
$this->_data     $this->cleanData($this->_data);
1805             
$this->_state    Doctrine_Record::STATE_TCLEAN;
1806             
$this->_modified = array();
1807         } elseif (
$id === true) {
1808             
$this->prepareIdentifiers(true);
1809             
$this->_state    Doctrine_Record::STATE_CLEAN;
1810             
$this->_modified = array();
1811         } else {
1812             if (
is_array($id)) {
1813                 foreach (
$id as $fieldName => $value) {
1814                     
$this->_id[$fieldName] = $value;
1815                     
$this->_data[$fieldName] = $value;
1816                 }
1817             } else {
1818                 
$name $this->_table->getIdentifier();
1819                 
$this->_id[$name] = $id;
1820                 
$this->_data[$name] = $id;
1821             }
1822             
$this->_state Doctrine_Record::STATE_CLEAN;
1823             
$this->_modified = array();
1824         }
1825     }
1826
1827     
/**
1828      * returns the primary keys of this object
1829      *
1830      * @return array
1831      */
1832     
public function identifier()
1833     {
1834         return 
$this->_id;
1835     }
1836
1837     
/**
1838      * returns the value of autoincremented primary key of this object (if any)
1839      *
1840      * @return integer
1841      * @todo Better name?
1842      */
1843     
final public function getIncremented()
1844     {
1845         
$id current($this->_id);
1846         if (
$id === false) {
1847             return 
null;
1848         }
1849
1850         return 
$id;
1851     }
1852
1853     
/**
1854      * getLast
1855      * this method is used internally be Doctrine_Query
1856      * it is needed to provide compatibility between
1857      * records and collections
1858      *
1859      * @return Doctrine_Record
1860      */
1861     
public function getLast()
1862     {
1863         return 
$this;
1864     }
1865
1866     
/**
1867      * hasRefence
1868      * @param string $name
1869      * @return boolean
1870      */
1871     
public function hasReference($name)
1872     {
1873         return isset(
$this->_references[$name]);
1874     }
1875
1876     
/**
1877      * reference
1878      *
1879      * @param string $name
1880      */
1881     
public function reference($name)
1882     {
1883         if (isset(
$this->_references[$name])) {
1884             return 
$this->_references[$name];
1885         }
1886     }
1887
1888     
/**
1889      * obtainReference
1890      *
1891      * @param string $name
1892      * @throws Doctrine_Record_Exception        if trying to get an unknown related component
1893      */
1894     
public function obtainReference($name)
1895     {
1896         if (isset(
$this->_references[$name])) {
1897             return 
$this->_references[$name];
1898         }
1899         throw new 
Doctrine_Record_Exception("Unknown reference $name");
1900     }
1901
1902     
/**
1903      * getReferences
1904      * @return array    all references
1905      */
1906     
public function getReferences()
1907     {
1908         return 
$this->_references;
1909     }
1910
1911     
/**
1912      * setRelated
1913      *
1914      * @param string $alias
1915      * @param Doctrine_Access $coll
1916      */
1917     
final public function setRelated($aliasDoctrine_Access $coll)
1918     {
1919         
$this->_references[$alias] = $coll;
1920     }
1921
1922     
/**
1923      * loadReference
1924      * loads a related component
1925      *
1926      * @throws Doctrine_Table_Exception             if trying to load an unknown related component
1927      * @param string $name
1928      * @return void
1929      */
1930     
public function loadReference($name)
1931     {
1932         
$rel $this->_table->getRelation($name);
1933         
$this->_references[$name] = $rel->fetchRelatedFor($this);
1934     }
1935
1936     
/**
1937      * call
1938      *
1939      * @param string|array $callback    valid callback
1940      * @param string $column            column name
1941      * @param mixed arg1 ... argN       optional callback arguments
1942      * @return Doctrine_Record
1943      */
1944     
public function call($callback$column)
1945     {
1946         
$args func_get_args();
1947         
array_shift($args);
1948
1949         if (isset(
$args[0])) {
1950             
$fieldName $args[0];
1951             
$args[0] = $this->get($fieldName);
1952
1953             
$newvalue call_user_func_array($callback$args);
1954
1955             
$this->_data[$fieldName] = $newvalue;
1956         }
1957         return 
$this;
1958     }
1959
1960     
/**
1961      * getter for node assciated with this record
1962      *
1963      * @return mixed if tree returns Doctrine_Node otherwise returns false
1964      */
1965     
public function getNode()
1966     {
1967         if ( ! 
$this->_table->isTree()) {
1968             return 
false;
1969         }
1970
1971         if ( ! isset(
$this->_node)) {
1972             
$this->_node Doctrine_Node::factory($this,
1973                                               
$this->getTable()->getOption('treeImpl'),
1974                                               
$this->getTable()->getOption('treeOptions')
1975                                               );
1976         }
1977
1978         return 
$this->_node;
1979     }
1980
1981     public function 
unshiftFilter(Doctrine_Record_Filter $filter)
1982     {
1983         return 
$this->_table->unshiftFilter($filter);
1984     }
1985
1986     
/**
1987      * unlink
1988      * removes links from this record to given records
1989      * if no ids are given, it removes all links
1990      *
1991      * @param string $alias     related component alias
1992      * @param array $ids        the identifiers of the related records
1993      * @return Doctrine_Record  this object
1994      */
1995     
public function unlink($alias$ids = array())
1996     {
1997         
$ids = (array) $ids;
1998
1999         
$q = new Doctrine_Query();
2000
2001         
$rel $this->getTable()->getRelation($alias);
2002
2003         if (
$rel instanceof Doctrine_Relation_Association) {
2004             
$q->delete()
2005               ->
from($rel->getAssociationTable()->getComponentName())
2006               ->
where($rel->getLocal() . ' = ?'array_values($this->identifier()));
2007
2008             if (
count($ids) > 0) {
2009                 
$q->whereIn($rel->getForeign(), $ids);
2010             }
2011
2012             
$q->execute();
2013
2014         } else if (
$rel instanceof Doctrine_Relation_ForeignKey) {
2015             
$q->update($rel->getTable()->getComponentName())
2016               ->
set($rel->getForeign(), '?', array(null))
2017               ->
addWhere($rel->getForeign() . ' = ?'array_values($this->identifier()));
2018
2019             if (
count($ids) > 0) {
2020                 
$q->whereIn($rel->getTable()->getIdentifier(), $ids);
2021             }
2022
2023             
$q->execute();
2024         }
2025         if (isset(
$this->_references[$alias])) {
2026             foreach (
$this->_references[$alias] as $k => $record) {
2027                 if (
in_array(current($record->identifier()), $ids)) {
2028                     
$this->_references[$alias]->remove($k);
2029                 }
2030             }
2031             
$this->_references[$alias]->takeSnapshot();
2032         }
2033         return 
$this;
2034     }
2035
2036     
/**
2037      * link
2038      * creates links from this record to given records
2039      *
2040      * @param string $alias     related component alias
2041      * @param array $ids        the identifiers of the related records
2042      * @return Doctrine_Record  this object
2043      */
2044     
public function link($alias$ids)
2045     {
2046         
$ids = (array) $ids;
2047
2048         if ( ! 
count($ids)) {
2049             return 
$this;
2050         }
2051
2052         
$identifier array_values($this->identifier());
2053         
$identifier array_shift($identifier);
2054
2055         
$rel $this->getTable()->getRelation($alias);
2056
2057         if (
$rel instanceof Doctrine_Relation_Association) {
2058
2059             
$modelClassName $rel->getAssociationTable()->getComponentName();
2060             
$localFieldName $rel->getLocalFieldName();
2061             
$localFieldDef  $rel->getAssociationTable()->getColumnDefinition($localFieldName);
2062             if (
$localFieldDef['type'] == 'integer') {
2063                 
$identifier = (integer) $identifier;
2064             }
2065             
$foreignFieldName $rel->getForeignFieldName();
2066             
$foreignFieldDef  $rel->getAssociationTable()->getColumnDefinition($foreignFieldName);
2067             if (
$foreignFieldDef['type'] == 'integer') {
2068                 foreach (
$ids as $i => $id) {
2069                     
$ids[$i] = (integer) $id;
2070                 }
2071             }
2072             foreach (
$ids as $id) {
2073                 
$record = new $modelClassName;
2074                 
$record[$localFieldName] = $identifier;
2075                 
$record[$foreignFieldName] = $id;
2076                 
$record->save();
2077             }
2078
2079         } else if (
$rel instanceof Doctrine_Relation_ForeignKey) {
2080
2081             
$q = new Doctrine_Query();
2082
2083             
$q->update($rel->getTable()->getComponentName())
2084               ->
set($rel->getForeign(), '?'array_values($this->identifier()));
2085
2086             if (
count($ids) > 0) {
2087                 
$q->whereIn($rel->getTable()->getIdentifier(), $ids);
2088             }
2089
2090             
$q->execute();
2091
2092         } else if (
$rel instanceof Doctrine_Relation_LocalKey) {
2093
2094             
$q = new Doctrine_Query();
2095
2096             
$q->update($this->getTable()->getComponentName())
2097               ->
set($rel->getLocalFieldName(), '?'$ids);
2098
2099             if (
count($ids) > 0) {
2100                 
$q->whereIn($rel->getTable()->getIdentifier(), array_values($this->identifier()));
2101             }
2102
2103             
$q->execute();
2104
2105         }
2106
2107         return 
$this;
2108     }
2109
2110
2111     
/**
2112      * __call
2113      * this method is a magic method that is being used for method overloading
2114      *
2115      * the function of this method is to try to find given method from the templates
2116      * this record is using and if it finds given method it will execute it
2117      *
2118      * So, in sense, this method replicates the usage of mixins (as seen in some programming languages)
2119      *
2120      * @param string $method        name of the method
2121      * @param array $args           method arguments
2122      * @return mixed                the return value of the given method
2123      */
2124     
public function __call($method$args)
2125     {
2126         if ((
$template $this->_table->getMethodOwner($method)) !== false) {
2127             
$template->setInvoker($this);
2128             return 
call_user_func_array(array($template$method), $args);
2129         }
2130
2131         foreach (
$this->_table->getTemplates() as $template) {
2132             if (
is_callable(array($template$method))) {
2133                 
$template->setInvoker($this);
2134                 
$this->_table->setMethodOwner($method$template);
2135
2136                 return 
call_user_func_array(array($template$method), $args);
2137             }
2138         }
2139
2140         throw new 
Doctrine_Record_Exception(sprintf('Unknown method %s::%s'get_class($this), $method));
2141     }
2142
2143     
/**
2144      * used to delete node from tree - MUST BE USE TO DELETE RECORD IF TABLE ACTS AS TREE
2145      *
2146      */
2147     
public function deleteNode()
2148     {
2149         
$this->getNode()->delete();
2150     }
2151     
2152     
/**
2153      * Helps freeing the memory occupied by the entity.
2154      * Cuts all references the entity has to other entities and removes the entity
2155      * from the instance pool.
2156      * Note: The entity is no longer useable after free() has been called. Any operations
2157      * done with the entity afterwards can lead to unpredictable results.
2158      */
2159     
public function free($deep false)
2160     {
2161         if (
$this->_state != self::STATE_LOCKED && $this->_state != self::STATE_TLOCKED) {
2162             
$this->_state $this->exists() ? self::STATE_LOCKED self::STATE_TLOCKED;
2163
2164             
$this->_table->getRepository()->evict($this->_oid);
2165             
$this->_table->removeRecord($this);
2166             
$this->_data = array();
2167             
$this->_id = array();
2168
2169             if (
$deep) {
2170                 foreach (
$this->_references as $name => $reference) {
2171                     if ( ! (
$reference instanceof Doctrine_Null)) {
2172                         
$reference->free($deep);
2173                     }
2174                 }
2175             }
2176
2177             
$this->_references = array();
2178         }
2179     }
2180
2181     
/**
2182      * __toString alias
2183      *
2184      * @return string $dump
2185      */
2186     
public function toString()
2187     {
2188         return 
Doctrine::dump(get_object_vars($this));
2189     }
2190
2191     
/**
2192      * returns a string representation of this object
2193      */
2194     
public function __toString()
2195     {
2196         return (string) 
$this->_oid;
2197     }
2198 }