[2.0] Refined implementation and semantics of the merge and detach operations. General cleanups and API improvements. Added a testcase for detaching/serializing->unserializing->modifying->merging to demonstrate the transparent serialization.
This commit is contained in:
parent
da07bf4a37
commit
28ca2acb8b
@ -25,9 +25,9 @@
|
|||||||
<xs:complexType name="cascade-type">
|
<xs:complexType name="cascade-type">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
|
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
|
||||||
<xs:element name="cascade-save" type="orm:emptyType" minOccurs="0"/>
|
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
|
||||||
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
|
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
|
||||||
<xs:element name="cascade-delete" type="orm:emptyType" minOccurs="0"/>
|
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
|
||||||
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
|
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
@ -416,21 +416,25 @@ class EntityManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detaches an entity from the EntityManager.
|
* Detaches an entity from the EntityManager, causing a managed entity to
|
||||||
|
* become detached. Unflushed changes made to the entity if any
|
||||||
|
* (including removal of the entity), will not be synchronized to the database.
|
||||||
|
* Entities which previously referenced the detached entity will continue to
|
||||||
|
* reference it.
|
||||||
*
|
*
|
||||||
* @param object $entity The entity to detach.
|
* @param object $entity The entity to detach.
|
||||||
* @return boolean
|
|
||||||
*/
|
*/
|
||||||
public function detach($entity)
|
public function detach($entity)
|
||||||
{
|
{
|
||||||
return $this->_unitOfWork->removeFromIdentityMap($entity);
|
$this->_unitOfWork->detach($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges the state of a detached entity into the persistence context
|
* Merges the state of a detached entity into the persistence context
|
||||||
* of this EntityManager.
|
* of this EntityManager and returns the managed copy of the entity.
|
||||||
|
* The entity passed to merge will not become associated/managed with this EntityManager.
|
||||||
*
|
*
|
||||||
* @param object $entity The entity to merge into the persistence context.
|
* @param object $entity The detached entity to merge into the persistence context.
|
||||||
* @return object The managed copy of the entity.
|
* @return object The managed copy of the entity.
|
||||||
*/
|
*/
|
||||||
public function merge($entity)
|
public function merge($entity)
|
||||||
|
@ -66,8 +66,8 @@ abstract class AssociationMapping
|
|||||||
);
|
);
|
||||||
|
|
||||||
public $cascades = array();
|
public $cascades = array();
|
||||||
public $isCascadeDelete;
|
public $isCascadeRemove;
|
||||||
public $isCascadeSave;
|
public $isCascadePersist;
|
||||||
public $isCascadeRefresh;
|
public $isCascadeRefresh;
|
||||||
public $isCascadeMerge;
|
public $isCascadeMerge;
|
||||||
|
|
||||||
@ -184,8 +184,8 @@ abstract class AssociationMapping
|
|||||||
(bool)$mapping['optional'] : true;
|
(bool)$mapping['optional'] : true;
|
||||||
$this->cascades = isset($mapping['cascade']) ?
|
$this->cascades = isset($mapping['cascade']) ?
|
||||||
(array)$mapping['cascade'] : array();
|
(array)$mapping['cascade'] : array();
|
||||||
$this->isCascadeDelete = in_array('delete', $this->cascades);
|
$this->isCascadeRemove = in_array('remove', $this->cascades);
|
||||||
$this->isCascadeSave = in_array('save', $this->cascades);
|
$this->isCascadePersist = in_array('persist', $this->cascades);
|
||||||
$this->isCascadeRefresh = in_array('refresh', $this->cascades);
|
$this->isCascadeRefresh = in_array('refresh', $this->cascades);
|
||||||
$this->isCascadeMerge = in_array('merge', $this->cascades);
|
$this->isCascadeMerge = in_array('merge', $this->cascades);
|
||||||
}
|
}
|
||||||
@ -196,9 +196,9 @@ abstract class AssociationMapping
|
|||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function isCascadeDelete()
|
public function isCascadeRemove()
|
||||||
{
|
{
|
||||||
return $this->isCascadeDelete;
|
return $this->isCascadeRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,9 +207,9 @@ abstract class AssociationMapping
|
|||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function isCascadeSave()
|
public function isCascadePersist()
|
||||||
{
|
{
|
||||||
return $this->isCascadeSave;
|
return $this->isCascadePersist;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -374,7 +374,7 @@ abstract class AssociationMapping
|
|||||||
*/
|
*/
|
||||||
public function usesJoinTable()
|
public function usesJoinTable()
|
||||||
{
|
{
|
||||||
return (bool)$this->joinTable;
|
return (bool) $this->joinTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -269,11 +269,11 @@ class XmlDriver extends AbstractFileDriver
|
|||||||
private function _getCascadeMappings($cascadeElement)
|
private function _getCascadeMappings($cascadeElement)
|
||||||
{
|
{
|
||||||
$cascades = array();
|
$cascades = array();
|
||||||
if (isset($cascadeElement->{'cascade-save'})) {
|
if (isset($cascadeElement->{'cascade-persist'})) {
|
||||||
$cascades[] = 'save';
|
$cascades[] = 'persist';
|
||||||
}
|
}
|
||||||
if (isset($cascadeElement->{'cascade-delete'})) {
|
if (isset($cascadeElement->{'cascade-remove'})) {
|
||||||
$cascades[] = 'delete';
|
$cascades[] = 'remove';
|
||||||
}
|
}
|
||||||
if (isset($cascadeElement->{'cascade-merge'})) {
|
if (isset($cascadeElement->{'cascade-merge'})) {
|
||||||
$cascades[] = 'merge';
|
$cascades[] = 'merge';
|
||||||
|
@ -255,11 +255,11 @@ class YamlDriver extends AbstractFileDriver
|
|||||||
private function _getCascadeMappings($cascadeElement)
|
private function _getCascadeMappings($cascadeElement)
|
||||||
{
|
{
|
||||||
$cascades = array();
|
$cascades = array();
|
||||||
if (isset($cascadeElement['cascadeSave'])) {
|
if (isset($cascadeElement['cascadePersist'])) {
|
||||||
$cascades[] = 'save';
|
$cascades[] = 'persist';
|
||||||
}
|
}
|
||||||
if (isset($cascadeElement['cascadeDelete'])) {
|
if (isset($cascadeElement['cascadeRemove'])) {
|
||||||
$cascades[] = 'delete';
|
$cascades[] = 'remove';
|
||||||
}
|
}
|
||||||
if (isset($cascadeElement['cascadeMerge'])) {
|
if (isset($cascadeElement['cascadeMerge'])) {
|
||||||
$cascades[] = 'merge';
|
$cascades[] = 'merge';
|
||||||
|
@ -250,11 +250,11 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
|
|||||||
// Set back reference to owner
|
// Set back reference to owner
|
||||||
if ($this->_association->isOneToMany()) {
|
if ($this->_association->isOneToMany()) {
|
||||||
// OneToMany
|
// OneToMany
|
||||||
$this->_typeClass->getReflectionProperty($this->_backRefFieldName)
|
$this->_typeClass->reflFields[$this->_backRefFieldName]
|
||||||
->setValue($value, $this->_owner);
|
->setValue($value, $this->_owner);
|
||||||
} else {
|
} else {
|
||||||
// ManyToMany
|
// ManyToMany
|
||||||
$this->_typeClass->getReflectionProperty($this->_backRefFieldName)
|
$this->_typeClass->reflFields[$this->_backRefFieldName]
|
||||||
->getValue($value)->add($this->_owner);
|
->getValue($value)->add($this->_owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,6 +418,16 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
|
|||||||
{
|
{
|
||||||
$this->_isDirty = $dirty;
|
$this->_isDirty = $dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param $bool
|
||||||
|
* @return unknown_type
|
||||||
|
*/
|
||||||
|
public function setInitialized($bool)
|
||||||
|
{
|
||||||
|
$this->_initialized = $bool;
|
||||||
|
}
|
||||||
|
|
||||||
/* Serializable implementation */
|
/* Serializable implementation */
|
||||||
|
|
||||||
|
@ -49,16 +49,12 @@ use Doctrine\ORM\EntityManager;
|
|||||||
class UnitOfWork implements PropertyChangedListener
|
class UnitOfWork implements PropertyChangedListener
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* An entity is in managed state when it has a primary key/identifier (and
|
* An entity is in MANAGED state when its persistence is managed by an EntityManager.
|
||||||
* therefore persistent state) and is managed by an EntityManager
|
|
||||||
* (registered in the identity map).
|
|
||||||
* In MANAGED state the entity is associated with an EntityManager that manages
|
|
||||||
* the persistent state of the Entity.
|
|
||||||
*/
|
*/
|
||||||
const STATE_MANAGED = 1;
|
const STATE_MANAGED = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An entity is new if it has just been instantiated
|
* An entity is new if it has just been instantiated (i.e. using the "new" operator)
|
||||||
* and is not (yet) managed by an EntityManager.
|
* and is not (yet) managed by an EntityManager.
|
||||||
*/
|
*/
|
||||||
const STATE_NEW = 2;
|
const STATE_NEW = 2;
|
||||||
@ -74,7 +70,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
* associated with an EntityManager, whose persistent state has been
|
* associated with an EntityManager, whose persistent state has been
|
||||||
* deleted (or is scheduled for deletion).
|
* deleted (or is scheduled for deletion).
|
||||||
*/
|
*/
|
||||||
const STATE_DELETED = 4;
|
const STATE_REMOVED = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The identity map that holds references to all managed entities that have
|
* The identity map that holds references to all managed entities that have
|
||||||
@ -87,14 +83,15 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
private $_identityMap = array();
|
private $_identityMap = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of all identifiers. Keys are object ids (spl_object_hash).
|
* Map of all identifiers of managed entities.
|
||||||
|
* Keys are object ids (spl_object_hash).
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $_entityIdentifiers = array();
|
private $_entityIdentifiers = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of the original entity data of entities fetched from the database.
|
* Map of the original entity data of managed entities.
|
||||||
* Keys are object ids (spl_object_hash). This is used for calculating changesets
|
* Keys are object ids (spl_object_hash). This is used for calculating changesets
|
||||||
* at commit time.
|
* at commit time.
|
||||||
*
|
*
|
||||||
@ -106,7 +103,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
private $_originalEntityData = array();
|
private $_originalEntityData = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of data changes. Keys are object ids (spl_object_hash).
|
* Map of entity changes. Keys are object ids (spl_object_hash).
|
||||||
* Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
|
* Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
@ -114,7 +111,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
private $_entityChangeSets = array();
|
private $_entityChangeSets = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The states of entities in this UnitOfWork.
|
* The (cached) states of any known entities.
|
||||||
* Keys are object ids (spl_object_hash).
|
* Keys are object ids (spl_object_hash).
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
@ -123,7 +120,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of entities that are scheduled for dirty checking at commit time.
|
* Map of entities that are scheduled for dirty checking at commit time.
|
||||||
* This is only used if automatic dirty checking is disabled.
|
* This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT.
|
||||||
* Keys are object ids (spl_object_hash).
|
* Keys are object ids (spl_object_hash).
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
@ -145,7 +142,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
private $_entityUpdates = array();
|
private $_entityUpdates = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any extra updates that have been scheduled by persisters.
|
* Any pending extra updates that have been scheduled by persisters.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -173,14 +170,14 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
//private $_collectionCreations = array();
|
//private $_collectionCreations = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All collection updates.
|
* All pending collection updates.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $_collectionUpdates = array();
|
private $_collectionUpdates = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of collections visited during a commit-phase of a UnitOfWork.
|
* List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
|
||||||
* At the end of the UnitOfWork all these collections will make new snapshots
|
* At the end of the UnitOfWork all these collections will make new snapshots
|
||||||
* of their data.
|
* of their data.
|
||||||
*
|
*
|
||||||
@ -218,21 +215,21 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
private $_collectionPersisters = array();
|
private $_collectionPersisters = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag for whether or not to use the C extension for hydration
|
* Flag for whether or not to make use of the C extension.
|
||||||
*
|
*
|
||||||
* @var boolean
|
* @var boolean
|
||||||
*/
|
*/
|
||||||
private $_useCExtension = false;
|
private $_useCExtension = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The EventManager.
|
* The EventManager used for dispatching events.
|
||||||
*
|
*
|
||||||
* @var EventManager
|
* @var EventManager
|
||||||
*/
|
*/
|
||||||
private $_evm;
|
private $_evm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orphaned entities scheduled for removal.
|
* Orphaned entities that are scheduled for removal.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -253,7 +250,8 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits the UnitOfWork, executing all operations that have been postponed
|
* Commits the UnitOfWork, executing all operations that have been postponed
|
||||||
* up to this point.
|
* up to this point. The state of all managed entities will be synchronized with
|
||||||
|
* the database.
|
||||||
*/
|
*/
|
||||||
public function commit()
|
public function commit()
|
||||||
{
|
{
|
||||||
@ -552,8 +550,8 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$this->_visitedCollections[] = $value;
|
$this->_visitedCollections[] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $assoc->isCascadeSave) {
|
if ( ! $assoc->isCascadePersist) {
|
||||||
return; // "Persistence by reachability" only if save cascade specified
|
return; // "Persistence by reachability" only if persist cascade specified
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look through the entities, and in any of their associations, for transient
|
// Look through the entities, and in any of their associations, for transient
|
||||||
@ -563,7 +561,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
|
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
|
||||||
foreach ($value as $entry) {
|
foreach ($value as $entry) {
|
||||||
$state = $this->getEntityState($entry);
|
$state = $this->getEntityState($entry, self::STATE_NEW);
|
||||||
$oid = spl_object_hash($entry);
|
$oid = spl_object_hash($entry);
|
||||||
if ($state == self::STATE_NEW) {
|
if ($state == self::STATE_NEW) {
|
||||||
// Get identifier, if possible (not post-insert)
|
// Get identifier, if possible (not post-insert)
|
||||||
@ -596,8 +594,8 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$this->_entityInsertions[$oid] = $entry;
|
$this->_entityInsertions[$oid] = $entry;
|
||||||
$this->_entityChangeSets[$oid] = $changeSet;
|
$this->_entityChangeSets[$oid] = $changeSet;
|
||||||
$this->_originalEntityData[$oid] = $data;
|
$this->_originalEntityData[$oid] = $data;
|
||||||
} else if ($state == self::STATE_DELETED) {
|
} else if ($state == self::STATE_REMOVED) {
|
||||||
throw DoctrineException::updateMe("Deleted entity in collection detected during flush."
|
throw DoctrineException::updateMe("Removed entity in collection detected during flush."
|
||||||
. " Make sure you properly remove deleted entities from collections.");
|
. " Make sure you properly remove deleted entities from collections.");
|
||||||
}
|
}
|
||||||
// MANAGED associated entities are already taken into account
|
// MANAGED associated entities are already taken into account
|
||||||
@ -606,7 +604,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXPERIMENTAL:
|
* INTERNAL, EXPERIMENTAL:
|
||||||
* Computes the changeset of an individual entity, independently of the
|
* Computes the changeset of an individual entity, independently of the
|
||||||
* computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
|
* computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
|
||||||
*
|
*
|
||||||
@ -893,6 +891,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* INTERNAL:
|
||||||
* Schedules an extra update that will be executed immediately after the
|
* Schedules an extra update that will be executed immediately after the
|
||||||
* regular entity updates.
|
* regular entity updates.
|
||||||
*
|
*
|
||||||
@ -957,21 +956,6 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
return isset($this->_entityDeletions[spl_object_hash($entity)]);
|
return isset($this->_entityDeletions[spl_object_hash($entity)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches an entity from the persistence management. It's persistence will
|
|
||||||
* no longer be managed by Doctrine.
|
|
||||||
*
|
|
||||||
* @param object $entity The entity to detach.
|
|
||||||
*/
|
|
||||||
public function detach($entity)
|
|
||||||
{
|
|
||||||
$oid = spl_object_hash($entity);
|
|
||||||
$this->removeFromIdentityMap($entity);
|
|
||||||
unset($this->_entityInsertions[$oid], $this->_entityUpdates[$oid],
|
|
||||||
$this->_entityDeletions[$oid], $this->_entityIdentifiers[$oid],
|
|
||||||
$this->_entityStates[$oid]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether an entity is scheduled for insertion, update or deletion.
|
* Checks whether an entity is scheduled for insertion, update or deletion.
|
||||||
*
|
*
|
||||||
@ -987,6 +971,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* INTERNAL:
|
||||||
* Registers an entity in the identity map.
|
* Registers an entity in the identity map.
|
||||||
* Note that entities in a hierarchy are registered with the class name of
|
* Note that entities in a hierarchy are registered with the class name of
|
||||||
* the root entity.
|
* the root entity.
|
||||||
@ -1016,31 +1001,41 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the state of an entity within the current unit of work.
|
* Gets the state of an entity within the current unit of work.
|
||||||
|
*
|
||||||
|
* NOTE: This method sees entities that are not MANAGED or REMOVED and have a
|
||||||
|
* populated identifier, whether it is generated or manually assigned, as
|
||||||
|
* DETACHED. This can be incorrect for manually assigned identifiers.
|
||||||
*
|
*
|
||||||
* @param object $entity
|
* @param object $entity
|
||||||
|
* @param integer $assume The state to assume if the state is not yet known. This is usually
|
||||||
|
* used to avoid costly state lookups, in the worst case with a database
|
||||||
|
* lookup.
|
||||||
* @return int The entity state.
|
* @return int The entity state.
|
||||||
*/
|
*/
|
||||||
public function getEntityState($entity)
|
public function getEntityState($entity, $assume = null)
|
||||||
{
|
{
|
||||||
$oid = spl_object_hash($entity);
|
$oid = spl_object_hash($entity);
|
||||||
if ( ! isset($this->_entityStates[$oid])) {
|
if ( ! isset($this->_entityStates[$oid])) {
|
||||||
/*if (isset($this->_entityInsertions[$oid])) {
|
// State can only be NEW or DETACHED, because MANAGED/REMOVED states are immediately
|
||||||
$this->_entityStates[$oid] = self::STATE_NEW;
|
// set by the UnitOfWork directly. We treat all entities that have a populated
|
||||||
} else if ( ! isset($this->_entityIdentifiers[$oid])) {
|
// identifier as DETACHED and all others as NEW. This is not really correct for
|
||||||
// Either NEW (if no ID) or DETACHED (if ID)
|
// manually assigned identifiers but in that case we would need to hit the database
|
||||||
} else {
|
// and we would like to avoid that.
|
||||||
$this->_entityStates[$oid] = self::STATE_DETACHED;
|
if ($assume === null) {
|
||||||
}*/
|
if ($this->_em->getClassMetadata(get_class($entity))->getIdentifierValues($entity)) {
|
||||||
if (isset($this->_entityIdentifiers[$oid]) && ! isset($this->_entityInsertions[$oid])) {
|
$this->_entityStates[$oid] = self::STATE_DETACHED;
|
||||||
$this->_entityStates[$oid] = self::STATE_DETACHED;
|
} else {
|
||||||
|
$this->_entityStates[$oid] = self::STATE_NEW;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->_entityStates[$oid] = self::STATE_NEW;
|
$this->_entityStates[$oid] = $assume;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $this->_entityStates[$oid];
|
return $this->_entityStates[$oid];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* INTERNAL:
|
||||||
* Removes an entity from the identity map. This effectively detaches the
|
* Removes an entity from the identity map. This effectively detaches the
|
||||||
* entity from the persistence management of Doctrine.
|
* entity from the persistence management of Doctrine.
|
||||||
*
|
*
|
||||||
@ -1067,6 +1062,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* INTERNAL:
|
||||||
* Gets an entity in the identity map by its identifier hash.
|
* Gets an entity in the identity map by its identifier hash.
|
||||||
*
|
*
|
||||||
* @param string $idHash
|
* @param string $idHash
|
||||||
@ -1079,6 +1075,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* INTERNAL:
|
||||||
* Tries to get an entity by its identifier hash. If no entity is found for
|
* Tries to get an entity by its identifier hash. If no entity is found for
|
||||||
* the given hash, FALSE is returned.
|
* the given hash, FALSE is returned.
|
||||||
*
|
*
|
||||||
@ -1116,6 +1113,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* INTERNAL:
|
||||||
* Checks whether an identifier hash exists in the identity map.
|
* Checks whether an identifier hash exists in the identity map.
|
||||||
*
|
*
|
||||||
* @param string $idHash
|
* @param string $idHash
|
||||||
@ -1128,9 +1126,9 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves an entity as part of the current unit of work.
|
* Persists an entity as part of the current unit of work.
|
||||||
*
|
*
|
||||||
* @param object $entity The entity to save.
|
* @param object $entity The entity to persist.
|
||||||
*/
|
*/
|
||||||
public function persist($entity)
|
public function persist($entity)
|
||||||
{
|
{
|
||||||
@ -1142,11 +1140,11 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
* Saves an entity as part of the current unit of work.
|
* Saves an entity as part of the current unit of work.
|
||||||
* This method is internally called during save() cascades as it tracks
|
* This method is internally called during save() cascades as it tracks
|
||||||
* the already visited entities to prevent infinite recursions.
|
* the already visited entities to prevent infinite recursions.
|
||||||
|
*
|
||||||
|
* NOTE: This method always considers entities with a manually assigned identifier as NEW.
|
||||||
*
|
*
|
||||||
* @param object $entity The entity to save.
|
* @param object $entity The entity to persist.
|
||||||
* @param array $visited The already visited entities.
|
* @param array $visited The already visited entities.
|
||||||
* @param array $insertNow The entities that must be immediately inserted because of
|
|
||||||
* post-insert ID generation.
|
|
||||||
*/
|
*/
|
||||||
private function _doPersist($entity, array &$visited)
|
private function _doPersist($entity, array &$visited)
|
||||||
{
|
{
|
||||||
@ -1157,8 +1155,8 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
|
|
||||||
$visited[$oid] = $entity; // Mark visited
|
$visited[$oid] = $entity; // Mark visited
|
||||||
|
|
||||||
$class = $this->_em->getClassMetadata(get_class($entity));
|
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||||
switch ($this->getEntityState($entity)) {
|
switch ($this->getEntityState($entity, self::STATE_NEW)) {
|
||||||
case self::STATE_MANAGED:
|
case self::STATE_MANAGED:
|
||||||
// Nothing to do, except if policy is "deferred explicit"
|
// Nothing to do, except if policy is "deferred explicit"
|
||||||
if ($class->isChangeTrackingDeferredExplicit()) {
|
if ($class->isChangeTrackingDeferredExplicit()) {
|
||||||
@ -1190,7 +1188,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
case self::STATE_DETACHED:
|
case self::STATE_DETACHED:
|
||||||
throw DoctrineException::updateMe("Behavior of save() for a detached entity "
|
throw DoctrineException::updateMe("Behavior of save() for a detached entity "
|
||||||
. "is not yet defined.");
|
. "is not yet defined.");
|
||||||
case self::STATE_DELETED:
|
case self::STATE_REMOVED:
|
||||||
// Entity becomes managed again
|
// Entity becomes managed again
|
||||||
if ($this->isScheduledForDelete($entity)) {
|
if ($this->isScheduledForDelete($entity)) {
|
||||||
unset($this->_entityDeletions[$oid]);
|
unset($this->_entityDeletions[$oid]);
|
||||||
@ -1202,13 +1200,14 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
default:
|
default:
|
||||||
throw DoctrineException::updateMe("Encountered invalid entity state.");
|
throw DoctrineException::updateMe("Encountered invalid entity state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->_cascadePersist($entity, $visited);
|
$this->_cascadePersist($entity, $visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an entity as part of the current unit of work.
|
* Deletes an entity as part of the current unit of work.
|
||||||
*
|
*
|
||||||
* @param object $entity
|
* @param object $entity The entity to remove.
|
||||||
*/
|
*/
|
||||||
public function remove($entity)
|
public function remove($entity)
|
||||||
{
|
{
|
||||||
@ -1224,6 +1223,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
*
|
*
|
||||||
* @param object $entity The entity to delete.
|
* @param object $entity The entity to delete.
|
||||||
* @param array $visited The map of the already visited entities.
|
* @param array $visited The map of the already visited entities.
|
||||||
|
* @throws InvalidArgumentException If the instance is a detached entity.
|
||||||
*/
|
*/
|
||||||
private function _doRemove($entity, array &$visited)
|
private function _doRemove($entity, array &$visited)
|
||||||
{
|
{
|
||||||
@ -1237,7 +1237,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$class = $this->_em->getClassMetadata(get_class($entity));
|
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||||
switch ($this->getEntityState($entity)) {
|
switch ($this->getEntityState($entity)) {
|
||||||
case self::STATE_NEW:
|
case self::STATE_NEW:
|
||||||
case self::STATE_DELETED:
|
case self::STATE_REMOVED:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
break;
|
break;
|
||||||
case self::STATE_MANAGED:
|
case self::STATE_MANAGED:
|
||||||
@ -1250,10 +1250,11 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$this->scheduleForDelete($entity);
|
$this->scheduleForDelete($entity);
|
||||||
break;
|
break;
|
||||||
case self::STATE_DETACHED:
|
case self::STATE_DETACHED:
|
||||||
throw DoctrineException::updateMe("A detached entity can't be deleted.");
|
throw new \InvalidArgumentException("A detached entity can not be removed.");
|
||||||
default:
|
default:
|
||||||
throw DoctrineException::updateMe("Encountered invalid entity state.");
|
throw DoctrineException::updateMe("Encountered invalid entity state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->_cascadeRemove($entity, $visited);
|
$this->_cascadeRemove($entity, $visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1275,6 +1276,9 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
* @param object $entity
|
* @param object $entity
|
||||||
* @param array $visited
|
* @param array $visited
|
||||||
* @return object The managed copy of the entity.
|
* @return object The managed copy of the entity.
|
||||||
|
* @throws OptimisticLockException If the entity uses optimistic locking through a version
|
||||||
|
* attribute and the version check against the managed copy fails.
|
||||||
|
* @throws InvalidArgumentException If the entity instance is NEW.
|
||||||
*/
|
*/
|
||||||
private function _doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null)
|
private function _doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null)
|
||||||
{
|
{
|
||||||
@ -1282,39 +1286,70 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$id = $class->getIdentifierValues($entity);
|
$id = $class->getIdentifierValues($entity);
|
||||||
|
|
||||||
if ( ! $id) {
|
if ( ! $id) {
|
||||||
throw new \InvalidArgumentException('New entity passed to merge().');
|
throw new \InvalidArgumentException('New entity detected during merge.'
|
||||||
}
|
. ' Persist the new entity before merging.');
|
||||||
|
|
||||||
$managedCopy = $this->tryGetById($id, $class->rootEntityName);
|
|
||||||
if ($managedCopy) {
|
|
||||||
if ($this->getEntityState($managedCopy) == self::STATE_DELETED) {
|
|
||||||
throw new InvalidArgumentException('Can not merge with a deleted entity.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$managedCopy = $this->_em->find($class->name, $id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($class->isVersioned) {
|
// MANAGED entities are ignored by the merge operation
|
||||||
$managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
|
if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) {
|
||||||
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
$managedCopy = $entity;
|
||||||
// Throw exception if versions dont match.
|
} else {
|
||||||
if ($managedCopyVersion != $entity) {
|
// Try to look the entity up in the identity map.
|
||||||
throw OptimisticLockException::versionMismatch();
|
$managedCopy = $this->tryGetById($id, $class->rootEntityName);
|
||||||
|
if ($managedCopy) {
|
||||||
|
// We have the entity in-memory already, just make sure its not removed.
|
||||||
|
if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) {
|
||||||
|
throw new \InvalidArgumentException('Removed entity detected during merge.'
|
||||||
|
. ' Can not merge with a removed entity.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We need to fetch the managed copy in order to merge.
|
||||||
|
$managedCopy = $this->_em->find($class->name, $id);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if ($managedCopy === null) {
|
||||||
// Merge state of $entity into existing (managed) entity
|
throw new \InvalidArgumentException('New entity detected during merge.'
|
||||||
foreach ($class->reflFields as $name => $prop) {
|
. ' Persist the new entity before merging.');
|
||||||
if ( ! isset($class->associationMappings[$name])) {
|
|
||||||
$prop->setValue($managedCopy, $prop->getValue($entity));
|
|
||||||
}
|
}
|
||||||
if ($class->isChangeTrackingNotify()) {
|
|
||||||
|
if ($class->isVersioned) {
|
||||||
|
$managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
|
||||||
|
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
||||||
|
// Throw exception if versions dont match.
|
||||||
|
if ($managedCopyVersion != $entity) {
|
||||||
|
throw OptimisticLockException::versionMismatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge state of $entity into existing (managed) entity
|
||||||
|
foreach ($class->reflFields as $name => $prop) {
|
||||||
|
if ( ! isset($class->associationMappings[$name])) {
|
||||||
|
$prop->setValue($managedCopy, $prop->getValue($entity));
|
||||||
|
} else {
|
||||||
|
$assoc2 = $class->associationMappings[$name];
|
||||||
|
if ($assoc2->isOneToOne() && ! $assoc2->isCascadeMerge) {
|
||||||
|
//TODO: Only do this when allowPartialObjects == false?
|
||||||
|
$targetClass = $this->_em->getClassMetadata($assoc2->targetEntityName);
|
||||||
|
$prop->setValue($managedCopy, $this->_em->getProxyFactory()
|
||||||
|
->getReferenceProxy($assoc2->targetEntityName, $targetClass->getIdentifierValues($entity)));
|
||||||
|
} else {
|
||||||
|
//TODO: Only do this when allowPartialObjects == false?
|
||||||
|
$coll = new PersistentCollection($this->_em,
|
||||||
|
$this->_em->getClassMetadata($assoc2->targetEntityName)
|
||||||
|
);
|
||||||
|
$coll->setOwner($managedCopy, $assoc2);
|
||||||
|
$coll->setInitialized($assoc2->isCascadeMerge);
|
||||||
|
$prop->setValue($managedCopy, $coll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($class->isChangeTrackingNotify()) {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($class->isChangeTrackingDeferredExplicit()) {
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($class->isChangeTrackingDeferredExplicit()) {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($prevManagedCopy !== null) {
|
if ($prevManagedCopy !== null) {
|
||||||
$assocField = $assoc->sourceFieldName;
|
$assocField = $assoc->sourceFieldName;
|
||||||
@ -1322,7 +1357,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
if ($assoc->isOneToOne()) {
|
if ($assoc->isOneToOne()) {
|
||||||
$prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
|
$prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
|
||||||
} else {
|
} else {
|
||||||
$prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
|
$prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->hydrateAdd($managedCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1331,11 +1366,55 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
return $managedCopy;
|
return $managedCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaches an entity from the persistence management. It's persistence will
|
||||||
|
* no longer be managed by Doctrine.
|
||||||
|
*
|
||||||
|
* @param object $entity The entity to detach.
|
||||||
|
*/
|
||||||
|
public function detach($entity)
|
||||||
|
{
|
||||||
|
$visited = array();
|
||||||
|
$this->_doDetach($entity, $visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a detach operation on the given entity.
|
||||||
|
*
|
||||||
|
* @param object $entity
|
||||||
|
* @param array $visited
|
||||||
|
* @internal This method always considers entities with an assigned identifier as DETACHED.
|
||||||
|
*/
|
||||||
|
private function _doDetach($entity, array &$visited)
|
||||||
|
{
|
||||||
|
$oid = spl_object_hash($entity);
|
||||||
|
if (isset($visited[$oid])) {
|
||||||
|
return; // Prevent infinite recursion
|
||||||
|
}
|
||||||
|
|
||||||
|
$visited[$oid] = $entity; // mark visited
|
||||||
|
|
||||||
|
switch ($this->getEntityState($entity, self::STATE_DETACHED)) {
|
||||||
|
case self::STATE_MANAGED:
|
||||||
|
$this->removeFromIdentityMap($entity);
|
||||||
|
unset($this->_entityInsertions[$oid], $this->_entityUpdates[$oid],
|
||||||
|
$this->_entityDeletions[$oid], $this->_entityIdentifiers[$oid],
|
||||||
|
$this->_entityStates[$oid]);
|
||||||
|
break;
|
||||||
|
case self::STATE_NEW:
|
||||||
|
case self::STATE_DETACHED:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_cascadeDetach($entity, $visited);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the state of the given entity from the database, overwriting
|
* Refreshes the state of the given entity from the database, overwriting
|
||||||
* any local, unpersisted changes.
|
* any local, unpersisted changes.
|
||||||
*
|
*
|
||||||
* @param object $entity The entity to refresh.
|
* @param object $entity The entity to refresh.
|
||||||
|
* @throws InvalidArgumentException If the entity is not MANAGED.
|
||||||
*/
|
*/
|
||||||
public function refresh($entity)
|
public function refresh($entity)
|
||||||
{
|
{
|
||||||
@ -1348,6 +1427,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
*
|
*
|
||||||
* @param object $entity The entity to refresh.
|
* @param object $entity The entity to refresh.
|
||||||
* @param array $visited The already visited entities during cascades.
|
* @param array $visited The already visited entities during cascades.
|
||||||
|
* @throws InvalidArgumentException If the entity is not MANAGED.
|
||||||
*/
|
*/
|
||||||
private function _doRefresh($entity, array &$visited)
|
private function _doRefresh($entity, array &$visited)
|
||||||
{
|
{
|
||||||
@ -1367,8 +1447,9 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw DoctrineException::updateMe("NEW, REMOVED or DETACHED entity can not be refreshed.");
|
throw new \InvalidArgumentException("Entity is not MANAGED.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->_cascadeRefresh($entity, $visited);
|
$this->_cascadeRefresh($entity, $visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1395,6 +1476,30 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cascades a detach operation to associated entities.
|
||||||
|
*
|
||||||
|
* @param object $entity
|
||||||
|
* @param array $visited
|
||||||
|
*/
|
||||||
|
private function _cascadeDetach($entity, array &$visited)
|
||||||
|
{
|
||||||
|
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||||
|
foreach ($class->associationMappings as $assocMapping) {
|
||||||
|
if ( ! $assocMapping->isCascadeDetach) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]->getValue($entity);
|
||||||
|
if ($relatedEntities instanceof Collection) {
|
||||||
|
foreach ($relatedEntities as $relatedEntity) {
|
||||||
|
$this->_doDetach($relatedEntity, $visited);
|
||||||
|
}
|
||||||
|
} else if ($relatedEntities !== null) {
|
||||||
|
$this->_doDetach($relatedEntities, $visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cascades a merge operation to associated entities.
|
* Cascades a merge operation to associated entities.
|
||||||
@ -1410,8 +1515,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
if ( ! $assocMapping->isCascadeMerge) {
|
if ( ! $assocMapping->isCascadeMerge) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]
|
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]->getValue($entity);
|
||||||
->getValue($entity);
|
|
||||||
if ($relatedEntities instanceof Collection) {
|
if ($relatedEntities instanceof Collection) {
|
||||||
foreach ($relatedEntities as $relatedEntity) {
|
foreach ($relatedEntities as $relatedEntity) {
|
||||||
$this->_doMerge($relatedEntity, $visited, $managedCopy, $assocMapping);
|
$this->_doMerge($relatedEntity, $visited, $managedCopy, $assocMapping);
|
||||||
@ -1433,7 +1537,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
{
|
{
|
||||||
$class = $this->_em->getClassMetadata(get_class($entity));
|
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||||
foreach ($class->associationMappings as $assocMapping) {
|
foreach ($class->associationMappings as $assocMapping) {
|
||||||
if ( ! $assocMapping->isCascadeSave) {
|
if ( ! $assocMapping->isCascadePersist) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]->getValue($entity);
|
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]->getValue($entity);
|
||||||
@ -1457,7 +1561,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
{
|
{
|
||||||
$class = $this->_em->getClassMetadata(get_class($entity));
|
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||||
foreach ($class->associationMappings as $assocMapping) {
|
foreach ($class->associationMappings as $assocMapping) {
|
||||||
if ( ! $assocMapping->isCascadeDelete) {
|
if ( ! $assocMapping->isCascadeRemove) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]
|
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]
|
||||||
@ -1824,25 +1928,23 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
*/
|
*/
|
||||||
public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
|
public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
|
||||||
{
|
{
|
||||||
if ($this->getEntityState($entity) == self::STATE_MANAGED) {
|
$oid = spl_object_hash($entity);
|
||||||
$oid = spl_object_hash($entity);
|
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||||
$class = $this->_em->getClassMetadata(get_class($entity));
|
|
||||||
|
|
||||||
$this->_entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue);
|
$this->_entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue);
|
||||||
|
|
||||||
if (isset($class->associationMappings[$propertyName])) {
|
if (isset($class->associationMappings[$propertyName])) {
|
||||||
$assoc = $class->associationMappings[$propertyName];
|
$assoc = $class->associationMappings[$propertyName];
|
||||||
if ($assoc->isOneToOne() && $assoc->isOwningSide) {
|
if ($assoc->isOneToOne() && $assoc->isOwningSide) {
|
||||||
$this->_entityUpdates[$oid] = $entity;
|
|
||||||
} else if ($oldValue instanceof PersistentCollection) {
|
|
||||||
// A PersistentCollection was de-referenced, so delete it.
|
|
||||||
if ( ! in_array($oldValue, $this->_collectionDeletions, true)) {
|
|
||||||
$this->_collectionDeletions[] = $oldValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->_entityUpdates[$oid] = $entity;
|
$this->_entityUpdates[$oid] = $entity;
|
||||||
|
} else if ($oldValue instanceof PersistentCollection) {
|
||||||
|
// A PersistentCollection was de-referenced, so delete it.
|
||||||
|
if ( ! in_array($oldValue, $this->_collectionDeletions, true)) {
|
||||||
|
$this->_collectionDeletions[] = $oldValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$this->_entityUpdates[$oid] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,7 @@ class CmsUser
|
|||||||
*/
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
/**
|
/**
|
||||||
* @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"save", "delete"}, orphanRemoval=true)
|
* @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true)
|
||||||
*/
|
*/
|
||||||
public $phonenumbers;
|
public $phonenumbers;
|
||||||
/**
|
/**
|
||||||
@ -34,11 +34,11 @@ class CmsUser
|
|||||||
*/
|
*/
|
||||||
public $articles;
|
public $articles;
|
||||||
/**
|
/**
|
||||||
* @OneToOne(targetEntity="CmsAddress", mappedBy="user", cascade={"save"})
|
* @OneToOne(targetEntity="CmsAddress", mappedBy="user", cascade={"persist"})
|
||||||
*/
|
*/
|
||||||
public $address;
|
public $address;
|
||||||
/**
|
/**
|
||||||
* @ManyToMany(targetEntity="CmsGroup", cascade={"save"})
|
* @ManyToMany(targetEntity="CmsGroup", cascade={"persist"})
|
||||||
* @JoinTable(name="cms_users_groups",
|
* @JoinTable(name="cms_users_groups",
|
||||||
joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||||
inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")})
|
inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")})
|
||||||
|
@ -33,7 +33,7 @@ class ECommerceCart
|
|||||||
private $customer;
|
private $customer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ManyToMany(targetEntity="ECommerceProduct", cascade={"save"})
|
* @ManyToMany(targetEntity="ECommerceProduct", cascade={"persist"})
|
||||||
* @JoinTable(name="ecommerce_carts_products",
|
* @JoinTable(name="ecommerce_carts_products",
|
||||||
joinColumns={@JoinColumn(name="cart_id", referencedColumnName="id")},
|
joinColumns={@JoinColumn(name="cart_id", referencedColumnName="id")},
|
||||||
inverseJoinColumns={@JoinColumn(name="product_id", referencedColumnName="id")})
|
inverseJoinColumns={@JoinColumn(name="product_id", referencedColumnName="id")})
|
||||||
|
@ -32,7 +32,7 @@ class ECommerceCategory
|
|||||||
private $products;
|
private $products;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @OneToMany(targetEntity="ECommerceCategory", mappedBy="parent", cascade={"save"})
|
* @OneToMany(targetEntity="ECommerceCategory", mappedBy="parent", cascade={"persist"})
|
||||||
*/
|
*/
|
||||||
private $children;
|
private $children;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class ECommerceCustomer
|
|||||||
private $name;
|
private $name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @OneToOne(targetEntity="ECommerceCart", mappedBy="customer", cascade={"save"})
|
* @OneToOne(targetEntity="ECommerceCart", mappedBy="customer", cascade={"persist"})
|
||||||
*/
|
*/
|
||||||
private $cart;
|
private $cart;
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ class ECommerceCustomer
|
|||||||
* only one customer at the time, while a customer can choose only one
|
* only one customer at the time, while a customer can choose only one
|
||||||
* mentor. Not properly appropriate but it works.
|
* mentor. Not properly appropriate but it works.
|
||||||
*
|
*
|
||||||
* @OneToOne(targetEntity="ECommerceCustomer", cascade={"save"})
|
* @OneToOne(targetEntity="ECommerceCustomer", cascade={"persist"})
|
||||||
* @JoinColumn(name="mentor_id", referencedColumnName="id")
|
* @JoinColumn(name="mentor_id", referencedColumnName="id")
|
||||||
*/
|
*/
|
||||||
private $mentor;
|
private $mentor;
|
||||||
|
@ -27,18 +27,18 @@ class ECommerceProduct
|
|||||||
private $name;
|
private $name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @OneToOne(targetEntity="ECommerceShipping", cascade={"save"})
|
* @OneToOne(targetEntity="ECommerceShipping", cascade={"persist"})
|
||||||
* @JoinColumn(name="shipping_id", referencedColumnName="id")
|
* @JoinColumn(name="shipping_id", referencedColumnName="id")
|
||||||
*/
|
*/
|
||||||
private $shipping;
|
private $shipping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @OneToMany(targetEntity="ECommerceFeature", mappedBy="product", cascade={"save"})
|
* @OneToMany(targetEntity="ECommerceFeature", mappedBy="product", cascade={"persist"})
|
||||||
*/
|
*/
|
||||||
private $features;
|
private $features;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ManyToMany(targetEntity="ECommerceCategory", cascade={"save"})
|
* @ManyToMany(targetEntity="ECommerceCategory", cascade={"persist"})
|
||||||
* @JoinTable(name="ecommerce_products_categories",
|
* @JoinTable(name="ecommerce_products_categories",
|
||||||
joinColumns={@JoinColumn(name="product_id", referencedColumnName="id")},
|
joinColumns={@JoinColumn(name="product_id", referencedColumnName="id")},
|
||||||
inverseJoinColumns={@JoinColumn(name="category_id", referencedColumnName="id")})
|
inverseJoinColumns={@JoinColumn(name="category_id", referencedColumnName="id")})
|
||||||
@ -48,7 +48,7 @@ class ECommerceProduct
|
|||||||
/**
|
/**
|
||||||
* This relation is saved with two records in the association table for
|
* This relation is saved with two records in the association table for
|
||||||
* simplicity.
|
* simplicity.
|
||||||
* @ManyToMany(targetEntity="ECommerceProduct", cascade={"save"})
|
* @ManyToMany(targetEntity="ECommerceProduct", cascade={"persist"})
|
||||||
* @JoinTable(name="ecommerce_products_related",
|
* @JoinTable(name="ecommerce_products_related",
|
||||||
joinColumns={@JoinColumn(name="product_id", referencedColumnName="id")},
|
joinColumns={@JoinColumn(name="product_id", referencedColumnName="id")},
|
||||||
inverseJoinColumns={@JoinColumn(name="related_id", referencedColumnName="id")})
|
inverseJoinColumns={@JoinColumn(name="related_id", referencedColumnName="id")})
|
||||||
|
@ -19,7 +19,7 @@ class ForumUser
|
|||||||
*/
|
*/
|
||||||
public $username;
|
public $username;
|
||||||
/**
|
/**
|
||||||
* @OneToOne(targetEntity="ForumAvatar", cascade={"save"})
|
* @OneToOne(targetEntity="ForumAvatar", cascade={"persist"})
|
||||||
* @JoinColumn(name="avatar_id", referencedColumnName="id")
|
* @JoinColumn(name="avatar_id", referencedColumnName="id")
|
||||||
*/
|
*/
|
||||||
public $avatar;
|
public $avatar;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Doctrine\Tests\ORM\Functional;
|
namespace Doctrine\Tests\ORM\Functional;
|
||||||
|
|
||||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||||
use Doctrine\ORM\UnitOfWork;
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
|
||||||
require_once __DIR__ . '/../../TestInit.php';
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
@ -42,5 +43,48 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertTrue($this->_em->contains($user2));
|
$this->assertTrue($this->_em->contains($user2));
|
||||||
$this->assertEquals('Roman B.', $user2->name);
|
$this->assertEquals('Roman B.', $user2->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSerializeUnserializeModifyMerge()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Guilherme';
|
||||||
|
$user->username = 'gblanco';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$ph1 = new CmsPhonenumber;
|
||||||
|
$ph1->phonenumber = 1234;
|
||||||
|
$user->addPhonenumber($ph1);
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->assertTrue($this->_em->contains($user));
|
||||||
|
|
||||||
|
$serialized = serialize($user);
|
||||||
|
$this->_em->clear();
|
||||||
|
$this->assertFalse($this->_em->contains($user));
|
||||||
|
unset($user);
|
||||||
|
|
||||||
|
$user = unserialize($serialized);
|
||||||
|
|
||||||
|
$ph2 = new CmsPhonenumber;
|
||||||
|
$ph2->phonenumber = 56789;
|
||||||
|
$user->addPhonenumber($ph2);
|
||||||
|
$this->assertEquals(2, count($user->getPhonenumbers()));
|
||||||
|
$this->assertFalse($this->_em->contains($user));
|
||||||
|
|
||||||
|
$this->_em->persist($ph2);
|
||||||
|
|
||||||
|
//$removed = $user->removePhonenumber(1); // [romanb] this is currently broken, I'm on it.
|
||||||
|
|
||||||
|
// Merge back in
|
||||||
|
$user = $this->_em->merge($user); // merge cascaded to phonenumbers
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$this->assertTrue($this->_em->contains($user));
|
||||||
|
$this->assertEquals(2, count($user->getPhonenumbers()));
|
||||||
|
$phonenumbers = $user->getPhonenumbers();
|
||||||
|
$this->assertTrue($this->_em->contains($phonenumbers[0]));
|
||||||
|
$this->assertTrue($this->_em->contains($phonenumbers[1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class XmlDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
$this->assertTrue(isset($class->associationMappings['phonenumbers']));
|
$this->assertTrue(isset($class->associationMappings['phonenumbers']));
|
||||||
$this->assertFalse($class->associationMappings['phonenumbers']->isOwningSide);
|
$this->assertFalse($class->associationMappings['phonenumbers']->isOwningSide);
|
||||||
$this->assertTrue($class->associationMappings['phonenumbers']->isInverseSide());
|
$this->assertTrue($class->associationMappings['phonenumbers']->isInverseSide());
|
||||||
$this->assertTrue($class->associationMappings['phonenumbers']->isCascadeSave);
|
$this->assertTrue($class->associationMappings['phonenumbers']->isCascadePersist);
|
||||||
|
|
||||||
$this->assertTrue($class->associationMappings['groups'] instanceof \Doctrine\ORM\Mapping\ManyToManyMapping);
|
$this->assertTrue($class->associationMappings['groups'] instanceof \Doctrine\ORM\Mapping\ManyToManyMapping);
|
||||||
$this->assertTrue(isset($class->associationMappings['groups']));
|
$this->assertTrue(isset($class->associationMappings['groups']));
|
||||||
|
@ -41,7 +41,7 @@ class YamlDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
$this->assertTrue(isset($class->associationMappings['phonenumbers']));
|
$this->assertTrue(isset($class->associationMappings['phonenumbers']));
|
||||||
$this->assertFalse($class->associationMappings['phonenumbers']->isOwningSide);
|
$this->assertFalse($class->associationMappings['phonenumbers']->isOwningSide);
|
||||||
$this->assertTrue($class->associationMappings['phonenumbers']->isInverseSide());
|
$this->assertTrue($class->associationMappings['phonenumbers']->isInverseSide());
|
||||||
$this->assertTrue($class->associationMappings['phonenumbers']->isCascadeSave);
|
$this->assertTrue($class->associationMappings['phonenumbers']->isCascadePersist);
|
||||||
|
|
||||||
$this->assertTrue($class->associationMappings['groups'] instanceof \Doctrine\ORM\Mapping\ManyToManyMapping);
|
$this->assertTrue($class->associationMappings['groups'] instanceof \Doctrine\ORM\Mapping\ManyToManyMapping);
|
||||||
$this->assertTrue(isset($class->associationMappings['groups']));
|
$this->assertTrue(isset($class->associationMappings['groups']));
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<one-to-many field="phonenumbers" targetEntity="Phonenumber" mappedBy="user">
|
<one-to-many field="phonenumbers" targetEntity="Phonenumber" mappedBy="user">
|
||||||
<cascade>
|
<cascade>
|
||||||
<cascade-save/>
|
<cascade-persist/>
|
||||||
</cascade>
|
</cascade>
|
||||||
</one-to-many>
|
</one-to-many>
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ YamlMappingTest\User:
|
|||||||
phonenumbers:
|
phonenumbers:
|
||||||
targetEntity: Phonenumber
|
targetEntity: Phonenumber
|
||||||
mappedBy: user
|
mappedBy: user
|
||||||
cascade: cascadeSave
|
cascade: cascadePersist
|
||||||
manyToMany:
|
manyToMany:
|
||||||
groups:
|
groups:
|
||||||
targetEntity: Group
|
targetEntity: Group
|
||||||
|
@ -32,7 +32,7 @@ class InsertPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
|||||||
//$mem = memory_get_usage();
|
//$mem = memory_get_usage();
|
||||||
//echo "Memory usage before: " . ($mem / 1024) . " KB" . PHP_EOL;
|
//echo "Memory usage before: " . ($mem / 1024) . " KB" . PHP_EOL;
|
||||||
$batchSize = 20;
|
$batchSize = 20;
|
||||||
for ($i=0; $i<10000; ++$i) {
|
for ($i=1; $i<=10000; ++$i) {
|
||||||
$user = new CmsUser;
|
$user = new CmsUser;
|
||||||
$user->status = 'user';
|
$user->status = 'user';
|
||||||
$user->username = 'user' . $i;
|
$user->username = 'user' . $i;
|
||||||
@ -43,7 +43,6 @@ class InsertPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
|||||||
$this->_em->clear();
|
$this->_em->clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//$memAfter = memory_get_usage();
|
//$memAfter = memory_get_usage();
|
||||||
//echo "Memory usage after: " . ($memAfter / 1024) . " KB" . PHP_EOL;
|
//echo "Memory usage after: " . ($memAfter / 1024) . " KB" . PHP_EOL;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user