2009-04-29 10:13:20 +00:00
< ? php
/*
2010-01-18 10:27:17 +00:00
* $Id : Record . php 6619 2009 - 11 - 02 23 : 45 : 08 Z jwage $
2009-04-29 10:13:20 +00:00
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
* LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL . For more information , see
* < http :// www . phpdoctrine . org >.
*/
/**
* Doctrine_Record
* All record classes should inherit this super class
*
* @ package Doctrine
* @ subpackage Record
* @ author Konsta Vesterinen < kvesteri @ cc . hut . fi >
* @ license http :// www . opensource . org / licenses / lgpl - license . php LGPL
* @ link www . phpdoctrine . org
* @ since 1.0
2010-01-18 10:27:17 +00:00
* @ version $Revision : 6619 $
2009-04-29 10:13:20 +00:00
*/
abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Countable , IteratorAggregate , Serializable
{
/**
* STATE CONSTANTS
*/
/**
* DIRTY STATE
* a Doctrine_Record is in dirty state when its properties are changed
*/
const STATE_DIRTY = 1 ;
/**
* TDIRTY STATE
* a Doctrine_Record is in transient dirty state when it is created
* and some of its fields are modified but it is NOT yet persisted into database
*/
const STATE_TDIRTY = 2 ;
/**
* CLEAN STATE
* a Doctrine_Record is in clean state when all of its properties are loaded from the database
* and none of its properties are changed
*/
const STATE_CLEAN = 3 ;
/**
* PROXY STATE
* a Doctrine_Record is in proxy state when its properties are not fully loaded
*/
const STATE_PROXY = 4 ;
/**
* NEW TCLEAN
* a Doctrine_Record is in transient clean state when it is created and none of its fields are modified
*/
const STATE_TCLEAN = 5 ;
/**
* LOCKED STATE
* a Doctrine_Record is temporarily locked during deletes and saves
*
* This state is used internally to ensure that circular deletes
* and saves will not cause infinite loops
*/
const STATE_LOCKED = 6 ;
/**
* TLOCKED STATE
* a Doctrine_Record is temporarily locked ( and transient ) during deletes and saves
*
* This state is used internally to ensure that circular deletes
* and saves will not cause infinite loops
*/
const STATE_TLOCKED = 7 ;
/**
* @ var Doctrine_Node_ < TreeImpl > node object
*/
protected $_node ;
/**
* @ var integer $_id the primary keys of this object
*/
protected $_id = array ();
/**
* @ var array $_data the record data
*/
protected $_data = array ();
/**
* @ var array $_values the values array , aggregate values and such are mapped into this array
*/
protected $_values = array ();
/**
* @ var integer $_state the state of this record
* @ see STATE_ * constants
*/
protected $_state ;
/**
* @ var array $_lastModified an array containing field names that were modified in the previous transaction
*/
protected $_lastModified = array ();
/**
* @ var array $_modified an array containing field names that have been modified
* @ todo Better name ? $_modifiedFields ?
*/
protected $_modified = array ();
/**
* @ var array $_oldValues an array of the old values from set properties
*/
protected $_oldValues = array ();
/**
* @ var Doctrine_Validator_ErrorStack error stack object
*/
protected $_errorStack ;
/**
* @ var array $_references an array containing all the references
*/
protected $_references = array ();
/**
* Doctrine_Collection of objects needing to be deleted on save
*
* @ var string
*/
protected $_pendingDeletes = array ();
/**
* Array of pending un links in format alias => keys to be executed after save
*
* @ var array $_pendingUnlinks
*/
protected $_pendingUnlinks = array ();
/**
* Array of custom accessors for cache
*
* @ var array
*/
protected static $_customAccessors = array ();
/**
* Array of custom mutators for cache
*
* @ var array
*/
protected static $_customMutators = array ();
/**
* Whether or not to serialize references when a Doctrine_Record is serialized
*
* @ var boolean
*/
protected $_serializeReferences = false ;
/**
* Array containing the save hooks and events that have been invoked
*
* @ var array
*/
protected $_invokedSaveHooks = false ;
/**
* @ var integer $index this index is used for creating object identifiers
*/
private static $_index = 1 ;
/**
* @ var integer $oid object identifier , each Record object has a unique object identifier
*/
private $_oid ;
/**
* constructor
* @ param Doctrine_Table | null $table a Doctrine_Table object or null ,
* if null the table object is retrieved from current connection
*
* @ param boolean $isNewEntry whether or not this record is transient
*
* @ throws Doctrine_Connection_Exception if object is created using the new operator and there are no
* open connections
* @ throws Doctrine_Record_Exception if the cleanData operation fails somehow
*/
public function __construct ( $table = null , $isNewEntry = false )
{
if ( isset ( $table ) && $table instanceof Doctrine_Table ) {
$this -> _table = $table ;
$exists = ( ! $isNewEntry );
} else {
// get the table of this class
$class = get_class ( $this );
$this -> _table = Doctrine :: getTable ( $class );
$exists = false ;
}
// Check if the current connection has the records table in its registry
// If not this record is only used for creating table definition and setting up
// relations.
if ( ! $this -> _table -> getConnection () -> hasTable ( $this -> _table -> getComponentName ())) {
return ;
}
$this -> _oid = self :: $_index ;
self :: $_index ++ ;
// get the data array
$this -> _data = $this -> _table -> getData ();
// get the column count
$count = count ( $this -> _data );
$this -> _values = $this -> cleanData ( $this -> _data );
$this -> prepareIdentifiers ( $exists );
if ( ! $exists ) {
if ( $count > count ( $this -> _values )) {
$this -> _state = Doctrine_Record :: STATE_TDIRTY ;
} else {
$this -> _state = Doctrine_Record :: STATE_TCLEAN ;
}
// set the default values for this record
$this -> assignDefaultValues ();
} else {
$this -> _state = Doctrine_Record :: STATE_CLEAN ;
if ( $count < $this -> _table -> getColumnCount ()) {
$this -> _state = Doctrine_Record :: STATE_PROXY ;
}
}
$repository = $this -> _table -> getRepository ();
// Fix for #1682 and #1841.
// Doctrine_Table does not have the repository yet during dummy record creation.
if ( $repository ) {
$repository -> add ( $this );
$this -> construct ();
}
}
/**
* Set whether or not to serialize references .
* This is used by caching since we want to serialize references when caching
* but not when just normally serializing a instance
*
* @ param boolean $bool
* @ return boolean $bool
*/
public function serializeReferences ( $bool = null )
{
if ( ! is_null ( $bool )) {
$this -> _serializeReferences = $bool ;
}
return $this -> _serializeReferences ;
}
/**
2009-06-17 10:25:53 +00:00
* the current instance counter used to generate unique ids for php objects . Contains the next identifier .
2009-04-29 10:13:20 +00:00
*
* @ return integer
*/
public static function _index ()
{
return self :: $_index ;
}
/**
* setUp
* this method is used for setting up relations and attributes
* it should be implemented by child classes
*
* @ return void
*/
public function setUp ()
{ }
/**
* construct
* Empty template method to provide concrete Record classes with the possibility
* to hook into the constructor procedure
*
* @ return void
*/
public function construct ()
{ }
2009-06-17 10:25:53 +00:00
2009-04-29 10:13:20 +00:00
/**
2009-06-17 10:25:53 +00:00
* @ see $_oid ;
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ return integer the object identifier
2009-04-29 10:13:20 +00:00
*/
public function getOid ()
{
return $this -> _oid ;
}
public function oid ()
{
return $this -> _oid ;
}
2009-06-17 10:25:53 +00:00
/**
* calls a subclass hook . Idempotent until @ see clearInvokedSaveHooks () is called .
*
* < code >
* $this -> invokeSaveHooks ( 'pre' , 'save' );
* </ code >
*
* @ param string $when 'post' or 'pre'
* @ param string $type serialize , unserialize , save , delete , update , insert , validate , dqlSelect , dqlDelete , hydrate
* @ param Doctrine_Event $event event raised
* @ return Doctrine_Event the event generated using the type , if not specified
*/
2009-04-29 10:13:20 +00:00
public function invokeSaveHooks ( $when , $type , $event = null )
{
$func = $when . ucfirst ( $type );
if ( is_null ( $event )) {
$constant = constant ( 'Doctrine_Event::RECORD_' . strtoupper ( $type ));
//echo $func . " - " . 'Doctrine_Event::RECORD_' . strtoupper($type) . "\n";
$event = new Doctrine_Event ( $this , $constant );
}
if ( ! isset ( $this -> _invokedSaveHooks [ $func ])) {
$this -> $func ( $event );
$this -> getTable () -> getRecordListener () -> $func ( $event );
$this -> _invokedSaveHooks [ $func ] = $event ;
} else {
$event = $this -> _invokedSaveHooks [ $func ];
}
return $event ;
}
2009-06-17 10:25:53 +00:00
/**
* makes all the already used save hooks available again
*/
2009-04-29 10:13:20 +00:00
public function clearInvokedSaveHooks ()
{
$this -> _invokedSaveHooks = array ();
}
/**
2009-06-17 10:25:53 +00:00
* tests validity of the record using the current data .
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ param boolean $deep run the validation process on the relations
* @ param boolean $hooks invoke save hooks before start
* @ return boolean whether or not this record is valid
2009-04-29 10:13:20 +00:00
*/
public function isValid ( $deep = false , $hooks = true )
{
if ( ! $this -> _table -> getAttribute ( Doctrine :: ATTR_VALIDATE )) {
return true ;
}
if ( $this -> _state == self :: STATE_LOCKED || $this -> _state == self :: STATE_TLOCKED ) {
return true ;
}
if ( $hooks ) {
$this -> invokeSaveHooks ( 'pre' , 'save' );
$this -> invokeSaveHooks ( 'pre' , $this -> exists () ? 'update' : 'insert' );
}
// Clear the stack from any previous errors.
$this -> getErrorStack () -> clear ();
// Run validation process
$event = new Doctrine_Event ( $this , Doctrine_Event :: RECORD_VALIDATE );
$this -> preValidate ( $event );
$this -> getTable () -> getRecordListener () -> preValidate ( $event );
if ( ! $event -> skipOperation ) {
$validator = new Doctrine_Validator ();
$validator -> validateRecord ( $this );
$this -> validate ();
if ( $this -> _state == self :: STATE_TDIRTY || $this -> _state == self :: STATE_TCLEAN ) {
$this -> validateOnInsert ();
} else {
$this -> validateOnUpdate ();
}
}
$this -> getTable () -> getRecordListener () -> postValidate ( $event );
$this -> postValidate ( $event );
$valid = $this -> getErrorStack () -> count () == 0 ? true : false ;
if ( $valid && $deep ) {
$stateBeforeLock = $this -> _state ;
$this -> _state = $this -> exists () ? self :: STATE_LOCKED : self :: STATE_TLOCKED ;
foreach ( $this -> _references as $reference ) {
if ( $reference instanceof Doctrine_Record ) {
if ( ! $valid = $reference -> isValid ( $deep )) {
break ;
}
} else if ( $reference instanceof Doctrine_Collection ) {
foreach ( $reference as $record ) {
if ( ! $valid = $record -> isValid ( $deep )) {
break ;
}
}
}
}
$this -> _state = $stateBeforeLock ;
}
return $valid ;
}
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the validation procedure , doing any custom / specialized
* validations that are neccessary .
*/
protected function validate ()
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the validation procedure only when the record is going to be
* updated .
*/
protected function validateOnUpdate ()
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the validation procedure only when the record is going to be
* inserted into the data store the first time .
*/
protected function validateOnInsert ()
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the serializing procedure .
*/
public function preSerialize ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the serializing procedure .
*/
public function postSerialize ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the serializing procedure .
*/
public function preUnserialize ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the serializing procedure .
*/
public function postUnserialize ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure .
*/
public function preSave ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure .
*/
public function postSave ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the deletion procedure .
*/
public function preDelete ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the deletion procedure .
*/
public function postDelete ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure only when the record is going to be
* updated .
*/
public function preUpdate ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure only when the record is going to be
* updated .
*/
public function postUpdate ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure only when the record is going to be
* inserted into the data store the first time .
*/
public function preInsert ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure only when the record is going to be
* inserted into the data store the first time .
*/
public function postInsert ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the validation procedure . Useful for cleaning up data before
* validating it .
*/
public function preValidate ( $event )
{ }
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the validation procedure .
*/
public function postValidate ( $event )
{ }
/**
* Empty template method to provide Record classes with the ability to alter DQL select
* queries at runtime
*/
public function preDqlSelect ( $event )
{ }
/**
* Empty template method to provide Record classes with the ability to alter DQL update
* queries at runtime
*/
public function preDqlUpdate ( $event )
{ }
/**
* Empty template method to provide Record classes with the ability to alter DQL delete
* queries at runtime
*/
public function preDqlDelete ( $event )
{ }
/**
* Empty template method to provide Record classes with the ability to alter hydration
* before it runs
*/
public function preHydrate ( $event )
{ }
/**
* Empty template method to provide Record classes with the ability to alter hydration
* after it runs
*/
public function postHydrate ( $event )
{ }
/**
* Get the record error stack as a human readable string .
* Useful for outputting errors to user via web browser
*
* @ return string $message
*/
public function getErrorStackAsString ()
{
$errorStack = $this -> getErrorStack ();
if ( count ( $errorStack )) {
$message = sprintf ( " Validation failed in class %s \n \n " , get_class ( $this ));
$message .= " " . count ( $errorStack ) . " field " . ( count ( $errorStack ) > 1 ? 's' : null ) . " had validation error " . ( count ( $errorStack ) > 1 ? 's' : null ) . " : \n \n " ;
foreach ( $errorStack as $field => $errors ) {
$message .= " * " . count ( $errors ) . " validator " . ( count ( $errors ) > 1 ? 's' : null ) . " failed on $field ( " . implode ( " , " , $errors ) . " ) \n " ;
}
return $message ;
} else {
return false ;
}
}
/**
2009-06-17 10:25:53 +00:00
* retrieves the ErrorStack . To be called after a failed validation attempt ( @ see isValid ()) .
2009-04-29 10:13:20 +00:00
*
* @ return Doctrine_Validator_ErrorStack returns the errorStack associated with this record
*/
public function getErrorStack ()
{
if ( ! $this -> _errorStack ) {
$this -> _errorStack = new Doctrine_Validator_ErrorStack ( get_class ( $this ));
}
return $this -> _errorStack ;
}
/**
2009-06-17 10:25:53 +00:00
* assigns the ErrorStack or returns it if called without parameters
2009-04-29 10:13:20 +00:00
*
* @ param Doctrine_Validator_ErrorStack errorStack to be assigned for this record
* @ return void | Doctrine_Validator_ErrorStack returns the errorStack associated with this record
*/
public function errorStack ( $stack = null )
{
if ( $stack !== null ) {
if ( ! ( $stack instanceof Doctrine_Validator_ErrorStack )) {
throw new Doctrine_Record_Exception ( 'Argument should be an instance of Doctrine_Validator_ErrorStack.' );
}
$this -> _errorStack = $stack ;
} else {
return $this -> getErrorStack ();
}
}
/**
* Assign the inheritance column values
*
* @ return void
*/
public function assignInheritanceValues ()
{
$map = $this -> _table -> inheritanceMap ;
foreach ( $map as $k => $v ) {
$k = $this -> _table -> getFieldName ( $k );
$old = $this -> get ( $k , false );
if ((( string ) $old !== ( string ) $v || $old === null ) && ! in_array ( $k , $this -> _modified )) {
$this -> set ( $k , $v );
}
}
}
/**
* setDefaultValues
* sets the default values for records internal data
*
* @ param boolean $overwrite whether or not to overwrite the already set values
* @ return boolean
*/
public function assignDefaultValues ( $overwrite = false )
{
if ( ! $this -> _table -> hasDefaultValues ()) {
return false ;
}
foreach ( $this -> _data as $column => $value ) {
$default = $this -> _table -> getDefaultValueOf ( $column );
if ( $default === null ) {
continue ;
}
if ( $value === self :: $_null || $overwrite ) {
$this -> _data [ $column ] = $default ;
$this -> _modified [] = $column ;
$this -> _state = Doctrine_Record :: STATE_TDIRTY ;
}
}
}
/**
* cleanData
* leaves the $data array only with values whose key is a field inside this
* record and returns the values that were removed from $data . Also converts
* any values of 'null' to objects of type Doctrine_Null .
*
2009-06-17 10:25:53 +00:00
* @ param array $data data array to be cleaned
* @ return array values cleaned from data
2009-04-29 10:13:20 +00:00
*/
public function cleanData ( & $data )
{
$tmp = $data ;
$data = array ();
foreach ( $this -> getTable () -> getFieldNames () as $fieldName ) {
if ( isset ( $tmp [ $fieldName ])) {
$data [ $fieldName ] = $tmp [ $fieldName ];
} else if ( array_key_exists ( $fieldName , $tmp )) {
$data [ $fieldName ] = self :: $_null ;
} else if ( ! isset ( $this -> _data [ $fieldName ])) {
$data [ $fieldName ] = self :: $_null ;
}
unset ( $tmp [ $fieldName ]);
}
return $tmp ;
}
/**
* hydrate
* hydrates this object from given array
*
* @ param array $data
2009-06-17 10:25:53 +00:00
* @ param boolean $overwriteLocalChanges whether to overwrite the unsaved ( dirty ) data
* @ return void
2009-04-29 10:13:20 +00:00
*/
2009-05-14 08:02:01 +00:00
public function hydrate ( array $data , $overwriteLocalChanges = true )
2009-04-29 10:13:20 +00:00
{
2009-05-14 08:02:01 +00:00
if ( $overwriteLocalChanges ) {
2009-04-29 10:13:20 +00:00
$this -> _values = array_merge ( $this -> _values , $this -> cleanData ( $data ));
$this -> _data = array_merge ( $this -> _data , $data );
} else {
$this -> _values = array_merge ( $this -> cleanData ( $data ), $this -> _values );
$this -> _data = array_merge ( $data , $this -> _data );
}
2009-05-14 08:02:01 +00:00
if ( ! $this -> isModified () && count ( $this -> _values ) < $this -> _table -> getColumnCount ()) {
2009-04-29 10:13:20 +00:00
$this -> _state = self :: STATE_PROXY ;
}
}
/**
* prepareIdentifiers
* prepares identifiers for later use
*
* @ param boolean $exists whether or not this record exists in persistent data store
* @ return void
*/
private function prepareIdentifiers ( $exists = true )
{
switch ( $this -> _table -> getIdentifierType ()) {
case Doctrine :: IDENTIFIER_AUTOINC :
case Doctrine :: IDENTIFIER_SEQUENCE :
case Doctrine :: IDENTIFIER_NATURAL :
$name = $this -> _table -> getIdentifier ();
if ( is_array ( $name )) {
$name = $name [ 0 ];
}
if ( $exists ) {
if ( isset ( $this -> _data [ $name ]) && $this -> _data [ $name ] !== self :: $_null ) {
$this -> _id [ $name ] = $this -> _data [ $name ];
}
}
break ;
case Doctrine :: IDENTIFIER_COMPOSITE :
$names = $this -> _table -> getIdentifier ();
foreach ( $names as $name ) {
if ( $this -> _data [ $name ] === self :: $_null ) {
$this -> _id [ $name ] = null ;
} else {
$this -> _id [ $name ] = $this -> _data [ $name ];
}
}
break ;
}
}
/**
* serialize
2009-06-17 10:25:53 +00:00
* this method is automatically called when an instance of Doctrine_Record is serialized
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ return string
2009-04-29 10:13:20 +00:00
*/
public function serialize ()
{
$event = new Doctrine_Event ( $this , Doctrine_Event :: RECORD_SERIALIZE );
$this -> preSerialize ( $event );
$this -> getTable () -> getRecordListener () -> preSerialize ( $event );
$vars = get_object_vars ( $this );
if ( ! $this -> serializeReferences ()) {
unset ( $vars [ '_references' ]);
}
unset ( $vars [ '_table' ]);
unset ( $vars [ '_errorStack' ]);
unset ( $vars [ '_filter' ]);
unset ( $vars [ '_node' ]);
$data = $this -> _data ;
if ( $this -> exists ()) {
$data = array_merge ( $data , $this -> _id );
}
foreach ( $data as $k => $v ) {
if ( $v instanceof Doctrine_Record && $this -> _table -> getTypeOf ( $k ) != 'object' ) {
unset ( $vars [ '_data' ][ $k ]);
} elseif ( $v === self :: $_null ) {
unset ( $vars [ '_data' ][ $k ]);
} else {
switch ( $this -> _table -> getTypeOf ( $k )) {
case 'array' :
case 'object' :
$vars [ '_data' ][ $k ] = serialize ( $vars [ '_data' ][ $k ]);
break ;
case 'gzip' :
$vars [ '_data' ][ $k ] = gzcompress ( $vars [ '_data' ][ $k ]);
break ;
}
}
}
$str = serialize ( $vars );
$this -> postSerialize ( $event );
$this -> getTable () -> getRecordListener () -> postSerialize ( $event );
return $str ;
}
/**
2009-06-17 10:25:53 +00:00
* this method is automatically called everytime an instance is unserialized
2009-04-29 10:13:20 +00:00
*
* @ param string $serialized Doctrine_Record as serialized string
* @ throws Doctrine_Record_Exception if the cleanData operation fails somehow
* @ return void
*/
public function unserialize ( $serialized )
{
$event = new Doctrine_Event ( $this , Doctrine_Event :: RECORD_UNSERIALIZE );
$manager = Doctrine_Manager :: getInstance ();
$connection = $manager -> getConnectionForComponent ( get_class ( $this ));
$this -> _oid = self :: $_index ;
self :: $_index ++ ;
$this -> _table = $connection -> getTable ( get_class ( $this ));
$this -> preUnserialize ( $event );
$this -> getTable () -> getRecordListener () -> preUnserialize ( $event );
$array = unserialize ( $serialized );
foreach ( $array as $k => $v ) {
$this -> $k = $v ;
}
foreach ( $this -> _data as $k => $v ) {
switch ( $this -> _table -> getTypeOf ( $k )) {
case 'array' :
case 'object' :
$this -> _data [ $k ] = unserialize ( $this -> _data [ $k ]);
break ;
case 'gzip' :
$this -> _data [ $k ] = gzuncompress ( $this -> _data [ $k ]);
break ;
case 'enum' :
$this -> _data [ $k ] = $this -> _table -> enumValue ( $k , $this -> _data [ $k ]);
break ;
}
}
$this -> _table -> getRepository () -> add ( $this );
$this -> cleanData ( $this -> _data );
$this -> prepareIdentifiers ( $this -> exists ());
$this -> postUnserialize ( $event );
$this -> getTable () -> getRecordListener () -> postUnserialize ( $event );
}
/**
2009-06-17 10:25:53 +00:00
* assigns the state of this record or returns it if called without parameters
2009-04-29 10:13:20 +00:00
*
* @ param integer | string $state if set , this method tries to set the record state to $state
* @ see Doctrine_Record :: STATE_ * constants
*
* @ throws Doctrine_Record_State_Exception if trying to set an unknown state
* @ return null | integer
*/
public function state ( $state = null )
{
if ( $state == null ) {
return $this -> _state ;
}
$err = false ;
if ( is_integer ( $state )) {
if ( $state >= 1 && $state <= 7 ) {
$this -> _state = $state ;
} else {
$err = true ;
}
} else if ( is_string ( $state )) {
$upper = strtoupper ( $state );
$const = 'Doctrine_Record::STATE_' . $upper ;
if ( defined ( $const )) {
$this -> _state = constant ( $const );
} else {
$err = true ;
}
}
if ( $this -> _state === Doctrine_Record :: STATE_TCLEAN ||
$this -> _state === Doctrine_Record :: STATE_CLEAN ) {
$this -> _resetModified ();
}
if ( $err ) {
throw new Doctrine_Record_State_Exception ( 'Unknown record state ' . $state );
}
}
/**
* refresh
* refresh internal data from the database
*
* @ param bool $deep If true , fetch also current relations . Caution : this deletes
* any aggregated values you may have queried beforee
*
* @ throws Doctrine_Record_Exception When the refresh operation fails ( when the database row
* this record represents does not exist anymore )
* @ return boolean
*/
public function refresh ( $deep = false )
{
$id = $this -> identifier ();
if ( ! is_array ( $id )) {
$id = array ( $id );
}
if ( empty ( $id )) {
return false ;
}
$id = array_values ( $id );
$overwrite = $this -> getTable () -> getAttribute ( Doctrine :: ATTR_HYDRATE_OVERWRITE );
$this -> getTable () -> setAttribute ( Doctrine :: ATTR_HYDRATE_OVERWRITE , true );
if ( $deep ) {
$query = $this -> getTable () -> createQuery ();
foreach ( array_keys ( $this -> _references ) as $name ) {
$query -> leftJoin ( get_class ( $this ) . '.' . $name );
}
$query -> where ( implode ( ' = ? AND ' , ( array ) $this -> getTable () -> getIdentifier ()) . ' = ?' );
$this -> clearRelated ();
$record = $query -> fetchOne ( $id );
} else {
// Use HYDRATE_ARRAY to avoid clearing object relations
$record = $this -> getTable () -> find ( $id , Doctrine :: HYDRATE_ARRAY );
if ( $record ) {
$this -> hydrate ( $record );
}
}
$this -> getTable () -> setAttribute ( Doctrine :: ATTR_HYDRATE_OVERWRITE , $overwrite );
if ( $record === false ) {
throw new Doctrine_Record_Exception ( 'Failed to refresh. Record does not exist.' );
}
$this -> _resetModified ();
$this -> prepareIdentifiers ();
$this -> _state = Doctrine_Record :: STATE_CLEAN ;
return $this ;
}
/**
* refresh
* refresh data of related objects from the database
*
* @ param string $name name of a related component .
* if set , this method only refreshes the specified related component
*
* @ return Doctrine_Record this object
*/
public function refreshRelated ( $name = null )
{
if ( is_null ( $name )) {
foreach ( $this -> _table -> getRelations () as $rel ) {
2010-01-18 10:27:17 +00:00
$reference = $rel -> fetchRelatedFor ( $this );
if ( $reference instanceof Doctrine_Collection || ( $reference && $reference -> exists ())) {
$this -> _references [ $rel -> getAlias ()] = $reference ;
}
2009-04-29 10:13:20 +00:00
}
} else {
$rel = $this -> _table -> getRelation ( $name );
2010-01-18 10:27:17 +00:00
$reference = $rel -> fetchRelatedFor ( $this );
if ( $reference instanceof Doctrine_Collection || ( $reference && $reference -> exists ())) {
$this -> _references [ $name ] = $reference ;
}
2009-04-29 10:13:20 +00:00
}
}
/**
* clearRelated
* unsets all the relationships this object has
*
* ( references to related objects still remain on Table objects )
*/
public function clearRelated ()
{
$this -> _references = array ();
}
/**
2009-06-17 10:25:53 +00:00
* returns the table object for this record .
2009-04-29 10:13:20 +00:00
*
* @ return Doctrine_Table a Doctrine_Table object
*/
public function getTable ()
{
return $this -> _table ;
}
/**
2009-06-17 10:25:53 +00:00
* return all the internal data ( columns )
2009-04-29 10:13:20 +00:00
*
* @ return array an array containing all the properties
*/
public function getData ()
{
return $this -> _data ;
}
/**
2009-06-17 10:25:53 +00:00
* returns the value of a property ( column ) . If the property is not yet loaded
* this method does NOT load it .
2009-04-29 10:13:20 +00:00
*
* @ param $name name of the property
* @ throws Doctrine_Record_Exception if trying to get an unknown property
* @ return mixed
*/
public function rawGet ( $fieldName )
{
if ( ! isset ( $this -> _data [ $fieldName ])) {
throw new Doctrine_Record_Exception ( 'Unknown property ' . $fieldName );
}
if ( $this -> _data [ $fieldName ] === self :: $_null ) {
return null ;
}
return $this -> _data [ $fieldName ];
}
/**
2009-06-17 10:25:53 +00:00
* loads all the uninitialized properties from the database .
2009-04-29 10:13:20 +00:00
* Used to move a record from PROXY to CLEAN / DIRTY state .
*
2009-06-17 10:25:53 +00:00
* @ param array $data overwriting data to load in the record . Instance is hydrated from the table if not specified .
2009-04-29 10:13:20 +00:00
* @ return boolean
*/
public function load ( array $data = array ())
{
// only load the data from database if the Doctrine_Record is in proxy state
if ( $this -> _state == Doctrine_Record :: STATE_PROXY ) {
$id = $this -> identifier ();
if ( ! is_array ( $id )) {
$id = array ( $id );
}
if ( empty ( $id )) {
return false ;
}
$data = empty ( $data ) ? $this -> getTable () -> find ( $id , Doctrine :: HYDRATE_ARRAY ) : $data ;
foreach ( $data as $field => $value ) {
if ( ! isset ( $this -> _data [ $field ]) || $this -> _data [ $field ] === self :: $_null ) {
2009-05-14 08:02:01 +00:00
// Ticket #2031: null value was causing removal of field during load
if ( $value === null ) {
$value = self :: $_null ;
}
2009-04-29 10:13:20 +00:00
$this -> _data [ $field ] = $value ;
}
}
if ( $this -> isModified ()) {
$this -> _state = Doctrine_Record :: STATE_DIRTY ;
} else if ( count ( $data ) >= $this -> _table -> getColumnCount ()) {
$this -> _state = Doctrine_Record :: STATE_CLEAN ;
}
return true ;
}
return false ;
}
/**
2009-06-17 10:25:53 +00:00
* sets a fieldname to have a custom accessor or check if a field has a custom
* accessor defined ( when called without $accessor parameter ) .
2009-04-29 10:13:20 +00:00
*
* @ param string $fieldName
* @ param string $accessor
* @ return boolean
*/
public function hasAccessor ( $fieldName , $accessor = null )
{
$componentName = $this -> _table -> getComponentName ();
if ( $accessor ) {
self :: $_customAccessors [ $componentName ][ $fieldName ] = $accessor ;
} else {
return ( isset ( self :: $_customAccessors [ $componentName ][ $fieldName ]) && self :: $_customAccessors [ $componentName ][ $fieldName ]);
}
}
/**
2009-06-17 10:25:53 +00:00
* clears the accessor for a field name
2009-04-29 10:13:20 +00:00
*
* @ param string $fieldName
* @ return void
*/
public function clearAccessor ( $fieldName )
{
$componentName = $this -> _table -> getComponentName ();
unset ( self :: $_customAccessors [ $componentName ][ $fieldName ]);
}
/**
2009-06-17 10:25:53 +00:00
* gets the custom accessor for a field name
2009-04-29 10:13:20 +00:00
*
* @ param string $fieldName
* @ return string $accessor
*/
public function getAccessor ( $fieldName )
{
if ( $this -> hasAccessor ( $fieldName )) {
$componentName = $this -> _table -> getComponentName ();
return self :: $_customAccessors [ $componentName ][ $fieldName ];
}
}
/**
2009-06-17 10:25:53 +00:00
* gets all accessors for this component instance
2009-04-29 10:13:20 +00:00
*
* @ return array $accessors
*/
public function getAccessors ()
{
$componentName = $this -> _table -> getComponentName ();
return self :: $_customAccessors [ $componentName ];
}
/**
2009-06-17 10:25:53 +00:00
* sets a fieldname to have a custom mutator or check if a field has a custom
* mutator defined ( when called without the $mutator parameter )
2009-04-29 10:13:20 +00:00
*
* @ param string $fieldName
* @ param string $mutator
* @ return boolean
*/
public function hasMutator ( $fieldName , $mutator = null )
{
$componentName = $this -> _table -> getComponentName ();
if ( $mutator ) {
self :: $_customMutators [ $componentName ][ $fieldName ] = $mutator ;
} else {
return ( isset ( self :: $_customMutators [ $componentName ][ $fieldName ]) && self :: $_customMutators [ $componentName ][ $fieldName ]);
}
}
/**
2009-06-17 10:25:53 +00:00
* gets the custom mutator for a field name
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ param string $fieldname
* @ return string
2009-04-29 10:13:20 +00:00
*/
public function getMutator ( $fieldName )
{
if ( $this -> hasMutator ( $fieldName )) {
$componentName = $this -> _table -> getComponentName ();
return self :: $_customMutators [ $componentName ][ $fieldName ];
}
}
/**
2009-06-17 10:25:53 +00:00
* clears the custom mutator for a field name
2009-04-29 10:13:20 +00:00
*
* @ param string $fieldName
* @ return void
*/
public function clearMutator ( $fieldName )
{
$componentName = $this -> _table -> getComponentName ();
unset ( self :: $_customMutators [ $componentName ][ $fieldName ]);
}
/**
2009-06-17 10:25:53 +00:00
* gets all custom mutators for this component instance
2009-04-29 10:13:20 +00:00
*
* @ return array $mutators
*/
public function getMutators ()
{
$componentName = $this -> _table -> getComponentName ();
return self :: $_customMutators [ $componentName ];
}
/**
* Set a fieldname to have a custom accessor and mutator
*
* @ param string $fieldname
* @ param string $accessor
* @ param string $mutator
*/
public function hasAccessorMutator ( $fieldName , $accessor , $mutator )
{
$this -> hasAccessor ( $fieldName , $accessor );
$this -> hasMutator ( $fieldName , $mutator );
}
/**
* returns a value of a property or a related component
*
2009-06-17 10:25:53 +00:00
* @ param mixed $fieldName name of the property or related component
2009-04-29 10:13:20 +00:00
* @ param boolean $load whether or not to invoke the loading procedure
* @ throws Doctrine_Record_Exception if trying to get a value of unknown property / related component
* @ return mixed
*/
public function get ( $fieldName , $load = true )
{
if ( $this -> _table -> getAttribute ( Doctrine :: ATTR_AUTO_ACCESSOR_OVERRIDE ) || $this -> hasAccessor ( $fieldName )) {
$componentName = $this -> _table -> getComponentName ();
$accessor = $this -> hasAccessor ( $fieldName )
? $this -> getAccessor ( $fieldName )
: 'get' . Doctrine_Inflector :: classify ( $fieldName );
if ( $this -> hasAccessor ( $fieldName ) || method_exists ( $this , $accessor )) {
$this -> hasAccessor ( $fieldName , $accessor );
return $this -> $accessor ( $load );
}
}
return $this -> _get ( $fieldName , $load );
}
protected function _get ( $fieldName , $load = true )
{
$value = self :: $_null ;
if ( array_key_exists ( $fieldName , $this -> _values )) {
return $this -> _values [ $fieldName ];
}
if ( array_key_exists ( $fieldName , $this -> _data )) {
// check if the value is the Doctrine_Null object located in self::$_null)
if ( $this -> _data [ $fieldName ] === self :: $_null && $load ) {
$this -> load ();
}
if ( $this -> _data [ $fieldName ] === self :: $_null ) {
$value = null ;
} else {
$value = $this -> _data [ $fieldName ];
}
return $value ;
}
try {
if ( ! isset ( $this -> _references [ $fieldName ]) && $load ) {
$rel = $this -> _table -> getRelation ( $fieldName );
$this -> _references [ $fieldName ] = $rel -> fetchRelatedFor ( $this );
}
if ( $this -> _references [ $fieldName ] === self :: $_null ) {
return null ;
}
return $this -> _references [ $fieldName ];
} catch ( Doctrine_Table_Exception $e ) {
$success = false ;
foreach ( $this -> _table -> getFilters () as $filter ) {
try {
$value = $filter -> filterGet ( $this , $fieldName );
$success = true ;
} catch ( Doctrine_Exception $e ) {}
}
if ( $success ) {
return $value ;
} else {
throw $e ;
}
}
}
/**
2009-06-17 10:25:53 +00:00
* sets a value that will be managed as if it were a field by magic accessor and mutators , @ see get () and @ see set () .
* Normally used by Doctrine for the mapping of aggregate values .
2009-04-29 10:13:20 +00:00
*
* @ param string $name the name of the mapped value
* @ param mixed $value mixed value to be mapped
* @ return void
*/
public function mapValue ( $name , $value = null )
{
$this -> _values [ $name ] = $value ;
}
/**
2009-06-17 10:25:53 +00:00
* alters mapped values , properties and related components .
2009-04-29 10:13:20 +00:00
*
* @ param mixed $name name of the property or reference
* @ param mixed $value value of the property or reference
* @ param boolean $load whether or not to refresh / load the uninitialized record data
*
* @ throws Doctrine_Record_Exception if trying to set a value for unknown property / related component
* @ throws Doctrine_Record_Exception if trying to set a value of wrong type for related component
*
* @ return Doctrine_Record
*/
public function set ( $fieldName , $value , $load = true )
{
if ( $this -> _table -> getAttribute ( Doctrine :: ATTR_AUTO_ACCESSOR_OVERRIDE ) || $this -> hasMutator ( $fieldName )) {
$componentName = $this -> _table -> getComponentName ();
$mutator = $this -> hasMutator ( $fieldName )
? $this -> getMutator ( $fieldName ) :
'set' . Doctrine_Inflector :: classify ( $fieldName );
if ( $this -> hasMutator ( $fieldName ) || method_exists ( $this , $mutator )) {
$this -> hasMutator ( $fieldName , $mutator );
return $this -> $mutator ( $value , $load );
}
}
return $this -> _set ( $fieldName , $value , $load );
}
protected function _set ( $fieldName , $value , $load = true )
{
if ( array_key_exists ( $fieldName , $this -> _values )) {
$this -> _values [ $fieldName ] = $value ;
} else if ( isset ( $this -> _data [ $fieldName ])) {
$type = $this -> _table -> getTypeOf ( $fieldName );
if ( $value instanceof Doctrine_Record ) {
$id = $value -> getIncremented ();
if ( $id !== null && $type !== 'object' ) {
$value = $id ;
}
}
if ( $load ) {
$old = $this -> get ( $fieldName , $load );
} else {
$old = $this -> _data [ $fieldName ];
}
if ( $this -> _isValueModified ( $type , $old , $value )) {
if ( $value === null ) {
$default = $this -> _table -> getDefaultValueOf ( $fieldName );
$value = ( $default === null ) ? self :: $_null : $default ;
}
$this -> _data [ $fieldName ] = $value ;
$this -> _modified [] = $fieldName ;
$this -> _oldValues [ $fieldName ] = $old ;
switch ( $this -> _state ) {
case Doctrine_Record :: STATE_CLEAN :
case Doctrine_Record :: STATE_PROXY :
$this -> _state = Doctrine_Record :: STATE_DIRTY ;
break ;
case Doctrine_Record :: STATE_TCLEAN :
$this -> _state = Doctrine_Record :: STATE_TDIRTY ;
break ;
}
}
} else {
try {
$this -> coreSetRelated ( $fieldName , $value );
} catch ( Doctrine_Table_Exception $e ) {
$success = false ;
foreach ( $this -> _table -> getFilters () as $filter ) {
try {
$value = $filter -> filterSet ( $this , $fieldName , $value );
$success = true ;
} catch ( Doctrine_Exception $e ) {}
}
if ( $success ) {
return $value ;
} else {
throw $e ;
}
}
}
return $this ;
}
/**
* Check if a value has changed according to Doctrine
* Doctrine is loose with type checking in the same ways PHP is for consistancy of behavior
*
* This function basically says if what is being set is of Doctrine type boolean and something
* like current_value == 1 && new_value = true would not be considered modified
*
* Simply doing $old !== $new will return false for boolean columns would mark the field as modified
* and change it in the database when it is not necessary
*
* @ param string $type Doctrine type of the column
* @ param string $old Old value
* @ param string $new New value
* @ return boolean $modified Whether or not Doctrine considers the value modified
*/
protected function _isValueModified ( $type , $old , $new )
{
2010-01-18 10:27:17 +00:00
if ( $new instanceof Doctrine_Expression ) {
return true ;
}
2009-04-29 10:13:20 +00:00
if ( $type == 'boolean' && ( is_bool ( $old ) || is_numeric ( $old )) && ( is_bool ( $new ) || is_numeric ( $new )) && $old == $new ) {
return false ;
} else if ( in_array ( $type , array ( 'decimal' , 'float' )) && is_numeric ( $old ) && is_numeric ( $new )) {
return $old * 100 != $new * 100 ;
} else if ( in_array ( $type , array ( 'integer' , 'int' )) && is_numeric ( $old ) && is_numeric ( $new )) {
return ( int ) $old !== ( int ) $new ;
2010-01-18 10:27:17 +00:00
} else if ( $type == 'timestamp' || $type == 'date' ) {
return strtotime ( $old ) !== strtotime ( $new );
2009-04-29 10:13:20 +00:00
} else {
return $old !== $new ;
}
}
/**
2009-06-17 10:25:53 +00:00
* Places a related component in the object graph .
*
* This method inserts a related component instance in this record
* relations , populating the foreign keys accordingly .
*
* @ param string $name related component alias in the relation
* @ param Doctrine_Record | Doctrine_Collection $value object to be linked as a related component
2009-04-29 10:13:20 +00:00
* @ todo Refactor . What about composite keys ?
*/
public function coreSetRelated ( $name , $value )
{
$rel = $this -> _table -> getRelation ( $name );
if ( $value === null ) {
$value = self :: $_null ;
}
// 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 new Doctrine_Record_Exception ( " Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references. " );
}
if ( isset ( $this -> _references [ $name ])) {
$this -> _references [ $name ] -> setData ( $value -> getData ());
return $this ;
}
} else {
$localFieldName = $this -> _table -> getFieldName ( $rel -> getLocal ());
if ( $value !== self :: $_null ) {
$relatedTable = $value -> getTable ();
$foreignFieldName = $relatedTable -> getFieldName ( $rel -> getForeign ());
}
// one-to-one relation found
if ( ! ( $value instanceof Doctrine_Record ) && ! ( $value instanceof Doctrine_Null )) {
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. " );
}
if ( $rel instanceof Doctrine_Relation_LocalKey ) {
if ( $value !== self :: $_null && ! empty ( $foreignFieldName ) && $foreignFieldName != $value -> getTable () -> getIdentifier ()) {
$this -> set ( $localFieldName , $value -> rawGet ( $foreignFieldName ), false );
} else {
// FIX: Ticket #1280 fits in this situation
$this -> set ( $localFieldName , $value , false );
}
} elseif ( $value !== self :: $_null ) {
// We should only be able to reach $foreignFieldName if we have a Doctrine_Record on hands
$value -> set ( $foreignFieldName , $this , false );
}
}
} else if ( $rel instanceof Doctrine_Relation_Association ) {
// join table relation found
if ( ! ( $value instanceof Doctrine_Collection )) {
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. " );
}
}
$this -> _references [ $name ] = $value ;
}
/**
2009-06-17 10:25:53 +00:00
* test whether a field ( column , mapped value , related component ) is accessible by @ see get ()
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ param string $fieldName
2009-04-29 10:13:20 +00:00
* @ return boolean
*/
public function contains ( $fieldName )
{
if ( isset ( $this -> _data [ $fieldName ])) {
// this also returns true if the field is a Doctrine_Null.
// imho this is not correct behavior.
return true ;
}
if ( isset ( $this -> _id [ $fieldName ])) {
return true ;
}
if ( isset ( $this -> _values [ $fieldName ])) {
return true ;
}
if ( isset ( $this -> _references [ $fieldName ]) &&
$this -> _references [ $fieldName ] !== self :: $_null ) {
return true ;
}
return false ;
}
/**
2009-06-17 10:25:53 +00:00
* deletes a column or a related component .
2009-04-29 10:13:20 +00:00
* @ param string $name
* @ return void
*/
public function __unset ( $name )
{
if ( isset ( $this -> _data [ $name ])) {
$this -> _data [ $name ] = array ();
} else if ( isset ( $this -> _references [ $name ])) {
if ( $this -> _references [ $name ] instanceof Doctrine_Record ) {
$this -> _pendingDeletes [] = $this -> $name ;
$this -> _references [ $name ] = self :: $_null ;
} elseif ( $this -> _references [ $name ] instanceof Doctrine_Collection ) {
$this -> _pendingDeletes [] = $this -> $name ;
$this -> _references [ $name ] -> setData ( array ());
}
}
}
/**
2009-06-17 10:25:53 +00:00
* returns Doctrine_Record instances which need to be deleted on save
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ return array
2009-04-29 10:13:20 +00:00
*/
public function getPendingDeletes ()
{
return $this -> _pendingDeletes ;
}
/**
2009-06-17 10:25:53 +00:00
* returns Doctrine_Record instances which need to be unlinked ( deleting the relation ) on save
2009-04-29 10:13:20 +00:00
*
* @ return array $pendingUnlinks
*/
public function getPendingUnlinks ()
{
return $this -> _pendingUnlinks ;
}
/**
2009-06-17 10:25:53 +00:00
* resets pending record unlinks
2009-04-29 10:13:20 +00:00
*
* @ return void
*/
public function resetPendingUnlinks ()
{
$this -> _pendingUnlinks = array ();
}
/**
* applies the changes made to this object into database
* this method is smart enough to know if any changes are made
* and whether to use INSERT or UPDATE statement
*
* this method also saves the related components
*
2009-06-17 10:25:53 +00:00
* @ param Doctrine_Connection $conn optional connection parameter
* @ throws Exception if record is not valid and validation is active
2009-04-29 10:13:20 +00:00
* @ return void
*/
public function save ( Doctrine_Connection $conn = null )
{
if ( $conn === null ) {
$conn = $this -> _table -> getConnection ();
}
$conn -> unitOfWork -> saveGraph ( $this );
}
/**
2009-06-17 10:25:53 +00:00
* tries to save the object and all its related components .
2009-04-29 10:13:20 +00:00
* In contrast to Doctrine_Record :: save (), this method does not
* throw an exception when validation fails but returns TRUE on
* success or FALSE on failure .
*
* @ param Doctrine_Connection $conn optional connection parameter
* @ return TRUE if the record was saved sucessfully without errors , FALSE otherwise .
*/
public function trySave ( Doctrine_Connection $conn = null ) {
try {
$this -> save ( $conn );
return true ;
} catch ( Doctrine_Validator_Exception $ignored ) {
return false ;
}
}
/**
2009-06-17 10:25:53 +00:00
* executes a SQL REPLACE query . A REPLACE query is identical to a INSERT
2009-04-29 10:13:20 +00:00
* query , except that if there is already a row in the table with the same
* key field values , the REPLACE query just updates its values instead of
* inserting a new row .
*
* The REPLACE type of query does not make part of the SQL standards . Since
* practically only MySQL and SQLIte implement it natively , this type of
* query isemulated through this method for other DBMS using standard types
* of queries inside a transaction to assure the atomicity of the operation .
*
* @ param Doctrine_Connection $conn optional connection parameter
* @ throws Doctrine_Connection_Exception if some of the key values was null
* @ throws Doctrine_Connection_Exception if there were no key fields
* @ throws Doctrine_Connection_Exception if something fails at database level
* @ return integer number of rows affected
*/
public function replace ( Doctrine_Connection $conn = null )
{
if ( $conn === null ) {
$conn = $this -> _table -> getConnection ();
}
if ( $this -> exists ()) {
return $this -> save ();
} else {
if ( $this -> isValid ()) {
$identifier = ( array ) $this -> getTable () -> getIdentifier ();
$data = $this -> getPrepared ();
return $conn -> replace ( $this -> _table , $data , $identifier );
} else {
return false ;
}
}
}
/**
2009-06-17 10:25:53 +00:00
* retrieves an array of modified fields and associated new values .
*
* @ param boolean $old pick the old values ( instead of the new ones )
* @ param boolean $last pick only lastModified values ( @ see getLastModified ())
2009-04-29 10:13:20 +00:00
* @ return array $a
*/
public function getModified ( $old = false , $last = false )
{
$a = array ();
$modified = $last ? $this -> _lastModified : $this -> _modified ;
foreach ( $modified as $fieldName ) {
if ( $old ) {
$a [ $fieldName ] = isset ( $this -> _oldValues [ $fieldName ])
? $this -> _oldValues [ $fieldName ]
: $this -> getTable () -> getDefaultValueOf ( $fieldName );
} else {
$a [ $fieldName ] = $this -> _data [ $fieldName ];
}
}
return $a ;
}
/**
* returns an array of the modified fields from the last transaction .
*
2009-06-17 10:25:53 +00:00
* @ param boolean $old pick the old values ( instead of the new ones )
* @ return array
2009-04-29 10:13:20 +00:00
*/
public function getLastModified ( $old = false )
{
return $this -> getModified ( $old , true );
}
/**
2009-06-17 10:25:53 +00:00
* Retrieves data prepared for a sql transaction .
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* Returns an array of modified fields and values with data preparation ;
* adds column aggregation inheritance and converts Records into primary
* key values .
2009-04-29 10:13:20 +00:00
*
* @ param array $array
* @ return array
* @ todo What about a little bit more expressive name ? getPreparedData ?
*/
public function getPrepared ( array $array = array ())
{
$a = array ();
if ( empty ( $array )) {
$modifiedFields = $this -> _modified ;
}
foreach ( $modifiedFields as $field ) {
$type = $this -> _table -> getTypeOf ( $field );
if ( $this -> _data [ $field ] === self :: $_null ) {
$a [ $field ] = null ;
continue ;
}
switch ( $type ) {
case 'array' :
case 'object' :
$a [ $field ] = serialize ( $this -> _data [ $field ]);
break ;
case 'gzip' :
$a [ $field ] = gzcompress ( $this -> _data [ $field ], 5 );
break ;
case 'boolean' :
$a [ $field ] = $this -> getTable () -> getConnection () -> convertBooleans ( $this -> _data [ $field ]);
break ;
default :
if ( $this -> _data [ $field ] instanceof Doctrine_Record ) {
$a [ $field ] = $this -> _data [ $field ] -> getIncremented ();
if ( $a [ $field ] !== null ) {
$this -> _data [ $field ] = $a [ $field ];
}
} else {
$a [ $field ] = $this -> _data [ $field ];
}
/** TODO :
if ( $this -> _data [ $v ] === null ) {
throw new Doctrine_Record_Exception ( 'Unexpected null value.' );
}
*/
}
}
return $a ;
}
/**
2009-06-17 10:25:53 +00:00
* implements Countable interface
2009-04-29 10:13:20 +00:00
*
* @ return integer the number of columns in this record
*/
public function count ()
{
return count ( $this -> _data );
}
/**
2009-06-17 10:25:53 +00:00
* alias for @ see count ()
2009-04-29 10:13:20 +00:00
*
* @ return integer the number of columns in this record
*/
public function columnCount ()
{
return $this -> count ();
}
/**
2009-06-17 10:25:53 +00:00
* returns the record representation as an array
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ link http :// www . doctrine - project . org / documentation / manual / 1_1 / en / working - with - models
* @ param boolean $deep whether to include relations
* @ param boolean $prefixKey not used
2009-04-29 10:13:20 +00:00
* @ return array
*/
public function toArray ( $deep = true , $prefixKey = false )
{
if ( $this -> _state == self :: STATE_LOCKED || $this -> _state == self :: STATE_TLOCKED ) {
return false ;
}
$stateBeforeLock = $this -> _state ;
$this -> _state = $this -> exists () ? self :: STATE_LOCKED : self :: STATE_TLOCKED ;
$a = array ();
foreach ( $this as $column => $value ) {
if ( $value === self :: $_null || is_object ( $value )) {
$value = null ;
}
2009-06-17 10:25:53 +00:00
$columnValue = $this -> get ( $column );
2009-08-03 10:38:43 +00:00
if ( $columnValue instanceof Doctrine_Record ) {
$a [ $column ] = $columnValue -> getIncremented ();
} else {
$a [ $column ] = $columnValue ;
}
2009-04-29 10:13:20 +00:00
}
if ( $this -> _table -> getIdentifierType () == Doctrine :: IDENTIFIER_AUTOINC ) {
$i = $this -> _table -> getIdentifier ();
$a [ $i ] = $this -> getIncremented ();
}
2009-06-17 10:25:53 +00:00
2009-04-29 10:13:20 +00:00
if ( $deep ) {
foreach ( $this -> _references as $key => $relation ) {
2009-06-17 10:25:53 +00:00
if ( ! $relation instanceof Doctrine_Null ) {
2009-04-29 10:13:20 +00:00
$a [ $key ] = $relation -> toArray ( $deep , $prefixKey );
}
}
}
// [FIX] Prevent mapped Doctrine_Records from being displayed fully
foreach ( $this -> _values as $key => $value ) {
2009-06-17 10:25:53 +00:00
$a [ $key ] = ( $value instanceof Doctrine_Record )
? $value -> toArray ( $deep , $prefixKey ) : $value ;
2009-04-29 10:13:20 +00:00
}
2009-06-17 10:25:53 +00:00
2009-04-29 10:13:20 +00:00
$this -> _state = $stateBeforeLock ;
return $a ;
}
/**
2009-06-17 10:25:53 +00:00
* merges this record with an array of values
2009-04-29 10:13:20 +00:00
* or with another existing instance of this object
*
2009-06-17 10:25:53 +00:00
* @ see fromArray ()
* @ link http :// www . doctrine - project . org / documentation / manual / 1_1 / en / working - with - models
* @ param string $array array of data to merge , see link for documentation
* @ param bool $deep whether or not to merge relations
2009-04-29 10:13:20 +00:00
* @ return void
*/
public function merge ( $data , $deep = true )
{
if ( $data instanceof $this ) {
$array = $data -> toArray ( $deep );
} else if ( is_array ( $data )) {
$array = $data ;
}
return $this -> fromArray ( $array , $deep );
}
/**
2009-06-17 10:25:53 +00:00
* imports data from a php array
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ link http :// www . doctrine - project . org / documentation / manual / 1_1 / en / working - with - models
* @ param string $array array of data , see link for documentation
* @ param bool $deep whether or not to act on relations
* @ return void
2009-04-29 10:13:20 +00:00
*/
public function fromArray ( array $array , $deep = true )
{
$refresh = false ;
foreach ( $array as $key => $value ) {
if ( $key == '_identifier' ) {
$refresh = true ;
$this -> assignIdentifier ( $value );
continue ;
}
if ( $deep && $this -> getTable () -> hasRelation ( $key )) {
if ( ! $this -> $key ) {
$this -> refreshRelated ( $key );
}
if ( is_array ( $value )) {
if ( isset ( $value [ 0 ]) && ! is_array ( $value [ 0 ])) {
$this -> unlink ( $key , array (), false );
foreach ( $value as $id ) {
$this -> link ( $key , $id , false );
}
} else {
$this -> $key -> fromArray ( $value , $deep );
}
}
2009-08-03 10:38:43 +00:00
} else if ( $this -> getTable () -> hasField ( $key ) || array_key_exists ( $key , $this -> _values )) {
2009-04-29 10:13:20 +00:00
$this -> set ( $key , $value );
} else {
$method = 'set' . Doctrine_Inflector :: classify ( $key );
2009-05-14 08:02:01 +00:00
try {
if ( is_callable ( array ( $this , $method ))) {
$this -> $method ( $value );
}
} catch ( Doctrine_Record_Exception $e ) {}
2009-04-29 10:13:20 +00:00
}
}
if ( $refresh ) {
$this -> refresh ();
}
}
/**
2009-06-17 10:25:53 +00:00
* synchronizes a Doctrine_Record instance and its relations with data from an array
2009-04-29 10:13:20 +00:00
*
* it expects an array representation of a Doctrine_Record similar to the return
* value of the toArray () method . If the array contains relations it will create
* those that don ' t exist , update the ones that do , and delete the ones missing
2009-06-17 10:25:53 +00:00
* on the array but available on the Doctrine_Record ( unlike @ see fromArray () that
* does not touch what it is not in $array )
2009-04-29 10:13:20 +00:00
*
* @ param array $array representation of a Doctrine_Record
2009-06-17 10:25:53 +00:00
* @ param bool $deep whether or not to act on relations
2009-04-29 10:13:20 +00:00
*/
public function synchronizeWithArray ( array $array , $deep = true )
{
$refresh = false ;
foreach ( $array as $key => $value ) {
if ( $key == '_identifier' ) {
$refresh = true ;
$this -> assignIdentifier ( $value );
continue ;
}
if ( $deep && $this -> getTable () -> hasRelation ( $key )) {
if ( ! $this -> $key ) {
$this -> refreshRelated ( $key );
}
if ( is_array ( $value )) {
if ( isset ( $value [ 0 ]) && ! is_array ( $value [ 0 ])) {
$this -> unlink ( $key , array (), false );
foreach ( $value as $id ) {
$this -> link ( $key , $id , false );
}
} else {
$this -> $key -> synchronizeWithArray ( $value );
}
}
2010-01-18 10:27:17 +00:00
} else if ( $this -> getTable () -> hasField ( $key ) || array_key_exists ( $key , $this -> _values )) {
2009-04-29 10:13:20 +00:00
$this -> set ( $key , $value );
}
}
2009-05-14 08:02:01 +00:00
// Eliminate relationships missing in the $array
2009-04-29 10:13:20 +00:00
foreach ( $this -> _references as $name => $relation ) {
2009-05-14 08:02:01 +00:00
$rel = $this -> getTable () -> getRelation ( $name );
2009-06-17 10:25:53 +00:00
if ( ! isset ( $array [ $name ]) && ( ! $rel -> isOneToOne () || ! isset ( $array [ $rel -> getLocalFieldName ()]))) {
2009-04-29 10:13:20 +00:00
unset ( $this -> $name );
}
}
if ( $refresh ) {
$this -> refresh ();
}
}
/**
2009-06-17 10:25:53 +00:00
* exports instance to a chosen format
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ param string $type format type : array , xml , yml , json
* @ param string $deep whether or not to export all relationships
* @ return string representation as $type format . Array is $type is array
2009-04-29 10:13:20 +00:00
*/
public function exportTo ( $type , $deep = true )
{
if ( $type == 'array' ) {
return $this -> toArray ( $deep );
} else {
return Doctrine_Parser :: dump ( $this -> toArray ( $deep , true ), $type );
}
}
/**
2009-06-17 10:25:53 +00:00
* imports data from a chosen format in the current instance
2009-04-29 10:13:20 +00:00
*
* @ param string $type Format type : xml , yml , json
* @ param string $data Data to be parsed and imported
* @ return void
*/
public function importFrom ( $type , $data )
{
if ( $type == 'array' ) {
return $this -> fromArray ( $data );
} else {
return $this -> fromArray ( Doctrine_Parser :: load ( $data , $type ));
}
}
/**
2009-06-17 10:25:53 +00:00
* returns true if this record is saved in the database , otherwise false ( it is transient )
2009-04-29 10:13:20 +00:00
*
* @ return boolean
*/
public function exists ()
{
return ( $this -> _state !== Doctrine_Record :: STATE_TCLEAN &&
$this -> _state !== Doctrine_Record :: STATE_TDIRTY &&
$this -> _state !== Doctrine_Record :: STATE_TLOCKED &&
$this -> _state !== null );
}
/**
* returns true if this record was modified , otherwise false
*
2009-06-17 10:25:53 +00:00
* @ param boolean $deep whether to process also the relations for changes
2009-04-29 10:13:20 +00:00
* @ return boolean
*/
public function isModified ( $deep = false )
{
$modified = ( $this -> _state === Doctrine_Record :: STATE_DIRTY ||
$this -> _state === Doctrine_Record :: STATE_TDIRTY );
if ( ! $modified && $deep ) {
if ( $this -> _state == self :: STATE_LOCKED || $this -> _state == self :: STATE_TLOCKED ) {
return false ;
}
$stateBeforeLock = $this -> _state ;
$this -> _state = $this -> exists () ? self :: STATE_LOCKED : self :: STATE_TLOCKED ;
foreach ( $this -> _references as $reference ) {
if ( $reference instanceof Doctrine_Record ) {
if ( $modified = $reference -> isModified ( $deep )) {
break ;
}
} else if ( $reference instanceof Doctrine_Collection ) {
foreach ( $reference as $record ) {
if ( $modified = $record -> isModified ( $deep )) {
2010-01-18 10:27:17 +00:00
break 2 ;
2009-04-29 10:13:20 +00:00
}
}
}
}
$this -> _state = $stateBeforeLock ;
}
return $modified ;
}
/**
2009-06-17 10:25:53 +00:00
* checks existence of properties and related components
* @ param mixed $fieldName name of the property or reference
2009-04-29 10:13:20 +00:00
* @ return boolean
*/
public function hasRelation ( $fieldName )
{
if ( isset ( $this -> _data [ $fieldName ]) || isset ( $this -> _id [ $fieldName ])) {
return true ;
}
return $this -> _table -> hasRelation ( $fieldName );
}
/**
2009-06-17 10:25:53 +00:00
* implements IteratorAggregate interface
* @ return Doctrine_Record_Iterator iterator through data
2009-04-29 10:13:20 +00:00
*/
public function getIterator ()
{
return new Doctrine_Record_Iterator ( $this );
}
/**
* deletes this data access object and all the related composites
* this operation is isolated by a transaction
*
* this event can be listened by the onPreDelete and onDelete listeners
*
2009-06-17 10:25:53 +00:00
* @ return boolean true if successful
2009-04-29 10:13:20 +00:00
*/
public function delete ( Doctrine_Connection $conn = null )
{
if ( $conn == null ) {
$conn = $this -> _table -> getConnection ();
}
return $conn -> unitOfWork -> delete ( $this );
}
/**
2009-06-17 10:25:53 +00:00
* generates a copy of this object . Returns an instance of the same class of $this .
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ param boolean $deep whether to duplicates the objects targeted by the relations
2009-04-29 10:13:20 +00:00
* @ return Doctrine_Record
*/
public function copy ( $deep = false )
{
$data = $this -> _data ;
if ( $this -> _table -> getIdentifierType () === Doctrine :: IDENTIFIER_AUTOINC ) {
$id = $this -> _table -> getIdentifier ();
unset ( $data [ $id ]);
}
$ret = $this -> _table -> create ( $data );
$modified = array ();
foreach ( $data as $key => $val ) {
if ( ! ( $val instanceof Doctrine_Null )) {
$ret -> _modified [] = $key ;
}
}
if ( $deep ) {
foreach ( $this -> _references as $key => $value ) {
if ( $value instanceof Doctrine_Collection ) {
foreach ( $value as $record ) {
$ret -> { $key }[] = $record -> copy ( $deep );
}
} else if ( $value instanceof Doctrine_Record ) {
$ret -> set ( $key , $value -> copy ( $deep ));
}
}
}
return $ret ;
}
/**
2009-06-17 10:25:53 +00:00
* assigns an identifier to the instance , for database storage
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ param mixed $id a key value or an array of keys
2009-04-29 10:13:20 +00:00
* @ return void
*/
public function assignIdentifier ( $id = false )
{
if ( $id === false ) {
$this -> _id = array ();
$this -> _data = $this -> cleanData ( $this -> _data );
$this -> _state = Doctrine_Record :: STATE_TCLEAN ;
$this -> _resetModified ();
} elseif ( $id === true ) {
$this -> prepareIdentifiers ( true );
$this -> _state = Doctrine_Record :: STATE_CLEAN ;
$this -> _resetModified ();
} else {
if ( is_array ( $id )) {
foreach ( $id as $fieldName => $value ) {
$this -> _id [ $fieldName ] = $value ;
$this -> _data [ $fieldName ] = $value ;
}
} else {
$name = $this -> _table -> getIdentifier ();
$this -> _id [ $name ] = $id ;
$this -> _data [ $name ] = $id ;
}
$this -> _state = Doctrine_Record :: STATE_CLEAN ;
$this -> _resetModified ();
}
}
/**
* returns the primary keys of this object
*
* @ return array
*/
public function identifier ()
{
return $this -> _id ;
}
/**
* returns the value of autoincremented primary key of this object ( if any )
*
* @ return integer
* @ todo Better name ?
*/
final public function getIncremented ()
{
$id = current ( $this -> _id );
if ( $id === false ) {
return null ;
}
return $id ;
}
/**
* getLast
2009-06-17 10:25:53 +00:00
* this method is used internally by Doctrine_Query
2009-04-29 10:13:20 +00:00
* it is needed to provide compatibility between
* records and collections
*
* @ return Doctrine_Record
*/
public function getLast ()
{
return $this ;
}
/**
2009-06-17 10:25:53 +00:00
* tests whether a relation is set
* @ param string $name relation alias
2009-04-29 10:13:20 +00:00
* @ return boolean
*/
public function hasReference ( $name )
{
return isset ( $this -> _references [ $name ]);
}
/**
2009-06-17 10:25:53 +00:00
* gets a related component
2009-04-29 10:13:20 +00:00
*
* @ param string $name
2009-06-17 10:25:53 +00:00
* @ return Doctrine_Record | Doctrine_Collection
2009-04-29 10:13:20 +00:00
*/
public function reference ( $name )
{
if ( isset ( $this -> _references [ $name ])) {
return $this -> _references [ $name ];
}
}
/**
2009-06-17 10:25:53 +00:00
* gets a related component and fails if it does not exist
2009-04-29 10:13:20 +00:00
*
* @ param string $name
* @ throws Doctrine_Record_Exception if trying to get an unknown related component
*/
public function obtainReference ( $name )
{
if ( isset ( $this -> _references [ $name ])) {
return $this -> _references [ $name ];
}
throw new Doctrine_Record_Exception ( " Unknown reference $name " );
}
/**
2009-06-17 10:25:53 +00:00
* get all related components
* @ return array various Doctrine_Collection or Doctrine_Record instances
2009-04-29 10:13:20 +00:00
*/
public function getReferences ()
{
return $this -> _references ;
}
/**
2009-06-17 10:25:53 +00:00
* set a related component
2009-04-29 10:13:20 +00:00
*
* @ param string $alias
* @ param Doctrine_Access $coll
*/
final public function setRelated ( $alias , Doctrine_Access $coll )
{
$this -> _references [ $alias ] = $coll ;
}
/**
* loadReference
* loads a related component
*
* @ throws Doctrine_Table_Exception if trying to load an unknown related component
2009-06-17 10:25:53 +00:00
* @ param string $name alias of the relation
2009-04-29 10:13:20 +00:00
* @ return void
*/
public function loadReference ( $name )
{
$rel = $this -> _table -> getRelation ( $name );
$this -> _references [ $name ] = $rel -> fetchRelatedFor ( $this );
}
/**
* call
*
* @ param string | array $callback valid callback
* @ param string $column column name
* @ param mixed arg1 ... argN optional callback arguments
2009-06-17 10:25:53 +00:00
* @ return Doctrine_Record provides a fluent interface
2009-04-29 10:13:20 +00:00
*/
public function call ( $callback , $column )
{
$args = func_get_args ();
array_shift ( $args );
if ( isset ( $args [ 0 ])) {
$fieldName = $args [ 0 ];
$args [ 0 ] = $this -> get ( $fieldName );
$newvalue = call_user_func_array ( $callback , $args );
$this -> _data [ $fieldName ] = $newvalue ;
}
return $this ;
}
/**
2009-06-17 10:25:53 +00:00
* getter for node associated with this record
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* @ return Doctrine_Node false if component is not a Tree
2009-04-29 10:13:20 +00:00
*/
public function getNode ()
{
if ( ! $this -> _table -> isTree ()) {
return false ;
}
if ( ! isset ( $this -> _node )) {
$this -> _node = Doctrine_Node :: factory ( $this ,
$this -> getTable () -> getOption ( 'treeImpl' ),
$this -> getTable () -> getOption ( 'treeOptions' )
);
}
return $this -> _node ;
}
public function unshiftFilter ( Doctrine_Record_Filter $filter )
{
return $this -> _table -> unshiftFilter ( $filter );
}
/**
* unlink
* removes links from this record to given records
* if no ids are given , it removes all links
*
* @ param string $alias related component alias
* @ param array $ids the identifiers of the related records
2009-06-17 10:25:53 +00:00
* @ param boolean $now whether or not to execute now or set as pending unlinks
* @ return Doctrine_Record this object ( fluent interface )
2009-04-29 10:13:20 +00:00
*/
public function unlink ( $alias , $ids = array (), $now = false )
{
$ids = ( array ) $ids ;
// fix for #1622
if ( ! isset ( $this -> _references [ $alias ]) && $this -> hasRelation ( $alias )) {
$this -> loadReference ( $alias );
}
if ( isset ( $this -> _references [ $alias ])) {
foreach ( $this -> _references [ $alias ] as $k => $record ) {
if ( in_array ( current ( $record -> identifier ()), $ids ) || empty ( $ids )) {
$this -> _references [ $alias ] -> remove ( $k );
}
}
$this -> _references [ $alias ] -> takeSnapshot ();
}
if ( ! $this -> exists () || $now === false ) {
if ( count ( $ids )) {
foreach ( $ids as $id ) {
$this -> _pendingUnlinks [ $alias ][ $id ] = true ;
}
} else {
$this -> _pendingUnlinks [ $alias ] = false ;
}
return $this ;
} else {
return $this -> unlinkInDb ( $alias , $ids );
}
}
2009-06-17 10:25:53 +00:00
/**
* unlink now the related components , querying the db
* @ param string $alias related component alias
* @ param array $ids the identifiers of the related records
* @ return Doctrine_Record this object ( fluent interface )
*/
2009-04-29 10:13:20 +00:00
public function unlinkInDb ( $alias , $ids = array ())
{
$q = new Doctrine_Query ();
$rel = $this -> getTable () -> getRelation ( $alias );
if ( $rel instanceof Doctrine_Relation_Association ) {
$q -> delete ()
-> from ( $rel -> getAssociationTable () -> getComponentName ())
-> where ( $rel -> getLocal () . ' = ?' , array_values ( $this -> identifier ()));
if ( count ( $ids ) > 0 ) {
$q -> whereIn ( $rel -> getForeign (), $ids );
}
$q -> execute ();
} else if ( $rel instanceof Doctrine_Relation_ForeignKey ) {
$q -> update ( $rel -> getTable () -> getComponentName ())
-> set ( $rel -> getForeign (), '?' , array ( null ))
-> addWhere ( $rel -> getForeign () . ' = ?' , array_values ( $this -> identifier ()));
if ( count ( $ids ) > 0 ) {
$q -> whereIn ( $rel -> getTable () -> getIdentifier (), $ids );
}
$q -> execute ();
}
return $this ;
}
/**
* creates links from this record to given records
*
* @ param string $alias related component alias
* @ param array $ids the identifiers of the related records
* @ param boolean $now wether or not to execute now or set pending
2009-06-17 10:25:53 +00:00
* @ return Doctrine_Record this object ( fluent interface )
2009-04-29 10:13:20 +00:00
*/
public function link ( $alias , $ids , $now = false )
{
$ids = ( array ) $ids ;
if ( ! count ( $ids )) {
return $this ;
}
if ( ! $this -> exists () || $now === false ) {
$relTable = $this -> getTable () -> getRelation ( $alias ) -> getTable ();
$records = $relTable -> createQuery ()
-> whereIn ( $relTable -> getIdentifier (), $ids )
-> execute ();
foreach ( $records as $record ) {
$this -> $alias -> add ( $record );
}
foreach ( $ids as $id ) {
if ( isset ( $this -> _pendingUnlinks [ $alias ][ $id ])) {
unset ( $this -> _pendingUnlinks [ $alias ][ $id ]);
}
}
return $this ;
} else {
return $this -> linkInDb ( $alias , $ids );
}
}
2009-06-17 10:25:53 +00:00
/**
* creates links from this record to given records now , querying the db
*
* @ param string $alias related component alias
* @ param array $ids the identifiers of the related records
* @ return Doctrine_Record this object ( fluent interface )
*/
2009-04-29 10:13:20 +00:00
public function linkInDb ( $alias , $ids )
{
$identifier = array_values ( $this -> identifier ());
$identifier = array_shift ( $identifier );
$rel = $this -> getTable () -> getRelation ( $alias );
if ( $rel instanceof Doctrine_Relation_Association ) {
$modelClassName = $rel -> getAssociationTable () -> getComponentName ();
$localFieldName = $rel -> getLocalFieldName ();
$localFieldDef = $rel -> getAssociationTable () -> getColumnDefinition ( $localFieldName );
if ( $localFieldDef [ 'type' ] == 'integer' ) {
$identifier = ( integer ) $identifier ;
}
$foreignFieldName = $rel -> getForeignFieldName ();
$foreignFieldDef = $rel -> getAssociationTable () -> getColumnDefinition ( $foreignFieldName );
if ( $foreignFieldDef [ 'type' ] == 'integer' ) {
foreach ( $ids as $i => $id ) {
$ids [ $i ] = ( integer ) $id ;
}
}
foreach ( $ids as $id ) {
$record = new $modelClassName ;
$record [ $localFieldName ] = $identifier ;
$record [ $foreignFieldName ] = $id ;
$record -> save ();
}
} else if ( $rel instanceof Doctrine_Relation_ForeignKey ) {
$q = new Doctrine_Query ();
$q -> update ( $rel -> getTable () -> getComponentName ())
-> set ( $rel -> getForeign (), '?' , array_values ( $this -> identifier ()));
if ( count ( $ids ) > 0 ) {
$q -> whereIn ( $rel -> getTable () -> getIdentifier (), $ids );
}
$q -> execute ();
} else if ( $rel instanceof Doctrine_Relation_LocalKey ) {
$q = new Doctrine_Query ();
$q -> update ( $this -> getTable () -> getComponentName ())
-> set ( $rel -> getLocalFieldName (), '?' , $ids );
if ( count ( $ids ) > 0 ) {
$q -> whereIn ( $rel -> getTable () -> getIdentifier (), array_values ( $this -> identifier ()));
}
$q -> execute ();
}
return $this ;
}
/**
* Reset the modified array and store the old array in lastModified so it
2009-06-17 10:25:53 +00:00
* can be accessed by users after saving a record , since the modified array
2009-04-29 10:13:20 +00:00
* is reset after the object is saved .
*
* @ return void
*/
protected function _resetModified ()
{
if ( ! empty ( $this -> _modified )) {
$this -> _lastModified = $this -> _modified ;
$this -> _modified = array ();
}
}
/**
2009-06-17 10:25:53 +00:00
* magic method used for method overloading
2009-04-29 10:13:20 +00:00
*
2009-06-17 10:25:53 +00:00
* the function of this method is to try to find a given method from the templates ( behaviors )
* the record is using , and if found , execute it . Note that already existing methods would not be
* overloaded .
2009-04-29 10:13:20 +00:00
*
* So , in sense , this method replicates the usage of mixins ( as seen in some programming languages )
*
* @ param string $method name of the method
* @ param array $args method arguments
* @ return mixed the return value of the given method
*/
public function __call ( $method , $args )
{
if (( $template = $this -> _table -> getMethodOwner ( $method )) !== false ) {
$template -> setInvoker ( $this );
return call_user_func_array ( array ( $template , $method ), $args );
}
foreach ( $this -> _table -> getTemplates () as $template ) {
if ( is_callable ( array ( $template , $method ))) {
$template -> setInvoker ( $this );
$this -> _table -> setMethodOwner ( $method , $template );
return call_user_func_array ( array ( $template , $method ), $args );
}
}
throw new Doctrine_Record_Exception ( sprintf ( 'Unknown method %s::%s' , get_class ( $this ), $method ));
}
/**
* used to delete node from tree - MUST BE USE TO DELETE RECORD IF TABLE ACTS AS TREE
*
*/
public function deleteNode ()
{
$this -> getNode () -> delete ();
}
/**
* 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 .
2009-06-17 10:25:53 +00:00
* @ param boolean $deep whether to free also the related components
2009-04-29 10:13:20 +00:00
*/
public function free ( $deep = false )
{
if ( $this -> _state != self :: STATE_LOCKED && $this -> _state != self :: STATE_TLOCKED ) {
$this -> _state = $this -> exists () ? self :: STATE_LOCKED : self :: STATE_TLOCKED ;
$this -> _table -> getRepository () -> evict ( $this -> _oid );
$this -> _table -> removeRecord ( $this );
$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 ();
}
}
/**
* __toString alias
*
2009-06-17 10:25:53 +00:00
* @ return string
2009-04-29 10:13:20 +00:00
*/
public function toString ()
{
return Doctrine :: dump ( get_object_vars ( $this ));
}
/**
2009-06-17 10:25:53 +00:00
* magic method
* @ return string representation of this object
2009-04-29 10:13:20 +00:00
*/
public function __toString ()
{
return ( string ) $this -> _oid ;
}
2009-06-17 10:25:53 +00:00
}