1
0
mirror of synced 2025-01-18 14:31:40 +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
Entities returned from EntityManager#getPartialReference() are now marked as read-only if they

View File

@ -20,7 +20,8 @@
namespace Doctrine\ORM;
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.
@ -91,34 +92,15 @@ abstract class AbstractQuery
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* The locally set cache driver used for caching result sets of this query.
*
* @var CacheDriver
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_resultCacheDriver;
/**
* 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;
protected $_queryCacheProfile;
/**
* @var boolean Boolean value that indicates whether or not expire the result cache.
*/
protected $_expireResultCache = false;
/**
* @var int Result Cache lifetime.
*/
protected $_resultCacheTTL;
/**
* 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
* @return Doctrine\ORM\AbstractQuery
@ -270,9 +252,10 @@ abstract class AbstractQuery
if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
throw ORMException::invalidResultCacheDriver();
}
$this->_resultCacheDriver = $resultCacheDriver;
if ($resultCacheDriver) {
$this->_useResultCache = true;
if ($this->_queryCacheProfile) {
$this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver);
} else {
$this->_queryCacheProfile = new QueryCacheProfile(0, null, $resultCacheDriver);
}
return $this;
}
@ -280,12 +263,13 @@ abstract class AbstractQuery
/**
* Returns the cache driver used for caching result sets.
*
* @deprecated
* @return Doctrine\Common\Cache\Cache Cache driver
*/
public function getResultCacheDriver()
{
if ($this->_resultCacheDriver) {
return $this->_resultCacheDriver;
if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
return $this->_queryCacheProfile->getResultCacheDriver();
} else {
return $this->_em->getConfiguration()->getResultCacheImpl();
}
@ -296,18 +280,17 @@ abstract class AbstractQuery
* how long and which ID to use for the cache entry.
*
* @param boolean $bool
* @param integer $timeToLive
* @param integer $lifetime
* @param string $resultCacheId
* @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 ($timeToLive) {
$this->setResultCacheLifetime($timeToLive);
}
if ($resultCacheId) {
$this->_resultCacheId = $resultCacheId;
if ($bool) {
$this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId);
} else {
$this->_queryCacheProfile = null;
}
return $this;
}
@ -315,27 +298,33 @@ abstract class AbstractQuery
/**
* 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.
*/
public function setResultCacheLifetime($timeToLive)
public function setResultCacheLifetime($lifetime)
{
if ($timeToLive !== null) {
$timeToLive = (int) $timeToLive;
if ($lifetime === null) {
$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;
}
/**
* Retrieves the lifetime of resultset cache.
*
* @deprecated
* @return integer
*/
public function getResultCacheLifetime()
{
return $this->_resultCacheTTL;
return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
}
/**
@ -360,6 +349,14 @@ abstract class AbstractQuery
return $this->_expireResultCache;
}
/**
* @return QueryCacheProfile
*/
public function getQueryCacheProfile()
{
return $this->_queryCacheProfile;
}
/**
* 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 integer $hydrationMode The hydration mode to use.
* @return IterableResult
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/
public function iterate(array $params = array(), $hydrationMode = null)
{
@ -584,28 +581,6 @@ abstract class AbstractQuery
$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();
if (is_numeric($stmt)) {
@ -627,43 +602,23 @@ abstract class AbstractQuery
*/
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;
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
* automatically generated for you.
* Get the result cache id to use to store the result set cache entry if set.
*
* @return array ($key, $hash)
* @deprecated
* @return string
*/
protected function getResultCacheId()
public function getResultCacheId()
{
if ($this->_resultCacheId) {
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));
}
return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
}
/**

View File

@ -209,27 +209,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
$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).
*

View File

@ -325,13 +325,17 @@ class EntityManager implements ObjectManager
* This effectively synchronizes the in-memory state of managed objects with the
* 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
* makes use of optimistic locking fails.
*/
public function flush()
public function flush($entity = null)
{
$this->errorIfClosed();
$this->unitOfWork->commit();
$this->unitOfWork->commit($entity);
}
/**

View File

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

View File

@ -19,42 +19,59 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
/**
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
* of entities.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Roman Borschel <roman@code-factory.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
*/
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->_em = $em;
}
public function getEntity()
{
return $this->_entity;
$this->entity = $entity;
$this->em = $em;
}
/**
* @return EntityManager
* Retireve associated Entity.
*
* @return object
*/
public function getEntity()
{
return $this->entity;
}
/**
* Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->_em;
return $this->em;
}
}

View File

@ -1,9 +1,25 @@
<?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;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\EntityManager;
@ -16,18 +32,20 @@ use Doctrine\ORM\EntityManager;
class LoadClassMetadataEventArgs extends EventArgs
{
/**
* @var ClassMetadata
* @var Doctrine\ORM\Mapping\ClassMetadata
*/
private $classMetadata;
/**
* @var EntityManager
* @var Doctrine\ORM\EntityManager
*/
private $em;
/**
* @param ClassMetadataInfo $classMetadata
* @param EntityManager $em
* Constructor.
*
* @param Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata
* @param Doctrine\ORM\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()
{
@ -44,7 +64,9 @@ class LoadClassMetadataEventArgs extends EventArgs
}
/**
* @return EntityManager
* Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{

View File

@ -23,16 +23,15 @@ namespace Doctrine\ORM\Event;
* Provides event arguments for the onClear event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class OnClearEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var \Doctrine\ORM\EntityManager
* @var Doctrine\ORM\EntityManager
*/
private $em;
@ -42,7 +41,10 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
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)
{
@ -51,7 +53,9 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
}
/**
* @return \Doctrine\ORM\EntityManager
* Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
@ -75,6 +79,6 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
*/
public function clearsAllEntities()
{
return $this->entityClass === null;
return ($this->entityClass === null);
}
}

View File

@ -21,37 +21,45 @@
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
/**
* Provides event arguments for the preFlush event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class OnFlushEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var EntityManager
* @var Doctirne\ORM\EntityManager
*/
private $_em;
private $em;
//private $_entitiesToPersist = array();
//private $_entitiesToRemove = array();
//private $entitiesToPersist = 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()
{
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
/*
* $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;
@ -8,6 +27,7 @@ use Doctrine\Common\EventArgs,
/**
* Class that holds event arguments for a preInsert/preUpdate event.
*
* @author Guilherme Blanco <guilehrmeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
@ -17,33 +37,40 @@ class PreUpdateEventArgs extends LifecycleEventArgs
/**
* @var array
*/
private $_entityChangeSet;
private $entityChangeSet;
/**
* Constructor.
*
* @param object $entity
* @param EntityManager $em
* @param Doctrine\ORM\EntityManager $em
* @param array $changeSet
*/
public function __construct($entity, $em, array &$changeSet)
public function __construct($entity, EntityManager $em, array &$changeSet)
{
parent::__construct($entity, $em);
$this->_entityChangeSet = &$changeSet;
}
public function getEntityChangeSet()
{
return $this->_entityChangeSet;
$this->entityChangeSet = &$changeSet;
}
/**
* 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)
{
return isset($this->_entityChangeSet[$field]);
return isset($this->entityChangeSet[$field]);
}
/**
@ -54,9 +81,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
*/
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)
{
$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)
{
$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])) {
throw new \InvalidArgumentException(
"Field '".$field."' is not a valid field of the entity ".
"'".get_class($this->getEntity())."' in PreUpdateEventArgs."
);
if ( ! isset($this->entityChangeSet[$field])) {
throw new \InvalidArgumentException(sprintf(
'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.',
$field,
get_class($this->getEntity())
));
}
}
}

View File

@ -120,6 +120,17 @@ final class Events
*/
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,
* 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];
}
} else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
$coll->setOwner($parentObject, $relation);
$reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField);
}
} else {
// PATH B: Single-valued association

View File

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

View File

@ -318,7 +318,7 @@ class ClassMetadataInfo implements ClassMetadata
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.
*
* @var array
@ -1111,23 +1111,23 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function getIdentifierColumnNames()
{
if ($this->isIdentifierComposite) {
$columnNames = array();
foreach ($this->identifier as $idField) {
if (isset($this->associationMappings[$idField])) {
// no composite pk as fk entity assumption:
$columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name'];
} else {
$columnNames[] = $this->fieldMappings[$idField]['columnName'];
foreach ($this->identifier as $idProperty) {
if (isset($this->fieldMappings[$idProperty])) {
$columnNames[] = $this->fieldMappings[$idProperty]['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;
} 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;
}
/**
* 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
* in an SQL statement.
@ -1914,7 +1950,9 @@ class ClassMetadataInfo implements ClassMetadata
*/
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)
{
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 avoid this exception from occuring."
);

View File

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

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

View File

@ -230,10 +230,12 @@ class ProxyFactory
*/
private function isShortIdentifierGetter($method, $class)
{
$identifier = lcfirst(substr($method->getName(), 3));
return (
$method->getNumberOfParameters() == 0 &&
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)
);
}

View File

@ -232,6 +232,9 @@ final class Query extends AbstractQuery
protected function _doExecute()
{
$executor = $this->_parse()->getSqlExecutor();
if ($this->_queryCacheProfile) {
$executor->setQueryCacheProfile($this->_queryCacheProfile);
}
// Prepare parameters
$paramMappings = $this->_parserResult->getParameterMappings();
@ -523,7 +526,7 @@ final class Query extends AbstractQuery
*
* @param array $params The query parameters.
* @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)
{

View File

@ -1,7 +1,5 @@
<?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
@ -22,6 +20,7 @@
namespace Doctrine\ORM\Query\Exec;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Cache\QueryCacheProfile;
/**
* Base class for SQL statement executors.
@ -34,8 +33,16 @@ use Doctrine\DBAL\Connection;
*/
abstract class AbstractSqlExecutor
{
/**
* @var array
*/
protected $_sqlStatements;
/**
* @var QueryCacheProfile
*/
protected $queryCacheProfile;
/**
* Gets the SQL statements that are executed by the executor.
*
@ -46,11 +53,17 @@ abstract class AbstractSqlExecutor
return $this->_sqlStatements;
}
public function setQueryCacheProfile(QueryCacheProfile $qcp)
{
$this->queryCacheProfile = $qcp;
}
/**
* Executes all sql statements.
*
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
* @param array $types The parameter types.
* @return Doctrine\DBAL\Driver\Statement
*/
abstract public function execute(Connection $conn, array $params, array $types);

View File

@ -1,7 +1,5 @@
<?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
@ -32,7 +30,6 @@ use Doctrine\DBAL\Connection,
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.doctrine-project.org
* @since 2.0
* @version $Revision$
*/
class MultiTableDeleteExecutor extends AbstractSqlExecutor
{
@ -102,11 +99,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
}
/**
* Executes all SQL statements.
*
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
* @override
* {@inheritDoc}
*/
public function execute(Connection $conn, array $params, array $types)
{

View File

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

View File

@ -1,7 +1,5 @@
<?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
@ -30,7 +28,6 @@ use Doctrine\DBAL\Connection,
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @author Roman Borschel <roman@code-factory.org>
* @version $Revision$
* @link www.doctrine-project.org
* @since 2.0
*/
@ -41,8 +38,11 @@ class SingleSelectExecutor extends AbstractSqlExecutor
$this->_sqlStatements = $sqlWalker->walkSelectStatement($AST);
}
/**
* {@inheritDoc}
*/
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
/*
* $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
@ -30,7 +28,6 @@ use Doctrine\DBAL\Connection,
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @author Roman Borschel <roman@code-factory.org>
* @version $Revision$
* @link www.doctrine-project.org
* @since 2.0
* @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)
{
return $conn->executeUpdate($this->_sqlStatements, $params, $types);

View File

@ -40,10 +40,12 @@ class Expr
*
* [php]
* // (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
* at least one defined when converting to string.
* @param Doctrine\ORM\Query\Expr\Comparison |
* 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
*/
public function andX($x = null)

View File

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

View File

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

View File

@ -254,11 +254,17 @@ class UnitOfWork implements PropertyChangedListener
* 4) All collection updates
* 5) All entity deletions
*
* @param object $entity
* @return void
*/
public function commit()
public function commit($entity = null)
{
// Compute changes done since last commit.
if ($entity === null) {
$this->computeChangeSets();
} else {
$this->computeSingleEntityChangeSet($entity);
}
if ( ! ($this->entityInsertions ||
$this->entityDeletions ||
@ -334,6 +340,11 @@ class UnitOfWork implements PropertyChangedListener
$coll->takeSnapshot();
}
// Raise postFlush
if ($this->evm->hasListeners(Events::postFlush)) {
$this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em));
}
// Clear up
$this->entityInsertions =
$this->entityUpdates =
@ -347,6 +358,61 @@ class UnitOfWork implements PropertyChangedListener
$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.
*/
@ -526,10 +592,7 @@ class UnitOfWork implements PropertyChangedListener
public function computeChangeSets()
{
// Compute changes for INSERTed entities first. This must always happen.
foreach ($this->entityInsertions as $entity) {
$class = $this->em->getClassMetadata(get_class($entity));
$this->computeChangeSet($class, $entity);
}
$this->computeScheduleInsertsChangeSets();
// Compute changes for other MANAGED entities. Change tracking policies take effect here.
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.
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.
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
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 */
private $id;
/** @Column */
private $title;

View File

@ -1030,4 +1030,173 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$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('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->assertEquals(1, count($metadatas['CmsGroups']->associationMappings));
$this->assertArrayHasKey('user', $metadatas['CmsGroups']->associationMappings);

View File

@ -20,7 +20,9 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function tearDown()
{
if ($this->_em) {
$this->_em->getConfiguration()->setEntityNamespaces(array());
}
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');
$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))
->method('doFetch')
->with($this->isType('string'))
@ -135,7 +135,7 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
->will($this->returnValue($sqlExecMock));
$cache = $this->getMock('Doctrine\Common\Cache\CacheProvider',
array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush'));
array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush', 'doGetStats'));
$cache->expects($this->once())
->method('doFetch')
->with($this->isType('string'))

View File

@ -90,10 +90,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testUseResultCache()
{
$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->useResultCache(true);
$query->setResultCacheDriver($cache);
$query->setResultCacheId('testing_result_cache_id');
$users = $query->getResult();
@ -108,11 +108,11 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testUseResultCacheParams()
{
$cache = new \Doctrine\Common\Cache\ArrayCache();
$this->_em->getConfiguration()->setResultCacheImpl($cache);
$sqlCount = count($this->_sqlLoggerStack->queries);
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux WHERE ux.id = ?1');
$query->setParameter(1, 1);
$query->setResultCacheDriver($cache);
$query->useResultCache(true);
$query->getResult();
@ -149,10 +149,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
}
/**
* @param <type> $query
* @param string $query
* @depends testNativeQueryResultCaching
*/
public function testResultCacheDependsOnQueryHints($query)
public function testResultCacheNotDependsOnQueryHints($query)
{
$cache = $query->getResultCacheDriver();
$cacheCount = $this->getCacheSize($cache);
@ -160,7 +160,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query->setHint('foo', 'bar');
$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
* @depends testNativeQueryResultCaching
*/
public function testResultCacheDependsOnHydrationMode($query)
public function testResultCacheNotDependsOnHydrationMode($query)
{
$cache = $query->getResultCacheDriver();
$cacheCount = $this->getCacheSize($cache);
@ -190,7 +190,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode());
$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);
$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_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_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_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(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_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[7]);
$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 ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id)", $sql[5]);
$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_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()

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()
{
$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');
}

View File

@ -604,7 +604,24 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
. ' WHERE EXISTS ('
. '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'
);
}
/**
* @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 {
$sm = $realConn->getSchemaManager();
$tableNames = $sm->listTableNames();
/* @var $schema Schema */
$schema = $sm->createSchema();
$stmts = $schema->toDropSql($realConn->getDatabasePlatform());
foreach ($tableNames AS $tableName) {
$sm->dropTable($tableName);
foreach ($stmts AS $stmt) {
try {
$realConn->exec($stmt);
} catch(\Exception $e) {
// TODO: Now is this a real good idea?
}
}
}