1
0
mirror of synced 2025-02-07 07:49:27 +03:00

Merge origin/master into DDC-1384

This commit is contained in:
Benjamin Eberlei 2011-10-30 12:12:18 +01:00
commit 64d405f7dd
45 changed files with 1383 additions and 793 deletions

View File

@ -1,3 +1,17 @@
# ResultCache implementation rewritten
The result cache is completly rewritten and now works on the database result level, not inside the ORM AbstractQuery
anymore. This means that for result cached queries the hydration will now always be performed again, regardless of
the hydration mode. Affected areas are:
1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork
leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore.
2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result.
The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now
deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile`
instance with access to result cache driver, lifetime and cache key.
# EntityManager#getPartialReference() creates read-only entity # EntityManager#getPartialReference() creates read-only entity
Entities returned from EntityManager#getPartialReference() are now marked as read-only if they Entities returned from EntityManager#getPartialReference() are now marked as read-only if they

View File

@ -20,7 +20,8 @@
namespace Doctrine\ORM; namespace Doctrine\ORM;
use Doctrine\DBAL\Types\Type, use Doctrine\DBAL\Types\Type,
Doctrine\ORM\Query\QueryException; Doctrine\ORM\Query\QueryException,
Doctrine\DBAL\Cache\QueryCacheProfile;
/** /**
* Base contract for ORM queries. Base class for Query and NativeQuery. * Base contract for ORM queries. Base class for Query and NativeQuery.
@ -91,34 +92,15 @@ abstract class AbstractQuery
protected $_hydrationMode = self::HYDRATE_OBJECT; protected $_hydrationMode = self::HYDRATE_OBJECT;
/** /**
* The locally set cache driver used for caching result sets of this query. * @param \Doctrine\DBAL\Cache\QueryCacheProfile
*
* @var CacheDriver
*/ */
protected $_resultCacheDriver; protected $_queryCacheProfile;
/**
* Boolean flag for whether or not to cache the results of this query.
*
* @var boolean
*/
protected $_useResultCache;
/**
* @var string The id to store the result cache entry under.
*/
protected $_resultCacheId;
/** /**
* @var boolean Boolean value that indicates whether or not expire the result cache. * @var boolean Boolean value that indicates whether or not expire the result cache.
*/ */
protected $_expireResultCache = false; protected $_expireResultCache = false;
/**
* @var int Result Cache lifetime.
*/
protected $_resultCacheTTL;
/** /**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>. * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
* *
@ -260,7 +242,7 @@ abstract class AbstractQuery
} }
/** /**
* Defines a cache driver to be used for caching result sets. * Defines a cache driver to be used for caching result sets and implictly enables caching.
* *
* @param Doctrine\Common\Cache\Cache $driver Cache driver * @param Doctrine\Common\Cache\Cache $driver Cache driver
* @return Doctrine\ORM\AbstractQuery * @return Doctrine\ORM\AbstractQuery
@ -270,9 +252,10 @@ abstract class AbstractQuery
if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
throw ORMException::invalidResultCacheDriver(); throw ORMException::invalidResultCacheDriver();
} }
$this->_resultCacheDriver = $resultCacheDriver; if ($this->_queryCacheProfile) {
if ($resultCacheDriver) { $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver);
$this->_useResultCache = true; } else {
$this->_queryCacheProfile = new QueryCacheProfile(0, null, $resultCacheDriver);
} }
return $this; return $this;
} }
@ -280,12 +263,13 @@ abstract class AbstractQuery
/** /**
* Returns the cache driver used for caching result sets. * Returns the cache driver used for caching result sets.
* *
* @deprecated
* @return Doctrine\Common\Cache\Cache Cache driver * @return Doctrine\Common\Cache\Cache Cache driver
*/ */
public function getResultCacheDriver() public function getResultCacheDriver()
{ {
if ($this->_resultCacheDriver) { if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
return $this->_resultCacheDriver; return $this->_queryCacheProfile->getResultCacheDriver();
} else { } else {
return $this->_em->getConfiguration()->getResultCacheImpl(); return $this->_em->getConfiguration()->getResultCacheImpl();
} }
@ -296,18 +280,17 @@ abstract class AbstractQuery
* how long and which ID to use for the cache entry. * how long and which ID to use for the cache entry.
* *
* @param boolean $bool * @param boolean $bool
* @param integer $timeToLive * @param integer $lifetime
* @param string $resultCacheId * @param string $resultCacheId
* @return Doctrine\ORM\AbstractQuery This query instance. * @return Doctrine\ORM\AbstractQuery This query instance.
*/ */
public function useResultCache($bool, $timeToLive = null, $resultCacheId = null) public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
{ {
$this->_useResultCache = $bool; if ($bool) {
if ($timeToLive) { $this->setResultCacheLifetime($lifetime);
$this->setResultCacheLifetime($timeToLive); $this->setResultCacheId($resultCacheId);
} } else {
if ($resultCacheId) { $this->_queryCacheProfile = null;
$this->_resultCacheId = $resultCacheId;
} }
return $this; return $this;
} }
@ -315,27 +298,33 @@ abstract class AbstractQuery
/** /**
* Defines how long the result cache will be active before expire. * Defines how long the result cache will be active before expire.
* *
* @param integer $timeToLive How long the cache entry is valid. * @param integer $lifetime How long the cache entry is valid.
* @return Doctrine\ORM\AbstractQuery This query instance. * @return Doctrine\ORM\AbstractQuery This query instance.
*/ */
public function setResultCacheLifetime($timeToLive) public function setResultCacheLifetime($lifetime)
{ {
if ($timeToLive !== null) { if ($lifetime === null) {
$timeToLive = (int) $timeToLive; $lifetime = 0;
} else {
$lifetime = (int)$lifetime;
}
if ($this->_queryCacheProfile) {
$this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime);
} else {
$this->_queryCacheProfile = new QueryCacheProfile($lifetime);
} }
$this->_resultCacheTTL = $timeToLive;
return $this; return $this;
} }
/** /**
* Retrieves the lifetime of resultset cache. * Retrieves the lifetime of resultset cache.
* *
* @deprecated
* @return integer * @return integer
*/ */
public function getResultCacheLifetime() public function getResultCacheLifetime()
{ {
return $this->_resultCacheTTL; return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
} }
/** /**
@ -360,6 +349,14 @@ abstract class AbstractQuery
return $this->_expireResultCache; return $this->_expireResultCache;
} }
/**
* @return QueryCacheProfile
*/
public function getQueryCacheProfile()
{
return $this->_queryCacheProfile;
}
/** /**
* Change the default fetch mode of an association for this query. * Change the default fetch mode of an association for this query.
* *
@ -548,7 +545,7 @@ abstract class AbstractQuery
* *
* @param array $params The query parameters. * @param array $params The query parameters.
* @param integer $hydrationMode The hydration mode to use. * @param integer $hydrationMode The hydration mode to use.
* @return IterableResult * @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/ */
public function iterate(array $params = array(), $hydrationMode = null) public function iterate(array $params = array(), $hydrationMode = null)
{ {
@ -584,28 +581,6 @@ abstract class AbstractQuery
$this->setParameters($params); $this->setParameters($params);
} }
// Check result cache
if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
list($key, $hash) = $this->getResultCacheId();
$cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash);
if ($cached === false || !isset($cached[$key])) {
// Cache miss.
$stmt = $this->_doExecute();
$result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
$cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL);
return $result;
} else {
// Cache hit.
return $cached[$key];
}
}
$stmt = $this->_doExecute(); $stmt = $this->_doExecute();
if (is_numeric($stmt)) { if (is_numeric($stmt)) {
@ -627,43 +602,23 @@ abstract class AbstractQuery
*/ */
public function setResultCacheId($id) public function setResultCacheId($id)
{ {
$this->_resultCacheId = $id; if ($this->_queryCacheProfile) {
$this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id);
} else {
$this->_queryCacheProfile = new QueryCacheProfile(0, $id);
}
return $this; return $this;
} }
/** /**
* Get the result cache id to use to store the result set cache entry. * Get the result cache id to use to store the result set cache entry if set.
* Will return the configured id if it exists otherwise a hash will be
* automatically generated for you.
* *
* @return array ($key, $hash) * @deprecated
* @return string
*/ */
protected function getResultCacheId() public function getResultCacheId()
{ {
if ($this->_resultCacheId) { return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
return array($this->_resultCacheId, $this->_resultCacheId);
} else {
$params = $this->_params;
foreach ($params AS $key => $value) {
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
} else {
$class = $this->_em->getClassMetadata(get_class($value));
$idValues = $class->getIdentifierValues($value);
}
$params[$key] = $idValues;
} else {
$params[$key] = $value;
}
}
$sql = $this->getSql();
ksort($this->_hints);
$key = implode(";", (array)$sql) . var_export($params, true) .
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode;
return array($key, md5($key));
}
} }
/** /**

View File

@ -209,27 +209,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->_attributes['metadataDriverImpl'] : null; $this->_attributes['metadataDriverImpl'] : null;
} }
/**
* Gets the cache driver implementation that is used for query result caching.
*
* @return \Doctrine\Common\Cache\Cache
*/
public function getResultCacheImpl()
{
return isset($this->_attributes['resultCacheImpl']) ?
$this->_attributes['resultCacheImpl'] : null;
}
/**
* Sets the cache driver implementation that is used for query result caching.
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*/
public function setResultCacheImpl(Cache $cacheImpl)
{
$this->_attributes['resultCacheImpl'] = $cacheImpl;
}
/** /**
* Gets the cache driver implementation that is used for the query cache (SQL cache). * Gets the cache driver implementation that is used for the query cache (SQL cache).
* *

View File

@ -325,13 +325,17 @@ class EntityManager implements ObjectManager
* This effectively synchronizes the in-memory state of managed objects with the * This effectively synchronizes the in-memory state of managed objects with the
* database. * database.
* *
* If an entity is explicitly passed to this method only this entity and
* the cascade-persist semantics + scheduled inserts/removals are synchronized.
*
* @param object $entity
* @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails. * makes use of optimistic locking fails.
*/ */
public function flush() public function flush($entity = null)
{ {
$this->errorIfClosed(); $this->errorIfClosed();
$this->unitOfWork->commit(); $this->unitOfWork->commit($entity);
} }
/** /**

View File

@ -19,13 +19,15 @@
namespace Doctrine\ORM\Event; namespace Doctrine\ORM\Event;
use \Doctrine\Common\EventSubscriber; use Doctrine\Common\EventSubscriber;
use \LogicException; use LogicException;
/** /**
* Delegate events only for certain entities they are registered for. * Delegate events only for certain entities they are registered for.
* *
* @link www.doctrine-project.org
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.2 * @since 2.2
*/ */
class EntityEventDelegator implements EventSubscriber class EntityEventDelegator implements EventSubscriber
@ -54,17 +56,23 @@ class EntityEventDelegator implements EventSubscriber
public function addEventListener($events, $entities, $listener) public function addEventListener($events, $entities, $listener)
{ {
if ($this->frozen) { if ($this->frozen) {
throw new LogicException("Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " . throw new LogicException(
"is called once. This happens when you register the delegator with the event manager."); "Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " .
"is called once. This happens when you register the delegator with the event manager."
);
} }
// Picks the hash code related to that listener // Picks the hash code related to that listener
$hash = spl_object_hash($listener); $hash = spl_object_hash($listener);
$entities = array_flip((array) $entities);
foreach ((array) $events as $event) { foreach ((array) $events as $event) {
// Overrides listener if a previous one was associated already // Overrides listener if a previous one was associated already
// Prevents duplicate listeners on same event (same instance only) // Prevents duplicate listeners on same event (same instance only)
$this->listeners[$event][$hash] = array('listener' => $listener, 'entities' => array_flip((array)$entities)); $this->listeners[$event][$hash] = array(
'listener' => $listener,
'entities' => $entities
);
} }
} }
@ -73,6 +81,7 @@ class EntityEventDelegator implements EventSubscriber
* interested in and added as a listener for these events. * interested in and added as a listener for these events.
* *
* @param Doctrine\Common\EventSubscriber $subscriber The subscriber. * @param Doctrine\Common\EventSubscriber $subscriber The subscriber.
* @param array $entities
*/ */
public function addEventSubscriber(EventSubscriber $subscriber, $entities) public function addEventSubscriber(EventSubscriber $subscriber, $entities)
{ {
@ -87,24 +96,27 @@ class EntityEventDelegator implements EventSubscriber
public function getSubscribedEvents() public function getSubscribedEvents()
{ {
$this->frozen = true; $this->frozen = true;
return array_keys($this->listeners); return array_keys($this->listeners);
} }
/** /**
* Delegate the event to an appropriate listener * Delegate the event to an appropriate listener
* *
* @param $eventName * @param string $eventName
* @param $event * @param array $args
* @return void * @return void
*/ */
public function __call($eventName, $args) public function __call($eventName, $args)
{ {
$event = $args[0]; $event = $args[0];
foreach ($this->listeners[$eventName] AS $listenerData) { foreach ($this->listeners[$eventName] AS $listenerData) {
$class = get_class($event->getEntity()); $class = get_class($event->getEntity());
if (isset($listenerData['entities'][$class])) {
if ( ! isset($listenerData['entities'][$class])) continue;
$listenerData['listener']->$eventName($event); $listenerData['listener']->$eventName($event);
} }
} }
}
} }

View File

@ -19,42 +19,59 @@
namespace Doctrine\ORM\Event; namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
/** /**
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
* of entities. * of entities.
* *
* @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.de> * @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
*/ */
class LifecycleEventArgs extends \Doctrine\Common\EventArgs class LifecycleEventArgs extends EventArgs
{ {
/** /**
* @var EntityManager * @var Doctrine\ORM\EntityManager
*/ */
private $_em; private $em;
/** /**
* @var object * @var object
*/ */
private $_entity; private $entity;
public function __construct($entity, $em) /**
* Constructor
*
* @param object $entity
* @param Doctrine\ORM\EntityManager $em
*/
public function __construct($entity, EntityManager $em)
{ {
$this->_entity = $entity; $this->entity = $entity;
$this->_em = $em; $this->em = $em;
}
public function getEntity()
{
return $this->_entity;
} }
/** /**
* @return EntityManager * Retireve associated Entity.
*
* @return object
*/
public function getEntity()
{
return $this->entity;
}
/**
* Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/ */
public function getEntityManager() public function getEntityManager()
{ {
return $this->_em; return $this->em;
} }
} }

View File

@ -1,9 +1,25 @@
<?php <?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
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Event; namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs; use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
@ -16,18 +32,20 @@ use Doctrine\ORM\EntityManager;
class LoadClassMetadataEventArgs extends EventArgs class LoadClassMetadataEventArgs extends EventArgs
{ {
/** /**
* @var ClassMetadata * @var Doctrine\ORM\Mapping\ClassMetadata
*/ */
private $classMetadata; private $classMetadata;
/** /**
* @var EntityManager * @var Doctrine\ORM\EntityManager
*/ */
private $em; private $em;
/** /**
* @param ClassMetadataInfo $classMetadata * Constructor.
* @param EntityManager $em *
* @param Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata
* @param Doctrine\ORM\EntityManager $em
*/ */
public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em) public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em)
{ {
@ -36,7 +54,9 @@ class LoadClassMetadataEventArgs extends EventArgs
} }
/** /**
* @return ClassMetadataInfo * Retrieve associated ClassMetadata.
*
* @return Doctrine\ORM\Mapping\ClassMetadataInfo
*/ */
public function getClassMetadata() public function getClassMetadata()
{ {
@ -44,7 +64,9 @@ class LoadClassMetadataEventArgs extends EventArgs
} }
/** /**
* @return EntityManager * Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/ */
public function getEntityManager() public function getEntityManager()
{ {

View File

@ -15,7 +15,7 @@
* This software consists of voluntary contributions made by many individuals * This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see * and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>. * <http://www.doctrine-project.org>.
*/ */
namespace Doctrine\ORM\Event; namespace Doctrine\ORM\Event;
@ -23,16 +23,15 @@ namespace Doctrine\ORM\Event;
* Provides event arguments for the onClear event. * Provides event arguments for the onClear event.
* *
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.de> * @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
*/ */
class OnClearEventArgs extends \Doctrine\Common\EventArgs class OnClearEventArgs extends \Doctrine\Common\EventArgs
{ {
/** /**
* @var \Doctrine\ORM\EntityManager * @var Doctrine\ORM\EntityManager
*/ */
private $em; private $em;
@ -42,7 +41,10 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
private $entityClass; private $entityClass;
/** /**
* @param \Doctrine\ORM\EntityManager $em * Constructor.
*
* @param Doctrine\ORM\EntityManager $em
* @param string $entityClass Optional entity class
*/ */
public function __construct($em, $entityClass = null) public function __construct($em, $entityClass = null)
{ {
@ -51,7 +53,9 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
} }
/** /**
* @return \Doctrine\ORM\EntityManager * Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/ */
public function getEntityManager() public function getEntityManager()
{ {
@ -75,6 +79,6 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
*/ */
public function clearsAllEntities() public function clearsAllEntities()
{ {
return $this->entityClass === null; return ($this->entityClass === null);
} }
} }

View File

@ -21,37 +21,45 @@
namespace Doctrine\ORM\Event; namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
/** /**
* Provides event arguments for the preFlush event. * Provides event arguments for the preFlush event.
* *
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.de> * @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
*/ */
class OnFlushEventArgs extends \Doctrine\Common\EventArgs class OnFlushEventArgs extends \Doctrine\Common\EventArgs
{ {
/** /**
* @var EntityManager * @var Doctirne\ORM\EntityManager
*/ */
private $_em; private $em;
//private $_entitiesToPersist = array(); //private $entitiesToPersist = array();
//private $_entitiesToRemove = array(); //private $entitiesToRemove = array();
public function __construct($em) /**
* Constructor.
*
* @param Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{ {
$this->_em = $em; $this->em = $em;
} }
/** /**
* @return EntityManager * Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/ */
public function getEntityManager() public function getEntityManager()
{ {
return $this->_em; return $this->em;
} }
/* /*

View File

@ -0,0 +1,61 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\EventArgs;
/**
* Provides event arguments for the postFlush event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Daniel Freudenberger <df@rebuy.de>
*/
class PostFlushEventArgs extends EventArgs
{
/**
* @var Doctrine\ORM\EntityManager
*/
private $em;
/**
* Constructor.
*
* @param Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->em;
}
}

View File

@ -1,4 +1,23 @@
<?php <?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Event; namespace Doctrine\ORM\Event;
@ -8,6 +27,7 @@ use Doctrine\Common\EventArgs,
/** /**
* Class that holds event arguments for a preInsert/preUpdate event. * Class that holds event arguments for a preInsert/preUpdate event.
* *
* @author Guilherme Blanco <guilehrmeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0 * @since 2.0
@ -17,33 +37,40 @@ class PreUpdateEventArgs extends LifecycleEventArgs
/** /**
* @var array * @var array
*/ */
private $_entityChangeSet; private $entityChangeSet;
/** /**
* Constructor.
* *
* @param object $entity * @param object $entity
* @param EntityManager $em * @param Doctrine\ORM\EntityManager $em
* @param array $changeSet * @param array $changeSet
*/ */
public function __construct($entity, $em, array &$changeSet) public function __construct($entity, EntityManager $em, array &$changeSet)
{ {
parent::__construct($entity, $em); parent::__construct($entity, $em);
$this->_entityChangeSet = &$changeSet;
}
public function getEntityChangeSet() $this->entityChangeSet = &$changeSet;
{
return $this->_entityChangeSet;
} }
/** /**
* Field has a changeset? * Retrieve entity changeset.
* *
* @return bool * @return array
*/
public function getEntityChangeSet()
{
return $this->entityChangeSet;
}
/**
* Check if field has a changeset.
*
* @return boolean
*/ */
public function hasChangedField($field) public function hasChangedField($field)
{ {
return isset($this->_entityChangeSet[$field]); return isset($this->entityChangeSet[$field]);
} }
/** /**
@ -54,9 +81,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
*/ */
public function getOldValue($field) public function getOldValue($field)
{ {
$this->_assertValidField($field); $this->assertValidField($field);
return $this->_entityChangeSet[$field][0]; return $this->entityChangeSet[$field][0];
} }
/** /**
@ -67,9 +94,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
*/ */
public function getNewValue($field) public function getNewValue($field)
{ {
$this->_assertValidField($field); $this->assertValidField($field);
return $this->_entityChangeSet[$field][1]; return $this->entityChangeSet[$field][1];
} }
/** /**
@ -80,18 +107,24 @@ class PreUpdateEventArgs extends LifecycleEventArgs
*/ */
public function setNewValue($field, $value) public function setNewValue($field, $value)
{ {
$this->_assertValidField($field); $this->assertValidField($field);
$this->_entityChangeSet[$field][1] = $value; $this->entityChangeSet[$field][1] = $value;
} }
private function _assertValidField($field) /**
* Assert the field exists in changeset.
*
* @param string $field
*/
private function assertValidField($field)
{ {
if (!isset($this->_entityChangeSet[$field])) { if ( ! isset($this->entityChangeSet[$field])) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(sprintf(
"Field '".$field."' is not a valid field of the entity ". 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.',
"'".get_class($this->getEntity())."' in PreUpdateEventArgs." $field,
); get_class($this->getEntity())
));
} }
} }
} }

View File

@ -120,6 +120,17 @@ final class Events
*/ */
const onFlush = 'onFlush'; const onFlush = 'onFlush';
/**
* The postFlush event occurs when the EntityManager#flush() operation is invoked and
* after all actual database operations are executed successfully. The event is only raised if there is
* actually something to do for the underlying UnitOfWork. If nothing needs to be done,
* the postFlush event is not raised. The event won't be raised if an error occurs during the
* flush operation.
*
* @var string
*/
const postFlush = 'postFlush';
/** /**
* The onClear event occurs when the EntityManager#clear() operation is invoked, * The onClear event occurs when the EntityManager#clear() operation is invoked,
* after all references to entities have been removed from the unit of work. * after all references to entities have been removed from the unit of work.

View File

@ -369,10 +369,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
} }
} else if ( ! $reflField->getValue($parentObject)) { } else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField);
$coll->setOwner($parentObject, $relation);
$reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
} }
} else { } else {
// PATH B: Single-valued association // PATH B: Single-valued association

View File

@ -494,6 +494,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
*/ */
public function isTransient($class) public function isTransient($class)
{ {
if ( ! $this->initialized) {
$this->initialize();
}
return $this->driver->isTransient($class); return $this->driver->isTransient($class);
} }
} }

View File

@ -318,7 +318,7 @@ class ClassMetadataInfo implements ClassMetadata
public $discriminatorMap = array(); public $discriminatorMap = array();
/** /**
* READ-ONLY: The definition of the descriminator column used in JOINED and SINGLE_TABLE * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
* inheritance mappings. * inheritance mappings.
* *
* @var array * @var array
@ -1111,23 +1111,23 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getIdentifierColumnNames() public function getIdentifierColumnNames()
{ {
if ($this->isIdentifierComposite) {
$columnNames = array(); $columnNames = array();
foreach ($this->identifier as $idField) {
if (isset($this->associationMappings[$idField])) { foreach ($this->identifier as $idProperty) {
// no composite pk as fk entity assumption: if (isset($this->fieldMappings[$idProperty])) {
$columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name']; $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
} else {
$columnNames[] = $this->fieldMappings[$idField]['columnName']; continue;
} }
// Association defined as Id field
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
$assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
$columnNames = array_merge($columnNames, $assocColumnNames);
} }
return $columnNames; return $columnNames;
} else if(isset($this->fieldMappings[$this->identifier[0]])) {
return array($this->fieldMappings[$this->identifier[0]]['columnName']);
} else {
// no composite pk as fk entity assumption:
return array($this->associationMappings[$this->identifier[0]]['joinColumns'][0]['name']);
}
} }
/** /**
@ -1904,6 +1904,42 @@ class ClassMetadataInfo implements ClassMetadata
return $this->name; return $this->name;
} }
/**
* Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
*
* @param AbstractPlatform $platform
* @return array
*/
public function getQuotedIdentifierColumnNames($platform)
{
$quotedColumnNames = array();
foreach ($this->identifier as $idProperty) {
if (isset($this->fieldMappings[$idProperty])) {
$quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
: $this->fieldMappings[$idProperty]['columnName'];
continue;
}
// Association defined as Id field
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
$assocQuotedColumnNames = array_map(
function ($joinColumn) {
return isset($joinColumn['quoted'])
? $platform->quoteIdentifier($joinColumn['name'])
: $joinColumn['name'];
},
$joinColumns
);
$quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
}
return $quotedColumnNames;
}
/** /**
* Gets the (possibly quoted) column name of a mapped field for safe use * Gets the (possibly quoted) column name of a mapped field for safe use
* in an SQL statement. * in an SQL statement.
@ -1914,7 +1950,9 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getQuotedColumnName($field, $platform) public function getQuotedColumnName($field, $platform)
{ {
return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName']; return isset($this->fieldMappings[$field]['quoted'])
? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
: $this->fieldMappings[$field]['columnName'];
} }
/** /**

View File

@ -288,7 +288,7 @@ class MappingException extends \Doctrine\ORM\ORMException
public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName) public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName)
{ {
return new self( return new self(
"Entity '" . $className . "' has to be part of the descriminator map of '" . $rootClassName . "' " . "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " .
"to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " . "to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " .
"to avoid this exception from occuring." "to avoid this exception from occuring."
); );

View File

@ -57,17 +57,17 @@ final class NativeQuery extends AbstractQuery
*/ */
protected function _doExecute() protected function _doExecute()
{ {
$stmt = $this->_em->getConnection()->prepare($this->_sql);
$params = $this->_params; $params = $this->_params;
foreach ($params as $key => $value) { $types = $this->_paramTypes;
if (isset($this->_paramTypes[$key])) { if ($params) {
$stmt->bindValue($key, $value, $this->_paramTypes[$key]); if (is_int(key($params))) {
} else { ksort($params);
$stmt->bindValue($key, $value); ksort($types);
$params = array_values($params);
$types = array_values($types);
} }
} }
$stmt->execute();
return $stmt; return $this->_em->getConnection()->executeQuery($this->_sql, $params, $types, $this->_queryCacheProfile);
} }
} }

View File

@ -1490,8 +1490,8 @@ class BasicEntityPersister
$criteria = array_merge($criteria, $extraConditions); $criteria = array_merge($criteria, $extraConditions);
} }
$sql = 'SELECT 1' $sql = 'SELECT 1 '
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name) . $this->getLockTablesSql()
. ' WHERE ' . $this->_getSelectConditionSQL($criteria); . ' WHERE ' . $this->_getSelectConditionSQL($criteria);
list($params, $types) = $this->expandParameters($criteria); list($params, $types) = $this->expandParameters($criteria);

View File

@ -230,10 +230,12 @@ class ProxyFactory
*/ */
private function isShortIdentifierGetter($method, $class) private function isShortIdentifierGetter($method, $class)
{ {
$identifier = lcfirst(substr($method->getName(), 3));
return ( return (
$method->getNumberOfParameters() == 0 && $method->getNumberOfParameters() == 0 &&
substr($method->getName(), 0, 3) == "get" && substr($method->getName(), 0, 3) == "get" &&
in_array(lcfirst(substr($method->getName(), 3)), $class->identifier, true) && in_array($identifier, $class->identifier, true) &&
$class->hasField($identifier) &&
(($method->getEndLine() - $method->getStartLine()) <= 4) (($method->getEndLine() - $method->getStartLine()) <= 4)
); );
} }

View File

@ -232,6 +232,9 @@ final class Query extends AbstractQuery
protected function _doExecute() protected function _doExecute()
{ {
$executor = $this->_parse()->getSqlExecutor(); $executor = $this->_parse()->getSqlExecutor();
if ($this->_queryCacheProfile) {
$executor->setQueryCacheProfile($this->_queryCacheProfile);
}
// Prepare parameters // Prepare parameters
$paramMappings = $this->_parserResult->getParameterMappings(); $paramMappings = $this->_parserResult->getParameterMappings();
@ -523,7 +526,7 @@ final class Query extends AbstractQuery
* *
* @param array $params The query parameters. * @param array $params The query parameters.
* @param integer $hydrationMode The hydration mode to use. * @param integer $hydrationMode The hydration mode to use.
* @return IterableResult * @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/ */
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
{ {

View File

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -22,6 +20,7 @@
namespace Doctrine\ORM\Query\Exec; namespace Doctrine\ORM\Query\Exec;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Cache\QueryCacheProfile;
/** /**
* Base class for SQL statement executors. * Base class for SQL statement executors.
@ -34,8 +33,16 @@ use Doctrine\DBAL\Connection;
*/ */
abstract class AbstractSqlExecutor abstract class AbstractSqlExecutor
{ {
/**
* @var array
*/
protected $_sqlStatements; protected $_sqlStatements;
/**
* @var QueryCacheProfile
*/
protected $queryCacheProfile;
/** /**
* Gets the SQL statements that are executed by the executor. * Gets the SQL statements that are executed by the executor.
* *
@ -46,11 +53,17 @@ abstract class AbstractSqlExecutor
return $this->_sqlStatements; return $this->_sqlStatements;
} }
public function setQueryCacheProfile(QueryCacheProfile $qcp)
{
$this->queryCacheProfile = $qcp;
}
/** /**
* Executes all sql statements. * Executes all sql statements.
* *
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries. * @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters. * @param array $params The parameters.
* @param array $types The parameter types.
* @return Doctrine\DBAL\Driver\Statement * @return Doctrine\DBAL\Driver\Statement
*/ */
abstract public function execute(Connection $conn, array $params, array $types); abstract public function execute(Connection $conn, array $params, array $types);

View File

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -32,7 +30,6 @@ use Doctrine\DBAL\Connection,
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.doctrine-project.org * @link http://www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$
*/ */
class MultiTableDeleteExecutor extends AbstractSqlExecutor class MultiTableDeleteExecutor extends AbstractSqlExecutor
{ {
@ -102,11 +99,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
} }
/** /**
* Executes all SQL statements. * {@inheritDoc}
*
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
* @override
*/ */
public function execute(Connection $conn, array $params, array $types) public function execute(Connection $conn, array $params, array $types)
{ {

View File

@ -141,11 +141,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
} }
/** /**
* Executes all SQL statements. * {@inheritDoc}
*
* @param Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
* @override
*/ */
public function execute(Connection $conn, array $params, array $types) public function execute(Connection $conn, array $params, array $types)
{ {

View File

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -30,7 +28,6 @@ use Doctrine\DBAL\Connection,
* *
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @version $Revision$
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
*/ */
@ -41,8 +38,11 @@ class SingleSelectExecutor extends AbstractSqlExecutor
$this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST);
} }
/**
* {@inheritDoc}
*/
public function execute(Connection $conn, array $params, array $types) public function execute(Connection $conn, array $params, array $types)
{ {
return $conn->executeQuery($this->_sqlStatements, $params, $types); return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile);
} }
} }

View File

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -30,7 +28,6 @@ use Doctrine\DBAL\Connection,
* *
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @version $Revision$
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor.
@ -46,6 +43,9 @@ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor
} }
} }
/**
* {@inheritDoc}
*/
public function execute(Connection $conn, array $params, array $types) public function execute(Connection $conn, array $params, array $types)
{ {
return $conn->executeUpdate($this->_sqlStatements, $params, $types); return $conn->executeUpdate($this->_sqlStatements, $params, $types);

View File

@ -40,10 +40,12 @@ class Expr
* *
* [php] * [php]
* // (u.type = ?1) AND (u.role = ?2) * // (u.type = ?1) AND (u.role = ?2)
* $expr->andX('u.type = ?1', 'u.role = ?2')); * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2'));
* *
* @param mixed $x Optional clause. Defaults = null, but requires * @param Doctrine\ORM\Query\Expr\Comparison |
* at least one defined when converting to string. * Doctrine\ORM\Query\Expr\Func |
* Doctrine\ORM\Query\Expr\Orx
* $x Optional clause. Defaults = null, but requires at least one defined when converting to string.
* @return Expr\Andx * @return Expr\Andx
*/ */
public function andX($x = null) public function andX($x = null)

View File

@ -57,7 +57,7 @@ abstract class Base
public function add($arg) public function add($arg)
{ {
if ( $arg !== null || ($arg instanceof self && $arg->count() > 0)) { if ( $arg !== null || ($arg instanceof self && $arg->count() > 0) ) {
// If we decide to keep Expr\Base instances, we can use this check // If we decide to keep Expr\Base instances, we can use this check
if ( ! is_string($arg)) { if ( ! is_string($arg)) {
$class = get_class($arg); $class = get_class($arg);

View File

@ -157,33 +157,25 @@ class SqlWalker implements TreeWalker
*/ */
public function getExecutor($AST) public function getExecutor($AST)
{ {
$isDeleteStatement = $AST instanceof AST\DeleteStatement; switch (true) {
$isUpdateStatement = $AST instanceof AST\UpdateStatement; case ($AST instanceof AST\DeleteStatement):
$primaryClass = $this->_em->getClassMetadata($AST->deleteClause->abstractSchemaName);
if ($isDeleteStatement) { return ($primaryClass->isInheritanceTypeJoined())
$primaryClass = $this->_em->getClassMetadata( ? new Exec\MultiTableDeleteExecutor($AST, $this)
$AST->deleteClause->abstractSchemaName : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
);
if ($primaryClass->isInheritanceTypeJoined()) { case ($AST instanceof AST\UpdateStatement):
return new Exec\MultiTableDeleteExecutor($AST, $this); $primaryClass = $this->_em->getClassMetadata($AST->updateClause->abstractSchemaName);
} else {
return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
}
} else if ($isUpdateStatement) {
$primaryClass = $this->_em->getClassMetadata(
$AST->updateClause->abstractSchemaName
);
if ($primaryClass->isInheritanceTypeJoined()) { return ($primaryClass->isInheritanceTypeJoined())
return new Exec\MultiTableUpdateExecutor($AST, $this); ? new Exec\MultiTableUpdateExecutor($AST, $this)
} else { : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
}
}
default:
return new Exec\SingleSelectExecutor($AST, $this); return new Exec\SingleSelectExecutor($AST, $this);
} }
}
/** /**
* Generates a unique, short SQL table alias. * Generates a unique, short SQL table alias.
@ -260,10 +252,9 @@ class SqlWalker implements TreeWalker
$sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($class->identifier as $idField) { foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) {
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$columnName = $class->getQuotedColumnName($idField, $this->_platform);
$sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
} }
} }
@ -276,10 +267,9 @@ class SqlWalker implements TreeWalker
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($class->identifier as $idField) { foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) {
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$columnName = $class->getQuotedColumnName($idField, $this->_platform);
$sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
} }
} }
@ -1277,83 +1267,77 @@ class SqlWalker implements TreeWalker
*/ */
public function walkSimpleSelectExpression($simpleSelectExpression) public function walkSimpleSelectExpression($simpleSelectExpression)
{ {
$sql = '';
$expr = $simpleSelectExpression->expression; $expr = $simpleSelectExpression->expression;
$sql = ' ';
if ($expr instanceof AST\PathExpression) { switch (true) {
case ($expr instanceof AST\PathExpression):
$sql .= $this->walkPathExpression($expr); $sql .= $this->walkPathExpression($expr);
} else if ($expr instanceof AST\AggregateExpression) { break;
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++; case ($expr instanceof AST\AggregateExpression):
} else { $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
} else if ($expr instanceof AST\Subselect) { break;
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++; case ($expr instanceof AST\Subselect):
} else { $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
$alias = $simpleSelectExpression->fieldIdentificationVariable;
} $columnAlias = 'sclr' . $this->_aliasCounter++;
$this->_scalarResultAliasMap[$alias] = $columnAlias;
$columnAlias = $this->getSQLColumnAlias('sclr');
$sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias; break;
} else if ($expr instanceof AST\Functions\FunctionNode) {
if ( ! $simpleSelectExpression->fieldIdentificationVariable) { case ($expr instanceof AST\Functions\FunctionNode):
$alias = $this->_scalarResultCounter++; $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
} else {
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$columnAlias = $this->getSQLColumnAlias('sclr'); $columnAlias = $this->getSQLColumnAlias('sclr');
$this->_scalarResultAliasMap[$alias] = $columnAlias;
$sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias; break;
} else if (
$expr instanceof AST\SimpleArithmeticExpression || case ($expr instanceof AST\SimpleArithmeticExpression):
$expr instanceof AST\ArithmeticTerm || case ($expr instanceof AST\ArithmeticTerm):
$expr instanceof AST\ArithmeticFactor || case ($expr instanceof AST\ArithmeticFactor):
$expr instanceof AST\ArithmeticPrimary case ($expr instanceof AST\ArithmeticPrimary):
) { case ($expr instanceof AST\Literal):
if ( ! $simpleSelectExpression->fieldIdentificationVariable) { $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
$alias = $this->_scalarResultCounter++;
} else {
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$columnAlias = $this->getSQLColumnAlias('sclr'); $columnAlias = $this->getSQLColumnAlias('sclr');
$this->_scalarResultAliasMap[$alias] = $columnAlias;
$sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias; break;
} else if (
$expr instanceof AST\NullIfExpression || case ($expr instanceof AST\NullIfExpression):
$expr instanceof AST\CoalesceExpression || case ($expr instanceof AST\CoalesceExpression):
$expr instanceof AST\GeneralCaseExpression || case ($expr instanceof AST\GeneralCaseExpression):
$expr instanceof AST\SimpleCaseExpression case ($expr instanceof AST\SimpleCaseExpression):
) { $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++;
} else {
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$columnAlias = $this->getSQLColumnAlias('sclr'); $columnAlias = $this->getSQLColumnAlias('sclr');
$sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias; $this->_scalarResultAliasMap[$alias] = $columnAlias;
} else {
// IdentificationVariable $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias;
break;
default: // IdentificationVariable
$class = $this->_queryComponents[$expr]['metadata']; $class = $this->_queryComponents[$expr]['metadata'];
$tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr);
$first = true; $sqlParts = array();
foreach ($class->identifier as $identifier) { foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) {
if ($first) $first = false; else $sql .= ', '; $sqlParts[] = $tableAlias . '.' . $columnName;
$sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform);
}
} }
return ' ' . $sql; $sql .= implode(', ', $sqlParts);
break;
}
return $sql;
} }
/** /**
@ -1376,25 +1360,24 @@ class SqlWalker implements TreeWalker
*/ */
public function walkGroupByClause($groupByClause) public function walkGroupByClause($groupByClause)
{ {
$sql = ''; $sqlParts = array();
foreach ($groupByClause->groupByItems AS $groupByItem) { foreach ($groupByClause->groupByItems AS $groupByItem) {
if (is_string($groupByItem)) { if ( ! is_string($groupByItem)) {
foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { $sqlParts[] = $this->walkGroupByItem($groupByItem);
if ($sql != '') {
$sql .= ', '; continue;
} }
foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) {
$groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField);
$groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD;
$sql .= $this->walkGroupByItem($groupByItem);
} $sqlParts[] = $this->walkGroupByItem($groupByItem);
} else {
if ($sql != '') {
$sql .= ', ';
}
$sql .= $this->walkGroupByItem($groupByItem);
} }
} }
return ' GROUP BY ' . $sql;
return ' GROUP BY ' . implode(', ', $sqlParts);
} }
/** /**
@ -1416,12 +1399,11 @@ class SqlWalker implements TreeWalker
*/ */
public function walkDeleteClause(AST\DeleteClause $deleteClause) public function walkDeleteClause(AST\DeleteClause $deleteClause)
{ {
$sql = 'DELETE FROM ';
$class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform); $tableName = $class->getTableName();
$sql = 'DELETE FROM ' . $class->getQuotedTableName($this->_platform);
$this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $deleteClause->aliasIdentificationVariable);
$this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
$this->_rootAliases[] = $deleteClause->aliasIdentificationVariable; $this->_rootAliases[] = $deleteClause->aliasIdentificationVariable;
return $sql; return $sql;
@ -1435,17 +1417,14 @@ class SqlWalker implements TreeWalker
*/ */
public function walkUpdateClause($updateClause) public function walkUpdateClause($updateClause)
{ {
$sql = 'UPDATE ';
$class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform); $tableName = $class->getTableName();
$sql = 'UPDATE ' . $class->getQuotedTableName($this->_platform);
$this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $updateClause->aliasIdentificationVariable);
$this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
$this->_rootAliases[] = $updateClause->aliasIdentificationVariable; $this->_rootAliases[] = $updateClause->aliasIdentificationVariable;
$sql .= ' SET ' . implode( $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems));
', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems)
);
return $sql; return $sql;
} }
@ -1462,15 +1441,20 @@ class SqlWalker implements TreeWalker
$this->_useSqlTableAliases = false; $this->_useSqlTableAliases = false;
$sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
$newValue = $updateItem->newValue; $newValue = $updateItem->newValue;
if ($newValue === null) { switch (true) {
$sql .= 'NULL'; case ($newValue instanceof AST\Node):
} else if ($newValue instanceof AST\Node) {
$sql .= $newValue->dispatch($this); $sql .= $newValue->dispatch($this);
} else { break;
case ($newValue === null):
$sql .= 'NULL';
break;
default:
$sql .= $this->_conn->quote($newValue); $sql .= $this->_conn->quote($newValue);
break;
} }
$this->_useSqlTableAliases = $useTableAliasesBefore; $this->_useSqlTableAliases = $useTableAliasesBefore;
@ -1492,7 +1476,9 @@ class SqlWalker implements TreeWalker
if ($condSql) { if ($condSql) {
return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
} else if ($discrSql) { }
if ($discrSql) {
return ' WHERE ' . $discrSql; return ' WHERE ' . $discrSql;
} }
@ -1509,11 +1495,11 @@ class SqlWalker implements TreeWalker
{ {
// Phase 2 AST optimization: Skip processment of ConditionalExpression // Phase 2 AST optimization: Skip processment of ConditionalExpression
// if only one ConditionalTerm is defined // if only one ConditionalTerm is defined
return ( ! ($condExpr instanceof AST\ConditionalExpression)) if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
? $this->walkConditionalTerm($condExpr) return $this->walkConditionalTerm($condExpr);
: implode( }
' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)
); return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms));
} }
/** /**
@ -1526,11 +1512,11 @@ class SqlWalker implements TreeWalker
{ {
// Phase 2 AST optimization: Skip processment of ConditionalTerm // Phase 2 AST optimization: Skip processment of ConditionalTerm
// if only one ConditionalFactor is defined // if only one ConditionalFactor is defined
return ( ! ($condTerm instanceof AST\ConditionalTerm)) if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
? $this->walkConditionalFactor($condTerm) return $this->walkConditionalFactor($condTerm);
: implode( }
' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors)
); return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors));
} }
/** /**
@ -1558,7 +1544,9 @@ class SqlWalker implements TreeWalker
{ {
if ($primary->isSimpleConditionalExpression()) { if ($primary->isSimpleConditionalExpression()) {
return $primary->simpleConditionalExpression->dispatch($this); return $primary->simpleConditionalExpression->dispatch($this);
} else if ($primary->isConditionalExpression()) { }
if ($primary->isConditionalExpression()) {
$condExpr = $primary->conditionalExpression; $condExpr = $primary->conditionalExpression;
return '(' . $this->walkConditionalExpression($condExpr) . ')'; return '(' . $this->walkConditionalExpression($condExpr) . ')';
@ -1629,12 +1617,11 @@ class SqlWalker implements TreeWalker
$sql .= ' AND '; $sql .= ' AND ';
$first = true; $first = true;
foreach ($targetClass->identifier as $idField) { foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) {
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
$sql .= $targetTableAlias . '.' $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?';
. $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
} }
} else { // many-to-many } else { // many-to-many
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
@ -1679,12 +1666,11 @@ class SqlWalker implements TreeWalker
$sql .= ' AND '; $sql .= ' AND ';
$first = true; $first = true;
foreach ($targetClass->identifier as $idField) { foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) {
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
$sql .= $targetTableAlias . '.' $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?';
. $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
} }
} }
@ -1740,11 +1726,9 @@ class SqlWalker implements TreeWalker
$sql = $this->walkPathExpression($inExpr->pathExpression) $sql = $this->walkPathExpression($inExpr->pathExpression)
. ($inExpr->not ? ' NOT' : '') . ' IN ('; . ($inExpr->not ? ' NOT' : '') . ' IN (';
if ($inExpr->subselect) { $sql .= ($inExpr->subselect)
$sql .= $this->walkSubselect($inExpr->subselect); ? $this->walkSubselect($inExpr->subselect)
} else { : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals));
$sql .= implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals));
}
$sql .= ')'; $sql .= ')';
@ -1819,9 +1803,9 @@ class SqlWalker implements TreeWalker
*/ */
public function walkInParameter($inParam) public function walkInParameter($inParam)
{ {
return $inParam instanceof AST\InputParameter ? return $inParam instanceof AST\InputParameter
$this->walkInputParameter($inParam) : ? $this->walkInputParameter($inParam)
$this->walkLiteral($inParam); : $this->walkLiteral($inParam);
} }
/** /**
@ -1914,23 +1898,19 @@ class SqlWalker implements TreeWalker
*/ */
public function walkComparisonExpression($compExpr) public function walkComparisonExpression($compExpr)
{ {
$sql = '';
$leftExpr = $compExpr->leftExpression; $leftExpr = $compExpr->leftExpression;
$rightExpr = $compExpr->rightExpression; $rightExpr = $compExpr->rightExpression;
$sql = '';
if ($leftExpr instanceof AST\Node) { $sql .= ($leftExpr instanceof AST\Node)
$sql .= $leftExpr->dispatch($this); ? $leftExpr->dispatch($this)
} else { : (is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr));
$sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr);
}
$sql .= ' ' . $compExpr->operator . ' '; $sql .= ' ' . $compExpr->operator . ' ';
if ($rightExpr instanceof AST\Node) { $sql .= ($rightExpr instanceof AST\Node)
$sql .= $rightExpr->dispatch($this); ? $rightExpr->dispatch($this)
} else { : (is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr));
$sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr);
}
return $sql; return $sql;
} }
@ -1969,11 +1949,11 @@ class SqlWalker implements TreeWalker
*/ */
public function walkSimpleArithmeticExpression($simpleArithmeticExpr) public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
{ {
return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
? $this->walkArithmeticTerm($simpleArithmeticExpr) return $this->walkArithmeticTerm($simpleArithmeticExpr);
: implode( }
' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms)
); return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms));
} }
/** /**
@ -1985,22 +1965,18 @@ class SqlWalker implements TreeWalker
public function walkArithmeticTerm($term) public function walkArithmeticTerm($term)
{ {
if (is_string($term)) { if (is_string($term)) {
if (isset($this->_queryComponents[$term])) { return (isset($this->_queryComponents[$term]))
$columnName = $this->_queryComponents[$term]['token']['value']; ? $this->_scalarResultAliasMap[$this->_queryComponents[$term]['token']['value']]
: $term;
return $this->_scalarResultAliasMap[$columnName];
}
return $term;
} }
// Phase 2 AST optimization: Skip processment of ArithmeticTerm // Phase 2 AST optimization: Skip processment of ArithmeticTerm
// if only one ArithmeticFactor is defined // if only one ArithmeticFactor is defined
return ( ! ($term instanceof AST\ArithmeticTerm)) if ( ! ($term instanceof AST\ArithmeticTerm)) {
? $this->walkArithmeticFactor($term) return $this->walkArithmeticFactor($term);
: implode( }
' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors)
); return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors));
} }
/** /**
@ -2017,10 +1993,13 @@ class SqlWalker implements TreeWalker
// Phase 2 AST optimization: Skip processment of ArithmeticFactor // Phase 2 AST optimization: Skip processment of ArithmeticFactor
// if only one ArithmeticPrimary is defined // if only one ArithmeticPrimary is defined
return ( ! ($factor instanceof AST\ArithmeticFactor)) if ( ! ($factor instanceof AST\ArithmeticFactor)) {
? $this->walkArithmeticPrimary($factor) return $this->walkArithmeticPrimary($factor);
: ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')) }
. $this->walkArithmeticPrimary($factor->arithmeticPrimary);
$sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
} }
/** /**
@ -2033,7 +2012,9 @@ class SqlWalker implements TreeWalker
{ {
if ($primary instanceof AST\SimpleArithmeticExpression) { if ($primary instanceof AST\SimpleArithmeticExpression) {
return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
} else if ($primary instanceof AST\Node) { }
if ($primary instanceof AST\Node) {
return $primary->dispatch($this); return $primary->dispatch($this);
} }

View File

@ -139,6 +139,9 @@ class XmlExporter extends AbstractExporter
if (isset($field['columnName'])) { if (isset($field['columnName'])) {
$idXml->addAttribute('column', $field['columnName']); $idXml->addAttribute('column', $field['columnName']);
} }
if (isset($field['associationKey']) && $field['associationKey']) {
$idXml->addAttribute('association-key', 'true');
}
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$generatorXml = $idXml->addChild('generator'); $generatorXml = $idXml->addChild('generator');
$generatorXml->addAttribute('strategy', $idGeneratorType); $generatorXml->addAttribute('strategy', $idGeneratorType);

View File

@ -254,11 +254,17 @@ class UnitOfWork implements PropertyChangedListener
* 4) All collection updates * 4) All collection updates
* 5) All entity deletions * 5) All entity deletions
* *
* @param object $entity
* @return void
*/ */
public function commit() public function commit($entity = null)
{ {
// Compute changes done since last commit. // Compute changes done since last commit.
if ($entity === null) {
$this->computeChangeSets(); $this->computeChangeSets();
} else {
$this->computeSingleEntityChangeSet($entity);
}
if ( ! ($this->entityInsertions || if ( ! ($this->entityInsertions ||
$this->entityDeletions || $this->entityDeletions ||
@ -334,6 +340,11 @@ class UnitOfWork implements PropertyChangedListener
$coll->takeSnapshot(); $coll->takeSnapshot();
} }
// Raise postFlush
if ($this->evm->hasListeners(Events::postFlush)) {
$this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em));
}
// Clear up // Clear up
$this->entityInsertions = $this->entityInsertions =
$this->entityUpdates = $this->entityUpdates =
@ -347,6 +358,61 @@ class UnitOfWork implements PropertyChangedListener
$this->orphanRemovals = array(); $this->orphanRemovals = array();
} }
/**
* Compute the changesets of all entities scheduled for insertion
*
* @return void
*/
private function computeScheduleInsertsChangeSets()
{
foreach ($this->entityInsertions as $entity) {
$class = $this->em->getClassMetadata(get_class($entity));
$this->computeChangeSet($class, $entity);
}
}
/**
* Only flush the given entity according to a ruleset that keeps the UoW consistent.
*
* 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.
*
* @param object $entity
* @return void
*/
private function computeSingleEntityChangeSet($entity)
{
if ( ! $this->isInIdentityMap($entity) ) {
throw new \InvalidArgumentException("Entity has to be managed for single computation " . self::objToStr($entity));
}
$class = $this->em->getClassMetadata(get_class($entity));
if ($class->isChangeTrackingDeferredImplicit()) {
$this->persist($entity);
}
// Compute changes for INSERTed entities first. This must always happen even in this case.
$this->computeScheduleInsertsChangeSets();
if ( $class->isReadOnly ) {
return;
}
// Ignore uninitialized proxy objects
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
return;
}
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here.
$oid = spl_object_hash($entity);
if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) {
$this->computeChangeSet($class, $entity);
}
}
/** /**
* Executes any extra updates that have been scheduled. * Executes any extra updates that have been scheduled.
*/ */
@ -526,10 +592,7 @@ class UnitOfWork implements PropertyChangedListener
public function computeChangeSets() public function computeChangeSets()
{ {
// Compute changes for INSERTed entities first. This must always happen. // Compute changes for INSERTed entities first. This must always happen.
foreach ($this->entityInsertions as $entity) { $this->computeScheduleInsertsChangeSets();
$class = $this->em->getClassMetadata(get_class($entity));
$this->computeChangeSet($class, $entity);
}
// Compute changes for other MANAGED entities. Change tracking policies take effect here. // Compute changes for other MANAGED entities. Change tracking policies take effect here.
foreach ($this->identityMap as $className => $entities) { foreach ($this->identityMap as $className => $entities) {
@ -1991,6 +2054,11 @@ class UnitOfWork implements PropertyChangedListener
// Loading the entity right here, if its in the eager loading map get rid of it there. // Loading the entity right here, if its in the eager loading map get rid of it there.
unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
if (isset($this->eagerLoadingEntities[$class->rootEntityName]) &&
! $this->eagerLoadingEntities[$class->rootEntityName]) {
unset($this->eagerLoadingEntities[$class->rootEntityName]);
}
// Properly initialize any unfetched associations, if partial objects are not allowed. // Properly initialize any unfetched associations, if partial objects are not allowed.
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
foreach ($class->associationMappings as $field => $assoc) { foreach ($class->associationMappings as $field => $assoc) {

@ -1 +1 @@
Subproject commit ef431a14852d7e8f2d0ea789487509ab266e5ce2 Subproject commit b2fd909b4b5476df01744c9d34c7a23723a687b6

@ -1 +1 @@
Subproject commit f91395b6f469b5076f52fefd64574c443b076485 Subproject commit 537de7ea6a34edbcc40bc6ca92e0a3f816b59330

View File

@ -9,6 +9,7 @@ class DDC117Article
{ {
/** @Id @Column(type="integer", name="article_id") @GeneratedValue */ /** @Id @Column(type="integer", name="article_id") @GeneratedValue */
private $id; private $id;
/** @Column */ /** @Column */
private $title; private $title;

View File

@ -1030,4 +1030,173 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address)); $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address));
} }
/**
* @group DDC-720
*/
public function testFlushSingleManagedEntity()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->flush();
$user->status = 'administrator';
$this->_em->flush($user);
$this->_em->clear();
$user = $this->_em->find(get_class($user), $user->id);
$this->assertEquals('administrator', $user->status);
}
/**
* @group DDC-720
*/
public function testFlushSingleUnmanagedEntity()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->setExpectedException('InvalidArgumentException', 'Entity has to be managed for single computation');
$this->_em->flush($user);
}
/**
* @group DDC-720
*/
public function testFlushSingleAndNewEntity()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->flush();
$otherUser = new CmsUser;
$otherUser->name = 'Dominik2';
$otherUser->username = 'domnikl2';
$otherUser->status = 'developer';
$user->status = 'administrator';
$this->_em->persist($otherUser);
$this->_em->flush($user);
$this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager");
$this->assertTrue($otherUser->id > 0, "other user has an id");
}
/**
* @group DDC-720
*/
public function testFlushAndCascadePersist()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->flush();
$address = new CmsAddress();
$address->city = "Springfield";
$address->zip = "12354";
$address->country = "Germany";
$address->street = "Foo Street";
$address->user = $user;
$user->address = $address;
$this->_em->flush($user);
$this->assertTrue($this->_em->contains($address), "Other user is contained in EntityManager");
$this->assertTrue($address->id > 0, "other user has an id");
}
/**
* @group DDC-720
*/
public function testFlushSingleAndNoCascade()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->flush();
$article1 = new CmsArticle();
$article1->topic = 'Foo';
$article1->text = 'Foo Text';
$article1->author = $user;
$user->articles[] = $article1;
$this->setExpectedException('InvalidArgumentException', "A new entity was found through the relationship 'Doctrine\Tests\Models\CMS\CmsUser#articles'");
$this->_em->flush($user);
}
/**
* @group DDC-720
*/
public function testProxyIsIgnored()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->flush();
$this->_em->clear();
$user = $this->_em->getReference(get_class($user), $user->id);
$otherUser = new CmsUser;
$otherUser->name = 'Dominik2';
$otherUser->username = 'domnikl2';
$otherUser->status = 'developer';
$this->_em->persist($otherUser);
$this->_em->flush($user);
$this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager");
$this->assertTrue($otherUser->id > 0, "other user has an id");
}
/**
* @group DDC-720
*/
public function testFlushSingleSaveOnlySingle()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$this->_em->persist($user);
$user2 = new CmsUser;
$user2->name = 'Dominik';
$user2->username = 'domnikl2';
$user2->status = 'developer';
$this->_em->persist($user2);
$this->_em->flush();
$user->status = 'admin';
$user2->status = 'admin';
$this->_em->flush($user);
$this->_em->clear();
$user2 = $this->_em->find(get_class($user2), $user2->id);
$this->assertEquals('developer', $user2->status);
}
} }

View File

@ -98,7 +98,7 @@ class DatabaseDriverTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertArrayHasKey('CmsUsers', $metadatas, 'CmsUsers entity was not detected.'); $this->assertArrayHasKey('CmsUsers', $metadatas, 'CmsUsers entity was not detected.');
$this->assertArrayHasKey('CmsGroups', $metadatas, 'CmsGroups entity was not detected.'); $this->assertArrayHasKey('CmsGroups', $metadatas, 'CmsGroups entity was not detected.');
$this->assertEquals(1, count($metadatas['CmsUsers']->associationMappings)); $this->assertEquals(2, count($metadatas['CmsUsers']->associationMappings));
$this->assertArrayHasKey('group', $metadatas['CmsUsers']->associationMappings); $this->assertArrayHasKey('group', $metadatas['CmsUsers']->associationMappings);
$this->assertEquals(1, count($metadatas['CmsGroups']->associationMappings)); $this->assertEquals(1, count($metadatas['CmsGroups']->associationMappings));
$this->assertArrayHasKey('user', $metadatas['CmsGroups']->associationMappings); $this->assertArrayHasKey('user', $metadatas['CmsGroups']->associationMappings);

View File

@ -20,7 +20,9 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function tearDown() public function tearDown()
{ {
if ($this->_em) {
$this->_em->getConfiguration()->setEntityNamespaces(array()); $this->_em->getConfiguration()->setEntityNamespaces(array());
}
parent::tearDown(); parent::tearDown();
} }

View File

@ -0,0 +1,95 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Events;
require_once __DIR__ . '/../../TestInit.php';
/**
* PostFlushEventTest
*
* @author Daniel Freudenberger <df@rebuy.de>
*/
class PostFlushEventTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
/**
* @var PostFlushListener
*/
private $listener;
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
$this->listener = new PostFlushListener();
$evm = $this->_em->getEventManager();
$evm->addEventListener(Events::postFlush, $this->listener);
}
public function testListenerShouldBeNotified()
{
$this->_em->persist($this->createNewValidUser());
$this->_em->flush();
$this->assertTrue($this->listener->wasNotified);
}
public function testListenerShouldNotBeNotifiedWhenFlushThrowsException()
{
$user = new CmsUser();
$user->username = 'dfreudenberger';
$this->_em->persist($user);
$exceptionRaised = false;
try {
$this->_em->flush();
} catch (\Exception $ex) {
$exceptionRaised = true;
}
$this->assertTrue($exceptionRaised);
$this->assertFalse($this->listener->wasNotified);
}
public function testListenerShouldReceiveEntityManagerThroughArgs()
{
$this->_em->persist($this->createNewValidUser());
$this->_em->flush();
$receivedEm = $this->listener->receivedArgs->getEntityManager();
$this->assertSame($this->_em, $receivedEm);
}
/**
* @return CmsUser
*/
private function createNewValidUser()
{
$user = new CmsUser();
$user->username = 'dfreudenberger';
$user->name = 'Daniel Freudenberger';
return $user;
}
}
class PostFlushListener
{
/**
* @var bool
*/
public $wasNotified = false;
/**
* @var PostFlushEventArgs
*/
public $receivedArgs;
/**
* @param PostFlushEventArgs $args
*/
public function postFlush(PostFlushEventArgs $args)
{
$this->wasNotified = true;
$this->receivedArgs = $args;
}
}

View File

@ -104,7 +104,7 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
$cache = $this->getMock('Doctrine\Common\Cache\ArrayCache', array('doFetch', 'doSave')); $cache = $this->getMock('Doctrine\Common\Cache\ArrayCache', array('doFetch', 'doSave', 'doGetStats'));
$cache->expects($this->at(0)) $cache->expects($this->at(0))
->method('doFetch') ->method('doFetch')
->with($this->isType('string')) ->with($this->isType('string'))
@ -135,7 +135,7 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
->will($this->returnValue($sqlExecMock)); ->will($this->returnValue($sqlExecMock));
$cache = $this->getMock('Doctrine\Common\Cache\CacheProvider', $cache = $this->getMock('Doctrine\Common\Cache\CacheProvider',
array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush')); array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush', 'doGetStats'));
$cache->expects($this->once()) $cache->expects($this->once())
->method('doFetch') ->method('doFetch')
->with($this->isType('string')) ->with($this->isType('string'))

View File

@ -90,10 +90,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testUseResultCache() public function testUseResultCache()
{ {
$cache = new \Doctrine\Common\Cache\ArrayCache(); $cache = new \Doctrine\Common\Cache\ArrayCache();
$this->_em->getConfiguration()->setResultCacheImpl($cache);
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
$query->useResultCache(true); $query->useResultCache(true);
$query->setResultCacheDriver($cache);
$query->setResultCacheId('testing_result_cache_id'); $query->setResultCacheId('testing_result_cache_id');
$users = $query->getResult(); $users = $query->getResult();
@ -108,11 +108,11 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testUseResultCacheParams() public function testUseResultCacheParams()
{ {
$cache = new \Doctrine\Common\Cache\ArrayCache(); $cache = new \Doctrine\Common\Cache\ArrayCache();
$this->_em->getConfiguration()->setResultCacheImpl($cache);
$sqlCount = count($this->_sqlLoggerStack->queries); $sqlCount = count($this->_sqlLoggerStack->queries);
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux WHERE ux.id = ?1'); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux WHERE ux.id = ?1');
$query->setParameter(1, 1); $query->setParameter(1, 1);
$query->setResultCacheDriver($cache);
$query->useResultCache(true); $query->useResultCache(true);
$query->getResult(); $query->getResult();
@ -149,10 +149,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
} }
/** /**
* @param <type> $query * @param string $query
* @depends testNativeQueryResultCaching * @depends testNativeQueryResultCaching
*/ */
public function testResultCacheDependsOnQueryHints($query) public function testResultCacheNotDependsOnQueryHints($query)
{ {
$cache = $query->getResultCacheDriver(); $cache = $query->getResultCacheDriver();
$cacheCount = $this->getCacheSize($cache); $cacheCount = $this->getCacheSize($cache);
@ -160,7 +160,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query->setHint('foo', 'bar'); $query->setHint('foo', 'bar');
$query->getResult(); $query->getResult();
$this->assertEquals($cacheCount + 1, $this->getCacheSize($cache)); $this->assertEquals($cacheCount, $this->getCacheSize($cache));
} }
/** /**
@ -182,7 +182,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
* @param <type> $query * @param <type> $query
* @depends testNativeQueryResultCaching * @depends testNativeQueryResultCaching
*/ */
public function testResultCacheDependsOnHydrationMode($query) public function testResultCacheNotDependsOnHydrationMode($query)
{ {
$cache = $query->getResultCacheDriver(); $cache = $query->getResultCacheDriver();
$cacheCount = $this->getCacheSize($cache); $cacheCount = $this->getCacheSize($cache);
@ -190,7 +190,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode()); $this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode());
$query->getArrayResult(); $query->getArrayResult();
$this->assertEquals($cacheCount + 1, $this->getCacheSize($cache)); $this->assertEquals($cacheCount, $this->getCacheSize($cache));
} }
/** /**

View File

@ -27,15 +27,16 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
$tool = new SchemaTool($this->_em); $tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes); $sql = $tool->getCreateSchemaSql($classes);
$this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); $this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]);
$this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]); $this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]);
$this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) ENGINE = InnoDB", $sql[2]); $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) ENGINE = InnoDB", $sql[2]);
$this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) ENGINE = InnoDB", $sql[3]); $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) ENGINE = InnoDB", $sql[3]);
$this->assertEquals("ALTER TABLE cms_addresses ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[4]); $this->assertEquals("ALTER TABLE cms_addresses ADD CONSTRAINT FK_ACAC157BA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[4]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[5]); $this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id)", $sql[5]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (group_id) REFERENCES cms_groups(id)", $sql[6]); $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[6]);
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[7]); $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id)", $sql[7]);
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[8]);
$this->assertEquals(8, count($sql)); $this->assertEquals(9, count($sql));
} }
public function testGetCreateSchemaSql2() public function testGetCreateSchemaSql2()

View File

@ -0,0 +1,69 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\UnitOfWork;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1454
*/
class DDC1454Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1454File'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1454Picture'),
));
} catch (\Exception $ignored) {
}
}
public function testFailingCase()
{
$pic = new DDC1454Picture();
$this->_em->getUnitOfWork()->getEntityState($pic);
}
}
/**
* @Entity
*/
class DDC1454Picture extends DDC1454File
{
}
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"picture" = "DDC1454Picture"})
*/
class DDC1454File
{
/**
* @Column(name="file_id", type="integer")
* @Id
*/
public $fileId;
public function __construct() {
$this->fileId = rand();
}
/**
* Get fileId
*/
public function getFileId() {
return $this->fileId;
}
}

View File

@ -114,7 +114,7 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase
*/ */
public function testUnmappedEntityInHierachy() public function testUnmappedEntityInHierachy()
{ {
$this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "Entity 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' has to be part of the descriminator map of 'Doctrine\Tests\ORM\Mapping\HierachyBase' to be properly mapped in the inheritance hierachy. Alternatively you can make 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' an abstract class to avoid this exception from occuring."); $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "Entity 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' has to be part of the discriminator map of 'Doctrine\Tests\ORM\Mapping\HierachyBase' to be properly mapped in the inheritance hierachy. Alternatively you can make 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' an abstract class to avoid this exception from occuring.");
$class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\HierachyE'); $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\HierachyE');
} }

View File

@ -604,7 +604,24 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
. ' WHERE EXISTS (' . ' WHERE EXISTS ('
. 'SELECT c1_.id FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' . 'SELECT c1_.id FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id'
. ')' . ')'
);
}
public function testExistsExpressionWithSimpleSelectReturningScalar()
{
$this->assertSqlGeneration(
// DQL
// The result of this query consists of all employees whose spouses are also employees.
'SELECT DISTINCT emp FROM Doctrine\Tests\Models\CMS\CmsEmployee emp
WHERE EXISTS (
SELECT 1
FROM Doctrine\Tests\Models\CMS\CmsEmployee spouseEmp
WHERE spouseEmp = emp.spouse)',
// SQL
'SELECT DISTINCT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_'
. ' WHERE EXISTS ('
. 'SELECT 1 AS sclr2 FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id'
. ')'
); );
} }
@ -1274,6 +1291,16 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
'SELECT c0_.name AS name0 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id' 'SELECT c0_.name AS name0 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id'
); );
} }
/**
* @group DDC-1435
*/
public function testForeignKeyAsPrimaryKeySubselect()
{
$this->assertSqlGeneration(
"SELECT s FROM Doctrine\Tests\Models\DDC117\DDC117Article s WHERE EXISTS (SELECT r FROM Doctrine\Tests\Models\DDC117\DDC117Reference r WHERE r.source = s)",
"SELECT d0_.article_id AS article_id0, d0_.title AS title1 FROM DDC117Article d0_ WHERE EXISTS (SELECT d1_.source_id, d1_.target_id FROM DDC117Reference d1_ WHERE d1_.source_id = d0_.article_id)"
);
}
} }

View File

@ -70,10 +70,16 @@ class TestUtil
} else { } else {
$sm = $realConn->getSchemaManager(); $sm = $realConn->getSchemaManager();
$tableNames = $sm->listTableNames(); /* @var $schema Schema */
$schema = $sm->createSchema();
$stmts = $schema->toDropSql($realConn->getDatabasePlatform());
foreach ($tableNames AS $tableName) { foreach ($stmts AS $stmt) {
$sm->dropTable($tableName); try {
$realConn->exec($stmt);
} catch(\Exception $e) {
// TODO: Now is this a real good idea?
}
} }
} }