2007-10-23 01:47:05 +04:00
|
|
|
<?php
|
|
|
|
/*
|
|
|
|
* 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
|
2012-05-26 16:37:00 +04:00
|
|
|
* and is licensed under the MIT license. For more information, see
|
2009-02-04 19:35:36 +03:00
|
|
|
* <http://www.doctrine-project.org>.
|
2007-10-23 01:47:05 +04:00
|
|
|
*/
|
2008-05-01 13:41:47 +04:00
|
|
|
|
2009-01-22 22:38:10 +03:00
|
|
|
namespace Doctrine\ORM;
|
2008-05-30 16:09:24 +04:00
|
|
|
|
2015-01-24 16:30:40 +03:00
|
|
|
use Doctrine\Common\Persistence\Mapping\RuntimeReflectionService;
|
2014-02-01 21:05:47 +04:00
|
|
|
use Doctrine\DBAL\LockMode;
|
2014-04-05 10:45:00 +04:00
|
|
|
use Doctrine\ORM\Internal\HydrationCompleteHandler;
|
2015-01-24 16:30:40 +03:00
|
|
|
use Doctrine\ORM\Mapping\Reflection\ReflectionPropertiesGetter;
|
2012-10-12 15:53:20 +04:00
|
|
|
use Exception;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
use UnexpectedValueException;
|
2012-07-31 05:26:55 +04:00
|
|
|
|
2012-10-12 15:53:20 +04:00
|
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
|
|
use Doctrine\Common\Collections\Collection;
|
|
|
|
use Doctrine\Common\NotifyPropertyChanged;
|
|
|
|
use Doctrine\Common\PropertyChangedListener;
|
|
|
|
use Doctrine\Common\Persistence\ObjectManagerAware;
|
|
|
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
|
|
|
use Doctrine\ORM\Proxy\Proxy;
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2012-07-31 05:26:55 +04:00
|
|
|
use Doctrine\ORM\Event\LifecycleEventArgs;
|
|
|
|
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
|
|
|
use Doctrine\ORM\Event\PreFlushEventArgs;
|
|
|
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
|
|
|
use Doctrine\ORM\Event\PostFlushEventArgs;
|
2012-10-14 04:46:24 +04:00
|
|
|
use Doctrine\ORM\Event\ListenersInvoker;
|
2012-07-31 05:26:55 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
2015-01-15 08:01:52 +03:00
|
|
|
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
|
|
|
use Doctrine\ORM\Persisters\Entity\SingleTablePersister;
|
|
|
|
use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
|
|
|
|
use Doctrine\ORM\Persisters\Collection\OneToManyPersister;
|
|
|
|
use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
|
2014-08-07 17:56:27 +04:00
|
|
|
use Doctrine\ORM\Utility\IdentifierFlattener;
|
2015-01-19 01:18:41 +03:00
|
|
|
use Doctrine\ORM\Cache\AssociationCacheEntry;
|
2013-02-14 02:42:13 +04:00
|
|
|
|
2007-10-23 01:47:05 +04:00
|
|
|
/**
|
2008-07-04 20:32:19 +04:00
|
|
|
* The UnitOfWork is responsible for tracking changes to objects during an
|
2008-07-21 00:13:24 +04:00
|
|
|
* "object-level" transaction and for writing out changes to the database
|
2008-07-04 20:32:19 +04:00
|
|
|
* in the correct order.
|
2007-10-23 01:47:05 +04:00
|
|
|
*
|
2015-03-15 18:53:34 +03:00
|
|
|
* Internal note: This class contains highly performance-sensitive code.
|
|
|
|
*
|
2008-05-01 13:41:47 +04:00
|
|
|
* @since 2.0
|
2010-03-31 01:14:17 +04:00
|
|
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
|
|
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
|
|
|
* @author Jonathan Wage <jonwage@gmail.com>
|
2008-02-24 01:04:39 +03:00
|
|
|
* @author Roman Borschel <roman@code-factory.org>
|
2014-08-07 17:56:27 +04:00
|
|
|
* @author Rob Caiger <rob@clocal.co.uk>
|
2007-10-23 01:47:05 +04:00
|
|
|
*/
|
2009-04-09 22:12:48 +04:00
|
|
|
class UnitOfWork implements PropertyChangedListener
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
2008-12-18 17:08:11 +03:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* An entity is in MANAGED state when its persistence is managed by an EntityManager.
|
2008-12-18 17:08:11 +03:00
|
|
|
*/
|
|
|
|
const STATE_MANAGED = 1;
|
|
|
|
|
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* An entity is new if it has just been instantiated (i.e. using the "new" operator)
|
2008-12-18 17:08:11 +03:00
|
|
|
* and is not (yet) managed by an EntityManager.
|
|
|
|
*/
|
|
|
|
const STATE_NEW = 2;
|
|
|
|
|
|
|
|
/**
|
2010-07-08 02:20:54 +04:00
|
|
|
* A detached entity is an instance with persistent state and identity that is not
|
2008-12-18 17:08:11 +03:00
|
|
|
* (or no longer) associated with an EntityManager (and a UnitOfWork).
|
|
|
|
*/
|
|
|
|
const STATE_DETACHED = 3;
|
|
|
|
|
|
|
|
/**
|
2009-05-21 23:18:14 +04:00
|
|
|
* A removed entity instance is an instance with a persistent identity,
|
2010-07-08 02:20:54 +04:00
|
|
|
* associated with an EntityManager, whose persistent state will be deleted
|
|
|
|
* on commit.
|
2008-12-18 17:08:11 +03:00
|
|
|
*/
|
2009-07-25 20:33:29 +04:00
|
|
|
const STATE_REMOVED = 4;
|
2008-12-18 17:08:11 +03:00
|
|
|
|
2013-04-08 13:27:22 +04:00
|
|
|
/**
|
|
|
|
* Hint used to collect all primary keys of associated entities during hydration
|
|
|
|
* and execute it in a dedicated query afterwards
|
|
|
|
* @see https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight=eager#temporarily-change-fetch-mode-in-dql
|
|
|
|
*/
|
|
|
|
const HINT_DEFEREAGERLOAD = 'deferEagerLoad';
|
|
|
|
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
|
|
|
* The identity map that holds references to all managed entities that have
|
2008-03-05 14:24:33 +03:00
|
|
|
* an identity. The entities are grouped by their class name.
|
2008-05-01 13:41:47 +04:00
|
|
|
* Since all classes in a hierarchy must share the same identifier set,
|
|
|
|
* we always take the root class name of the hierarchy.
|
2008-02-24 01:04:39 +03:00
|
|
|
*
|
2008-05-01 13:41:47 +04:00
|
|
|
* @var array
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $identityMap = array();
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2009-01-03 22:50:13 +03:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* Map of all identifiers of managed entities.
|
|
|
|
* Keys are object ids (spl_object_hash).
|
2009-01-03 22:50:13 +03:00
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $entityIdentifiers = array();
|
2009-01-03 22:50:13 +03:00
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* Map of the original entity data of managed entities.
|
2009-07-19 20:54:53 +04:00
|
|
|
* Keys are object ids (spl_object_hash). This is used for calculating changesets
|
|
|
|
* at commit time.
|
2008-12-18 17:08:11 +03:00
|
|
|
*
|
2015-03-15 18:53:34 +03:00
|
|
|
* Internal note: Note that PHPs "copy-on-write" behavior helps a lot with memory usage.
|
|
|
|
* A value will only really be copied if the value in the entity is modified
|
|
|
|
* by the user.
|
|
|
|
*
|
2008-12-18 17:08:11 +03:00
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $originalEntityData = array();
|
2008-12-18 17:08:11 +03:00
|
|
|
|
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* Map of entity changes. Keys are object ids (spl_object_hash).
|
2009-05-17 23:27:12 +04:00
|
|
|
* Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
|
2008-12-18 17:08:11 +03:00
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $entityChangeSets = array();
|
2008-12-18 17:08:11 +03:00
|
|
|
|
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* The (cached) states of any known entities.
|
2009-07-19 20:54:53 +04:00
|
|
|
* Keys are object ids (spl_object_hash).
|
2008-12-18 17:08:11 +03:00
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $entityStates = array();
|
2008-12-18 17:08:11 +03:00
|
|
|
|
2009-01-29 20:00:44 +03:00
|
|
|
/**
|
|
|
|
* Map of entities that are scheduled for dirty checking at commit time.
|
2009-07-25 20:33:29 +04:00
|
|
|
* This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT.
|
2009-07-19 20:54:53 +04:00
|
|
|
* Keys are object ids (spl_object_hash).
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-07-19 20:54:53 +04:00
|
|
|
* @var array
|
2009-01-29 20:00:44 +03:00
|
|
|
*/
|
2015-01-19 06:21:18 +03:00
|
|
|
private $scheduledForSynchronization = array();
|
2009-01-29 20:00:44 +03:00
|
|
|
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
2009-02-07 20:02:13 +03:00
|
|
|
* A list of all pending entity insertions.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
|
|
|
* @var array
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $entityInsertions = array();
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
2009-02-07 20:02:13 +03:00
|
|
|
* A list of all pending entity updates.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
|
|
|
* @var array
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $entityUpdates = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-18 15:41:37 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* Any pending extra updates that have been scheduled by persisters.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-07-18 15:41:37 +04:00
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $extraUpdates = array();
|
2009-07-03 21:36:41 +04:00
|
|
|
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
2009-02-07 20:02:13 +03:00
|
|
|
* A list of all pending entity deletions.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
|
|
|
* @var array
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $entityDeletions = array();
|
2009-07-03 21:36:41 +04:00
|
|
|
|
2008-08-16 23:40:59 +04:00
|
|
|
/**
|
2009-02-07 20:02:13 +03:00
|
|
|
* All pending collection deletions.
|
2008-08-16 23:40:59 +04:00
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $collectionDeletions = array();
|
2009-07-03 21:36:41 +04:00
|
|
|
|
2008-08-16 23:40:59 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* All pending collection updates.
|
2008-08-16 23:40:59 +04:00
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $collectionUpdates = array();
|
2009-02-02 14:55:50 +03:00
|
|
|
|
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
|
2009-02-02 14:55:50 +03:00
|
|
|
* At the end of the UnitOfWork all these collections will make new snapshots
|
|
|
|
* of their data.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $visitedCollections = array();
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
/**
|
2009-01-09 19:25:06 +03:00
|
|
|
* The EntityManager that "owns" this UnitOfWork instance.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
2014-12-05 16:02:47 +03:00
|
|
|
* @var EntityManagerInterface
|
2008-04-13 00:11:11 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $em;
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
2008-06-05 23:01:58 +04:00
|
|
|
* The calculator used to calculate the order in which changes to
|
|
|
|
* entities need to be written to the database.
|
2008-02-24 01:04:39 +03:00
|
|
|
*
|
2011-12-12 00:52:29 +04:00
|
|
|
* @var \Doctrine\ORM\Internal\CommitOrderCalculator
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $commitOrderCalculator;
|
2009-02-02 14:55:50 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The entity persister instances used to persist entity instances.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $persisters = array();
|
2009-02-02 14:55:50 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The collection persister instances used to persist collections.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $collectionPersisters = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-18 15:41:37 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* The EventManager used for dispatching events.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2012-09-06 20:10:30 +04:00
|
|
|
* @var \Doctrine\Common\EventManager
|
2009-07-18 15:41:37 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $evm;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-08-13 05:10:47 +04:00
|
|
|
/**
|
2012-10-14 04:46:24 +04:00
|
|
|
* The ListenersInvoker used for dispatching events.
|
2012-08-13 05:10:47 +04:00
|
|
|
*
|
2012-10-14 04:46:24 +04:00
|
|
|
* @var \Doctrine\ORM\Event\ListenersInvoker
|
2012-08-13 05:10:47 +04:00
|
|
|
*/
|
2012-10-14 04:46:24 +04:00
|
|
|
private $listenersInvoker;
|
2012-08-13 05:10:47 +04:00
|
|
|
|
2014-08-07 17:56:27 +04:00
|
|
|
/**
|
|
|
|
* The IdentifierFlattener used for manipulating identifiers
|
|
|
|
*
|
|
|
|
* @var \Doctrine\ORM\Utility\IdentifierFlattener
|
|
|
|
*/
|
|
|
|
private $identifierFlattener;
|
|
|
|
|
2009-07-23 13:52:16 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* Orphaned entities that are scheduled for removal.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-07-23 13:52:16 +04:00
|
|
|
* @var array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private $orphanRemovals = array();
|
2011-10-15 13:52:41 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Read-Only objects are never evaluated
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private $readOnlyObjects = array();
|
2009-07-11 01:47:42 +04:00
|
|
|
|
2010-12-31 15:11:01 +03:00
|
|
|
/**
|
|
|
|
* Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2010-12-31 15:11:01 +03:00
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private $eagerLoadingEntities = array();
|
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
/**
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
protected $hasCache = false;
|
|
|
|
|
2014-04-04 21:30:13 +04:00
|
|
|
/**
|
2014-04-05 10:45:00 +04:00
|
|
|
* Helper for handling completion of hydration
|
2014-04-04 21:30:13 +04:00
|
|
|
*
|
2014-04-05 10:45:00 +04:00
|
|
|
* @var HydrationCompleteHandler
|
2014-04-04 21:30:13 +04:00
|
|
|
*/
|
2014-04-05 10:45:00 +04:00
|
|
|
private $hydrationCompleteHandler;
|
2014-04-04 21:30:13 +04:00
|
|
|
|
2015-01-24 16:30:40 +03:00
|
|
|
/**
|
|
|
|
* @var ReflectionPropertiesGetter
|
|
|
|
*/
|
|
|
|
private $reflectionPropertiesGetter;
|
|
|
|
|
2008-07-04 20:32:19 +04:00
|
|
|
/**
|
2009-01-09 19:25:06 +03:00
|
|
|
* Initializes a new UnitOfWork instance, bound to the given EntityManager.
|
2008-07-04 20:32:19 +04:00
|
|
|
*
|
2014-12-05 16:02:47 +03:00
|
|
|
* @param EntityManagerInterface $em
|
2008-07-04 20:32:19 +04:00
|
|
|
*/
|
2014-12-05 16:02:47 +03:00
|
|
|
public function __construct(EntityManagerInterface $em)
|
2008-07-04 20:32:19 +04:00
|
|
|
{
|
2015-01-24 16:30:40 +03:00
|
|
|
$this->em = $em;
|
|
|
|
$this->evm = $em->getEventManager();
|
|
|
|
$this->listenersInvoker = new ListenersInvoker($em);
|
|
|
|
$this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled();
|
|
|
|
$this->identifierFlattener = new IdentifierFlattener($this, $em->getMetadataFactory());
|
|
|
|
$this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em);
|
|
|
|
$this->reflectionPropertiesGetter = new ReflectionPropertiesGetter(new RuntimeReflectionService());
|
2008-07-04 20:32:19 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
2009-01-29 20:00:44 +03:00
|
|
|
* Commits the UnitOfWork, executing all operations that have been postponed
|
2009-07-25 20:33:29 +04:00
|
|
|
* up to this point. The state of all managed entities will be synchronized with
|
|
|
|
* the database.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* The operations are executed in the following order:
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* 1) All entity insertions
|
|
|
|
* 2) All entity updates
|
|
|
|
* 3) All collection deletions
|
|
|
|
* 4) All collection updates
|
|
|
|
* 5) All entity deletions
|
2011-10-23 12:05:46 +04:00
|
|
|
*
|
2012-03-16 01:53:50 +04:00
|
|
|
* @param null|object|array $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2011-10-23 12:05:46 +04:00
|
|
|
* @return void
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @throws \Exception
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2011-10-22 15:44:33 +04:00
|
|
|
public function commit($entity = null)
|
2008-01-05 22:55:56 +03:00
|
|
|
{
|
2011-10-23 19:39:16 +04:00
|
|
|
// Raise preFlush
|
|
|
|
if ($this->evm->hasListeners(Events::preFlush)) {
|
2012-07-31 05:26:55 +04:00
|
|
|
$this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
|
2011-10-23 19:39:16 +04:00
|
|
|
}
|
|
|
|
|
2009-04-09 22:12:48 +04:00
|
|
|
// Compute changes done since last commit.
|
2011-10-22 15:44:33 +04:00
|
|
|
if ($entity === null) {
|
|
|
|
$this->computeChangeSets();
|
2012-03-16 01:53:50 +04:00
|
|
|
} elseif (is_object($entity)) {
|
2011-10-22 15:44:33 +04:00
|
|
|
$this->computeSingleEntityChangeSet($entity);
|
2012-03-16 01:53:50 +04:00
|
|
|
} elseif (is_array($entity)) {
|
|
|
|
foreach ($entity as $object) {
|
|
|
|
$this->computeSingleEntityChangeSet($object);
|
|
|
|
}
|
2011-10-22 15:44:33 +04:00
|
|
|
}
|
2009-01-29 20:00:44 +03:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
if ( ! ($this->entityInsertions ||
|
|
|
|
$this->entityDeletions ||
|
|
|
|
$this->entityUpdates ||
|
|
|
|
$this->collectionUpdates ||
|
|
|
|
$this->collectionDeletions ||
|
|
|
|
$this->orphanRemovals)) {
|
|
|
|
$this->dispatchOnFlushEvent();
|
|
|
|
$this->dispatchPostFlushEvent();
|
|
|
|
|
|
|
|
return; // Nothing to do.
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->orphanRemovals) {
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($this->orphanRemovals as $orphan) {
|
2009-07-23 13:52:16 +04:00
|
|
|
$this->remove($orphan);
|
|
|
|
}
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
$this->dispatchOnFlushEvent();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
// Now we need a commit order to maintain referential integrity
|
|
|
|
$commitOrder = $this->getCommitOrder();
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
$conn = $this->em->getConnection();
|
|
|
|
$conn->beginTransaction();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
try {
|
|
|
|
if ($this->entityInsertions) {
|
|
|
|
foreach ($commitOrder as $class) {
|
|
|
|
$this->executeInserts($class);
|
2009-07-18 15:41:37 +04:00
|
|
|
}
|
2012-07-23 03:14:02 +04:00
|
|
|
}
|
2010-05-11 01:51:56 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
if ($this->entityUpdates) {
|
|
|
|
foreach ($commitOrder as $class) {
|
|
|
|
$this->executeUpdates($class);
|
2009-07-18 15:41:37 +04:00
|
|
|
}
|
2012-07-23 03:14:02 +04:00
|
|
|
}
|
2009-07-18 15:41:37 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
// Extra updates that were requested by persisters.
|
|
|
|
if ($this->extraUpdates) {
|
|
|
|
$this->executeExtraUpdates();
|
|
|
|
}
|
2009-05-19 20:11:08 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
// Collection deletions (deletions of complete collections)
|
|
|
|
foreach ($this->collectionDeletions as $collectionToDelete) {
|
|
|
|
$this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
|
|
|
|
}
|
2013-02-14 02:42:13 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
// Collection updates (deleteRows, updateRows, insertRows)
|
|
|
|
foreach ($this->collectionUpdates as $collectionToUpdate) {
|
|
|
|
$this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
|
|
|
|
}
|
2009-05-19 20:11:08 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
// Entity deletions come last and need to be in reverse commit order
|
|
|
|
if ($this->entityDeletions) {
|
2014-02-25 17:00:33 +04:00
|
|
|
for ($count = count($commitOrder), $i = $count - 1; $i >= 0 && $this->entityDeletions; --$i) {
|
2012-07-23 03:14:02 +04:00
|
|
|
$this->executeDeletions($commitOrder[$i]);
|
2009-07-18 15:41:37 +04:00
|
|
|
}
|
2012-07-11 02:21:13 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
$conn->commit();
|
|
|
|
} catch (Exception $e) {
|
|
|
|
$this->em->close();
|
|
|
|
$conn->rollback();
|
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
$this->afterTransactionRolledBack();
|
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
throw $e;
|
2009-02-02 14:55:50 +03:00
|
|
|
}
|
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
$this->afterTransactionComplete();
|
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
// Take new snapshots from visited collections
|
|
|
|
foreach ($this->visitedCollections as $coll) {
|
|
|
|
$coll->takeSnapshot();
|
2011-10-23 03:27:09 +04:00
|
|
|
}
|
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
$this->dispatchPostFlushEvent();
|
|
|
|
|
2009-01-29 20:00:44 +03:00
|
|
|
// Clear up
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityInsertions =
|
|
|
|
$this->entityUpdates =
|
|
|
|
$this->entityDeletions =
|
|
|
|
$this->extraUpdates =
|
|
|
|
$this->entityChangeSets =
|
|
|
|
$this->collectionUpdates =
|
|
|
|
$this->collectionDeletions =
|
|
|
|
$this->visitedCollections =
|
2015-01-19 06:21:18 +03:00
|
|
|
$this->scheduledForSynchronization =
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->orphanRemovals = array();
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
2011-10-22 15:44:33 +04:00
|
|
|
|
|
|
|
/**
|
2012-12-13 21:56:25 +04:00
|
|
|
* Computes the changesets of all entities scheduled for insertion.
|
2011-10-23 12:05:46 +04:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function computeScheduleInsertsChangeSets()
|
|
|
|
{
|
|
|
|
foreach ($this->entityInsertions as $entity) {
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-10-23 12:05:46 +04:00
|
|
|
$this->computeChangeSet($class, $entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-12-13 21:56:25 +04:00
|
|
|
* Only flushes the given entity according to a ruleset that keeps the UoW consistent.
|
2011-10-22 15:44:33 +04:00
|
|
|
*
|
|
|
|
* 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well!
|
|
|
|
* 2. Read Only entities are skipped.
|
|
|
|
* 3. Proxies are skipped.
|
|
|
|
* 4. Only if entity is properly managed.
|
|
|
|
*
|
2012-12-13 21:56:25 +04:00
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2011-10-23 12:05:46 +04:00
|
|
|
* @return void
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @throws \InvalidArgumentException
|
2011-10-22 15:44:33 +04:00
|
|
|
*/
|
|
|
|
private function computeSingleEntityChangeSet($entity)
|
|
|
|
{
|
2013-05-04 15:36:37 +04:00
|
|
|
$state = $this->getEntityState($entity);
|
|
|
|
|
|
|
|
if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) {
|
|
|
|
throw new \InvalidArgumentException("Entity has to be managed or scheduled for removal for single computation " . self::objToStr($entity));
|
2011-10-22 15:44:33 +04:00
|
|
|
}
|
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-10-22 15:44:33 +04:00
|
|
|
|
2013-05-04 15:36:37 +04:00
|
|
|
if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) {
|
2011-10-22 15:44:33 +04:00
|
|
|
$this->persist($entity);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute changes for INSERTed entities first. This must always happen even in this case.
|
2011-10-23 12:05:46 +04:00
|
|
|
$this->computeScheduleInsertsChangeSets();
|
2011-10-22 15:44:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
if ($class->isReadOnly) {
|
2011-10-22 15:44:33 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore uninitialized proxy objects
|
|
|
|
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-12 03:11:50 +04:00
|
|
|
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
|
2011-10-22 15:44:33 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-11-12 03:11:50 +04:00
|
|
|
if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
|
2011-10-22 15:44:33 +04:00
|
|
|
$this->computeChangeSet($class, $entity);
|
|
|
|
}
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-24 15:33:38 +04:00
|
|
|
/**
|
|
|
|
* Executes any extra updates that have been scheduled.
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function executeExtraUpdates()
|
2009-07-03 21:36:41 +04:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($this->extraUpdates as $oid => $update) {
|
2009-07-03 21:36:41 +04:00
|
|
|
list ($entity, $changeset) = $update;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityChangeSets[$oid] = $changeset;
|
2012-04-01 18:27:31 +04:00
|
|
|
$this->getEntityPersister(get_class($entity))->update($entity);
|
2009-07-03 21:36:41 +04:00
|
|
|
}
|
2014-05-15 02:18:50 +04:00
|
|
|
|
|
|
|
$this->extraUpdates = array();
|
2009-07-03 21:36:41 +04:00
|
|
|
}
|
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
/**
|
2009-01-09 19:25:06 +03:00
|
|
|
* Gets the changeset for an entity.
|
2008-12-18 17:08:11 +03:00
|
|
|
*
|
2012-09-06 20:10:30 +04:00
|
|
|
* @param object $entity
|
|
|
|
*
|
2008-12-18 17:08:11 +03:00
|
|
|
* @return array
|
|
|
|
*/
|
2009-01-09 19:25:06 +03:00
|
|
|
public function getEntityChangeSet($entity)
|
2008-12-18 17:08:11 +03:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
if (isset($this->entityChangeSets[$oid])) {
|
|
|
|
return $this->entityChangeSets[$oid];
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
return array();
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
|
|
|
|
2010-02-22 00:55:39 +03:00
|
|
|
/**
|
2010-03-18 16:43:38 +03:00
|
|
|
* Computes the changes that happened to a single entity.
|
2009-05-05 22:39:25 +04:00
|
|
|
*
|
|
|
|
* Modifies/populates the following properties:
|
|
|
|
*
|
|
|
|
* {@link _originalEntityData}
|
|
|
|
* If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
|
|
|
|
* then it was not fetched from the database and therefore we have no original
|
|
|
|
* entity data yet. All of the current entity data is stored as the original entity data.
|
|
|
|
*
|
|
|
|
* {@link _entityChangeSets}
|
|
|
|
* The changes detected on all properties of the entity are stored there.
|
|
|
|
* A change is a tuple array where the first entry is the old value and the second
|
|
|
|
* entry is the new value of the property. Changesets are used by persisters
|
|
|
|
* to INSERT/UPDATE the persistent entity state.
|
|
|
|
*
|
|
|
|
* {@link _entityUpdates}
|
|
|
|
* If the entity is already fully MANAGED (has been fetched from the database before)
|
|
|
|
* and any changes to its properties are detected, then a reference to the entity is stored
|
|
|
|
* there to mark it for an update.
|
|
|
|
*
|
|
|
|
* {@link _collectionDeletions}
|
|
|
|
* If a PersistentCollection has been de-referenced in a fully MANAGED entity,
|
|
|
|
* then this collection is marked for deletion.
|
|
|
|
*
|
2011-10-15 20:11:14 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2011-10-15 20:11:14 +04:00
|
|
|
* @internal Don't call from the outside.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2009-05-05 22:39:25 +04:00
|
|
|
* @param ClassMetadata $class The class descriptor of the entity.
|
|
|
|
* @param object $entity The entity for which to compute the changes.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2009-05-05 22:39:25 +04:00
|
|
|
*/
|
2010-08-09 23:44:52 +04:00
|
|
|
public function computeChangeSet(ClassMetadata $class, $entity)
|
2009-05-05 22:39:25 +04:00
|
|
|
{
|
2012-11-09 22:46:56 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-10-15 13:52:41 +04:00
|
|
|
|
|
|
|
if (isset($this->readOnlyObjects[$oid])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-10-30 21:46:07 +04:00
|
|
|
if ( ! $class->isInheritanceTypeNone()) {
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-10-30 21:46:07 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-09-19 14:13:12 +04:00
|
|
|
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
|
2011-11-03 18:24:47 +04:00
|
|
|
|
2012-11-10 00:14:51 +04:00
|
|
|
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
2013-02-04 23:46:51 +04:00
|
|
|
$this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke);
|
2012-07-31 05:26:55 +04:00
|
|
|
}
|
|
|
|
|
2009-05-05 22:39:25 +04:00
|
|
|
$actualData = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-19 20:24:17 +04:00
|
|
|
foreach ($class->reflFields as $name => $refProp) {
|
2010-07-08 02:20:54 +04:00
|
|
|
$value = $refProp->getValue($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2014-02-09 17:27:23 +04:00
|
|
|
if ($class->isCollectionValuedAssociation($name) && $value !== null) {
|
|
|
|
if ($value instanceof PersistentCollection) {
|
|
|
|
if ($value->getOwner() === $entity) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$value = new ArrayCollection($value->getValues());
|
|
|
|
}
|
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
// If $value is not a Collection then use an ArrayCollection.
|
|
|
|
if ( ! $value instanceof Collection) {
|
|
|
|
$value = new ArrayCollection($value);
|
2009-06-14 21:34:28 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-19 20:24:17 +04:00
|
|
|
$assoc = $class->associationMappings[$name];
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-05 22:39:25 +04:00
|
|
|
// Inject PersistentCollection
|
2011-11-05 09:09:14 +04:00
|
|
|
$value = new PersistentCollection(
|
|
|
|
$this->em, $this->em->getClassMetadata($assoc['targetEntity']), $value
|
2009-09-06 07:25:44 +04:00
|
|
|
);
|
2011-11-05 09:09:14 +04:00
|
|
|
$value->setOwner($entity, $assoc);
|
|
|
|
$value->setDirty( ! $value->isEmpty());
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$class->reflFields[$name]->setValue($entity, $value);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$actualData[$name] = $value;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) {
|
2010-07-08 02:20:54 +04:00
|
|
|
$actualData[$name] = $value;
|
2009-05-05 22:39:25 +04:00
|
|
|
}
|
|
|
|
}
|
2009-01-29 20:00:44 +03:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if ( ! isset($this->originalEntityData[$oid])) {
|
2009-07-18 15:41:37 +04:00
|
|
|
// Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
|
|
|
|
// These result in an INSERT.
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->originalEntityData[$oid] = $actualData;
|
|
|
|
$changeSet = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($actualData as $propName => $actualValue) {
|
2011-11-05 09:09:14 +04:00
|
|
|
if ( ! isset($class->associationMappings[$propName])) {
|
|
|
|
$changeSet[$propName] = array(null, $actualValue);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$assoc = $class->associationMappings[$propName];
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
|
2010-07-08 02:20:54 +04:00
|
|
|
$changeSet[$propName] = array(null, $actualValue);
|
|
|
|
}
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityChangeSets[$oid] = $changeSet;
|
2009-05-05 22:39:25 +04:00
|
|
|
} else {
|
|
|
|
// Entity is "fully" MANAGED: it was already fully persisted before
|
|
|
|
// and we have a copy of the original data
|
2011-11-05 09:09:14 +04:00
|
|
|
$originalData = $this->originalEntityData[$oid];
|
2010-07-16 13:16:06 +04:00
|
|
|
$isChangeTrackingNotify = $class->isChangeTrackingNotify();
|
2011-11-15 02:05:33 +04:00
|
|
|
$changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
|
|
|
|
? $this->entityChangeSets[$oid]
|
2011-11-05 09:09:14 +04:00
|
|
|
: array();
|
2009-05-05 22:39:25 +04:00
|
|
|
|
|
|
|
foreach ($actualData as $propName => $actualValue) {
|
2011-11-05 09:09:14 +04:00
|
|
|
// skip field, its a partially omitted one!
|
2011-11-07 07:27:20 +04:00
|
|
|
if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$orgValue = $originalData[$propName];
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-04-06 18:11:52 +04:00
|
|
|
// skip if value haven't changed
|
2011-11-07 07:27:20 +04:00
|
|
|
if ($orgValue === $actualValue) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
// if regular field
|
|
|
|
if ( ! isset($class->associationMappings[$propName])) {
|
2011-11-07 07:27:20 +04:00
|
|
|
if ($isChangeTrackingNotify) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$changeSet[$propName] = array($orgValue, $actualValue);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-07 07:27:20 +04:00
|
|
|
$assoc = $class->associationMappings[$propName];
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-02-18 03:40:42 +04:00
|
|
|
// Persistent collection was exchanged with the "originally"
|
|
|
|
// created one. This can only mean it was cloned and replaced
|
|
|
|
// on another entity.
|
|
|
|
if ($actualValue instanceof PersistentCollection) {
|
|
|
|
$owner = $actualValue->getOwner();
|
|
|
|
if ($owner === null) { // cloned
|
|
|
|
$actualValue->setOwner($entity, $assoc);
|
|
|
|
} else if ($owner !== $entity) { // no clone, we have to fix
|
|
|
|
if (!$actualValue->isInitialized()) {
|
|
|
|
$actualValue->initialize(); // we have to do this otherwise the cols share state
|
|
|
|
}
|
|
|
|
$newValue = clone $actualValue;
|
|
|
|
$newValue->setOwner($entity, $assoc);
|
|
|
|
$class->reflFields[$propName]->setValue($entity, $newValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
if ($orgValue instanceof PersistentCollection) {
|
|
|
|
// A PersistentCollection was de-referenced, so delete it.
|
|
|
|
$coid = spl_object_hash($orgValue);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-07 07:27:20 +04:00
|
|
|
if (isset($this->collectionDeletions[$coid])) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$this->collectionDeletions[$coid] = $orgValue;
|
|
|
|
$changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored.
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-10-15 13:52:41 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
|
|
|
if ($assoc['isOwningSide']) {
|
|
|
|
$changeSet[$propName] = array($orgValue, $actualValue);
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
if ($orgValue !== null && $assoc['orphanRemoval']) {
|
|
|
|
$this->scheduleOrphanRemoval($orgValue);
|
2010-07-08 02:20:54 +04:00
|
|
|
}
|
2009-05-05 22:39:25 +04:00
|
|
|
}
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-05 22:39:25 +04:00
|
|
|
if ($changeSet) {
|
2011-11-05 09:09:14 +04:00
|
|
|
$this->entityChangeSets[$oid] = $changeSet;
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->originalEntityData[$oid] = $actualData;
|
2011-11-05 09:09:14 +04:00
|
|
|
$this->entityUpdates[$oid] = $entity;
|
2009-05-05 22:39:25 +04:00
|
|
|
}
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
2010-05-01 14:14:16 +04:00
|
|
|
|
2010-03-18 16:43:38 +03:00
|
|
|
// Look for changes in associations of the entity
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($class->associationMappings as $field => $assoc) {
|
2013-02-14 02:42:13 +04:00
|
|
|
|
|
|
|
if (($val = $class->reflFields[$field]->getValue($entity)) === null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-01-18 03:05:36 +03:00
|
|
|
$this->computeAssociationChanges($assoc, $val);
|
2013-02-14 02:42:13 +04:00
|
|
|
|
|
|
|
if ( ! isset($this->entityChangeSets[$oid]) &&
|
|
|
|
$assoc['isOwningSide'] &&
|
|
|
|
$assoc['type'] == ClassMetadata::MANY_TO_MANY &&
|
|
|
|
$val instanceof PersistentCollection &&
|
|
|
|
$val->isDirty()) {
|
|
|
|
|
|
|
|
$this->entityChangeSets[$oid] = array();
|
|
|
|
$this->originalEntityData[$oid] = $actualData;
|
|
|
|
$this->entityUpdates[$oid] = $entity;
|
2010-03-18 16:43:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Computes all the changes that have been done to entities and collections
|
|
|
|
* since the last commit and stores these changes in the _entityChangeSet map
|
|
|
|
* temporarily for access by the persisters, until the UoW commit is finished.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2010-03-18 16:43:38 +03:00
|
|
|
*/
|
|
|
|
public function computeChangeSets()
|
|
|
|
{
|
|
|
|
// Compute changes for INSERTed entities first. This must always happen.
|
2011-10-23 12:05:46 +04:00
|
|
|
$this->computeScheduleInsertsChangeSets();
|
2010-03-18 16:43:38 +03:00
|
|
|
|
|
|
|
// Compute changes for other MANAGED entities. Change tracking policies take effect here.
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($this->identityMap as $className => $entities) {
|
|
|
|
$class = $this->em->getClassMetadata($className);
|
2010-03-18 16:43:38 +03:00
|
|
|
|
2010-07-15 17:52:42 +04:00
|
|
|
// Skip class if instances are read-only
|
2011-11-07 07:27:20 +04:00
|
|
|
if ($class->isReadOnly) {
|
|
|
|
continue;
|
|
|
|
}
|
2010-03-18 16:43:38 +03:00
|
|
|
|
2010-07-15 17:52:42 +04:00
|
|
|
// If change tracking is explicit or happens through notification, then only compute
|
|
|
|
// changes on entities of that type that are explicitly marked for synchronization.
|
2011-11-06 08:03:34 +04:00
|
|
|
switch (true) {
|
|
|
|
case ($class->isChangeTrackingDeferredImplicit()):
|
|
|
|
$entitiesToProcess = $entities;
|
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2015-01-19 06:21:18 +03:00
|
|
|
case (isset($this->scheduledForSynchronization[$className])):
|
|
|
|
$entitiesToProcess = $this->scheduledForSynchronization[$className];
|
2011-11-06 08:03:34 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
default:
|
|
|
|
$entitiesToProcess = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
}
|
2010-03-18 16:43:38 +03:00
|
|
|
|
|
|
|
foreach ($entitiesToProcess as $entity) {
|
|
|
|
// Ignore uninitialized proxy objects
|
2011-11-07 07:27:20 +04:00
|
|
|
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-11-17 14:31:21 +04:00
|
|
|
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
|
2010-03-18 16:43:38 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-11-17 14:31:21 +04:00
|
|
|
if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
|
2010-03-18 16:43:38 +03:00
|
|
|
$this->computeChangeSet($class, $entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2009-01-09 19:25:06 +03:00
|
|
|
/**
|
2009-02-02 14:55:50 +03:00
|
|
|
* Computes the changes of an association.
|
2009-01-09 19:25:06 +03:00
|
|
|
*
|
2013-02-14 02:42:13 +04:00
|
|
|
* @param array $assoc The association mapping.
|
|
|
|
* @param mixed $value The value of the association.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException
|
|
|
|
* @throws ORMException
|
|
|
|
*
|
|
|
|
* @return void
|
2009-01-09 19:25:06 +03:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function computeAssociationChanges($assoc, $value)
|
2009-01-09 19:25:06 +03:00
|
|
|
{
|
2011-11-05 09:09:14 +04:00
|
|
|
if ($value instanceof Proxy && ! $value->__isInitialized__) {
|
|
|
|
return;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-02-07 20:02:13 +03:00
|
|
|
if ($value instanceof PersistentCollection && $value->isDirty()) {
|
2011-10-15 23:47:16 +04:00
|
|
|
$coid = spl_object_hash($value);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-08-09 15:13:21 +04:00
|
|
|
if ($assoc['isOwningSide']) {
|
2011-10-15 23:47:16 +04:00
|
|
|
$this->collectionUpdates[$coid] = $value;
|
2009-02-07 20:02:13 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-10-15 23:47:16 +04:00
|
|
|
$this->visitedCollections[$coid] = $value;
|
2009-02-07 20:02:13 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
|
|
|
// Look through the entities, and in any of their associations,
|
2011-11-07 07:27:20 +04:00
|
|
|
// for transient (new) entities, recursively. ("Persistence by reachability")
|
2011-11-05 09:09:14 +04:00
|
|
|
// Unwrap. Uninitialized collections will simply be empty.
|
|
|
|
$unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap();
|
|
|
|
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
foreach ($unwrappedValue as $key => $entry) {
|
2015-01-13 22:15:53 +03:00
|
|
|
if (! ($entry instanceof $targetClass->name)) {
|
2015-01-13 22:02:39 +03:00
|
|
|
throw ORMInvalidArgumentException::invalidAssociation($targetClass, $assoc, $entry);
|
2014-04-23 18:50:15 +04:00
|
|
|
}
|
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$state = $this->getEntityState($entry, self::STATE_NEW);
|
2012-01-19 08:09:23 +04:00
|
|
|
|
|
|
|
if ( ! ($entry instanceof $assoc['targetEntity'])) {
|
2013-02-14 02:42:13 +04:00
|
|
|
throw ORMException::unexpectedAssociationValue($assoc['sourceEntity'], $assoc['fieldName'], get_class($entry), $assoc['targetEntity']);
|
2012-01-15 17:58:56 +04:00
|
|
|
}
|
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
switch ($state) {
|
|
|
|
case self::STATE_NEW:
|
|
|
|
if ( ! $assoc['isCascadePersist']) {
|
2011-12-22 02:56:25 +04:00
|
|
|
throw ORMInvalidArgumentException::newEntityFoundThroughRelationship($assoc, $entry);
|
2011-11-05 09:09:14 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$this->persistNew($targetClass, $entry);
|
|
|
|
$this->computeChangeSet($targetClass, $entry);
|
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
case self::STATE_REMOVED:
|
2011-11-15 02:05:33 +04:00
|
|
|
// Consume the $value as array (it's either an array or an ArrayAccess)
|
2011-11-05 09:09:14 +04:00
|
|
|
// and remove the element from Collection.
|
|
|
|
if ($assoc['type'] & ClassMetadata::TO_MANY) {
|
|
|
|
unset($value[$key]);
|
|
|
|
}
|
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
case self::STATE_DETACHED:
|
|
|
|
// Can actually not happen right now as we assume STATE_NEW,
|
|
|
|
// so the exception will be raised from the DBAL layer (constraint violation).
|
2011-12-22 02:56:25 +04:00
|
|
|
throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($assoc, $entry);
|
2011-11-05 09:09:14 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
default:
|
|
|
|
// MANAGED associated entities are already taken into account
|
|
|
|
// during changeset calculation anyway, since they are in the identity map.
|
2009-01-09 19:25:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-07-04 20:55:49 +04:00
|
|
|
|
2012-09-06 20:10:30 +04:00
|
|
|
/**
|
2012-07-31 05:26:55 +04:00
|
|
|
* @param \Doctrine\ORM\Mapping\ClassMetadata $class
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity
|
|
|
|
*
|
|
|
|
* @return void
|
2012-09-06 20:10:30 +04:00
|
|
|
*/
|
2010-07-04 20:55:49 +04:00
|
|
|
private function persistNew($class, $entity)
|
|
|
|
{
|
2012-11-09 22:46:56 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
|
|
|
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
|
2012-07-31 05:26:55 +04:00
|
|
|
|
2012-11-10 00:14:51 +04:00
|
|
|
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
2012-11-09 22:46:56 +04:00
|
|
|
$this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
|
2010-07-04 20:55:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
$idGen = $class->idGenerator;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-04 20:55:49 +04:00
|
|
|
if ( ! $idGen->isPostInsertGenerator()) {
|
2010-07-08 02:20:54 +04:00
|
|
|
$idValue = $idGen->generate($this->em, $entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-04 20:55:49 +04:00
|
|
|
if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) {
|
2011-11-05 09:09:14 +04:00
|
|
|
$idValue = array($class->identifier[0] => $idValue);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$class->setIdentifierValues($entity, $idValue);
|
2010-07-04 20:55:49 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
$this->entityIdentifiers[$oid] = $idValue;
|
2010-07-04 20:55:49 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityStates[$oid] = self::STATE_MANAGED;
|
2010-07-04 20:55:49 +04:00
|
|
|
|
|
|
|
$this->scheduleForInsert($entity);
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-18 15:41:37 +04:00
|
|
|
/**
|
2009-08-13 14:13:06 +04:00
|
|
|
* INTERNAL:
|
2009-07-18 15:41:37 +04:00
|
|
|
* Computes the changeset of an individual entity, independently of the
|
|
|
|
* computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-11-13 19:39:28 +03:00
|
|
|
* The passed entity must be a managed entity. If the entity already has a change set
|
|
|
|
* because this method is invoked during a commit cycle then the change sets are added.
|
|
|
|
* whereby changes detected in this method prevail.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-10-06 14:04:32 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @param ClassMetadata $class The class descriptor of the entity.
|
|
|
|
* @param object $entity The entity for which to (re)calculate the change set.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-13 21:56:25 +04:00
|
|
|
* @return void
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
|
2009-07-18 15:41:37 +04:00
|
|
|
*/
|
2011-10-10 10:44:17 +04:00
|
|
|
public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
|
2009-07-18 15:41:37 +04:00
|
|
|
{
|
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if ( ! isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) {
|
2011-10-22 14:49:33 +04:00
|
|
|
throw ORMInvalidArgumentException::entityNotManaged($entity);
|
2009-11-13 19:39:28 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-05 09:09:14 +04:00
|
|
|
// skip if change tracking is "NOTIFY"
|
2009-11-13 22:34:10 +03:00
|
|
|
if ($class->isChangeTrackingNotify()) {
|
|
|
|
return;
|
2011-11-05 09:09:14 +04:00
|
|
|
}
|
2009-07-18 15:41:37 +04:00
|
|
|
|
|
|
|
if ( ! $class->isInheritanceTypeNone()) {
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2009-07-18 15:41:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
$actualData = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-18 15:41:37 +04:00
|
|
|
foreach ($class->reflFields as $name => $refProp) {
|
2014-03-23 15:35:54 +04:00
|
|
|
if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity())
|
|
|
|
&& ($name !== $class->versionField)
|
|
|
|
&& ! $class->isCollectionValuedAssociation($name)) {
|
2009-07-18 15:41:37 +04:00
|
|
|
$actualData[$name] = $refProp->getValue($entity);
|
|
|
|
}
|
|
|
|
}
|
2010-03-29 17:20:41 +04:00
|
|
|
|
2014-03-23 16:16:33 +04:00
|
|
|
if ( ! isset($this->originalEntityData[$oid])) {
|
|
|
|
throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
|
|
|
|
}
|
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$originalData = $this->originalEntityData[$oid];
|
2009-11-13 19:39:28 +03:00
|
|
|
$changeSet = array();
|
|
|
|
|
|
|
|
foreach ($actualData as $propName => $actualValue) {
|
|
|
|
$orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2014-03-23 15:35:54 +04:00
|
|
|
if ($orgValue !== $actualValue) {
|
2009-11-13 19:39:28 +03:00
|
|
|
$changeSet[$propName] = array($orgValue, $actualValue);
|
2009-07-18 15:41:37 +04:00
|
|
|
}
|
2009-11-13 19:39:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($changeSet) {
|
2014-06-27 01:15:57 +04:00
|
|
|
if (isset($this->entityChangeSets[$oid])) {
|
|
|
|
$this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
|
|
|
|
} else if ( ! isset($this->entityInsertions[$oid])) {
|
|
|
|
$this->entityChangeSets[$oid] = $changeSet;
|
|
|
|
$this->entityUpdates[$oid] = $entity;
|
|
|
|
}
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->originalEntityData[$oid] = $actualData;
|
2009-07-18 15:41:37 +04:00
|
|
|
}
|
|
|
|
}
|
2009-01-09 19:25:06 +03:00
|
|
|
|
2008-09-07 17:48:40 +04:00
|
|
|
/**
|
|
|
|
* Executes all entity insertions for entities of the specified type.
|
|
|
|
*
|
2011-12-12 00:52:29 +04:00
|
|
|
* @param \Doctrine\ORM\Mapping\ClassMetadata $class
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2008-09-07 17:48:40 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function executeInserts($class)
|
2008-01-05 22:55:56 +03:00
|
|
|
{
|
2012-11-09 22:46:56 +04:00
|
|
|
$entities = array();
|
|
|
|
$className = $class->name;
|
|
|
|
$persister = $this->getEntityPersister($className);
|
|
|
|
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($this->entityInsertions as $oid => $entity) {
|
2013-02-14 02:42:13 +04:00
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
2011-11-07 07:27:20 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$persister->addInsert($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
unset($this->entityInsertions[$oid]);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-11-10 00:14:51 +04:00
|
|
|
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
2011-11-06 08:03:34 +04:00
|
|
|
$entities[] = $entity;
|
2009-05-19 20:11:08 +04:00
|
|
|
}
|
|
|
|
}
|
2009-12-18 15:30:19 +03:00
|
|
|
|
2009-05-19 20:11:08 +04:00
|
|
|
$postInsertIds = $persister->executeInserts();
|
2010-03-29 17:20:41 +04:00
|
|
|
|
2009-05-19 20:11:08 +04:00
|
|
|
if ($postInsertIds) {
|
2009-11-13 22:34:10 +03:00
|
|
|
// Persister returned post-insert IDs
|
2015-03-23 19:51:04 +03:00
|
|
|
foreach ($postInsertIds as $postInsertId) {
|
|
|
|
$id = $postInsertId['generatedId'];
|
|
|
|
$entity = $postInsertId['entity'];
|
2011-11-06 08:03:34 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2009-05-19 20:11:08 +04:00
|
|
|
$idField = $class->identifier[0];
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-19 20:11:08 +04:00
|
|
|
$class->reflFields[$idField]->setValue($entity, $id);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityIdentifiers[$oid] = array($idField => $id);
|
|
|
|
$this->entityStates[$oid] = self::STATE_MANAGED;
|
|
|
|
$this->originalEntityData[$oid][$idField] = $id;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-19 20:11:08 +04:00
|
|
|
$this->addToIdentityMap($entity);
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
}
|
2012-07-31 05:26:55 +04:00
|
|
|
|
2012-11-09 22:46:56 +04:00
|
|
|
foreach ($entities as $entity) {
|
|
|
|
$this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
|
2009-07-18 22:06:30 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
2008-04-13 00:11:11 +04:00
|
|
|
|
2008-09-07 17:48:40 +04:00
|
|
|
/**
|
|
|
|
* Executes all entity updates for entities of the specified type.
|
|
|
|
*
|
2011-12-12 00:52:29 +04:00
|
|
|
* @param \Doctrine\ORM\Mapping\ClassMetadata $class
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2008-09-07 17:48:40 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function executeUpdates($class)
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
2012-11-09 22:46:56 +04:00
|
|
|
$className = $class->name;
|
|
|
|
$persister = $this->getEntityPersister($className);
|
|
|
|
$preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
|
|
|
|
$postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($this->entityUpdates as $oid => $entity) {
|
2013-02-14 02:42:13 +04:00
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
2011-11-06 08:03:34 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-11-09 22:46:56 +04:00
|
|
|
if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
|
|
|
|
$this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid]), $preUpdateInvoke);
|
2011-11-06 08:03:34 +04:00
|
|
|
$this->recomputeSingleEntityChangeSet($class, $entity);
|
|
|
|
}
|
2010-02-23 23:36:07 +03:00
|
|
|
|
2012-11-09 22:46:56 +04:00
|
|
|
if ( ! empty($this->entityChangeSets[$oid])) {
|
2011-11-06 08:03:34 +04:00
|
|
|
$persister->update($entity);
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
unset($this->entityUpdates[$oid]);
|
|
|
|
|
2012-11-09 22:46:56 +04:00
|
|
|
if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
|
|
|
|
$this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
}
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-09-07 17:48:40 +04:00
|
|
|
/**
|
|
|
|
* Executes all entity deletions for entities of the specified type.
|
|
|
|
*
|
2011-12-12 00:52:29 +04:00
|
|
|
* @param \Doctrine\ORM\Mapping\ClassMetadata $class
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2008-09-07 17:48:40 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function executeDeletions($class)
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
2012-11-09 22:46:56 +04:00
|
|
|
$className = $class->name;
|
|
|
|
$persister = $this->getEntityPersister($className);
|
|
|
|
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
foreach ($this->entityDeletions as $oid => $entity) {
|
2012-04-01 18:27:31 +04:00
|
|
|
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
2011-11-06 08:03:34 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$persister->delete($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
unset(
|
|
|
|
$this->entityDeletions[$oid],
|
|
|
|
$this->entityIdentifiers[$oid],
|
|
|
|
$this->originalEntityData[$oid],
|
|
|
|
$this->entityStates[$oid]
|
|
|
|
);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
// Entity with this $oid after deletion treated as NEW, even if the $oid
|
|
|
|
// is obtained by a new entity because the old one went out of scope.
|
|
|
|
//$this->entityStates[$oid] = self::STATE_NEW;
|
|
|
|
if ( ! $class->isIdentifierNatural()) {
|
|
|
|
$class->reflFields[$class->identifier[0]]->setValue($entity, null);
|
|
|
|
}
|
2010-07-08 02:20:54 +04:00
|
|
|
|
2012-11-10 00:14:51 +04:00
|
|
|
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
2012-11-09 22:46:56 +04:00
|
|
|
$this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the commit order.
|
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array|null $entityChangeSet
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2008-07-21 00:13:24 +04:00
|
|
|
* @return array
|
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function getCommitOrder(array $entityChangeSet = null)
|
2009-02-07 20:02:13 +03:00
|
|
|
{
|
2009-03-14 12:05:52 +03:00
|
|
|
if ($entityChangeSet === null) {
|
2011-11-06 08:03:34 +04:00
|
|
|
$entityChangeSet = array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions);
|
2008-09-07 17:48:40 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-12-21 14:06:27 +03:00
|
|
|
$calc = $this->getCommitOrderCalculator();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
// See if there are any new classes in the changeset, that are not in the
|
2013-04-06 18:11:52 +04:00
|
|
|
// commit order graph yet (don't have a node).
|
2011-11-06 08:03:34 +04:00
|
|
|
// We have to inspect changeSet to be able to correctly build dependencies.
|
2011-11-15 02:05:33 +04:00
|
|
|
// It is not possible to use IdentityMap here because post inserted ids
|
2011-11-06 08:03:34 +04:00
|
|
|
// are not yet available.
|
2008-07-21 00:13:24 +04:00
|
|
|
$newNodes = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-01-19 08:09:23 +04:00
|
|
|
foreach ($entityChangeSet as $entity) {
|
2013-08-04 03:03:10 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-08-04 03:03:10 +04:00
|
|
|
if ($calc->hasClass($class->name)) {
|
2011-11-07 07:27:20 +04:00
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$calc->addClass($class);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$newNodes[] = $class;
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
// Calculate dependencies for new nodes
|
2011-08-14 21:20:04 +04:00
|
|
|
while ($class = array_pop($newNodes)) {
|
2009-10-26 00:40:57 +03:00
|
|
|
foreach ($class->associationMappings as $assoc) {
|
2011-11-07 07:27:20 +04:00
|
|
|
if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
if ( ! $calc->hasClass($targetClass->name)) {
|
|
|
|
$calc->addClass($targetClass);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$newNodes[] = $targetClass;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$calc->addDependency($targetClass, $class);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
// If the target class has mapped subclasses, these share the same dependency.
|
2011-11-07 07:27:20 +04:00
|
|
|
if ( ! $targetClass->subClasses) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
foreach ($targetClass->subClasses as $subClassName) {
|
|
|
|
$targetSubClass = $this->em->getClassMetadata($subClassName);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
if ( ! $calc->hasClass($subClassName)) {
|
|
|
|
$calc->addClass($targetSubClass);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$newNodes[] = $targetSubClass;
|
2010-06-06 20:23:37 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$calc->addDependency($targetSubClass, $class);
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-21 14:06:27 +03:00
|
|
|
return $calc->getCommitOrder();
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
/**
|
2009-07-19 20:54:53 +04:00
|
|
|
* Schedules an entity for insertion into the database.
|
2009-05-21 23:18:14 +04:00
|
|
|
* If the entity already has an identifier, it will be added to the identity map.
|
2009-07-03 21:36:41 +04:00
|
|
|
*
|
2009-08-13 14:13:06 +04:00
|
|
|
* @param object $entity The entity to schedule for insertion.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @return void
|
|
|
|
*
|
2012-09-06 20:10:30 +04:00
|
|
|
* @throws ORMInvalidArgumentException
|
|
|
|
* @throws \InvalidArgumentException
|
2008-04-13 00:11:11 +04:00
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function scheduleForInsert($entity)
|
2008-01-05 22:55:56 +03:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->entityUpdates[$oid])) {
|
|
|
|
throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->entityDeletions[$oid])) {
|
2011-10-22 14:40:12 +04:00
|
|
|
throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
|
|
|
|
}
|
|
|
|
if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
|
|
|
|
throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->entityInsertions[$oid])) {
|
2011-10-22 14:40:12 +04:00
|
|
|
throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
|
2008-04-13 00:11:11 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityInsertions[$oid] = $entity;
|
2009-12-18 15:30:19 +03:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->entityIdentifiers[$oid])) {
|
2008-07-21 00:13:24 +04:00
|
|
|
$this->addToIdentityMap($entity);
|
|
|
|
}
|
2012-07-07 19:47:29 +04:00
|
|
|
|
|
|
|
if ($entity instanceof NotifyPropertyChanged) {
|
|
|
|
$entity->addPropertyChangedListener($this);
|
|
|
|
}
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
|
|
|
/**
|
2009-08-13 14:13:06 +04:00
|
|
|
* Checks whether an entity is scheduled for insertion.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
2009-05-21 23:18:14 +04:00
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2008-07-21 00:13:24 +04:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function isScheduledForInsert($entity)
|
2008-05-01 13:41:47 +04:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return isset($this->entityInsertions[spl_object_hash($entity)]);
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
/**
|
2009-08-13 14:13:06 +04:00
|
|
|
* Schedules an entity for being updated.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
2009-08-13 14:13:06 +04:00
|
|
|
* @param object $entity The entity to schedule for being updated.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @return void
|
|
|
|
*
|
2012-09-06 20:10:30 +04:00
|
|
|
* @throws ORMInvalidArgumentException
|
2008-04-13 00:11:11 +04:00
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function scheduleForUpdate($entity)
|
2008-04-13 00:11:11 +04:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if ( ! isset($this->entityIdentifiers[$oid])) {
|
2011-10-22 14:57:55 +04:00
|
|
|
throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update");
|
2008-05-06 17:41:22 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->entityDeletions[$oid])) {
|
2011-10-22 14:57:55 +04:00
|
|
|
throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
|
2008-04-13 00:11:11 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
|
|
|
|
$this->entityUpdates[$oid] = $entity;
|
2008-05-06 17:41:22 +04:00
|
|
|
}
|
2008-04-13 00:11:11 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-18 15:41:37 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* INTERNAL:
|
2009-07-18 15:41:37 +04:00
|
|
|
* Schedules an extra update that will be executed immediately after the
|
2009-08-13 14:13:06 +04:00
|
|
|
* regular entity updates within the currently running commit cycle.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-11-15 14:42:05 +03:00
|
|
|
* Extra updates for entities are stored as (entity, changeset) tuples.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @param object $entity The entity for which to schedule an extra update.
|
|
|
|
* @param array $changeset The changeset of the entity (what to update).
|
|
|
|
*
|
|
|
|
* @return void
|
2009-07-18 15:41:37 +04:00
|
|
|
*/
|
2009-07-03 21:36:41 +04:00
|
|
|
public function scheduleExtraUpdate($entity, array $changeset)
|
|
|
|
{
|
2011-11-06 08:03:34 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
|
|
|
$extraUpdate = array($entity, $changeset);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->extraUpdates[$oid])) {
|
|
|
|
list($ignored, $changeset2) = $this->extraUpdates[$oid];
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$extraUpdate = array($entity, $changeset + $changeset2);
|
2009-11-15 14:42:05 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$this->extraUpdates[$oid] = $extraUpdate;
|
2009-07-03 21:36:41 +04:00
|
|
|
}
|
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
/**
|
|
|
|
* Checks whether an entity is registered as dirty in the unit of work.
|
|
|
|
* Note: Is not very useful currently as dirty entities are only registered
|
|
|
|
* at commit time.
|
|
|
|
*
|
2009-07-18 15:41:37 +04:00
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2008-07-21 00:13:24 +04:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function isScheduledForUpdate($entity)
|
2008-05-01 13:41:47 +04:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return isset($this->entityUpdates[spl_object_hash($entity)]);
|
2008-05-01 13:41:47 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
/**
|
|
|
|
* Checks whether an entity is registered to be checked in the unit of work.
|
|
|
|
*
|
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2011-11-06 08:03:34 +04:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2010-07-15 17:52:42 +04:00
|
|
|
public function isScheduledForDirtyCheck($entity)
|
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$rootEntityName = $this->em->getClassMetadata(get_class($entity))->rootEntityName;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2015-01-19 06:21:18 +03:00
|
|
|
return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]);
|
2010-07-15 17:52:42 +04:00
|
|
|
}
|
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
/**
|
2010-02-24 22:19:04 +03:00
|
|
|
* INTERNAL:
|
|
|
|
* Schedules an entity for deletion.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-07-19 20:54:53 +04:00
|
|
|
* @param object $entity
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2008-04-13 00:11:11 +04:00
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function scheduleForDelete($entity)
|
2008-04-13 00:11:11 +04:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->entityInsertions[$oid])) {
|
2009-11-11 19:20:29 +03:00
|
|
|
if ($this->isInIdentityMap($entity)) {
|
|
|
|
$this->removeFromIdentityMap($entity);
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-06-28 23:37:53 +04:00
|
|
|
unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
return; // entity has not been persisted yet, so nothing more to do.
|
2008-05-06 17:41:22 +04:00
|
|
|
}
|
2010-02-10 13:47:42 +03:00
|
|
|
|
2009-11-11 19:20:29 +03:00
|
|
|
if ( ! $this->isInIdentityMap($entity)) {
|
2011-11-06 08:03:34 +04:00
|
|
|
return;
|
2009-11-11 19:20:29 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-11-11 19:20:29 +03:00
|
|
|
$this->removeFromIdentityMap($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->entityUpdates[$oid])) {
|
|
|
|
unset($this->entityUpdates[$oid]);
|
2008-09-07 17:48:40 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if ( ! isset($this->entityDeletions[$oid])) {
|
|
|
|
$this->entityDeletions[$oid] = $entity;
|
2011-11-06 08:03:34 +04:00
|
|
|
$this->entityStates[$oid] = self::STATE_REMOVED;
|
2008-05-06 17:41:22 +04:00
|
|
|
}
|
2008-04-13 00:11:11 +04:00
|
|
|
}
|
|
|
|
|
2007-10-23 01:47:05 +04:00
|
|
|
/**
|
2008-07-21 00:13:24 +04:00
|
|
|
* Checks whether an entity is registered as removed/deleted with the unit
|
|
|
|
* of work.
|
2007-10-23 01:47:05 +04:00
|
|
|
*
|
2009-07-18 15:41:37 +04:00
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2008-07-21 00:13:24 +04:00
|
|
|
* @return boolean
|
2007-10-23 01:47:05 +04:00
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function isScheduledForDelete($entity)
|
2007-10-23 01:47:05 +04:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return isset($this->entityDeletions[spl_object_hash($entity)]);
|
2007-10-23 01:47:05 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2009-07-18 15:41:37 +04:00
|
|
|
/**
|
2009-07-19 20:54:53 +04:00
|
|
|
* Checks whether an entity is scheduled for insertion, update or deletion.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2009-07-19 20:54:53 +04:00
|
|
|
* @return boolean
|
2009-07-18 15:41:37 +04:00
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function isEntityScheduled($entity)
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
|
|
|
return isset($this->entityInsertions[$oid])
|
|
|
|
|| isset($this->entityUpdates[$oid])
|
2011-11-06 08:03:34 +04:00
|
|
|
|| isset($this->entityDeletions[$oid]);
|
2008-02-28 18:30:55 +03:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-02-28 18:30:55 +03:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* INTERNAL:
|
2008-03-05 14:24:33 +03:00
|
|
|
* Registers an entity in the identity map.
|
2008-07-21 00:13:24 +04:00
|
|
|
* Note that entities in a hierarchy are registered with the class name of
|
|
|
|
* the root entity.
|
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2009-05-21 23:18:14 +04:00
|
|
|
* @param object $entity The entity to register.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @return boolean TRUE if the registration was successful, FALSE if the identity of
|
|
|
|
* the entity in question is already managed.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @throws ORMInvalidArgumentException
|
2008-02-28 18:30:55 +03:00
|
|
|
*/
|
2008-12-18 17:08:11 +03:00
|
|
|
public function addToIdentityMap($entity)
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$classMetadata = $this->em->getClassMetadata(get_class($entity));
|
2011-11-06 08:03:34 +04:00
|
|
|
$idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
if ($idHash === '') {
|
2011-10-22 14:40:12 +04:00
|
|
|
throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity);
|
2008-03-05 14:24:33 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-14 14:03:09 +04:00
|
|
|
$className = $classMetadata->rootEntityName;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->identityMap[$className][$idHash])) {
|
2008-02-28 18:30:55 +03:00
|
|
|
return false;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->identityMap[$className][$idHash] = $entity;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-02-28 18:30:55 +03:00
|
|
|
return true;
|
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
/**
|
2010-07-04 20:55:49 +04:00
|
|
|
* Gets the state of an entity with regard to the current unit of work.
|
2008-12-18 17:08:11 +03:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity
|
|
|
|
* @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
|
|
|
|
* This parameter can be set to improve performance of entity state detection
|
|
|
|
* by potentially avoiding a database lookup if the distinction between NEW and DETACHED
|
|
|
|
* is either known or does not matter for the caller of the method.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2009-05-21 23:18:14 +04:00
|
|
|
* @return int The entity state.
|
2008-12-18 17:08:11 +03:00
|
|
|
*/
|
2009-07-25 20:33:29 +04:00
|
|
|
public function getEntityState($entity, $assume = null)
|
2008-12-18 17:08:11 +03:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
if (isset($this->entityStates[$oid])) {
|
|
|
|
return $this->entityStates[$oid];
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
if ($assume !== null) {
|
|
|
|
return $assume;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
// State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
|
|
|
|
// Note that you can not remember the NEW or DETACHED state in _entityStates since
|
|
|
|
// the UoW does not hold references to such objects and the object hash can be reused.
|
|
|
|
// More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-11-06 01:21:35 +04:00
|
|
|
$id = $class->getIdentifierValues($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
if ( ! $id) {
|
|
|
|
return self::STATE_NEW;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-01-20 23:11:08 +04:00
|
|
|
if ($class->containsForeignIdentifier) {
|
2014-08-07 17:56:27 +04:00
|
|
|
$id = $this->identifierFlattener->flattenIdentifier($class, $id);
|
2013-01-20 23:11:08 +04:00
|
|
|
}
|
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
switch (true) {
|
2014-05-27 11:37:40 +04:00
|
|
|
case ($class->isIdentifierNatural()):
|
2011-11-06 01:21:35 +04:00
|
|
|
// Check for a version field, if available, to avoid a db lookup.
|
|
|
|
if ($class->isVersioned) {
|
|
|
|
return ($class->getFieldValue($entity, $class->versionField))
|
|
|
|
? self::STATE_DETACHED
|
|
|
|
: self::STATE_NEW;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
// Last try before db lookup: check the identity map.
|
|
|
|
if ($this->tryGetById($id, $class->rootEntityName)) {
|
2010-07-04 20:55:49 +04:00
|
|
|
return self::STATE_DETACHED;
|
2009-07-25 20:33:29 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
// db lookup
|
2012-03-31 01:15:44 +04:00
|
|
|
if ($this->getEntityPersister($class->name)->exists($entity)) {
|
2011-11-06 01:21:35 +04:00
|
|
|
return self::STATE_DETACHED;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
return self::STATE_NEW;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
case ( ! $class->idGenerator->isPostInsertGenerator()):
|
|
|
|
// if we have a pre insert generator we can't be sure that having an id
|
|
|
|
// really means that the entity exists. We have to verify this through
|
|
|
|
// the last resort: a db lookup
|
|
|
|
|
|
|
|
// Last try before db lookup: check the identity map.
|
|
|
|
if ($this->tryGetById($id, $class->rootEntityName)) {
|
|
|
|
return self::STATE_DETACHED;
|
2011-11-15 02:05:33 +04:00
|
|
|
}
|
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
// db lookup
|
2012-03-31 01:15:44 +04:00
|
|
|
if ($this->getEntityPersister($class->name)->exists($entity)) {
|
2011-11-06 01:21:35 +04:00
|
|
|
return self::STATE_DETACHED;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
return self::STATE_NEW;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
default:
|
|
|
|
return self::STATE_DETACHED;
|
2009-01-04 19:15:32 +03:00
|
|
|
}
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
|
|
|
|
2008-05-17 16:22:24 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* INTERNAL:
|
2009-01-04 19:15:32 +03:00
|
|
|
* Removes an entity from the identity map. This effectively detaches the
|
|
|
|
* entity from the persistence management of Doctrine.
|
2008-05-17 16:22:24 +04:00
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2009-01-03 22:50:13 +03:00
|
|
|
* @return boolean
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException
|
2008-05-17 16:22:24 +04:00
|
|
|
*/
|
2008-12-18 17:08:11 +03:00
|
|
|
public function removeFromIdentityMap($entity)
|
2008-03-05 14:24:33 +03:00
|
|
|
{
|
2011-11-06 08:03:34 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2012-04-01 18:27:31 +04:00
|
|
|
$classMetadata = $this->em->getClassMetadata(get_class($entity));
|
2011-11-06 08:03:34 +04:00
|
|
|
$idHash = implode(' ', $this->entityIdentifiers[$oid]);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
if ($idHash === '') {
|
2011-10-22 14:57:55 +04:00
|
|
|
throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
|
2008-03-05 14:24:33 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-14 14:03:09 +04:00
|
|
|
$className = $classMetadata->rootEntityName;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->identityMap[$className][$idHash])) {
|
|
|
|
unset($this->identityMap[$className][$idHash]);
|
2012-02-20 12:36:35 +04:00
|
|
|
unset($this->readOnlyObjects[$oid]);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
//$this->entityStates[$oid] = self::STATE_DETACHED;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-03-05 14:24:33 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-05-17 16:22:24 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* INTERNAL:
|
2009-01-04 19:15:32 +03:00
|
|
|
* Gets an entity in the identity map by its identifier hash.
|
2008-05-17 16:22:24 +04:00
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2008-07-21 00:13:24 +04:00
|
|
|
* @param string $idHash
|
|
|
|
* @param string $rootClassName
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2009-02-06 20:16:39 +03:00
|
|
|
* @return object
|
2008-05-17 16:22:24 +04:00
|
|
|
*/
|
2008-05-01 13:41:47 +04:00
|
|
|
public function getByIdHash($idHash, $rootClassName)
|
2008-03-05 14:24:33 +03:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->identityMap[$rootClassName][$idHash];
|
2008-05-01 13:41:47 +04:00
|
|
|
}
|
2008-12-18 17:08:11 +03:00
|
|
|
|
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* INTERNAL:
|
2008-12-18 17:08:11 +03:00
|
|
|
* Tries to get an entity by its identifier hash. If no entity is found for
|
|
|
|
* the given hash, FALSE is returned.
|
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2015-03-18 00:11:43 +03:00
|
|
|
* @param mixed $idHash (must be possible to cast it to string)
|
2009-01-04 19:15:32 +03:00
|
|
|
* @param string $rootClassName
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @return object|bool The found entity or FALSE.
|
2008-12-18 17:08:11 +03:00
|
|
|
*/
|
2008-05-01 13:41:47 +04:00
|
|
|
public function tryGetByIdHash($idHash, $rootClassName)
|
|
|
|
{
|
2015-03-18 00:11:43 +03:00
|
|
|
$stringIdHash = (string) $idHash;
|
2015-03-17 19:59:47 +03:00
|
|
|
|
2015-03-18 00:11:43 +03:00
|
|
|
if (isset($this->identityMap[$rootClassName][$stringIdHash])) {
|
|
|
|
return $this->identityMap[$rootClassName][$stringIdHash];
|
2011-11-06 01:21:35 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 01:21:35 +04:00
|
|
|
return false;
|
2008-05-01 13:41:47 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-05-01 13:41:47 +04:00
|
|
|
/**
|
2009-05-21 23:18:14 +04:00
|
|
|
* Checks whether an entity is registered in the identity map of this UnitOfWork.
|
2008-05-01 13:41:47 +04:00
|
|
|
*
|
2008-12-18 17:08:11 +03:00
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2008-05-01 13:41:47 +04:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2008-12-18 17:08:11 +03:00
|
|
|
public function isInIdentityMap($entity)
|
2008-05-01 13:41:47 +04:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if ( ! isset($this->entityIdentifiers[$oid])) {
|
2009-01-03 22:50:13 +03:00
|
|
|
return false;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
$classMetadata = $this->em->getClassMetadata(get_class($entity));
|
2011-11-06 01:21:35 +04:00
|
|
|
$idHash = implode(' ', $this->entityIdentifiers[$oid]);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
if ($idHash === '') {
|
2008-05-01 13:41:47 +04:00
|
|
|
return false;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]);
|
2008-04-13 00:11:11 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-05-17 16:22:24 +04:00
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* INTERNAL:
|
2008-05-17 16:22:24 +04:00
|
|
|
* Checks whether an identifier hash exists in the identity map.
|
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2008-05-17 16:22:24 +04:00
|
|
|
* @param string $idHash
|
|
|
|
* @param string $rootClassName
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2008-05-17 16:22:24 +04:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2008-05-01 13:41:47 +04:00
|
|
|
public function containsIdHash($idHash, $rootClassName)
|
2008-04-13 00:11:11 +04:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return isset($this->identityMap[$rootClassName][$idHash]);
|
2008-03-05 14:24:33 +03:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
|
|
|
/**
|
2009-07-25 20:33:29 +04:00
|
|
|
* Persists an entity as part of the current unit of work.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
2009-07-25 20:33:29 +04:00
|
|
|
* @param object $entity The entity to persist.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2008-07-21 00:13:24 +04:00
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function persist($entity)
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
|
|
|
$visited = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->doPersist($entity, $visited);
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-07-04 20:55:49 +04:00
|
|
|
* Persists an entity as part of the current unit of work.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2010-07-04 20:55:49 +04:00
|
|
|
* This method is internally called during persist() cascades as it tracks
|
|
|
|
* the already visited entities to prevent infinite recursions.
|
2008-07-21 00:13:24 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity The entity to persist.
|
|
|
|
* @param array $visited The already visited entities.
|
|
|
|
*
|
|
|
|
* @return void
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException
|
|
|
|
* @throws UnexpectedValueException
|
2008-07-21 00:13:24 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function doPersist($entity, array &$visited)
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
if (isset($visited[$oid])) {
|
2008-07-21 00:13:24 +04:00
|
|
|
return; // Prevent infinite recursion
|
|
|
|
}
|
|
|
|
|
2009-05-11 14:43:27 +04:00
|
|
|
$visited[$oid] = $entity; // Mark visited
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2010-07-08 02:20:54 +04:00
|
|
|
|
|
|
|
// We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
|
|
|
|
// If we would detect DETACHED here we would throw an exception anyway with the same
|
|
|
|
// consequences (not recoverable/programming error), so just assuming NEW here
|
|
|
|
// lets us avoid some database lookups for entities with natural identifiers.
|
|
|
|
$entityState = $this->getEntityState($entity, self::STATE_NEW);
|
2010-07-04 20:55:49 +04:00
|
|
|
|
2009-08-27 02:03:39 +04:00
|
|
|
switch ($entityState) {
|
2008-12-18 17:08:11 +03:00
|
|
|
case self::STATE_MANAGED:
|
2009-05-11 14:43:27 +04:00
|
|
|
// Nothing to do, except if policy is "deferred explicit"
|
2009-04-25 01:08:59 +04:00
|
|
|
if ($class->isChangeTrackingDeferredExplicit()) {
|
2009-01-29 20:00:44 +03:00
|
|
|
$this->scheduleForDirtyCheck($entity);
|
|
|
|
}
|
2008-07-10 21:17:58 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
case self::STATE_NEW:
|
2010-07-04 20:55:49 +04:00
|
|
|
$this->persistNew($class, $entity);
|
2008-07-10 21:17:58 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-25 20:33:29 +04:00
|
|
|
case self::STATE_REMOVED:
|
2009-05-11 14:43:27 +04:00
|
|
|
// Entity becomes managed again
|
2010-07-08 02:20:54 +04:00
|
|
|
unset($this->entityDeletions[$oid]);
|
2015-03-18 00:29:10 +03:00
|
|
|
$this->addToIdentityMap($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityStates[$oid] = self::STATE_MANAGED;
|
2008-07-10 21:17:58 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
case self::STATE_DETACHED:
|
|
|
|
// Can actually not happen right now since we assume STATE_NEW.
|
2011-10-22 14:57:55 +04:00
|
|
|
throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted");
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
default:
|
2011-10-22 14:49:33 +04:00
|
|
|
throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
|
2008-07-10 21:17:58 +04:00
|
|
|
}
|
2010-07-04 20:55:49 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->cascadePersist($entity, $visited);
|
2008-07-10 21:17:58 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes an entity as part of the current unit of work.
|
|
|
|
*
|
2009-07-25 20:33:29 +04:00
|
|
|
* @param object $entity The entity to remove.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2008-07-21 00:13:24 +04:00
|
|
|
*/
|
2009-07-19 20:54:53 +04:00
|
|
|
public function remove($entity)
|
2008-07-10 21:17:58 +04:00
|
|
|
{
|
2009-01-09 19:25:06 +03:00
|
|
|
$visited = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->doRemove($entity, $visited);
|
2008-07-10 21:17:58 +04:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2008-09-07 17:48:40 +04:00
|
|
|
/**
|
2009-01-29 20:00:44 +03:00
|
|
|
* Deletes an entity as part of the current unit of work.
|
2009-07-03 21:36:41 +04:00
|
|
|
*
|
2009-01-29 20:00:44 +03:00
|
|
|
* This method is internally called during delete() cascades as it tracks
|
|
|
|
* the already visited entities to prevent infinite recursions.
|
2008-09-07 17:48:40 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity The entity to delete.
|
|
|
|
* @param array $visited The map of the already visited entities.
|
|
|
|
*
|
|
|
|
* @return void
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException If the instance is a detached entity.
|
|
|
|
* @throws UnexpectedValueException
|
2008-09-07 17:48:40 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function doRemove($entity, array &$visited)
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
if (isset($visited[$oid])) {
|
2008-07-21 00:13:24 +04:00
|
|
|
return; // Prevent infinite recursion
|
|
|
|
}
|
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
$visited[$oid] = $entity; // mark visited
|
2011-11-15 02:05:33 +04:00
|
|
|
|
|
|
|
// Cascade first, because scheduleForDelete() removes the entity from the identity map, which
|
2011-06-05 11:59:16 +04:00
|
|
|
// can cause problems when a lazy proxy has to be initialized for the cascade operation.
|
|
|
|
$this->cascadeRemove($entity, $visited);
|
2008-07-21 00:13:24 +04:00
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2009-08-27 02:03:39 +04:00
|
|
|
$entityState = $this->getEntityState($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-08-27 02:03:39 +04:00
|
|
|
switch ($entityState) {
|
2008-12-18 17:08:11 +03:00
|
|
|
case self::STATE_NEW:
|
2009-07-25 20:33:29 +04:00
|
|
|
case self::STATE_REMOVED:
|
2009-01-29 20:00:44 +03:00
|
|
|
// nothing to do
|
2008-07-21 00:13:24 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
case self::STATE_MANAGED:
|
2012-11-09 22:46:56 +04:00
|
|
|
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove);
|
2012-07-31 05:26:55 +04:00
|
|
|
|
2012-11-10 00:14:51 +04:00
|
|
|
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
2012-11-09 22:46:56 +04:00
|
|
|
$this->listenersInvoker->invoke($class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
|
2009-07-18 22:06:30 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-19 20:54:53 +04:00
|
|
|
$this->scheduleForDelete($entity);
|
2008-07-21 00:13:24 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
case self::STATE_DETACHED:
|
2011-10-22 14:57:55 +04:00
|
|
|
throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed");
|
2008-07-21 00:13:24 +04:00
|
|
|
default:
|
2011-10-22 14:49:33 +04:00
|
|
|
throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
2009-08-27 02:03:39 +04:00
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
|
2009-05-11 14:43:27 +04:00
|
|
|
/**
|
|
|
|
* Merges the state of the given detached entity into this UnitOfWork.
|
|
|
|
*
|
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @return object The managed copy of the entity.
|
|
|
|
*
|
2010-05-11 01:51:56 +04:00
|
|
|
* @throws OptimisticLockException If the entity uses optimistic locking through a version
|
|
|
|
* attribute and the version check against the managed copy fails.
|
2010-05-13 15:19:59 +04:00
|
|
|
*
|
|
|
|
* @todo Require active transaction!? OptimisticLockException may result in undefined state!?
|
2009-05-11 14:43:27 +04:00
|
|
|
*/
|
|
|
|
public function merge($entity)
|
|
|
|
{
|
|
|
|
$visited = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->doMerge($entity, $visited);
|
2009-05-11 14:43:27 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes a merge operation on an entity.
|
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity
|
|
|
|
* @param array $visited
|
|
|
|
* @param object|null $prevManagedCopy
|
|
|
|
* @param array|null $assoc
|
|
|
|
*
|
|
|
|
* @return object The managed copy of the entity.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2009-07-25 20:33:29 +04:00
|
|
|
* @throws OptimisticLockException If the entity uses optimistic locking through a version
|
|
|
|
* attribute and the version check against the managed copy fails.
|
2012-09-06 20:10:30 +04:00
|
|
|
* @throws ORMInvalidArgumentException If the entity instance is NEW.
|
|
|
|
* @throws EntityNotFoundException
|
2009-05-11 14:43:27 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null)
|
2009-05-11 14:43:27 +04:00
|
|
|
{
|
2010-07-29 16:08:36 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-29 16:08:36 +04:00
|
|
|
if (isset($visited[$oid])) {
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
$managedCopy = $visited[$oid];
|
2010-05-11 01:51:56 +04:00
|
|
|
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
if ($prevManagedCopy !== null) {
|
2014-11-11 14:27:57 +03:00
|
|
|
$this->updateAssociationWithMergedEntity($entity, $assoc, $prevManagedCopy, $managedCopy);
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return $managedCopy;
|
|
|
|
}
|
2010-07-30 00:27:00 +04:00
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2010-07-29 16:08:36 +04:00
|
|
|
|
|
|
|
// First we assume DETACHED, although it can still be NEW but we can avoid
|
2010-07-30 00:27:00 +04:00
|
|
|
// an extra db-roundtrip this way. If it is not MANAGED but has an identity,
|
|
|
|
// we need to fetch it from the db anyway in order to merge.
|
2010-07-29 16:08:36 +04:00
|
|
|
// MANAGED entities are ignored by the merge operation.
|
2011-11-06 08:03:34 +04:00
|
|
|
$managedCopy = $entity;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) {
|
2009-07-25 20:33:29 +04:00
|
|
|
// Try to look the entity up in the identity map.
|
2010-07-29 16:08:36 +04:00
|
|
|
$id = $class->getIdentifierValues($entity);
|
|
|
|
|
|
|
|
// If there is no ID, it is actually NEW.
|
|
|
|
if ( ! $id) {
|
2011-11-19 15:41:06 +04:00
|
|
|
$managedCopy = $this->newInstance($class);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-29 16:08:36 +04:00
|
|
|
$this->persistNew($class, $managedCopy);
|
2009-07-25 20:33:29 +04:00
|
|
|
} else {
|
2013-01-20 23:11:08 +04:00
|
|
|
$flatId = ($class->containsForeignIdentifier)
|
2014-08-07 17:56:27 +04:00
|
|
|
? $this->identifierFlattener->flattenIdentifier($class, $id)
|
2013-01-20 23:11:08 +04:00
|
|
|
: $id;
|
2011-12-15 23:49:25 +04:00
|
|
|
|
|
|
|
$managedCopy = $this->tryGetById($flatId, $class->rootEntityName);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-29 16:08:36 +04:00
|
|
|
if ($managedCopy) {
|
|
|
|
// We have the entity in-memory already, just make sure its not removed.
|
|
|
|
if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) {
|
2011-10-22 14:57:55 +04:00
|
|
|
throw ORMInvalidArgumentException::entityIsRemoved($managedCopy, "merge");
|
2010-07-29 16:08:36 +04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// We need to fetch the managed copy in order to merge.
|
2011-12-15 23:49:25 +04:00
|
|
|
$managedCopy = $this->em->find($class->name, $flatId);
|
2010-07-29 16:08:36 +04:00
|
|
|
}
|
2010-04-10 02:00:36 +04:00
|
|
|
|
2010-07-29 16:08:36 +04:00
|
|
|
if ($managedCopy === null) {
|
|
|
|
// If the identifier is ASSIGNED, it is NEW, otherwise an error
|
|
|
|
// since the managed entity was not found.
|
2011-11-06 08:03:34 +04:00
|
|
|
if ( ! $class->isIdentifierNatural()) {
|
2015-01-13 04:51:47 +03:00
|
|
|
throw EntityNotFoundException::fromClassNameAndIdentifier(
|
|
|
|
$class->getName(),
|
|
|
|
$this->identifierFlattener->flattenIdentifier($class, $id)
|
|
|
|
);
|
2010-07-29 16:08:36 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-19 15:41:06 +04:00
|
|
|
$managedCopy = $this->newInstance($class);
|
2011-11-06 08:03:34 +04:00
|
|
|
$class->setIdentifierValues($managedCopy, $id);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$this->persistNew($class, $managedCopy);
|
2010-07-29 16:08:36 +04:00
|
|
|
}
|
2009-05-11 14:43:27 +04:00
|
|
|
}
|
2010-04-10 02:00:36 +04:00
|
|
|
|
2009-07-25 20:33:29 +04:00
|
|
|
if ($class->isVersioned) {
|
2013-01-06 18:11:30 +04:00
|
|
|
$reflField = $class->reflFields[$class->versionField];
|
2012-10-20 03:27:53 +04:00
|
|
|
$managedCopyVersion = $reflField->getValue($managedCopy);
|
2013-01-06 18:11:30 +04:00
|
|
|
$entityVersion = $reflField->getValue($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-04-06 18:11:52 +04:00
|
|
|
// Throw exception if versions don't match.
|
2009-08-31 20:21:29 +04:00
|
|
|
if ($managedCopyVersion != $entityVersion) {
|
2013-03-11 04:08:58 +04:00
|
|
|
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
|
2009-07-25 20:33:29 +04:00
|
|
|
}
|
|
|
|
}
|
2010-04-10 02:00:36 +04:00
|
|
|
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
$visited[$oid] = $managedCopy; // mark visited
|
|
|
|
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
if (!($entity instanceof Proxy && ! $entity->__isInitialized())) {
|
2015-01-20 17:48:58 +03:00
|
|
|
if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized()) {
|
|
|
|
$managedCopy->__load();
|
|
|
|
}
|
|
|
|
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
|
2009-07-25 20:33:29 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-30 19:18:25 +04:00
|
|
|
if ($class->isChangeTrackingDeferredExplicit()) {
|
2010-07-29 16:08:36 +04:00
|
|
|
$this->scheduleForDirtyCheck($entity);
|
2009-05-11 14:43:27 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($prevManagedCopy !== null) {
|
2014-11-11 14:27:57 +03:00
|
|
|
$this->updateAssociationWithMergedEntity($entity, $assoc, $prevManagedCopy, $managedCopy);
|
2009-05-11 14:43:27 +04:00
|
|
|
}
|
|
|
|
|
2010-07-30 00:27:00 +04:00
|
|
|
// Mark the managed copy visited as well
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
$visited[spl_object_hash($managedCopy)] = $managedCopy;
|
2010-07-30 00:27:00 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->cascadeMerge($entity, $managedCopy, $visited);
|
2009-05-11 14:43:27 +04:00
|
|
|
|
|
|
|
return $managedCopy;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
/**
|
2014-11-11 14:27:57 +03:00
|
|
|
* Sets/adds associated managed copies into the previous entity's association field
|
|
|
|
*
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
* @param object $entity
|
2014-11-11 14:27:57 +03:00
|
|
|
* @param array $association
|
|
|
|
* @param object $previousManagedCopy
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
* @param object $managedCopy
|
2014-11-11 14:27:57 +03:00
|
|
|
*
|
|
|
|
* @return void
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
*/
|
2014-11-11 14:27:57 +03:00
|
|
|
private function updateAssociationWithMergedEntity($entity, array $association, $previousManagedCopy, $managedCopy)
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
{
|
2014-11-11 14:27:57 +03:00
|
|
|
$assocField = $association['fieldName'];
|
|
|
|
$prevClass = $this->em->getClassMetadata(get_class($previousManagedCopy));
|
|
|
|
|
|
|
|
if ($association['type'] & ClassMetadata::TO_ONE) {
|
|
|
|
$prevClass->reflFields[$assocField]->setValue($previousManagedCopy, $managedCopy);
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-11 14:27:57 +03:00
|
|
|
$value = $prevClass->reflFields[$assocField]->getValue($previousManagedCopy);
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
$value[] = $managedCopy;
|
|
|
|
|
2014-11-11 14:27:57 +03:00
|
|
|
if ($association['type'] == ClassMetadata::ONE_TO_MANY) {
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2014-11-11 14:27:57 +03:00
|
|
|
|
|
|
|
$class->reflFields[$association['mappedBy']]->setValue($managedCopy, $previousManagedCopy);
|
Fix merging of entities with associations to identical entities.
Without this patch, when an entity that refers multiple times to the same
associated entity gets merged, the second references becomes null.
The main issue is that even though doMerge returns a managed copy, that value
is not used while cascading the merge. These identicial entities are already
detected through the visitor map, but they are ignored. There should be some
refactoring so cascadeMerge calls a function that checks if the parent must be
updated, based on the return value of its call to doMerge. However, this patch
tries to impact the code as little as possible, and only introduces a new
function to avoid duplicate code.
The secondary issue arises when using inverted associations. In that case, it
is possible that an entity to be merged is already merged, so the the visitor
map is looked up by the hash of a managed copy instead of the original entity.
This means that in this case the visitor map entries should also be set to the
entity, instead of being set to 'true'.
2014-11-05 23:13:13 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-25 20:33:29 +04:00
|
|
|
/**
|
|
|
|
* Detaches an entity from the persistence management. It's persistence will
|
|
|
|
* no longer be managed by Doctrine.
|
|
|
|
*
|
|
|
|
* @param object $entity The entity to detach.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2009-07-25 20:33:29 +04:00
|
|
|
*/
|
|
|
|
public function detach($entity)
|
|
|
|
{
|
|
|
|
$visited = array();
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->doDetach($entity, $visited);
|
2009-07-25 20:33:29 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-25 20:33:29 +04:00
|
|
|
/**
|
|
|
|
* Executes a detach operation on the given entity.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity
|
|
|
|
* @param array $visited
|
|
|
|
* @param boolean $noCascade if true, don't cascade detach operation.
|
|
|
|
*
|
|
|
|
* @return void
|
2009-07-25 20:33:29 +04:00
|
|
|
*/
|
2011-08-14 18:12:12 +04:00
|
|
|
private function doDetach($entity, array &$visited, $noCascade = false)
|
2009-07-25 20:33:29 +04:00
|
|
|
{
|
|
|
|
$oid = spl_object_hash($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2009-07-25 20:33:29 +04:00
|
|
|
if (isset($visited[$oid])) {
|
|
|
|
return; // Prevent infinite recursion
|
|
|
|
}
|
|
|
|
|
|
|
|
$visited[$oid] = $entity; // mark visited
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-25 20:33:29 +04:00
|
|
|
switch ($this->getEntityState($entity, self::STATE_DETACHED)) {
|
|
|
|
case self::STATE_MANAGED:
|
2010-12-10 23:55:48 +03:00
|
|
|
if ($this->isInIdentityMap($entity)) {
|
|
|
|
$this->removeFromIdentityMap($entity);
|
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
unset(
|
2011-12-01 19:00:26 +04:00
|
|
|
$this->entityInsertions[$oid],
|
2011-11-29 19:36:32 +04:00
|
|
|
$this->entityUpdates[$oid],
|
2011-12-01 19:00:26 +04:00
|
|
|
$this->entityDeletions[$oid],
|
2011-11-29 19:36:32 +04:00
|
|
|
$this->entityIdentifiers[$oid],
|
2011-12-01 19:00:26 +04:00
|
|
|
$this->entityStates[$oid],
|
2011-11-29 19:36:32 +04:00
|
|
|
$this->originalEntityData[$oid]
|
|
|
|
);
|
2009-07-25 20:33:29 +04:00
|
|
|
break;
|
|
|
|
case self::STATE_NEW:
|
|
|
|
case self::STATE_DETACHED:
|
|
|
|
return;
|
|
|
|
}
|
2011-08-14 18:12:12 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
if ( ! $noCascade) {
|
2011-08-14 18:12:12 +04:00
|
|
|
$this->cascadeDetach($entity, $visited);
|
|
|
|
}
|
2009-07-25 20:33:29 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-21 19:53:58 +04:00
|
|
|
/**
|
2009-07-21 19:57:11 +04:00
|
|
|
* Refreshes the state of the given entity from the database, overwriting
|
|
|
|
* any local, unpersisted changes.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-07-21 19:57:11 +04:00
|
|
|
* @param object $entity The entity to refresh.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @return void
|
|
|
|
*
|
2009-07-25 20:33:29 +04:00
|
|
|
* @throws InvalidArgumentException If the entity is not MANAGED.
|
2009-07-21 19:53:58 +04:00
|
|
|
*/
|
|
|
|
public function refresh($entity)
|
|
|
|
{
|
|
|
|
$visited = array();
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->doRefresh($entity, $visited);
|
2009-07-21 19:53:58 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-21 19:53:58 +04:00
|
|
|
/**
|
|
|
|
* Executes a refresh operation on an entity.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity The entity to refresh.
|
|
|
|
* @param array $visited The already visited entities during cascades.
|
|
|
|
*
|
|
|
|
* @return void
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException If the entity is not MANAGED.
|
2009-07-21 19:53:58 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function doRefresh($entity, array &$visited)
|
2009-07-21 19:53:58 +04:00
|
|
|
{
|
|
|
|
$oid = spl_object_hash($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2009-07-21 19:53:58 +04:00
|
|
|
if (isset($visited[$oid])) {
|
|
|
|
return; // Prevent infinite recursion
|
|
|
|
}
|
|
|
|
|
|
|
|
$visited[$oid] = $entity; // mark visited
|
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
|
2011-10-22 14:49:33 +04:00
|
|
|
throw ORMInvalidArgumentException::entityNotManaged($entity);
|
2009-07-21 19:53:58 +04:00
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
$this->getEntityPersister($class->name)->refresh(
|
|
|
|
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
|
|
|
|
$entity
|
|
|
|
);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->cascadeRefresh($entity, $visited);
|
2009-07-21 19:53:58 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-21 19:53:58 +04:00
|
|
|
/**
|
|
|
|
* Cascades a refresh operation to associated entities.
|
|
|
|
*
|
|
|
|
* @param object $entity
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array $visited
|
|
|
|
*
|
|
|
|
* @return void
|
2009-07-21 19:53:58 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function cascadeRefresh($entity, array &$visited)
|
2009-07-21 19:53:58 +04:00
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-12-01 19:00:26 +04:00
|
|
|
|
|
|
|
$associationMappings = array_filter(
|
|
|
|
$class->associationMappings,
|
|
|
|
function ($assoc) { return $assoc['isCascadeRefresh']; }
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($associationMappings as $assoc) {
|
2010-08-09 15:13:21 +04:00
|
|
|
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
switch (true) {
|
|
|
|
case ($relatedEntities instanceof PersistentCollection):
|
2009-11-13 22:34:10 +03:00
|
|
|
// Unwrap so that foreach() does not initialize
|
|
|
|
$relatedEntities = $relatedEntities->unwrap();
|
2011-11-29 19:36:32 +04:00
|
|
|
// break; is commented intentionally!
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
case ($relatedEntities instanceof Collection):
|
|
|
|
case (is_array($relatedEntities)):
|
|
|
|
foreach ($relatedEntities as $relatedEntity) {
|
|
|
|
$this->doRefresh($relatedEntity, $visited);
|
|
|
|
}
|
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
case ($relatedEntities !== null):
|
|
|
|
$this->doRefresh($relatedEntities, $visited);
|
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
default:
|
|
|
|
// Do nothing
|
2009-07-21 19:53:58 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-25 20:33:29 +04:00
|
|
|
/**
|
|
|
|
* Cascades a detach operation to associated entities.
|
|
|
|
*
|
|
|
|
* @param object $entity
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array $visited
|
|
|
|
*
|
|
|
|
* @return void
|
2009-07-25 20:33:29 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function cascadeDetach($entity, array &$visited)
|
2009-07-25 20:33:29 +04:00
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-12-01 19:00:26 +04:00
|
|
|
|
|
|
|
$associationMappings = array_filter(
|
|
|
|
$class->associationMappings,
|
|
|
|
function ($assoc) { return $assoc['isCascadeDetach']; }
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($associationMappings as $assoc) {
|
2010-08-09 15:13:21 +04:00
|
|
|
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
switch (true) {
|
|
|
|
case ($relatedEntities instanceof PersistentCollection):
|
2009-11-07 14:54:47 +03:00
|
|
|
// Unwrap so that foreach() does not initialize
|
|
|
|
$relatedEntities = $relatedEntities->unwrap();
|
2011-11-29 19:36:32 +04:00
|
|
|
// break; is commented intentionally!
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
case ($relatedEntities instanceof Collection):
|
|
|
|
case (is_array($relatedEntities)):
|
|
|
|
foreach ($relatedEntities as $relatedEntity) {
|
|
|
|
$this->doDetach($relatedEntity, $visited);
|
|
|
|
}
|
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
case ($relatedEntities !== null):
|
|
|
|
$this->doDetach($relatedEntities, $visited);
|
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
default:
|
|
|
|
// Do nothing
|
2009-07-25 20:33:29 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-05-11 14:43:27 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Cascades a merge operation to associated entities.
|
2009-07-03 21:36:41 +04:00
|
|
|
*
|
2009-05-28 15:13:12 +04:00
|
|
|
* @param object $entity
|
|
|
|
* @param object $managedCopy
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array $visited
|
|
|
|
*
|
|
|
|
* @return void
|
2009-05-11 14:43:27 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function cascadeMerge($entity, $managedCopy, array &$visited)
|
2009-05-11 14:43:27 +04:00
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-12-01 19:00:26 +04:00
|
|
|
|
|
|
|
$associationMappings = array_filter(
|
|
|
|
$class->associationMappings,
|
|
|
|
function ($assoc) { return $assoc['isCascadeMerge']; }
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($associationMappings as $assoc) {
|
2010-08-09 15:13:21 +04:00
|
|
|
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2009-07-29 16:00:08 +04:00
|
|
|
if ($relatedEntities instanceof Collection) {
|
2011-07-27 00:15:27 +04:00
|
|
|
if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-11-07 14:54:47 +03:00
|
|
|
if ($relatedEntities instanceof PersistentCollection) {
|
|
|
|
// Unwrap so that foreach() does not initialize
|
|
|
|
$relatedEntities = $relatedEntities->unwrap();
|
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2009-05-11 14:43:27 +04:00
|
|
|
foreach ($relatedEntities as $relatedEntity) {
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->doMerge($relatedEntity, $visited, $managedCopy, $assoc);
|
2009-05-11 14:43:27 +04:00
|
|
|
}
|
2009-07-02 15:48:44 +04:00
|
|
|
} else if ($relatedEntities !== null) {
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->doMerge($relatedEntities, $visited, $managedCopy, $assoc);
|
2009-05-11 14:43:27 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-21 00:13:24 +04:00
|
|
|
/**
|
|
|
|
* Cascades the save operation to associated entities.
|
|
|
|
*
|
2009-01-29 20:00:44 +03:00
|
|
|
* @param object $entity
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array $visited
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2008-07-21 00:13:24 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function cascadePersist($entity, array &$visited)
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-12-01 19:00:26 +04:00
|
|
|
$associationMappings = array_filter(
|
|
|
|
$class->associationMappings,
|
|
|
|
function ($assoc) { return $assoc['isCascadePersist']; }
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($associationMappings as $assoc) {
|
2010-08-09 15:13:21 +04:00
|
|
|
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
switch (true) {
|
|
|
|
case ($relatedEntities instanceof PersistentCollection):
|
2009-11-07 14:54:47 +03:00
|
|
|
// Unwrap so that foreach() does not initialize
|
|
|
|
$relatedEntities = $relatedEntities->unwrap();
|
2011-11-29 19:36:32 +04:00
|
|
|
// break; is commented intentionally!
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
case ($relatedEntities instanceof Collection):
|
|
|
|
case (is_array($relatedEntities)):
|
2015-01-18 02:53:57 +03:00
|
|
|
if (($assoc['type'] & ClassMetadata::TO_MANY) <= 0) {
|
|
|
|
throw ORMInvalidArgumentException::invalidAssociation(
|
|
|
|
$this->em->getClassMetadata($assoc['targetEntity']),
|
|
|
|
$assoc,
|
|
|
|
$relatedEntities
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
foreach ($relatedEntities as $relatedEntity) {
|
|
|
|
$this->doPersist($relatedEntity, $visited);
|
|
|
|
}
|
2015-01-18 02:53:57 +03:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
case ($relatedEntities !== null):
|
2015-01-18 03:05:36 +03:00
|
|
|
if (! $relatedEntities instanceof $assoc['targetEntity']) {
|
2015-01-18 02:53:57 +03:00
|
|
|
throw ORMInvalidArgumentException::invalidAssociation(
|
|
|
|
$this->em->getClassMetadata($assoc['targetEntity']),
|
|
|
|
$assoc,
|
|
|
|
$relatedEntities
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
$this->doPersist($relatedEntities, $visited);
|
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
default:
|
|
|
|
// Do nothing
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-04 19:15:32 +03:00
|
|
|
/**
|
|
|
|
* Cascades the delete operation to associated entities.
|
|
|
|
*
|
2009-01-29 20:00:44 +03:00
|
|
|
* @param object $entity
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array $visited
|
|
|
|
*
|
|
|
|
* @return void
|
2009-01-04 19:15:32 +03:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private function cascadeRemove($entity, array &$visited)
|
2008-07-10 21:17:58 +04:00
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-12-01 19:00:26 +04:00
|
|
|
$associationMappings = array_filter(
|
|
|
|
$class->associationMappings,
|
|
|
|
function ($assoc) { return $assoc['isCascadeRemove']; }
|
|
|
|
);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-11-04 15:42:23 +04:00
|
|
|
$entitiesToCascade = array();
|
|
|
|
|
2011-12-01 19:00:26 +04:00
|
|
|
foreach ($associationMappings as $assoc) {
|
2011-06-05 12:02:57 +04:00
|
|
|
if ($entity instanceof Proxy && !$entity->__isInitialized__) {
|
2011-06-05 11:59:16 +04:00
|
|
|
$entity->__load();
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-08-09 15:13:21 +04:00
|
|
|
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
switch (true) {
|
|
|
|
case ($relatedEntities instanceof Collection):
|
|
|
|
case (is_array($relatedEntities)):
|
|
|
|
// If its a PersistentCollection initialization is intended! No unwrap!
|
|
|
|
foreach ($relatedEntities as $relatedEntity) {
|
2013-11-04 15:42:23 +04:00
|
|
|
$entitiesToCascade[] = $relatedEntity;
|
2011-11-29 19:36:32 +04:00
|
|
|
}
|
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
case ($relatedEntities !== null):
|
2013-11-04 15:42:23 +04:00
|
|
|
$entitiesToCascade[] = $relatedEntities;
|
2011-11-29 19:36:32 +04:00
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
default:
|
|
|
|
// Do nothing
|
2009-01-04 19:15:32 +03:00
|
|
|
}
|
|
|
|
}
|
2013-11-04 15:42:23 +04:00
|
|
|
|
|
|
|
foreach ($entitiesToCascade as $relatedEntity) {
|
|
|
|
$this->doRemove($relatedEntity, $visited);
|
|
|
|
}
|
2008-07-10 21:17:58 +04:00
|
|
|
}
|
2009-01-04 19:15:32 +03:00
|
|
|
|
2010-04-09 00:50:06 +04:00
|
|
|
/**
|
|
|
|
* Acquire a lock on the given entity.
|
|
|
|
*
|
|
|
|
* @param object $entity
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param int $lockMode
|
|
|
|
* @param int $lockVersion
|
|
|
|
*
|
|
|
|
* @return void
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException
|
|
|
|
* @throws TransactionRequiredException
|
|
|
|
* @throws OptimisticLockException
|
2010-04-09 00:50:06 +04:00
|
|
|
*/
|
|
|
|
public function lock($entity, $lockMode, $lockVersion = null)
|
|
|
|
{
|
2013-05-01 21:39:21 +04:00
|
|
|
if ($entity === null) {
|
|
|
|
throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
|
|
|
|
}
|
|
|
|
|
2011-08-28 17:57:33 +04:00
|
|
|
if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) {
|
2011-10-22 14:49:33 +04:00
|
|
|
throw ORMInvalidArgumentException::entityNotManaged($entity);
|
2010-04-11 18:43:33 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2010-04-09 00:50:06 +04:00
|
|
|
|
2014-02-01 21:05:47 +04:00
|
|
|
switch (true) {
|
2014-05-27 11:37:40 +04:00
|
|
|
case LockMode::OPTIMISTIC === $lockMode:
|
2011-11-29 19:36:32 +04:00
|
|
|
if ( ! $class->isVersioned) {
|
2012-03-31 01:15:44 +04:00
|
|
|
throw OptimisticLockException::notVersioned($class->name);
|
2011-11-29 19:36:32 +04:00
|
|
|
}
|
2010-04-09 00:50:06 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
if ($lockVersion === null) {
|
|
|
|
return;
|
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2015-01-10 16:59:42 +03:00
|
|
|
if ($entity instanceof Proxy && !$entity->__isInitialized__) {
|
|
|
|
$entity->__load();
|
|
|
|
}
|
|
|
|
|
2010-04-09 00:50:06 +04:00
|
|
|
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2010-04-09 00:50:06 +04:00
|
|
|
if ($entityVersion != $lockVersion) {
|
2013-03-11 04:08:58 +04:00
|
|
|
throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
|
2010-04-09 00:50:06 +04:00
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2014-02-01 21:05:47 +04:00
|
|
|
case LockMode::NONE === $lockMode:
|
|
|
|
case LockMode::PESSIMISTIC_READ === $lockMode:
|
|
|
|
case LockMode::PESSIMISTIC_WRITE === $lockMode:
|
2011-11-29 19:36:32 +04:00
|
|
|
if (!$this->em->getConnection()->isTransactionActive()) {
|
|
|
|
throw TransactionRequiredException::transactionRequired();
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2010-04-09 00:50:06 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
$this->getEntityPersister($class->name)->lock(
|
|
|
|
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
|
|
|
|
$lockMode
|
|
|
|
);
|
|
|
|
break;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
default:
|
|
|
|
// Do nothing
|
2010-04-09 00:50:06 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-04 19:15:32 +03:00
|
|
|
/**
|
|
|
|
* Gets the CommitOrderCalculator used by the UnitOfWork to order commits.
|
|
|
|
*
|
2011-12-12 00:52:29 +04:00
|
|
|
* @return \Doctrine\ORM\Internal\CommitOrderCalculator
|
2009-01-04 19:15:32 +03:00
|
|
|
*/
|
2008-07-21 00:13:24 +04:00
|
|
|
public function getCommitOrderCalculator()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
if ($this->commitOrderCalculator === null) {
|
|
|
|
$this->commitOrderCalculator = new Internal\CommitOrderCalculator;
|
2009-12-21 14:06:27 +03:00
|
|
|
}
|
2011-12-04 11:14:47 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->commitOrderCalculator;
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
|
|
|
|
2009-01-04 19:15:32 +03:00
|
|
|
/**
|
2009-05-11 14:43:27 +04:00
|
|
|
* Clears the UnitOfWork.
|
2011-08-12 01:03:26 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param string|null $entityName if given, only entities of this type will get detached.
|
|
|
|
*
|
|
|
|
* @return void
|
2009-01-04 19:15:32 +03:00
|
|
|
*/
|
2011-08-12 01:03:26 +04:00
|
|
|
public function clear($entityName = null)
|
2008-07-21 00:13:24 +04:00
|
|
|
{
|
2011-08-12 01:03:26 +04:00
|
|
|
if ($entityName === null) {
|
|
|
|
$this->identityMap =
|
|
|
|
$this->entityIdentifiers =
|
|
|
|
$this->originalEntityData =
|
|
|
|
$this->entityChangeSets =
|
|
|
|
$this->entityStates =
|
2015-01-19 06:21:18 +03:00
|
|
|
$this->scheduledForSynchronization =
|
2011-08-12 01:03:26 +04:00
|
|
|
$this->entityInsertions =
|
|
|
|
$this->entityUpdates =
|
|
|
|
$this->entityDeletions =
|
|
|
|
$this->collectionDeletions =
|
|
|
|
$this->collectionUpdates =
|
|
|
|
$this->extraUpdates =
|
2012-02-20 12:36:35 +04:00
|
|
|
$this->readOnlyObjects =
|
2013-06-19 17:34:44 +04:00
|
|
|
$this->visitedCollections =
|
2011-08-12 01:03:26 +04:00
|
|
|
$this->orphanRemovals = array();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-08-12 01:03:26 +04:00
|
|
|
if ($this->commitOrderCalculator !== null) {
|
|
|
|
$this->commitOrderCalculator->clear();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$visited = array();
|
2014-06-27 01:15:57 +04:00
|
|
|
|
2011-08-12 01:03:26 +04:00
|
|
|
foreach ($this->identityMap as $className => $entities) {
|
2014-04-16 08:47:57 +04:00
|
|
|
if ($className !== $entityName) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-06-27 01:15:57 +04:00
|
|
|
|
2014-04-16 08:47:57 +04:00
|
|
|
foreach ($entities as $entity) {
|
|
|
|
$this->doDetach($entity, $visited, false);
|
2011-08-12 01:03:26 +04:00
|
|
|
}
|
|
|
|
}
|
2009-12-21 14:06:27 +03:00
|
|
|
}
|
2011-03-22 06:17:08 +03:00
|
|
|
|
|
|
|
if ($this->evm->hasListeners(Events::onClear)) {
|
2011-10-17 00:45:06 +04:00
|
|
|
$this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName));
|
2011-03-22 06:17:08 +03:00
|
|
|
}
|
2008-07-21 00:13:24 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-23 13:52:16 +04:00
|
|
|
/**
|
|
|
|
* INTERNAL:
|
|
|
|
* Schedules an orphaned entity for removal. The remove() operation will be
|
|
|
|
* invoked on that entity at the beginning of the next commit of this
|
|
|
|
* UnitOfWork.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2009-07-23 13:52:16 +04:00
|
|
|
* @param object $entity
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2009-07-23 13:52:16 +04:00
|
|
|
*/
|
|
|
|
public function scheduleOrphanRemoval($entity)
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->orphanRemovals[spl_object_hash($entity)] = $entity;
|
2009-07-23 13:52:16 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-07-23 13:52:16 +04:00
|
|
|
/**
|
|
|
|
* INTERNAL:
|
|
|
|
* Schedules a complete collection for removal when this UnitOfWork commits.
|
|
|
|
*
|
|
|
|
* @param PersistentCollection $coll
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2009-07-23 13:52:16 +04:00
|
|
|
*/
|
2009-02-02 14:55:50 +03:00
|
|
|
public function scheduleCollectionDeletion(PersistentCollection $coll)
|
2008-08-16 23:40:59 +04:00
|
|
|
{
|
2011-11-07 07:27:20 +04:00
|
|
|
$coid = spl_object_hash($coll);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-09-22 04:12:30 +04:00
|
|
|
// TODO: if $coll is already scheduled for recreation ... what to do?
|
2008-08-16 23:40:59 +04:00
|
|
|
// Just remove $coll from the scheduled recreations?
|
2011-11-07 07:27:20 +04:00
|
|
|
if (isset($this->collectionUpdates[$coid])) {
|
|
|
|
unset($this->collectionUpdates[$coid]);
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-07 07:27:20 +04:00
|
|
|
$this->collectionDeletions[$coid] = $coll;
|
2008-08-16 23:40:59 +04:00
|
|
|
}
|
2009-07-03 21:36:41 +04:00
|
|
|
|
2012-09-06 20:10:30 +04:00
|
|
|
/**
|
|
|
|
* @param PersistentCollection $coll
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
2009-02-02 14:55:50 +03:00
|
|
|
public function isCollectionScheduledForDeletion(PersistentCollection $coll)
|
2008-08-16 23:40:59 +04:00
|
|
|
{
|
2012-09-06 20:10:30 +04:00
|
|
|
return isset($this->collectionDeletions[spl_object_hash($coll)]);
|
2008-08-16 23:40:59 +04:00
|
|
|
}
|
2009-07-03 21:36:41 +04:00
|
|
|
|
2011-11-19 15:41:06 +04:00
|
|
|
/**
|
|
|
|
* @param ClassMetadata $class
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
|
|
|
* @return \Doctrine\Common\Persistence\ObjectManagerAware|object
|
2011-11-19 15:41:06 +04:00
|
|
|
*/
|
|
|
|
private function newInstance($class)
|
|
|
|
{
|
|
|
|
$entity = $class->newInstance();
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-19 15:41:06 +04:00
|
|
|
if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) {
|
|
|
|
$entity->injectObjectManager($this->em, $class);
|
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-19 15:41:06 +04:00
|
|
|
return $entity;
|
|
|
|
}
|
|
|
|
|
2008-07-10 21:17:58 +04:00
|
|
|
/**
|
2009-05-13 19:19:27 +04:00
|
|
|
* INTERNAL:
|
2010-08-08 15:10:53 +04:00
|
|
|
* Creates an entity. Used for reconstitution of persistent entities.
|
2008-07-10 21:17:58 +04:00
|
|
|
*
|
2015-03-15 18:53:34 +03:00
|
|
|
* Internal note: Highly performance-sensitive method.
|
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2009-12-16 00:06:32 +03:00
|
|
|
* @param string $className The name of the entity class.
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array $data The data for the entity.
|
|
|
|
* @param array $hints Any hints to account for during reconstitution/lookup of the entity.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2010-08-08 15:10:53 +04:00
|
|
|
* @return object The managed entity instance.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2009-10-28 13:31:47 +03:00
|
|
|
* @todo Rename: getOrCreateEntity
|
2008-07-10 21:17:58 +04:00
|
|
|
*/
|
2009-10-22 16:50:58 +04:00
|
|
|
public function createEntity($className, array $data, &$hints = array())
|
2008-12-18 17:08:11 +03:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
$class = $this->em->getClassMetadata($className);
|
2009-12-16 00:06:32 +03:00
|
|
|
//$isReadOnly = isset($hints[Query::HINT_READ_ONLY]);
|
2009-01-03 22:50:13 +03:00
|
|
|
|
2015-01-19 01:18:41 +03:00
|
|
|
$id = $this->identifierFlattener->flattenIdentifier($class, $data);
|
2014-04-16 08:20:18 +04:00
|
|
|
$idHash = implode(' ', $id);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
|
|
|
if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
|
2010-07-08 02:20:54 +04:00
|
|
|
$entity = $this->identityMap[$class->rootEntityName][$idHash];
|
2009-01-03 22:50:13 +03:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2012-10-20 03:27:53 +04:00
|
|
|
if (
|
|
|
|
isset($hints[Query::HINT_REFRESH])
|
|
|
|
&& isset($hints[Query::HINT_REFRESH_ENTITY])
|
|
|
|
&& ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
|
|
|
|
&& $unmanagedProxy instanceof Proxy
|
2014-02-01 04:57:51 +04:00
|
|
|
&& $this->isIdentifierEquals($unmanagedProxy, $entity)
|
2012-10-20 03:27:53 +04:00
|
|
|
) {
|
|
|
|
// DDC-1238 - we have a managed instance, but it isn't the provided one.
|
|
|
|
// Therefore we clear its identifier. Also, we must re-fetch metadata since the
|
|
|
|
// refreshed object may be anything
|
|
|
|
|
2014-02-01 04:57:51 +04:00
|
|
|
foreach ($class->identifier as $fieldName) {
|
|
|
|
$class->reflFields[$fieldName]->setValue($unmanagedProxy, null);
|
2012-10-20 03:27:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return $unmanagedProxy;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
|
|
|
$entity->__setInitialized(true);
|
2013-01-06 18:11:30 +04:00
|
|
|
|
2011-12-19 19:31:26 +04:00
|
|
|
$overrideLocalValues = true;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2010-05-15 23:32:34 +04:00
|
|
|
if ($entity instanceof NotifyPropertyChanged) {
|
|
|
|
$entity->addPropertyChangedListener($this);
|
|
|
|
}
|
2010-01-05 14:45:38 +03:00
|
|
|
} else {
|
|
|
|
$overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
|
2011-11-10 01:13:06 +04:00
|
|
|
|
2011-11-10 19:16:55 +04:00
|
|
|
// If only a specific entity is set to refresh, check that it's the one
|
2013-10-08 02:53:32 +04:00
|
|
|
if (isset($hints[Query::HINT_REFRESH_ENTITY])) {
|
2011-11-10 01:13:06 +04:00
|
|
|
$overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
|
|
|
|
}
|
2014-05-02 23:52:00 +04:00
|
|
|
}
|
2013-01-10 18:19:44 +04:00
|
|
|
|
2014-05-02 23:52:00 +04:00
|
|
|
if ($overrideLocalValues) {
|
2013-01-10 18:19:44 +04:00
|
|
|
// inject ObjectManager upon refresh.
|
2014-05-02 23:52:00 +04:00
|
|
|
if ($entity instanceof ObjectManagerAware) {
|
2013-01-10 18:19:44 +04:00
|
|
|
$entity->injectObjectManager($this->em, $class);
|
|
|
|
}
|
2011-06-25 16:38:44 +04:00
|
|
|
|
|
|
|
$this->originalEntityData[$oid] = $data;
|
|
|
|
}
|
2008-12-18 17:08:11 +03:00
|
|
|
} else {
|
2011-11-19 15:41:06 +04:00
|
|
|
$entity = $this->newInstance($class);
|
2012-07-07 19:47:29 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2012-07-07 19:47:29 +04:00
|
|
|
$this->entityIdentifiers[$oid] = $id;
|
|
|
|
$this->entityStates[$oid] = self::STATE_MANAGED;
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->originalEntityData[$oid] = $data;
|
2012-07-07 19:47:29 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->identityMap[$class->rootEntityName][$idHash] = $entity;
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2009-10-07 16:39:46 +04:00
|
|
|
if ($entity instanceof NotifyPropertyChanged) {
|
2009-07-18 15:41:37 +04:00
|
|
|
$entity->addPropertyChangedListener($this);
|
|
|
|
}
|
2011-12-19 19:31:26 +04:00
|
|
|
|
|
|
|
$overrideLocalValues = true;
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
if ( ! $overrideLocalValues) {
|
|
|
|
return $entity;
|
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
foreach ($data as $field => $value) {
|
|
|
|
if (isset($class->fieldMappings[$field])) {
|
|
|
|
$class->reflFields[$field]->setValue($entity, $value);
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
2011-11-29 19:36:32 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Loading the entity right here, if its in the eager loading map get rid of it there.
|
|
|
|
unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
|
2011-03-16 02:03:43 +03:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) {
|
|
|
|
unset($this->eagerLoadingEntities[$class->rootEntityName]);
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
// Properly initialize any unfetched associations, if partial objects are not allowed.
|
|
|
|
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
|
|
|
|
return $entity;
|
|
|
|
}
|
2011-12-01 19:00:26 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
foreach ($class->associationMappings as $field => $assoc) {
|
|
|
|
// Check if the association is not among the fetch-joined associations already.
|
|
|
|
if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
|
|
|
|
continue;
|
2011-10-26 21:04:49 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
|
|
|
|
|
|
|
switch (true) {
|
|
|
|
case ($assoc['type'] & ClassMetadata::TO_ONE):
|
|
|
|
if ( ! $assoc['isOwningSide']) {
|
2013-12-18 03:00:25 +04:00
|
|
|
|
|
|
|
// use the given entity association
|
|
|
|
if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
|
2014-02-01 21:05:47 +04:00
|
|
|
|
2013-12-18 03:00:25 +04:00
|
|
|
$this->originalEntityData[$oid][$field] = $data[$field];
|
|
|
|
|
|
|
|
$class->reflFields[$field]->setValue($entity, $data[$field]);
|
|
|
|
$targetClass->reflFields[$assoc['mappedBy']]->setValue($data[$field], $entity);
|
|
|
|
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
// Inverse side of x-to-one can never be lazy
|
|
|
|
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity));
|
|
|
|
|
|
|
|
continue 2;
|
2009-10-28 18:32:55 +03:00
|
|
|
}
|
2010-01-30 00:24:29 +03:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
// use the entity association
|
|
|
|
if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
|
|
|
|
$class->reflFields[$field]->setValue($entity, $data[$field]);
|
|
|
|
$this->originalEntityData[$oid][$field] = $data[$field];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
$associatedId = array();
|
2011-03-17 00:51:32 +03:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
// TODO: Is this even computed right in all cases of composite keys?
|
|
|
|
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
|
|
|
|
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-12-19 19:31:26 +04:00
|
|
|
if ($joinColumnValue !== null) {
|
|
|
|
if ($targetClass->containsForeignIdentifier) {
|
|
|
|
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
|
|
|
|
} else {
|
|
|
|
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
|
|
|
|
}
|
2015-02-16 04:02:56 +03:00
|
|
|
} elseif ($targetClass->containsForeignIdentifier
|
|
|
|
&& in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true)
|
|
|
|
) {
|
2015-01-19 01:18:41 +03:00
|
|
|
// the missing key is part of target's entity primary key
|
|
|
|
$associatedId = array();
|
|
|
|
break;
|
2009-10-28 18:32:55 +03:00
|
|
|
}
|
2009-10-22 16:50:58 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
if ( ! $associatedId) {
|
|
|
|
// Foreign key is NULL
|
|
|
|
$class->reflFields[$field]->setValue($entity, null);
|
|
|
|
$this->originalEntityData[$oid][$field] = null;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! isset($hints['fetchMode'][$class->name][$field])) {
|
|
|
|
$hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Foreign key is set
|
|
|
|
// Check identity map first
|
|
|
|
// FIXME: Can break easily with composite keys if join column values are in
|
|
|
|
// wrong order. The correct order is the one in ClassMetadata#identifier.
|
|
|
|
$relatedIdHash = implode(' ', $associatedId);
|
|
|
|
|
|
|
|
switch (true) {
|
|
|
|
case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])):
|
|
|
|
$newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
|
|
|
|
|
2012-01-19 08:09:23 +04:00
|
|
|
// If this is an uninitialized proxy, we are deferring eager loads,
|
2011-11-29 19:36:32 +04:00
|
|
|
// this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
|
2012-01-19 08:09:23 +04:00
|
|
|
// then we can append this entity for eager loading!
|
2011-11-29 19:36:32 +04:00
|
|
|
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER &&
|
2013-04-08 13:27:22 +04:00
|
|
|
isset($hints[self::HINT_DEFEREAGERLOAD]) &&
|
2011-11-29 19:36:32 +04:00
|
|
|
!$targetClass->isIdentifierComposite &&
|
|
|
|
$newValue instanceof Proxy &&
|
|
|
|
$newValue->__isInitialized__ === false) {
|
|
|
|
|
|
|
|
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ($targetClass->subClasses):
|
|
|
|
// If it might be a subtype, it can not be lazy. There isn't even
|
|
|
|
// a way to solve this with deferred eager loading, which means putting
|
|
|
|
// an entity with subclasses at a *-to-one location is really bad! (performance-wise)
|
|
|
|
$newValue = $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity, $associatedId);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
switch (true) {
|
|
|
|
// We are negating the condition here. Other cases will assume it is valid!
|
|
|
|
case ($hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER):
|
|
|
|
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Deferred eager load only works for single identifier classes
|
2013-04-08 13:27:22 +04:00
|
|
|
case (isset($hints[self::HINT_DEFEREAGERLOAD]) && ! $targetClass->isIdentifierComposite):
|
2011-11-29 19:36:32 +04:00
|
|
|
// TODO: Is there a faster approach?
|
|
|
|
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
|
|
|
|
|
|
|
|
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// TODO: This is very imperformant, ignore it?
|
|
|
|
$newValue = $this->em->find($assoc['targetEntity'], $associatedId);
|
|
|
|
break;
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
// PERF: Inlined & optimized code from UnitOfWork#registerManaged()
|
|
|
|
$newValueOid = spl_object_hash($newValue);
|
|
|
|
$this->entityIdentifiers[$newValueOid] = $associatedId;
|
|
|
|
$this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
|
2012-12-22 01:35:32 +04:00
|
|
|
|
2013-02-23 04:45:40 +04:00
|
|
|
if (
|
|
|
|
$newValue instanceof NotifyPropertyChanged &&
|
|
|
|
( ! $newValue instanceof Proxy || $newValue->__isInitialized())
|
|
|
|
) {
|
2012-12-21 01:31:46 +04:00
|
|
|
$newValue->addPropertyChangedListener($this);
|
|
|
|
}
|
2011-11-29 19:36:32 +04:00
|
|
|
$this->entityStates[$newValueOid] = self::STATE_MANAGED;
|
|
|
|
// make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->originalEntityData[$oid][$field] = $newValue;
|
|
|
|
$class->reflFields[$field]->setValue($entity, $newValue);
|
|
|
|
|
|
|
|
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
|
|
|
|
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
|
|
|
|
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2013-02-14 02:42:13 +04:00
|
|
|
// Ignore if its a cached collection
|
|
|
|
if (isset($hints[Query::HINT_CACHE_ENABLED]) && $class->getFieldValue($entity, $field) instanceof PersistentCollection) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// use the given collection
|
|
|
|
if (isset($data[$field]) && $data[$field] instanceof PersistentCollection) {
|
|
|
|
|
|
|
|
$data[$field]->setOwner($entity, $assoc);
|
|
|
|
|
|
|
|
$class->reflFields[$field]->setValue($entity, $data[$field]);
|
|
|
|
$this->originalEntityData[$oid][$field] = $data[$field];
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-11-29 19:36:32 +04:00
|
|
|
// Inject collection
|
|
|
|
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
|
|
|
|
$pColl->setOwner($entity, $assoc);
|
|
|
|
$pColl->setInitialized(false);
|
|
|
|
|
|
|
|
$reflField = $class->reflFields[$field];
|
|
|
|
$reflField->setValue($entity, $pColl);
|
|
|
|
|
|
|
|
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
|
|
|
|
$this->loadCollection($pColl);
|
|
|
|
$pColl->takeSnapshot();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->originalEntityData[$oid][$field] = $pColl;
|
|
|
|
break;
|
|
|
|
}
|
2009-07-18 15:41:37 +04:00
|
|
|
}
|
2009-05-11 14:43:27 +04:00
|
|
|
|
2012-02-18 02:26:42 +04:00
|
|
|
if ($overrideLocalValues) {
|
2014-04-04 21:30:13 +04:00
|
|
|
// defer invoking of postLoad event to hydration complete step
|
2014-04-05 10:45:00 +04:00
|
|
|
$this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity);
|
2012-02-18 02:26:42 +04:00
|
|
|
}
|
|
|
|
|
2009-05-11 14:43:27 +04:00
|
|
|
return $entity;
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
|
|
|
|
2010-08-09 23:44:52 +04:00
|
|
|
/**
|
2010-12-31 12:54:20 +03:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function triggerEagerLoads()
|
|
|
|
{
|
2011-11-06 08:03:34 +04:00
|
|
|
if ( ! $this->eagerLoadingEntities) {
|
2010-12-31 12:54:20 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-03-06 13:15:56 +03:00
|
|
|
// avoid infinite recursion
|
2011-11-06 08:03:34 +04:00
|
|
|
$eagerLoadingEntities = $this->eagerLoadingEntities;
|
2010-12-31 12:54:20 +03:00
|
|
|
$this->eagerLoadingEntities = array();
|
|
|
|
|
2011-11-07 07:27:20 +04:00
|
|
|
foreach ($eagerLoadingEntities as $entityName => $ids) {
|
2012-01-19 08:27:28 +04:00
|
|
|
if ( ! $ids) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-01-19 08:09:23 +04:00
|
|
|
|
2010-12-31 15:11:01 +03:00
|
|
|
$class = $this->em->getClassMetadata($entityName);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2012-01-19 08:09:23 +04:00
|
|
|
$this->getEntityPersister($entityName)->loadAll(
|
|
|
|
array_combine($class->identifier, array(array_values($ids)))
|
|
|
|
);
|
2010-12-31 12:54:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-08-10 00:27:34 +04:00
|
|
|
* Initializes (loads) an uninitialized persistent collection of an entity.
|
2010-08-09 23:44:52 +04:00
|
|
|
*
|
2012-09-06 20:10:30 +04:00
|
|
|
* @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
|
|
|
|
*
|
|
|
|
* @return void
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2010-08-09 23:44:52 +04:00
|
|
|
* @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
|
|
|
|
*/
|
2010-08-10 00:27:34 +04:00
|
|
|
public function loadCollection(PersistentCollection $collection)
|
2010-08-09 15:13:21 +04:00
|
|
|
{
|
2011-11-06 08:03:34 +04:00
|
|
|
$assoc = $collection->getMapping();
|
|
|
|
$persister = $this->getEntityPersister($assoc['targetEntity']);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-08-09 15:13:21 +04:00
|
|
|
switch ($assoc['type']) {
|
|
|
|
case ClassMetadata::ONE_TO_MANY:
|
2011-11-06 08:03:34 +04:00
|
|
|
$persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection);
|
2010-08-09 15:13:21 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-08-09 15:13:21 +04:00
|
|
|
case ClassMetadata::MANY_TO_MANY:
|
2011-11-06 08:03:34 +04:00
|
|
|
$persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection);
|
2010-08-09 15:13:21 +04:00
|
|
|
break;
|
|
|
|
}
|
2013-06-25 21:34:12 +04:00
|
|
|
|
|
|
|
$collection->setInitialized(true);
|
2010-08-09 15:13:21 +04:00
|
|
|
}
|
|
|
|
|
2008-12-18 17:08:11 +03:00
|
|
|
/**
|
|
|
|
* Gets the identity map of the UnitOfWork.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getIdentityMap()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->identityMap;
|
2008-12-18 17:08:11 +03:00
|
|
|
}
|
2009-01-03 22:50:13 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the original data of an entity. The original data is the data that was
|
|
|
|
* present at the time the entity was reconstituted from the database.
|
|
|
|
*
|
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2009-01-03 22:50:13 +03:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getOriginalEntityData($entity)
|
|
|
|
{
|
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->originalEntityData[$oid])) {
|
|
|
|
return $this->originalEntityData[$oid];
|
2009-01-03 22:50:13 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-01-03 22:50:13 +03:00
|
|
|
return array();
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-10-28 13:31:47 +03:00
|
|
|
/**
|
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @param object $entity
|
|
|
|
* @param array $data
|
|
|
|
*
|
|
|
|
* @return void
|
2009-10-28 13:31:47 +03:00
|
|
|
*/
|
|
|
|
public function setOriginalEntityData($entity, array $data)
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->originalEntityData[spl_object_hash($entity)] = $data;
|
2009-10-28 13:31:47 +03:00
|
|
|
}
|
2009-01-03 22:50:13 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* INTERNAL:
|
2009-05-11 14:43:27 +04:00
|
|
|
* Sets a property value of the original data array of an entity.
|
2009-01-03 22:50:13 +03:00
|
|
|
*
|
2009-10-07 16:39:46 +04:00
|
|
|
* @ignore
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
2009-01-03 22:50:13 +03:00
|
|
|
* @param string $oid
|
|
|
|
* @param string $property
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param mixed $value
|
|
|
|
*
|
|
|
|
* @return void
|
2009-01-03 22:50:13 +03:00
|
|
|
*/
|
|
|
|
public function setOriginalEntityProperty($oid, $property, $value)
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->originalEntityData[$oid][$property] = $value;
|
2009-01-03 22:50:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the identifier of an entity.
|
2009-01-09 19:25:06 +03:00
|
|
|
* The returned value is always an array of identifier values. If the entity
|
2009-03-30 23:43:05 +04:00
|
|
|
* has a composite identifier then the identifier values are in the same
|
2009-01-09 19:25:06 +03:00
|
|
|
* order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
|
2009-01-03 22:50:13 +03:00
|
|
|
*
|
|
|
|
* @param object $entity
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2009-01-03 22:50:13 +03:00
|
|
|
* @return array The identifier values.
|
|
|
|
*/
|
|
|
|
public function getEntityIdentifier($entity)
|
2011-11-15 02:05:33 +04:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->entityIdentifiers[spl_object_hash($entity)];
|
2009-01-03 22:50:13 +03:00
|
|
|
}
|
2009-01-07 20:46:02 +03:00
|
|
|
|
2012-10-20 06:18:01 +04:00
|
|
|
/**
|
2012-12-13 21:56:25 +04:00
|
|
|
* Processes an entity instance to extract their identifier values.
|
2012-10-20 06:18:01 +04:00
|
|
|
*
|
|
|
|
* @param object $entity The entity instance.
|
|
|
|
*
|
2012-12-13 21:56:25 +04:00
|
|
|
* @return mixed A scalar value.
|
2012-10-20 06:18:01 +04:00
|
|
|
*
|
|
|
|
* @throws \Doctrine\ORM\ORMInvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function getSingleIdentifierValue($entity)
|
|
|
|
{
|
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
|
|
|
|
|
|
|
if ($class->isIdentifierComposite) {
|
|
|
|
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
|
|
|
|
}
|
|
|
|
|
2013-05-01 20:46:41 +04:00
|
|
|
$values = $this->isInIdentityMap($entity)
|
2012-10-20 06:18:01 +04:00
|
|
|
? $this->getEntityIdentifier($entity)
|
|
|
|
: $class->getIdentifierValues($entity);
|
|
|
|
|
|
|
|
return isset($values[$class->identifier[0]]) ? $values[$class->identifier[0]] : null;
|
|
|
|
}
|
2013-08-04 03:03:10 +04:00
|
|
|
|
2009-01-07 20:46:02 +03:00
|
|
|
/**
|
2009-05-11 14:43:27 +04:00
|
|
|
* Tries to find an entity with the given identifier in the identity map of
|
|
|
|
* this UnitOfWork.
|
2009-01-07 20:46:02 +03:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param mixed $id The entity identifier to look for.
|
2009-05-11 14:43:27 +04:00
|
|
|
* @param string $rootClassName The name of the root class of the mapped entity hierarchy.
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2012-12-13 21:56:25 +04:00
|
|
|
* @return object|bool Returns the entity with the specified identifier if it exists in
|
|
|
|
* this UnitOfWork, FALSE otherwise.
|
2009-01-07 20:46:02 +03:00
|
|
|
*/
|
|
|
|
public function tryGetById($id, $rootClassName)
|
|
|
|
{
|
2009-08-13 14:13:06 +04:00
|
|
|
$idHash = implode(' ', (array) $id);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
if (isset($this->identityMap[$rootClassName][$idHash])) {
|
|
|
|
return $this->identityMap[$rootClassName][$idHash];
|
2009-01-07 20:46:02 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-01-07 20:46:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
2009-01-12 16:34:41 +03:00
|
|
|
|
2009-04-09 22:12:48 +04:00
|
|
|
/**
|
|
|
|
* Schedules an entity for dirty-checking at commit-time.
|
|
|
|
*
|
|
|
|
* @param object $entity The entity to schedule for dirty-checking.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*
|
2010-07-08 02:20:54 +04:00
|
|
|
* @todo Rename: scheduleForSynchronization
|
2009-04-09 22:12:48 +04:00
|
|
|
*/
|
2009-01-29 20:00:44 +03:00
|
|
|
public function scheduleForDirtyCheck($entity)
|
|
|
|
{
|
2012-04-01 18:27:31 +04:00
|
|
|
$rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2015-01-19 06:21:18 +03:00
|
|
|
$this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity;
|
2009-01-29 20:00:44 +03:00
|
|
|
}
|
|
|
|
|
2009-04-09 22:12:48 +04:00
|
|
|
/**
|
|
|
|
* Checks whether the UnitOfWork has any pending insertions.
|
|
|
|
*
|
|
|
|
* @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
|
|
|
|
*/
|
|
|
|
public function hasPendingInsertions()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return ! empty($this->entityInsertions);
|
2009-04-09 22:12:48 +04:00
|
|
|
}
|
|
|
|
|
2009-01-12 16:34:41 +03:00
|
|
|
/**
|
|
|
|
* Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
|
|
|
|
* number of entities in the identity map.
|
2009-01-29 20:00:44 +03:00
|
|
|
*
|
|
|
|
* @return integer
|
2009-01-12 16:34:41 +03:00
|
|
|
*/
|
|
|
|
public function size()
|
|
|
|
{
|
2011-11-06 08:03:34 +04:00
|
|
|
$countArray = array_map(function ($item) { return count($item); }, $this->identityMap);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
return array_sum($countArray);
|
2009-01-12 16:34:41 +03:00
|
|
|
}
|
2009-02-02 14:55:50 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the EntityPersister for an Entity.
|
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param string $entityName The name of the Entity.
|
2011-11-06 08:03:34 +04:00
|
|
|
*
|
2015-01-15 08:01:52 +03:00
|
|
|
* @return \Doctrine\ORM\Persisters\Entity\EntityPersister
|
2009-02-02 14:55:50 +03:00
|
|
|
*/
|
|
|
|
public function getEntityPersister($entityName)
|
|
|
|
{
|
2011-11-06 08:03:34 +04:00
|
|
|
if (isset($this->persisters[$entityName])) {
|
|
|
|
return $this->persisters[$entityName];
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$class = $this->em->getClassMetadata($entityName);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
switch (true) {
|
|
|
|
case ($class->isInheritanceTypeNone()):
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister = new BasicEntityPersister($this->em, $class);
|
2011-11-06 08:03:34 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
case ($class->isInheritanceTypeSingleTable()):
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister = new SingleTablePersister($this->em, $class);
|
2011-11-06 08:03:34 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
case ($class->isInheritanceTypeJoined()):
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister = new JoinedSubclassPersister($this->em, $class);
|
2011-11-06 08:03:34 +04:00
|
|
|
break;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
default:
|
2013-09-12 00:43:25 +04:00
|
|
|
throw new \RuntimeException('No persister found for entity.');
|
2009-02-02 14:55:50 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
if ($this->hasCache && $class->cache !== null) {
|
|
|
|
$persister = $this->em->getConfiguration()
|
2013-10-03 21:55:55 +04:00
|
|
|
->getSecondLevelCacheConfiguration()
|
|
|
|
->getCacheFactory()
|
2013-02-14 02:42:13 +04:00
|
|
|
->buildCachedEntityPersister($this->em, $persister, $class);
|
|
|
|
}
|
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$this->persisters[$entityName] = $persister;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->persisters[$entityName];
|
2009-02-02 14:55:50 +03:00
|
|
|
}
|
|
|
|
|
2009-02-06 20:16:39 +03:00
|
|
|
/**
|
|
|
|
* Gets a collection persister for a collection-valued association.
|
|
|
|
*
|
2012-09-06 20:10:30 +04:00
|
|
|
* @param array $association
|
2011-11-06 08:03:34 +04:00
|
|
|
*
|
2015-01-15 08:01:52 +03:00
|
|
|
* @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
|
2009-02-06 20:16:39 +03:00
|
|
|
*/
|
2010-08-09 15:13:21 +04:00
|
|
|
public function getCollectionPersister(array $association)
|
2009-02-02 14:55:50 +03:00
|
|
|
{
|
2013-02-14 02:42:13 +04:00
|
|
|
$role = isset($association['cache'])
|
|
|
|
? $association['sourceEntity'] . '::' . $association['fieldName']
|
|
|
|
: $association['type'];
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
if (isset($this->collectionPersisters[$role])) {
|
|
|
|
return $this->collectionPersisters[$role];
|
2011-11-06 08:03:34 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister = ClassMetadata::ONE_TO_MANY === $association['type']
|
|
|
|
? new OneToManyPersister($this->em)
|
|
|
|
: new ManyToManyPersister($this->em);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
if ($this->hasCache && isset($association['cache'])) {
|
|
|
|
$persister = $this->em->getConfiguration()
|
2013-10-03 21:55:55 +04:00
|
|
|
->getSecondLevelCacheConfiguration()
|
|
|
|
->getCacheFactory()
|
2013-02-14 02:42:13 +04:00
|
|
|
->buildCachedCollectionPersister($this->em, $persister, $association);
|
2009-02-02 14:55:50 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
$this->collectionPersisters[$role] = $persister;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
return $this->collectionPersisters[$role];
|
2009-02-02 14:55:50 +03:00
|
|
|
}
|
2009-04-09 22:12:48 +04:00
|
|
|
|
2009-05-13 19:19:27 +04:00
|
|
|
/**
|
|
|
|
* INTERNAL:
|
|
|
|
* Registers an entity as managed.
|
|
|
|
*
|
|
|
|
* @param object $entity The entity.
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param array $id The identifier values.
|
|
|
|
* @param array $data The original entity data.
|
|
|
|
*
|
|
|
|
* @return void
|
2009-05-13 19:19:27 +04:00
|
|
|
*/
|
2009-10-07 16:39:46 +04:00
|
|
|
public function registerManaged($entity, array $id, array $data)
|
2009-05-13 19:19:27 +04:00
|
|
|
{
|
|
|
|
$oid = spl_object_hash($entity);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
$this->entityIdentifiers[$oid] = $id;
|
|
|
|
$this->entityStates[$oid] = self::STATE_MANAGED;
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->originalEntityData[$oid] = $data;
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2009-05-13 19:19:27 +04:00
|
|
|
$this->addToIdentityMap($entity);
|
2012-07-07 19:47:29 +04:00
|
|
|
|
2013-02-23 04:45:40 +04:00
|
|
|
if ($entity instanceof NotifyPropertyChanged && ( ! $entity instanceof Proxy || $entity->__isInitialized())) {
|
2012-07-07 19:47:29 +04:00
|
|
|
$entity->addPropertyChangedListener($this);
|
|
|
|
}
|
2009-05-13 19:19:27 +04:00
|
|
|
}
|
2009-07-03 21:36:41 +04:00
|
|
|
|
2009-05-26 19:42:54 +04:00
|
|
|
/**
|
|
|
|
* INTERNAL:
|
|
|
|
* Clears the property changeset of the entity with the given OID.
|
|
|
|
*
|
|
|
|
* @param string $oid The entity's OID.
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @return void
|
2009-05-26 19:42:54 +04:00
|
|
|
*/
|
|
|
|
public function clearEntityChangeSet($oid)
|
|
|
|
{
|
2011-06-05 15:34:07 +04:00
|
|
|
$this->entityChangeSets[$oid] = array();
|
2009-05-26 19:42:54 +04:00
|
|
|
}
|
2009-05-13 19:19:27 +04:00
|
|
|
|
2009-04-09 22:12:48 +04:00
|
|
|
/* PropertyChangedListener implementation */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies this UnitOfWork of a property change in an entity.
|
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $entity The entity that owns the property.
|
2009-04-09 22:12:48 +04:00
|
|
|
* @param string $propertyName The name of the property that changed.
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param mixed $oldValue The old value of the property.
|
|
|
|
* @param mixed $newValue The new value of the property.
|
|
|
|
*
|
|
|
|
* @return void
|
2009-04-09 22:12:48 +04:00
|
|
|
*/
|
|
|
|
public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
|
|
|
|
{
|
2011-11-06 08:03:34 +04:00
|
|
|
$oid = spl_object_hash($entity);
|
2012-04-01 18:27:31 +04:00
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
2009-04-09 22:12:48 +04:00
|
|
|
|
2010-05-01 14:14:16 +04:00
|
|
|
$isAssocField = isset($class->associationMappings[$propertyName]);
|
|
|
|
|
|
|
|
if ( ! $isAssocField && ! isset($class->fieldMappings[$propertyName])) {
|
|
|
|
return; // ignore non-persistent fields
|
|
|
|
}
|
|
|
|
|
2010-07-15 17:52:42 +04:00
|
|
|
// Update changeset and mark entity for synchronization
|
2010-07-08 02:20:54 +04:00
|
|
|
$this->entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue);
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2015-01-19 06:21:18 +03:00
|
|
|
if ( ! isset($this->scheduledForSynchronization[$class->rootEntityName][$oid])) {
|
2010-07-15 17:52:42 +04:00
|
|
|
$this->scheduleForDirtyCheck($entity);
|
2009-07-23 13:52:16 +04:00
|
|
|
}
|
2009-04-09 22:12:48 +04:00
|
|
|
}
|
2010-07-15 17:52:42 +04:00
|
|
|
|
2010-02-24 22:19:04 +03:00
|
|
|
/**
|
|
|
|
* Gets the currently scheduled entity insertions in this UnitOfWork.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2010-02-24 22:19:04 +03:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getScheduledEntityInsertions()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->entityInsertions;
|
2010-02-24 22:19:04 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-02-24 22:19:04 +03:00
|
|
|
/**
|
|
|
|
* Gets the currently scheduled entity updates in this UnitOfWork.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2010-02-24 22:19:04 +03:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getScheduledEntityUpdates()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->entityUpdates;
|
2010-02-24 22:19:04 +03:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2010-02-24 22:19:04 +03:00
|
|
|
/**
|
|
|
|
* Gets the currently scheduled entity deletions in this UnitOfWork.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2010-02-24 22:19:04 +03:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getScheduledEntityDeletions()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->entityDeletions;
|
2010-02-24 22:19:04 +03:00
|
|
|
}
|
2010-03-13 12:19:12 +03:00
|
|
|
|
|
|
|
/**
|
2012-12-13 21:56:25 +04:00
|
|
|
* Gets the currently scheduled complete collection deletions
|
2010-03-13 12:19:12 +03:00
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getScheduledCollectionDeletions()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->collectionDeletions;
|
2010-03-13 12:19:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the currently scheduled collection inserts, updates and deletes.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getScheduledCollectionUpdates()
|
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return $this->collectionUpdates;
|
2010-07-04 20:55:49 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-06-05 10:44:38 +04:00
|
|
|
/**
|
|
|
|
* Helper method to initialize a lazy loading proxy or persistent collection.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $obj
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2011-06-05 10:44:38 +04:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function initializeObject($obj)
|
|
|
|
{
|
|
|
|
if ($obj instanceof Proxy) {
|
|
|
|
$obj->__load();
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
return;
|
2011-11-15 02:05:33 +04:00
|
|
|
}
|
|
|
|
|
2011-11-06 08:03:34 +04:00
|
|
|
if ($obj instanceof PersistentCollection) {
|
2011-06-05 10:44:38 +04:00
|
|
|
$obj->initialize();
|
|
|
|
}
|
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-06-05 10:44:38 +04:00
|
|
|
/**
|
|
|
|
* Helper method to show an object as string.
|
2011-11-15 02:05:33 +04:00
|
|
|
*
|
2012-12-01 20:28:06 +04:00
|
|
|
* @param object $obj
|
2012-09-06 20:10:30 +04:00
|
|
|
*
|
2011-11-15 02:05:33 +04:00
|
|
|
* @return string
|
2011-06-05 10:44:38 +04:00
|
|
|
*/
|
2010-07-08 02:20:54 +04:00
|
|
|
private static function objToStr($obj)
|
2010-07-04 20:55:49 +04:00
|
|
|
{
|
2010-07-08 02:20:54 +04:00
|
|
|
return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj);
|
2010-07-04 20:55:49 +04:00
|
|
|
}
|
2011-10-15 13:52:41 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
|
|
|
|
*
|
|
|
|
* This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
|
2012-09-06 20:10:30 +04:00
|
|
|
* on this object that might be necessary to perform a correct update.
|
|
|
|
*
|
|
|
|
* @param object $object
|
|
|
|
*
|
2011-10-15 13:52:41 +04:00
|
|
|
* @return void
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException
|
2011-10-15 13:52:41 +04:00
|
|
|
*/
|
|
|
|
public function markReadOnly($object)
|
|
|
|
{
|
|
|
|
if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
|
2011-10-22 14:40:12 +04:00
|
|
|
throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
|
2011-10-15 13:52:41 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-10-15 13:52:41 +04:00
|
|
|
$this->readOnlyObjects[spl_object_hash($object)] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is this entity read only?
|
|
|
|
*
|
2012-09-06 20:10:30 +04:00
|
|
|
* @param object $object
|
|
|
|
*
|
|
|
|
* @return bool
|
2012-12-01 20:28:06 +04:00
|
|
|
*
|
|
|
|
* @throws ORMInvalidArgumentException
|
2011-10-15 13:52:41 +04:00
|
|
|
*/
|
|
|
|
public function isReadOnly($object)
|
|
|
|
{
|
2012-01-19 08:09:23 +04:00
|
|
|
if ( ! is_object($object)) {
|
2011-10-22 14:40:12 +04:00
|
|
|
throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
|
2011-10-15 13:52:41 +04:00
|
|
|
}
|
2011-11-15 02:05:33 +04:00
|
|
|
|
2011-10-15 18:03:50 +04:00
|
|
|
return isset($this->readOnlyObjects[spl_object_hash($object)]);
|
2011-10-15 13:52:41 +04:00
|
|
|
}
|
2012-07-23 03:14:02 +04:00
|
|
|
|
2013-02-14 02:42:13 +04:00
|
|
|
/**
|
|
|
|
* Perform whatever processing is encapsulated here after completion of the transaction.
|
|
|
|
*/
|
|
|
|
private function afterTransactionComplete()
|
|
|
|
{
|
|
|
|
if ( ! $this->hasCache) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->persisters as $persister) {
|
2013-10-08 02:53:32 +04:00
|
|
|
if ($persister instanceof CachedPersister) {
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister->afterTransactionComplete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->collectionPersisters as $persister) {
|
2013-10-08 02:53:32 +04:00
|
|
|
if ($persister instanceof CachedPersister) {
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister->afterTransactionComplete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Perform whatever processing is encapsulated here after completion of the rolled-back.
|
|
|
|
*/
|
|
|
|
private function afterTransactionRolledBack()
|
|
|
|
{
|
|
|
|
if ( ! $this->hasCache) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->persisters as $persister) {
|
2013-10-08 02:53:32 +04:00
|
|
|
if ($persister instanceof CachedPersister) {
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister->afterTransactionRolledBack();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->collectionPersisters as $persister) {
|
2013-10-08 02:53:32 +04:00
|
|
|
if ($persister instanceof CachedPersister) {
|
2013-02-14 02:42:13 +04:00
|
|
|
$persister->afterTransactionRolledBack();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-23 03:14:02 +04:00
|
|
|
private function dispatchOnFlushEvent()
|
|
|
|
{
|
|
|
|
if ($this->evm->hasListeners(Events::onFlush)) {
|
2012-07-31 05:26:55 +04:00
|
|
|
$this->evm->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
|
2012-07-23 03:14:02 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function dispatchPostFlushEvent()
|
|
|
|
{
|
|
|
|
if ($this->evm->hasListeners(Events::postFlush)) {
|
2012-07-31 05:26:55 +04:00
|
|
|
$this->evm->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
|
2012-07-23 03:14:02 +04:00
|
|
|
}
|
|
|
|
}
|
2014-02-01 04:57:51 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Verifies if two given entities actually are the same based on identifier comparison
|
|
|
|
*
|
|
|
|
* @param object $entity1
|
|
|
|
* @param object $entity2
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function isIdentifierEquals($entity1, $entity2)
|
|
|
|
{
|
|
|
|
if ($entity1 === $entity2) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$class = $this->em->getClassMetadata(get_class($entity1));
|
|
|
|
|
|
|
|
if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$oid1 = spl_object_hash($entity1);
|
|
|
|
$oid2 = spl_object_hash($entity2);
|
|
|
|
|
|
|
|
$id1 = isset($this->entityIdentifiers[$oid1])
|
|
|
|
? $this->entityIdentifiers[$oid1]
|
2014-08-07 17:56:27 +04:00
|
|
|
: $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity1));
|
2014-02-01 04:57:51 +04:00
|
|
|
$id2 = isset($this->entityIdentifiers[$oid2])
|
|
|
|
? $this->entityIdentifiers[$oid2]
|
2014-08-07 17:56:27 +04:00
|
|
|
: $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity2));
|
2014-02-01 04:57:51 +04:00
|
|
|
|
|
|
|
return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
|
|
|
|
}
|
2014-04-04 21:30:13 +04:00
|
|
|
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
/**
|
|
|
|
* @param object $entity
|
|
|
|
* @param object $managedCopy
|
2015-01-17 00:54:30 +03:00
|
|
|
*
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
* @throws ORMException
|
|
|
|
* @throws OptimisticLockException
|
|
|
|
* @throws TransactionRequiredException
|
|
|
|
*/
|
|
|
|
private function mergeEntityStateIntoManagedCopy($entity, $managedCopy)
|
|
|
|
{
|
|
|
|
$class = $this->em->getClassMetadata(get_class($entity));
|
|
|
|
|
2015-01-24 16:30:40 +03:00
|
|
|
foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) {
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
$name = $prop->name;
|
2015-01-17 00:54:30 +03:00
|
|
|
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
$prop->setAccessible(true);
|
2015-01-17 00:54:30 +03:00
|
|
|
|
|
|
|
if ( ! isset($class->associationMappings[$name])) {
|
|
|
|
if ( ! $class->isIdentifier($name)) {
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
$prop->setValue($managedCopy, $prop->getValue($entity));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$assoc2 = $class->associationMappings[$name];
|
2015-01-17 00:54:30 +03:00
|
|
|
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
if ($assoc2['type'] & ClassMetadata::TO_ONE) {
|
|
|
|
$other = $prop->getValue($entity);
|
|
|
|
if ($other === null) {
|
|
|
|
$prop->setValue($managedCopy, null);
|
|
|
|
} else {
|
|
|
|
if ($other instanceof Proxy && !$other->__isInitialized()) {
|
|
|
|
// do not merge fields marked lazy that have not been fetched.
|
|
|
|
return;
|
|
|
|
}
|
2015-01-17 00:54:30 +03:00
|
|
|
|
|
|
|
if ( ! $assoc2['isCascadeMerge']) {
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
if ($this->getEntityState($other) === self::STATE_DETACHED) {
|
|
|
|
$targetClass = $this->em->getClassMetadata($assoc2['targetEntity']);
|
2015-01-17 00:54:30 +03:00
|
|
|
$relatedId = $targetClass->getIdentifierValues($other);
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
|
|
|
|
if ($targetClass->subClasses) {
|
|
|
|
$other = $this->em->find($targetClass->name, $relatedId);
|
|
|
|
} else {
|
|
|
|
$other = $this->em->getProxyFactory()->getProxy(
|
|
|
|
$assoc2['targetEntity'],
|
|
|
|
$relatedId
|
|
|
|
);
|
|
|
|
$this->registerManaged($other, $relatedId, array());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$prop->setValue($managedCopy, $other);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$mergeCol = $prop->getValue($entity);
|
2015-01-17 00:54:30 +03:00
|
|
|
|
|
|
|
if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) {
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
// do not merge fields marked lazy that have not been fetched.
|
|
|
|
// keep the lazy persistent collection of the managed copy.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$managedCol = $prop->getValue($managedCopy);
|
2015-01-17 00:54:30 +03:00
|
|
|
|
|
|
|
if ( ! $managedCol) {
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
$managedCol = new PersistentCollection(
|
|
|
|
$this->em,
|
|
|
|
$this->em->getClassMetadata($assoc2['targetEntity']),
|
|
|
|
new ArrayCollection
|
|
|
|
);
|
|
|
|
$managedCol->setOwner($managedCopy, $assoc2);
|
|
|
|
$prop->setValue($managedCopy, $managedCol);
|
2015-01-17 00:54:30 +03:00
|
|
|
|
|
|
|
$this->originalEntityData[spl_object_hash($entity)][$name] = $managedCol;
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
}
|
2015-01-17 00:54:30 +03:00
|
|
|
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
if ($assoc2['isCascadeMerge']) {
|
|
|
|
$managedCol->initialize();
|
|
|
|
|
|
|
|
// clear and set dirty a managed collection if its not also the same collection to merge from.
|
2015-01-17 00:54:30 +03:00
|
|
|
if ( ! $managedCol->isEmpty() && $managedCol !== $mergeCol) {
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
$managedCol->unwrap()->clear();
|
|
|
|
$managedCol->setDirty(true);
|
|
|
|
|
2015-01-17 00:54:30 +03:00
|
|
|
if ($assoc2['isOwningSide']
|
|
|
|
&& $assoc2['type'] == ClassMetadata::MANY_TO_MANY
|
|
|
|
&& $class->isChangeTrackingNotify()
|
Don't load detached proxies when merging them.
Ticket DDC-1392 fixed an issue where uninitialized proxies could not be merged
because the merge routine couldn't get the identifier from them. The soution
was to initialize the proxy.
Ticket DDC-1734 fixed the merging of *unserialized* uninitialized proxies by
resetting their internals, so these proxies were able to initialize, as required
by the fix for DDC-1392.
Somehow, in the meanwhile, the fix for DDC-1392 is not needed anymore:
reverting the patch will not break the associated test (but it does break the
test for DDC-1734). This means it is not needed anymore to initialize the proxy
when merging.
Uninitialized proxies that get merged should not be loaded at all. Since they
are not initialized, the entity data for sure hasn't changed, so it can be
safely ignored. Actually, the only thing the data is needed for while merging,
is to copy it into the managed entity, but that one is already supposed to be
up to date. By not initializing the proxy, a potential database roundtrip is
saved, and the fix for DDC-1734 is not needed anymore.
Besides optimizing the merge, this patch also solves an issue with merging.
Currently, when a detached uninitialized proxy is merged while there is already a
corresponding managed entity (proxy or not), the ORM returns a blank entity
instead of returning the already managed entity. This patch makes sure that
already existing managed entities are re-used.
2014-11-06 09:16:20 +03:00
|
|
|
) {
|
|
|
|
$this->scheduleForDirtyCheck($managedCopy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($class->isChangeTrackingNotify()) {
|
|
|
|
// Just treat all properties as changed, there is no other choice.
|
|
|
|
$this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-04 21:30:13 +04:00
|
|
|
/**
|
|
|
|
* This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
|
|
|
|
* Unit of work able to fire deferred events, related to loading events here.
|
|
|
|
*
|
|
|
|
* @internal should be called internally from object hydrators
|
|
|
|
*/
|
|
|
|
public function hydrationComplete()
|
|
|
|
{
|
2014-04-05 10:45:00 +04:00
|
|
|
$this->hydrationCompleteHandler->hydrationComplete();
|
2014-04-04 21:30:13 +04:00
|
|
|
}
|
2009-07-28 15:43:42 +04:00
|
|
|
}
|