diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2 index 220fb39f9..757413029 100644 --- a/UPGRADE_TO_2_2 +++ b/UPGRADE_TO_2_2 @@ -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 diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 5d71cf0aa..72eab194b 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -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 AbstractQuery. * @@ -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; } /** diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index ee6496087..eaa4df562 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -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). * diff --git a/lib/Doctrine/ORM/Event/EntityEventDelegator.php b/lib/Doctrine/ORM/Event/EntityEventDelegator.php index d7c46e68e..09532bc4b 100644 --- a/lib/Doctrine/ORM/Event/EntityEventDelegator.php +++ b/lib/Doctrine/ORM/Event/EntityEventDelegator.php @@ -19,14 +19,16 @@ 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. * - * @author Benjamin Eberlei - * @since 2.2 + * @link www.doctrine-project.org + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @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); + $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])) { - $listenerData['listener']->$eventName($event); - } + + if ( ! isset($listenerData['entities'][$class])) continue; + + $listenerData['listener']->$eventName($event); } } } diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php index a5dd39cfd..0c91d8475 100644 --- a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -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. * - * @since 2.0 + * @link www.doctrine-project.org + * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ -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; + $this->entity = $entity; + $this->em = $em; } + /** + * Retireve associated Entity. + * + * @return object + */ public function getEntity() { - return $this->_entity; + return $this->entity; } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { - return $this->_em; + return $this->em; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php index f00520a20..a87f45cc3 100644 --- a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php +++ b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php @@ -1,9 +1,25 @@ . + */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; - use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\EntityManager; @@ -11,32 +27,36 @@ use Doctrine\ORM\EntityManager; * Class that holds event arguments for a loadMetadata event. * * @author Jonathan H. Wage - * @since 2.0 + * @since 2.0 */ 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) { $this->classMetadata = $classMetadata; - $this->em = $em; + $this->em = $em; } /** - * @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() { diff --git a/lib/Doctrine/ORM/Event/OnClearEventArgs.php b/lib/Doctrine/ORM/Event/OnClearEventArgs.php index 60ce4b3eb..49b5e8695 100644 --- a/lib/Doctrine/ORM/Event/OnClearEventArgs.php +++ b/lib/Doctrine/ORM/Event/OnClearEventArgs.php @@ -15,7 +15,7 @@ * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * . -*/ + */ namespace Doctrine\ORM\Event; @@ -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 * @author Benjamin Eberlei */ class OnClearEventArgs extends \Doctrine\Common\EventArgs { /** - * @var \Doctrine\ORM\EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; @@ -42,16 +41,21 @@ 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) { - $this->em = $em; + $this->em = $em; $this->entityClass = $entityClass; } /** - * @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); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Event/OnFlushEventArgs.php b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php index 1b4cb9ba8..5e6e839fe 100644 --- a/lib/Doctrine/ORM/Event/OnFlushEventArgs.php +++ b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php @@ -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 * @author Benjamin Eberlei */ 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; } /* diff --git a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php index 92e88ae21..f500ad92f 100644 --- a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php +++ b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php @@ -17,9 +17,10 @@ * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * . -*/ + */ namespace Doctrine\ORM\Event; + use Doctrine\ORM\EntityManager; use Doctrine\Common\EventArgs; @@ -27,20 +28,21 @@ 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.com + * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Daniel Freudenberger */ class PostFlushEventArgs extends EventArgs { /** - * @var EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; /** - * @param EntityManager $em + * Constructor. + * + * @param Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { @@ -48,7 +50,9 @@ class PostFlushEventArgs extends EventArgs } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { diff --git a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php index ab1cc15de..35539591a 100644 --- a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php +++ b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\ORM\Event; @@ -8,42 +27,50 @@ use Doctrine\Common\EventArgs, /** * Class that holds event arguments for a preInsert/preUpdate event. * + * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei - * @since 2.0 + * @since 2.0 */ 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 array + */ + public function getEntityChangeSet() + { + return $this->entityChangeSet; + } + + /** + * Check if field has a changeset. * - * @return bool + * @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()) + )); } } } diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php index 90a35fa12..2e2d4f2f6 100644 --- a/lib/Doctrine/ORM/Id/AssignedGenerator.php +++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\ORMException; /** @@ -42,46 +43,29 @@ class AssignedGenerator extends AbstractIdGenerator */ public function generate(EntityManager $em, $entity) { - $class = $em->getClassMetadata(get_class($entity)); + $class = $em->getClassMetadata(get_class($entity)); + $idFields = $class->getIdentifierFieldNames(); $identifier = array(); - if ($class->isIdentifierComposite) { - $idFields = $class->getIdentifierFieldNames(); - foreach ($idFields as $idField) { - $value = $class->reflFields[$idField]->getValue($entity); - if (isset($value)) { - if (isset($class->associationMappings[$idField])) { - if (!$em->getUnitOfWork()->isInIdentityMap($value)) { - throw ORMException::entityMissingForeignAssignedId($entity, $value); - } - - // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. - $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); - } else { - $identifier[$idField] = $value; - } - } else { - throw ORMException::entityMissingAssignedIdForField($entity, $idField); - } - } - } else { - $idField = $class->identifier[0]; + + foreach ($idFields as $idField) { $value = $class->reflFields[$idField]->getValue($entity); - if (isset($value)) { - if (isset($class->associationMappings[$idField])) { - if (!$em->getUnitOfWork()->isInIdentityMap($value)) { - throw ORMException::entityMissingForeignAssignedId($entity, $value); - } - - // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. - $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); - } else { - $identifier[$idField] = $value; - } - } else { + + if ( ! isset($value)) { throw ORMException::entityMissingAssignedIdForField($entity, $idField); } - } + + if (isset($class->associationMappings[$idField])) { + if ( ! $em->getUnitOfWork()->isInIdentityMap($value)) { + throw ORMException::entityMissingForeignAssignedId($entity, $value); + } + // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. + $value = current($em->getUnitOfWork()->getEntityIdentifier($value)); + } + + $identifier[$idField] = $value; + } + return $identifier; } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 1287a138b..5e0c9c0be 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -62,7 +62,7 @@ class ObjectHydrator extends AbstractHydrator if (!isset($this->_hints['deferEagerLoad'])) { $this->_hints['deferEagerLoad'] = true; } - + foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; @@ -116,13 +116,13 @@ class ObjectHydrator extends AbstractHydrator protected function _cleanup() { $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; - + parent::_cleanup(); $this->_identifierMap = $this->_initializedCollections = $this->_existingCollections = $this->_resultPointers = array(); - + if ($eagerLoad) { $this->_em->getUnitOfWork()->triggerEagerLoads(); } @@ -192,7 +192,7 @@ class ObjectHydrator extends AbstractHydrator /** * Gets an entity instance. - * + * * @param $data The instance data. * @param $dqlAlias The DQL alias of the entity's class. * @return object The entity. @@ -205,12 +205,12 @@ class ObjectHydrator extends AbstractHydrator $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } - + if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { $class = $this->_ce[$className]; $this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } - + return $this->_uow->createEntity($className, $data, $this->_hints); } @@ -240,7 +240,7 @@ class ObjectHydrator extends AbstractHydrator * Gets a ClassMetadata instance from the local cache. * If the instance is not yet in the local cache, it is loaded into the * local cache. - * + * * @param string $className The name of the class. * @return ClassMetadata */ @@ -254,21 +254,21 @@ class ObjectHydrator extends AbstractHydrator /** * Hydrates a single row in an SQL result set. - * + * * @internal * First, the data of the row is split into chunks where each chunk contains data * that belongs to a particular component/class. Afterwards, all these chunks * are processed, one after the other. For each chunk of class data only one of the * following code paths is executed: - * + * * Path A: The data chunk belongs to a joined/associated object and the association * is collection-valued. * Path B: The data chunk belongs to a joined/associated object and the association * is single-valued. * Path C: The data chunk belongs to a root result element/object that appears in the topmost * level of the hydrated result. A typical example are the objects of the type - * specified by the FROM clause in a DQL query. - * + * specified by the FROM clause in a DQL query. + * * @param array $data The data of the row to process. * @param array $cache The cache to use. * @param array $result The result array to fill. @@ -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 diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 25a3350d2..10f7bc4b6 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1111,25 +1111,25 @@ 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']; - } + $columnNames = array(); + + foreach ($this->identifier as $idProperty) { + if (isset($this->fieldMappings[$idProperty])) { + $columnNames[] = $this->fieldMappings[$idProperty]['columnName']; + + continue; } - 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']); + + // 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; } - + /** * Sets the type of Id generator to use for the mapped class. */ @@ -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']; } /** diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index b676ca8da..fd6bf081a 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -89,7 +89,7 @@ class XmlDriver extends AbstractFileDriver if (isset($xmlRoot['schema'])) { $metadata->table['schema'] = (string)$xmlRoot['schema']; }*/ - + if (isset($xmlRoot['inheritance-type'])) { $inheritanceType = (string)$xmlRoot['inheritance-type']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); @@ -327,6 +327,8 @@ class XmlDriver extends AbstractFileDriver if (isset($oneToManyElement['index-by'])) { $mapping['indexBy'] = (string)$oneToManyElement['index-by']; + } else if (isset($oneToManyElement->{'index-by'})) { + throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapOneToMany($mapping); @@ -432,8 +434,10 @@ class XmlDriver extends AbstractFileDriver $mapping['orderBy'] = $orderBy; } - if (isset($manyToManyElement->{'index-by'})) { - $mapping['indexBy'] = (string)$manyToManyElement->{'index-by'}; + if (isset($manyToManyElement['index-by'])) { + $mapping['indexBy'] = (string)$manyToManyElement['index-by']; + } else if (isset($manyToManyElement->{'index-by'})) { + throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapManyToMany($mapping); diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php index 2c0a5ab28..dea223fa3 100644 --- a/lib/Doctrine/ORM/NativeQuery.php +++ b/lib/Doctrine/ORM/NativeQuery.php @@ -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); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 82d616686..b64cacfdc 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -93,21 +93,21 @@ final class PersistentCollection implements Collection /** * Whether the collection has already been initialized. - * + * * @var boolean */ private $initialized = true; - + /** * The wrapped Collection instance. - * + * * @var Collection */ private $coll; /** * Creates a new persistent collection. - * + * * @param EntityManager $em The EntityManager the collection will be associated with. * @param ClassMetadata $class The class descriptor of the entity type of this collection. * @param array The collection elements. @@ -144,7 +144,7 @@ final class PersistentCollection implements Collection { return $this->owner; } - + public function getTypeClass() { return $this->typeClass; @@ -154,7 +154,7 @@ final class PersistentCollection implements Collection * INTERNAL: * Adds an element to a collection during hydration. This will automatically * complete bidirectional associations in the case of a one-to-many association. - * + * * @param mixed $element The element to add. */ public function hydrateAdd($element) @@ -172,7 +172,7 @@ final class PersistentCollection implements Collection $this->owner); } } - + /** * INTERNAL: * Sets a keyed element in the collection during hydration. @@ -271,7 +271,7 @@ final class PersistentCollection implements Collection { return $this->association; } - + /** * Marks this collection as changed/dirty. */ @@ -306,17 +306,17 @@ final class PersistentCollection implements Collection { $this->isDirty = $dirty; } - + /** * Sets the initialized flag of the collection, forcing it into that state. - * + * * @param boolean $bool */ public function setInitialized($bool) { $this->initialized = $bool; } - + /** * Checks whether this collection has been initialized. * @@ -377,7 +377,7 @@ final class PersistentCollection implements Collection $this->em->getUnitOfWork()->getCollectionPersister($this->association) ->deleteRows($this, $element); }*/ - + $this->initialize(); $removed = $this->coll->removeElement($element); if ($removed) { @@ -410,7 +410,7 @@ final class PersistentCollection implements Collection ->getCollectionPersister($this->association) ->contains($this, $element); } - + $this->initialize(); return $this->coll->contains($element); } @@ -468,7 +468,7 @@ final class PersistentCollection implements Collection if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { return $this->em->getUnitOfWork() ->getCollectionPersister($this->association) - ->count($this) + $this->coll->count(); + ->count($this) + ($this->isDirty ? $this->coll->count() : 0); } $this->initialize(); @@ -503,7 +503,7 @@ final class PersistentCollection implements Collection $this->initialize(); return $this->coll->isEmpty(); } - + /** * {@inheritdoc} */ @@ -530,7 +530,7 @@ final class PersistentCollection implements Collection $this->initialize(); return $this->coll->filter($p); } - + /** * {@inheritdoc} */ @@ -548,7 +548,7 @@ final class PersistentCollection implements Collection $this->initialize(); return $this->coll->partition($p); } - + /** * {@inheritdoc} */ @@ -579,7 +579,7 @@ final class PersistentCollection implements Collection $this->takeSnapshot(); } } - + /** * Called by PHP when this collection is serialized. Ensures that only the * elements are properly serialized. @@ -591,7 +591,7 @@ final class PersistentCollection implements Collection { return array('coll', 'initialized'); } - + /* ArrayAccess implementation */ /** @@ -629,12 +629,12 @@ final class PersistentCollection implements Collection { return $this->remove($offset); } - + public function key() { return $this->coll->key(); } - + /** * Gets the element of the collection at the current iterator position. */ @@ -642,7 +642,7 @@ final class PersistentCollection implements Collection { return $this->coll->current(); } - + /** * Moves the internal iterator position to the next element. */ @@ -650,7 +650,7 @@ final class PersistentCollection implements Collection { return $this->coll->next(); } - + /** * Retrieves the wrapped Collection instance. */ @@ -672,7 +672,10 @@ final class PersistentCollection implements Collection */ public function slice($offset, $length = null) { - if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + if ( ! $this->initialized && + ! $this->isDirty && + $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + return $this->em->getUnitOfWork() ->getCollectionPersister($this->association) ->slice($this, $offset, $length); diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php index 670cf11e7..84540a337 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php @@ -62,7 +62,7 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister { $columnName = $class->columnNames[$field]; $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); - $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); + $columnAlias = $this->getSQLColumnAlias($columnName); $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name); return $sql . ' AS ' . $columnAlias; @@ -70,10 +70,9 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className) { - $columnAlias = $joinColumnName . $this->_sqlAliasCounter++; - $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName); + $columnAlias = $this->getSQLColumnAlias($joinColumnName); + $this->_rsm->addMetaResult('r', $columnAlias, $joinColumnName); return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index b747155b3..9d04cf5f1 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -92,7 +92,7 @@ class BasicEntityPersister /** * The database platform. - * + * * @var Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; @@ -110,12 +110,12 @@ class BasicEntityPersister * @var array */ protected $_queuedInserts = array(); - + /** * ResultSetMapping that is used for all queries. Is generated lazily once per request. - * + * * TODO: Evaluate Caching in combination with the other cached SQL snippets. - * + * * @var Query\ResultSetMapping */ protected $_rsm; @@ -123,7 +123,7 @@ class BasicEntityPersister /** * The map of column names to DBAL mapping types of all prepared columns used * when INSERTing or UPDATEing an entity. - * + * * @var array * @see _prepareInsertData($entity) * @see _prepareUpdateData($entity) @@ -133,7 +133,7 @@ class BasicEntityPersister /** * The INSERT SQL statement used for entities handled by this persister. * This SQL is only generated once per request, if at all. - * + * * @var string */ private $_insertSql; @@ -141,29 +141,29 @@ class BasicEntityPersister /** * The SELECT column list SQL fragment used for querying entities by this persister. * This SQL fragment is only generated once per request, if at all. - * + * * @var string */ protected $_selectColumnListSql; - + /** * The JOIN SQL fragement used to eagerly load all many-to-one and one-to-one * associations configured as FETCH_EAGER, aswell as all inverse one-to-one associations. - * + * * @var string */ protected $_selectJoinSql; /** * Counter for creating unique SQL table and column aliases. - * + * * @var integer */ protected $_sqlAliasCounter = 0; /** * Map from class names (FQCN) to the corresponding generated SQL table aliases. - * + * * @var array */ protected $_sqlTableAliases = array(); @@ -171,7 +171,7 @@ class BasicEntityPersister /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. - * + * * @param Doctrine\ORM\EntityManager $em * @param Doctrine\ORM\Mapping\ClassMetadata $class */ @@ -205,7 +205,7 @@ class BasicEntityPersister /** * Executes all queued entity insertions and returns any generated post-insert * identifiers that were created as a result of the insertions. - * + * * If no inserts are queued, invoking this method is a NOOP. * * @return array An array of any generated post-insert IDs. This will be an empty array @@ -229,7 +229,7 @@ class BasicEntityPersister if (isset($insertData[$tableName])) { $paramIndex = 1; - + foreach ($insertData[$tableName] as $column => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]); } @@ -257,7 +257,7 @@ class BasicEntityPersister /** * Retrieves the default version value which was created - * by the preceding INSERT statement and assigns it back in to the + * by the preceding INSERT statement and assigns it back in to the * entities version field. * * @param object $entity @@ -271,7 +271,7 @@ class BasicEntityPersister /** * Fetch the current version value of a versioned entity. - * + * * @param Doctrine\ORM\Mapping\ClassMetadata $versionedClass * @param mixed $id * @return mixed @@ -280,9 +280,9 @@ class BasicEntityPersister { $versionField = $versionedClass->versionField; $identifier = $versionedClass->getIdentifierColumnNames(); - + $versionFieldColumnName = $versionedClass->getQuotedColumnName($versionField, $this->_platform); - + //FIXME: Order with composite keys might not be correct $sql = 'SELECT ' . $versionFieldColumnName . ' FROM ' . $versionedClass->getQuotedTableName($this->_platform) @@ -299,7 +299,7 @@ class BasicEntityPersister * The data to update is retrieved through {@link _prepareUpdateData}. * Subclasses that override this method are supposed to obtain the update data * in the same way, through {@link _prepareUpdateData}. - * + * * Subclasses are also supposed to take care of versioning when overriding this method, * if necessary. The {@link _updateTable} method can be used to apply the data retrieved * from {@_prepareUpdateData} on the target tables, thereby optionally applying versioning. @@ -310,7 +310,7 @@ class BasicEntityPersister { $updateData = $this->_prepareUpdateData($entity); $tableName = $this->_class->getTableName(); - + if (isset($updateData[$tableName]) && $updateData[$tableName]) { $this->_updateTable( $entity, $this->_class->getQuotedTableName($this->_platform), @@ -338,17 +338,17 @@ class BasicEntityPersister $set = $params = $types = array(); foreach ($updateData as $columnName => $value) { - $set[] = (isset($this->_class->fieldNames[$columnName])) + $set[] = (isset($this->_class->fieldNames[$columnName])) ? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?' : $columnName . ' = ?'; - + $params[] = $value; $types[] = $this->_columnTypes[$columnName]; } $where = array(); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); - + foreach ($this->_class->identifier as $idField) { if (isset($this->_class->associationMappings[$idField])) { $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); @@ -366,13 +366,13 @@ class BasicEntityPersister $versionField = $this->_class->versionField; $versionFieldType = $this->_class->fieldMappings[$versionField]['type']; $versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform); - + if ($versionFieldType == Type::INTEGER) { $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; } else if ($versionFieldType == Type::DATETIME) { $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; } - + $where[] = $versionColumn; $params[] = $this->_class->reflFields[$versionField]->getValue($entity); $types[] = $this->_class->fieldMappings[$versionField]['type']; @@ -401,18 +401,18 @@ class BasicEntityPersister // @Todo this only covers scenarios with no inheritance or of the same level. Is there something // like self-referential relationship between different levels of an inheritance hierachy? I hope not! $selfReferential = ($mapping['targetEntity'] == $mapping['sourceEntity']); - + if ( ! $mapping['isOwningSide']) { $relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']); $mapping = $relatedClass->associationMappings[$mapping['mappedBy']]; $keys = array_keys($mapping['relationToTargetKeyColumns']); - + if ($selfReferential) { $otherKeys = array_keys($mapping['relationToSourceKeyColumns']); } } else { $keys = array_keys($mapping['relationToSourceKeyColumns']); - + if ($selfReferential) { $otherKeys = array_keys($mapping['relationToTargetKeyColumns']); } @@ -420,13 +420,13 @@ class BasicEntityPersister if ( ! isset($mapping['isOnDeleteCascade'])) { $this->_conn->delete( - $this->_class->getQuotedJoinTableName($mapping, $this->_platform), + $this->_class->getQuotedJoinTableName($mapping, $this->_platform), array_combine($keys, $identifier) ); if ($selfReferential) { $this->_conn->delete( - $this->_class->getQuotedJoinTableName($mapping, $this->_platform), + $this->_class->getQuotedJoinTableName($mapping, $this->_platform), array_combine($otherKeys, $identifier) ); } @@ -458,7 +458,7 @@ class BasicEntityPersister * Prepares the changeset of an entity for database insertion (UPDATE). * * The changeset is obtained from the currently running UnitOfWork. - * + * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. * @@ -493,7 +493,7 @@ class BasicEntityPersister if (isset($this->_class->associationMappings[$field])) { $assoc = $this->_class->associationMappings[$field]; - + // Only owning side of x-1 associations can have a FK column. if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) { continue; @@ -501,7 +501,7 @@ class BasicEntityPersister if ($newVal !== null) { $oid = spl_object_hash($newVal); - + if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { // The associated entity $newVal is not yet persisted, so we must // set $newVal = null, in order to insert a null value and schedule an @@ -528,7 +528,7 @@ class BasicEntityPersister } else { $result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]]; } - + $this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn); } } else { @@ -537,7 +537,7 @@ class BasicEntityPersister $result[$this->getOwningTable($field)][$columnName] = $newVal; } } - + return $result; } @@ -589,7 +589,7 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode, $limit); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); - + if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; @@ -597,7 +597,7 @@ class BasicEntityPersister $hydrator = $this->_em->newHydrator($this->_selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints); - + return $entities ? $entities[0] : null; } @@ -626,17 +626,17 @@ class BasicEntityPersister // Mark inverse side as fetched in the hints, otherwise the UoW would // try to load it in a separate query (remember: to-one inverse sides can not be lazy). $hints = array(); - + if ($isInverseSingleValued) { $hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true; - + if ($targetClass->subClasses) { foreach ($targetClass->subClasses as $targetSubclassName) { $hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true; } } } - + /* cascade read-only status if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) { $hints[Query::HINT_READ_ONLY] = true; @@ -652,7 +652,7 @@ class BasicEntityPersister } else { $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); - + // TRICKY: since the association is specular source and target are flipped foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) { @@ -660,12 +660,12 @@ class BasicEntityPersister $sourceClass->name, $sourceKeyColumn ); } - + // unset the old value and set the new sql aliased value here. By definition // unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method. $identifier[$this->_getSQLTableAlias($targetClass->name) . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - + unset($identifier[$targetKeyColumn]); } @@ -681,7 +681,7 @@ class BasicEntityPersister /** * Refreshes a managed entity. - * + * * @param array $id The identifier of the entity as an associative array from * column or field names to values. * @param object $entity The entity to refresh. @@ -691,16 +691,16 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($id); list($params, $types) = $this->expandParameters($id); $stmt = $this->_conn->executeQuery($sql, $params, $types); - + $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); $hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true)); if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) { $this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity); } - + $evm = $this->_em->getEventManager(); - + if ($evm->hasListeners(Events::postLoad)) { $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); } @@ -708,7 +708,7 @@ class BasicEntityPersister /** * Loads a list of entities by a list of field criteria. - * + * * @param array $criteria * @param array $orderBy * @param int $limit @@ -723,13 +723,13 @@ class BasicEntityPersister $stmt = $this->_conn->executeQuery($sql, $params, $types); $hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - + return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true)); } /** * Get (sliced or full) elements of the given collection. - * + * * @param array $assoc * @param object $sourceEntity * @param int|null $offset @@ -739,16 +739,16 @@ class BasicEntityPersister public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); - + return $this->loadArrayFromStatement($assoc, $stmt); } /** * Load an array of entities from a given dbal statement. - * + * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt - * + * * @return array */ private function loadArrayFromStatement($assoc, $stmt) @@ -763,21 +763,21 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - + return $hydrator->hydrateAll($stmt, $rsm, $hints); } /** * Hydrate a collection from a given dbal statement. - * + * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt * @param PersistentCollection $coll - * + * * @return array */ private function loadCollectionFromStatement($assoc, $stmt, $coll) - { + { $hints = array('deferEagerLoads' => true, 'collection' => $coll); if (isset($assoc['indexBy'])) { @@ -788,7 +788,7 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - + return $hydrator->hydrateAll($stmt, $rsm, $hints); } @@ -805,7 +805,7 @@ class BasicEntityPersister public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); - + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } @@ -813,15 +813,15 @@ class BasicEntityPersister { $criteria = array(); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); - + if ($assoc['isOwningSide']) { $quotedJoinTable = $sourceClass->getQuotedJoinTableName($assoc, $this->_platform); - + foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; @@ -839,18 +839,18 @@ class BasicEntityPersister } else { $owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']]; $quotedJoinTable = $sourceClass->getQuotedJoinTableName($owningAssoc, $this->_platform); - + // TRICKY: since the association is inverted source and target are flipped foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } - + $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value; } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); @@ -864,7 +864,7 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); - + return $this->_conn->executeQuery($sql, $params, $types); } @@ -890,7 +890,7 @@ class BasicEntityPersister $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : ''; $lockSql = ''; - + if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { @@ -908,7 +908,7 @@ class BasicEntityPersister /** * Gets the ORDER BY SQL snippet for ordered collections. - * + * * @param array $orderBy * @param string $baseTableAlias * @return string @@ -917,7 +917,7 @@ class BasicEntityPersister protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) { $orderBySql = ''; - + foreach ($orderBy as $fieldName => $orientation) { if ( ! isset($this->_class->fieldMappings[$fieldName])) { throw ORMException::unrecognizedField($fieldName); @@ -928,7 +928,7 @@ class BasicEntityPersister : $baseTableAlias; $columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform); - + $orderBySql .= $orderBySql ? ', ' : ' ORDER BY '; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; } @@ -944,7 +944,7 @@ class BasicEntityPersister * list SQL fragment. Note that in the implementation of BasicEntityPersister * the resulting SQL fragment is generated only once and cached in {@link _selectColumnListSql}. * Subclasses may or may not do the same. - * + * * @return string The SQL fragment. * @todo Rename: _getSelectColumnsSQL() */ @@ -961,58 +961,56 @@ class BasicEntityPersister // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { if ($columnList) $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL($field, $this->_class); } $this->_selectJoinSql = ''; $eagerAliasCounter = 0; - + foreach ($this->_class->associationMappings as $assocField => $assoc) { $assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class); - + if ($assocColumnSQL) { if ($columnList) $columnList .= ', '; - + $columnList .= $assocColumnSQL; } - + if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); - + if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) { continue; // now this is why you shouldn't use inheritance } - + $assocAlias = 'e' . ($eagerAliasCounter++); $this->_rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); - + foreach ($eagerEntity->fieldNames AS $field) { if ($columnList) $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias); } - + foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) { $assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias); - + if ($assoc2ColumnSQL) { if ($columnList) $columnList .= ', '; $columnList .= $assoc2ColumnSQL; } } - $first = true; - + if ($assoc['isOwningSide']) { $this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($assoc['joinColumns']); $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; - + foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { if ( ! $first) { $this->_selectJoinSql .= ' AND '; } - $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol; $first = false; @@ -1029,8 +1027,8 @@ class BasicEntityPersister if ( ! $first) { $this->_selectJoinSql .= ' AND '; } - - $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' + + $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol; $first = false; } @@ -1042,33 +1040,32 @@ class BasicEntityPersister return $this->_selectColumnListSql; } - + /** * Gets the SQL join fragment used when selecting entities from an association. - * + * * @param string $field * @param array $assoc * @param ClassMetadata $class * @param string $alias - * - * @return string + * + * @return string */ protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r') { $columnList = ''; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList) $columnList .= ', '; - $columnAlias = $srcColumn . $this->_sqlAliasCounter++; - $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); + $resultColumnName = $this->getSQLColumnAlias($srcColumn); $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $srcColumn . ' AS ' . $resultColumnName; $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); } } - + return $columnList; } @@ -1088,10 +1085,10 @@ class BasicEntityPersister $owningAssoc = $this->_em->getClassMetadata($manyToMany['targetEntity'])->associationMappings[$manyToMany['mappedBy']]; $joinClauses = $owningAssoc['relationToSourceKeyColumns']; } - + $joinTableName = $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform); $joinSql = ''; - + foreach ($joinClauses as $joinTableColumn => $sourceColumn) { if ($joinSql != '') $joinSql .= ' AND '; @@ -1110,7 +1107,7 @@ class BasicEntityPersister /** * Gets the INSERT SQL used by the persister to persist a new entity. - * + * * @return string */ protected function _getInsertSQL() @@ -1118,7 +1115,7 @@ class BasicEntityPersister if ($this->_insertSql === null) { $insertSql = ''; $columns = $this->_getInsertColumnList(); - + if (empty($columns)) { $insertSql = $this->_platform->getEmptyIdentityInsertSQL( $this->_class->getQuotedTableName($this->_platform), @@ -1131,10 +1128,10 @@ class BasicEntityPersister $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')'; } - + $this->_insertSql = $insertSql; } - + return $this->_insertSql; } @@ -1149,15 +1146,15 @@ class BasicEntityPersister protected function _getInsertColumnList() { $columns = array(); - + foreach ($this->_class->reflFields as $name => $field) { if ($this->_class->isVersioned && $this->_class->versionField == $name) { continue; } - + if (isset($this->_class->associationMappings[$name])) { $assoc = $this->_class->associationMappings[$name]; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $columns[] = $sourceCol; @@ -1181,11 +1178,10 @@ class BasicEntityPersister */ protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { - $columnName = $class->columnNames[$field]; $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); - $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); - + $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]); + $this->_rsm->addFieldResult($alias, $columnAlias, $field); return $sql . ' AS ' . $columnAlias; @@ -1193,7 +1189,7 @@ class BasicEntityPersister /** * Gets the SQL table alias for the given class name. - * + * * @param string $className * @return string The SQL table alias. * @todo Reconsider. Binding table aliases to class names is not such a good idea. @@ -1203,15 +1199,15 @@ class BasicEntityPersister if ($assocName) { $className .= '#' . $assocName; } - + if (isset($this->_sqlTableAliases[$className])) { return $this->_sqlTableAliases[$className]; } - + $tableAlias = 't' . $this->_sqlAliasCounter++; $this->_sqlTableAliases[$className] = $tableAlias; - + return $tableAlias; } @@ -1235,9 +1231,9 @@ class BasicEntityPersister $sql = 'SELECT 1 ' . $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode) . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql; - + list($params, $types) = $this->expandParameters($criteria); - + $stmt = $this->_conn->executeQuery($sql, $params, $types); } @@ -1266,25 +1262,25 @@ class BasicEntityPersister protected function _getSelectConditionSQL(array $criteria, $assoc = null) { $conditionSql = ''; - + foreach ($criteria as $field => $value) { $conditionSql .= $conditionSql ? ' AND ' : ''; if (isset($this->_class->columnNames[$field])) { $className = (isset($this->_class->fieldMappings[$field]['inherited'])) - ? $this->_class->fieldMappings[$field]['inherited'] + ? $this->_class->fieldMappings[$field]['inherited'] : $this->_class->name; - + $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform); } else if (isset($this->_class->associationMappings[$field])) { if ( ! $this->_class->associationMappings[$field]['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field); } - + $className = (isset($this->_class->associationMappings[$field]['inherited'])) ? $this->_class->associationMappings[$field]['inherited'] : $this->_class->name; - + $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name']; } else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { // very careless developers could potentially open up this normally hidden api for userland attacks, @@ -1295,7 +1291,7 @@ class BasicEntityPersister } else { throw ORMException::unrecognizedField($field); } - + $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?'); } return $conditionSql; @@ -1313,7 +1309,7 @@ class BasicEntityPersister public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); - + return $this->loadArrayFromStatement($assoc, $stmt); } @@ -1329,7 +1325,7 @@ class BasicEntityPersister public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); - + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } @@ -1354,12 +1350,12 @@ class BasicEntityPersister if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } - + $criteria[$tableAlias . "." . $targetKeyColumn] = $value; } else { $criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); @@ -1390,13 +1386,13 @@ class BasicEntityPersister $types[] = $this->getType($field, $value); $params[] = $this->getValue($value); } - + return array($params, $types); } - + /** * Infer field type to be used by parameter type casting. - * + * * @param string $field * @param mixed $value * @return integer @@ -1410,11 +1406,11 @@ class BasicEntityPersister case (isset($this->_class->associationMappings[$field])): $assoc = $this->_class->associationMappings[$field]; - + if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw Query\QueryException::associationPathCompositeKeyNotSupported(); } - + $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetColumn = $assoc['joinColumns'][0]['referencedColumnName']; $type = null; @@ -1432,36 +1428,36 @@ class BasicEntityPersister if (is_array($value)) { $type += Connection::ARRAY_PARAM_OFFSET; } - + return $type; } - + /** * Retrieve parameter value - * + * * @param mixed $value - * @return mixed + * @return mixed */ private function getValue($value) { if (is_array($value)) { $newValue = array(); - + foreach ($value as $itemValue) { $newValue[] = $this->getIndividualValue($itemValue); } - + return $newValue; } - + return $this->getIndividualValue($value); } - + /** * Retrieve an invidiual parameter value - * + * * @param mixed $value - * @return mixed + * @return mixed */ private function getIndividualValue($value) { @@ -1472,11 +1468,11 @@ class BasicEntityPersister $class = $this->_em->getClassMetadata(get_class($value)); $idValues = $class->getIdentifierValues($value); } - + $value = $idValues[key($idValues)]; } - - return $value; + + return $value; } /** @@ -1488,17 +1484,17 @@ class BasicEntityPersister public function exists($entity, array $extraConditions = array()) { $criteria = $this->_class->getIdentifierValues($entity); - + if ($extraConditions) { $criteria = array_merge($criteria, $extraConditions); } - $sql = 'SELECT 1' - . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name) + $sql = 'SELECT 1 ' + . $this->getLockTablesSql() . ' WHERE ' . $this->_getSelectConditionSQL($criteria); - + list($params, $types) = $this->expandParameters($criteria); - + return (bool) $this->_conn->fetchColumn($sql, $params); } @@ -1519,4 +1515,19 @@ class BasicEntityPersister return 'INNER JOIN'; } + + /** + * Gets an SQL column alias for a column name. + * + * @param string $columnName + * @return string + */ + public function getSQLColumnAlias($columnName) + { + // Trim the column alias to the maximum identifier length of the platform. + // If the alias is to long, characters are cut off from the beginning. + return $this->_platform->getSQLResultCasing( + substr($columnName . $this->_sqlAliasCounter++, -$this->_platform->getMaxIdentifierLength()) + ); + } } diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index c95fee755..fb60d5e32 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -46,7 +46,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister /** * Map of table to quoted table names. - * + * * @var array */ private $_quotedTableMap = array(); @@ -59,7 +59,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $class = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class; - + return $class->getTableName(); } @@ -73,10 +73,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister { if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; - + return $this->_em->getClassMetadata($definingClassName); } - + return $this->_class; } @@ -92,7 +92,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister if (isset($this->_owningTableMap[$fieldName])) { return $this->_owningTableMap[$fieldName]; } - + if (isset($this->_class->associationMappings[$fieldName]['inherited'])) { $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']); } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { @@ -130,15 +130,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Prepare statements for sub tables. $subTableStmts = array(); - + if ($rootClass !== $this->_class) { $subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL()); } - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $parentTableName = $parentClass->getTableName(); - + if ($parentClass !== $rootClass) { $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); @@ -153,11 +153,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Execute insert on root table $paramIndex = 1; - + foreach ($insertData[$rootTableName] as $columnName => $value) { $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } - + $rootTableStmt->execute(); if ($isPostInsertId) { @@ -172,23 +172,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister foreach ($subTableStmts as $tableName => $stmt) { $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); $paramIndex = 1; - + foreach ((array) $id as $idName => $idVal) { $type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING; - + $stmt->bindValue($paramIndex++, $idVal, $type); } - + foreach ($data as $columnName => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } - + $stmt->execute(); } } $rootTableStmt->closeCursor(); - + foreach ($subTableStmts as $stmt) { $stmt->closeCursor(); } @@ -220,7 +220,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName ); } - + // Make sure the table with the version column is updated even if no columns on that // table were affected. if ($isVersioned && ! isset($updateData[$versionedTable])) { @@ -251,7 +251,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } else { // Delete from all tables individually, starting from this class' table up to the root table. $this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id); - + foreach ($this->_class->parentClasses as $parentClass) { $this->_conn->delete( $this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id @@ -270,16 +270,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Create the column list fragment only once if ($this->_selectColumnListSql === null) { - + $this->_rsm = new ResultSetMapping(); $this->_rsm->addEntityResult($this->_class->name, 'r'); - + // Add regular columns $columnList = ''; - + foreach ($this->_class->fieldMappings as $fieldName => $mapping) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL( $fieldName, isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class @@ -290,12 +290,12 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister foreach ($this->_class->associationMappings as $assoc2) { if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) { $tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias; - + foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->getSelectJoinColumnSQL( - $tableAlias, + $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name ); @@ -309,23 +309,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $columnList .= ', ' . $tableAlias . '.' . $discrColumn; $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); - + $this->_rsm->setDiscriminatorColumn('r', $resultColumnName); $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); } // INNER JOIN parent tables $joinSql = ''; - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -339,7 +339,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Add subclass columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) continue; - + $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } @@ -348,9 +348,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->getSelectJoinColumnSQL( - $tableAlias, + $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name ); @@ -362,10 +362,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Add LEFT JOIN $joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -382,7 +382,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } $lockSql = ''; - + if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { @@ -408,29 +408,29 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // INNER JOIN parent tables $joinSql = ''; - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias . $joinSql; } - + /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ protected function _getSelectColumnListSQL() { throw new \BadMethodCallException("Illegal invocation of ".__METHOD__."."); } - + /** {@inheritdoc} */ protected function _getInsertColumnList() { diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index e0bbcec5a..ac1eb75d7 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -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) { diff --git a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php index 7879b0ff2..f44e383b9 100644 --- a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php @@ -1,7 +1,5 @@ _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); + abstract public function execute(Connection $conn, array $params, array $types); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php index a6c22cecd..5b07d4d02 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php @@ -1,7 +1,5 @@ - * @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); } } diff --git a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php index 94db13b05..facccb715 100644 --- a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php @@ -1,7 +1,5 @@ - * @version $Revision$ * @link www.doctrine-project.org * @since 2.0 * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. @@ -45,7 +42,10 @@ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor $this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST); } } - + + /** + * {@inheritDoc} + */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeUpdate($this->_sqlStatements, $params, $types); diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php index beac2bb9c..975d450cd 100644 --- a/lib/Doctrine/ORM/Query/Expr/Base.php +++ b/lib/Doctrine/ORM/Query/Expr/Base.php @@ -57,7 +57,7 @@ abstract class Base public function add($arg) { - if ( $arg !== null ) { + if ( $arg !== null || ($arg instanceof self && $arg->count() > 0) ) { // If we decide to keep Expr\Base instances, we can use this check if ( ! is_string($arg)) { $class = get_class($arg); diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index d1690b72c..f84686ad2 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -86,20 +86,22 @@ class ResultSetMappingBuilder extends ResultSetMapping if (isset($renamedColumns[$columnName])) { $columnName = $renamedColumns[$columnName]; } + $columnName = $platform->getSQLResultCasing($columnName); if (isset($this->fieldMappings[$columnName])) { throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); } - $this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName); + $this->addFieldResult($alias, $columnName, $propertyName); } foreach ($classMetadata->associationMappings AS $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { foreach ($associationMapping['joinColumns'] AS $joinColumn) { $columnName = $joinColumn['name']; $renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName; + $renamedColumnName = $platform->getSQLResultCasing($renamedColumnName); if (isset($this->metaMappings[$renamedColumnName])) { throw new \InvalidArgumentException("The column '$renamedColumnName' conflicts with another column in the mapper."); } - $this->addMetaResult($alias, $platform->getSQLResultCasing($renamedColumnName), $platform->getSQLResultCasing($columnName)); + $this->addMetaResult($alias, $renamedColumnName, $columnName); } } } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 188350197..d90dc28f2 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -157,32 +157,24 @@ 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); } - - return new Exec\SingleSelectExecutor($AST, $this); } /** @@ -229,7 +221,11 @@ class SqlWalker implements TreeWalker */ public function getSQLColumnAlias($columnName) { - return $columnName . $this->_aliasCounter++; + // Trim the column alias to the maximum identifier length of the platform. + // If the alias is to long, characters are cut off from the beginning. + return $this->_platform->getSQLResultCasing( + substr($columnName . $this->_aliasCounter++, -$this->_platform->getMaxIdentifierLength()) + ); } /** @@ -250,16 +246,15 @@ class SqlWalker implements TreeWalker foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias); - + // If this is a joined association we must use left joins to preserve the correct result. $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; $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; } } @@ -271,11 +266,10 @@ class SqlWalker implements TreeWalker $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); $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; } } @@ -287,10 +281,10 @@ class SqlWalker implements TreeWalker private function _generateOrderedCollectionOrderByItems() { $sql = ''; - + foreach ($this->_selectedClasses AS $dqlAlias => $class) { $qComp = $this->_queryComponents[$dqlAlias]; - + if (isset($qComp['relation']['orderBy'])) { foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { $tableName = ($qComp['metadata']->isInheritanceTypeJoined()) @@ -300,13 +294,13 @@ class SqlWalker implements TreeWalker if ($sql != '') { $sql .= ', '; } - - $sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' + + $sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . ' ' . $orientation; } } } - + return $sql; } @@ -327,7 +321,7 @@ class SqlWalker implements TreeWalker if ($class->isInheritanceTypeSingleTable()) { $conn = $this->_em->getConnection(); $values = array(); - + if ($class->discriminatorValue !== null) { // discrimnators can be 0 $values[] = $conn->quote($class->discriminatorValue); } @@ -399,7 +393,7 @@ class SqlWalker implements TreeWalker public function walkUpdateStatement(AST\UpdateStatement $AST) { $this->_useSqlTableAliases = false; - + return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause); } @@ -412,7 +406,7 @@ class SqlWalker implements TreeWalker public function walkDeleteStatement(AST\DeleteStatement $AST) { $this->_useSqlTableAliases = false; - + return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause); } @@ -477,7 +471,7 @@ class SqlWalker implements TreeWalker if ( ! $assoc['isOwningSide']) { throw QueryException::associationPathInverseSideNotSupported(); } - + // COMPOSITE KEYS NOT (YET?) SUPPORTED if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw QueryException::associationPathCompositeKeyNotSupported(); @@ -489,7 +483,7 @@ class SqlWalker implements TreeWalker $sql .= reset($assoc['targetToSourceKeyColumns']); break; - + default: throw QueryException::invalidPathExpression($pathExpr); } @@ -532,10 +526,9 @@ class SqlWalker implements TreeWalker $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); $discrColumn = $rootClass->discriminatorColumn; $columnAlias = $this->getSQLColumnAlias($discrColumn['name']); - + $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); @@ -550,14 +543,13 @@ class SqlWalker implements TreeWalker } else { $sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); } - + foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $columnAlias = $this->getSQLColumnAlias($srcColumn); - + $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); + + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } } } @@ -566,23 +558,22 @@ class SqlWalker implements TreeWalker // Add foreign key columns to SQL, if necessary if ($addMetaColumns) { $sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); - + foreach ($class->associationMappings as $assoc) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $columnAlias = $this->getSQLColumnAlias($srcColumn); - + $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); + + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } } } } } } - + $sql .= implode(', ', $sqlSelectExpressions); return $sql; @@ -603,7 +594,7 @@ class SqlWalker implements TreeWalker $rangeDecl = $identificationVariableDecl->rangeVariableDeclaration; $dqlAlias = $rangeDecl->aliasIdentificationVariable; - + $this->_rootAliases[] = $dqlAlias; $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); @@ -720,12 +711,12 @@ class SqlWalker implements TreeWalker $joinAssocPathExpr = $join->joinAssociationPathExpression; $joinedDqlAlias = $join->aliasIdentificationVariable; - + $relation = $this->_queryComponents[$joinedDqlAlias]['relation']; $targetClass = $this->_em->getClassMetadata($relation['targetEntity']); $sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']); $targetTableName = $targetClass->getQuotedTableName($this->_platform); - + $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias); $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $joinAssocPathExpr->identificationVariable); @@ -860,7 +851,7 @@ class SqlWalker implements TreeWalker return $sql; } - + /** * Walks down a CaseExpression AST node and generates the corresponding SQL. * @@ -872,21 +863,21 @@ class SqlWalker implements TreeWalker switch (true) { case ($expression instanceof AST\CoalesceExpression): return $this->walkCoalesceExpression($expression); - + case ($expression instanceof AST\NullIfExpression): return $this->walkNullIfExpression($expression); - + case ($expression instanceof AST\GeneralCaseExpression): return $this->walkGeneralCaseExpression($expression); - + case ($expression instanceof AST\SimpleCaseExpression): return $this->walkSimpleCaseExpression($expression); - + default: return ''; } } - + /** * Walks down a CoalesceExpression AST node and generates the corresponding SQL. * @@ -896,18 +887,18 @@ class SqlWalker implements TreeWalker public function walkCoalesceExpression($coalesceExpression) { $sql = 'COALESCE('; - + $scalarExpressions = array(); - + foreach ($coalesceExpression->scalarExpressions as $scalarExpression) { $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression); } - + $sql .= implode(', ', $scalarExpressions) . ')'; - + return $sql; } - + /** * Walks down a NullIfExpression AST node and generates the corresponding SQL. * @@ -916,17 +907,17 @@ class SqlWalker implements TreeWalker */ public function walkNullIfExpression($nullIfExpression) { - $firstExpression = is_string($nullIfExpression->firstExpression) + $firstExpression = is_string($nullIfExpression->firstExpression) ? $this->_conn->quote($nullIfExpression->firstExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression); - - $secondExpression = is_string($nullIfExpression->secondExpression) + + $secondExpression = is_string($nullIfExpression->secondExpression) ? $this->_conn->quote($nullIfExpression->secondExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression); - + return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')'; } - + /** * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL. * @@ -936,17 +927,17 @@ class SqlWalker implements TreeWalker public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression) { $sql = 'CASE'; - + foreach ($generalCaseExpression->whenClauses as $whenClause) { $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression); $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression); } - + $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END'; - + return $sql; } - + /** * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL. * @@ -956,14 +947,14 @@ class SqlWalker implements TreeWalker public function walkSimpleCaseExpression($simpleCaseExpression) { $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand); - + foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) { $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression); $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression); } - + $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END'; - + return $sql; } @@ -983,31 +974,26 @@ class SqlWalker implements TreeWalker if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { throw QueryException::invalidPathExpression($expr->type); } - + $fieldName = $expr->field; $dqlAlias = $expr->identificationVariable; $qComp = $this->_queryComponents[$dqlAlias]; $class = $qComp['metadata']; - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $fieldName; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } + $resultAlias = ( ! $selectExpression->fieldIdentificationVariable) + ? $fieldName + : $selectExpression->fieldIdentificationVariable; - if ($class->isInheritanceTypeJoined()) { - $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); - } else { - $tableName = $class->getTableName(); - } + $tableName = ($class->isInheritanceTypeJoined()) + ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) + : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); $columnAlias = $this->getSQLColumnAlias($columnName); $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - + if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1018,12 +1004,10 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1034,12 +1018,10 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1050,12 +1032,10 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1072,18 +1052,15 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; - + $columnAlias = $this->getSQLColumnAlias('sclr'); if ($expr instanceof AST\Literal) { $sql .= $this->walkLiteral($expr) . ' AS ' .$columnAlias; } else { $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; } - + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1099,14 +1076,11 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; - + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; - + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1126,16 +1100,16 @@ class SqlWalker implements TreeWalker if ( ! isset($this->_selectedClasses[$dqlAlias])) { $this->_selectedClasses[$dqlAlias] = $class; } - + $beginning = true; - + // Select all fields from the queried class foreach ($class->fieldMappings as $fieldName => $mapping) { if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { continue; } - $tableName = (isset($mapping['inherited'])) + $tableName = (isset($mapping['inherited'])) ? $this->_em->getClassMetadata($mapping['inherited'])->getTableName() : $class->getTableName(); @@ -1146,8 +1120,6 @@ class SqlWalker implements TreeWalker $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } @@ -1159,7 +1131,7 @@ class SqlWalker implements TreeWalker foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); - + foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { continue; @@ -1171,7 +1143,6 @@ class SqlWalker implements TreeWalker $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } @@ -1181,11 +1152,11 @@ class SqlWalker implements TreeWalker if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($beginning) $beginning = false; else $sql .= ', '; - + $columnAlias = $this->getSQLColumnAlias($srcColumn); $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - - $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); + + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } } @@ -1292,83 +1263,77 @@ class SqlWalker implements TreeWalker */ public function walkSimpleSelectExpression($simpleSelectExpression) { - $sql = ''; $expr = $simpleSelectExpression->expression; + $sql = ' '; - if ($expr instanceof AST\PathExpression) { - $sql .= $this->walkPathExpression($expr); - } else if ($expr instanceof AST\AggregateExpression) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } + switch (true) { + case ($expr instanceof AST\PathExpression): + $sql .= $this->walkPathExpression($expr); + break; - $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; - } else if ($expr instanceof AST\Subselect) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } + case ($expr instanceof AST\AggregateExpression): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; - $columnAlias = 'sclr' . $this->_aliasCounter++; - $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; - } + $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; + break; - $columnAlias = 'sclr' . $this->_aliasCounter++; - $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; - } + case ($expr instanceof AST\Subselect): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; - $columnAlias = 'sclr' . $this->_aliasCounter++; - $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; - } + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; - - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else { - // IdentificationVariable - $class = $this->_queryComponents[$expr]['metadata']; - $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); - $first = true; + $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; + break; - foreach ($class->identifier as $identifier) { - if ($first) $first = false; else $sql .= ', '; - $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); - } + 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; + 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; + 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'); + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; + break; + + default: // IdentificationVariable + $class = $this->_queryComponents[$expr]['metadata']; + $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); + $sqlParts = array(); + + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { + $sqlParts[] = $tableAlias . '.' . $columnName; + } + + $sql .= implode(', ', $sqlParts); + break; } - return ' ' . $sql; + return $sql; } /** @@ -1391,25 +1356,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 .= ', '; - } - $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); + 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; + + $sqlParts[] = $this->walkGroupByItem($groupByItem); } } - return ' GROUP BY ' . $sql; + + return ' GROUP BY ' . implode(', ', $sqlParts); } /** @@ -1431,12 +1395,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); + $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); + $tableName = $class->getTableName(); + $sql = 'DELETE FROM ' . $class->getQuotedTableName($this->_platform); + $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable); $this->_rootAliases[] = $deleteClause->aliasIdentificationVariable; return $sql; @@ -1450,17 +1413,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); + $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); + $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; } @@ -1476,16 +1436,21 @@ class SqlWalker implements TreeWalker $useTableAliasesBefore = $this->_useSqlTableAliases; $this->_useSqlTableAliases = false; - $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; - + $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; $newValue = $updateItem->newValue; - if ($newValue === null) { - $sql .= 'NULL'; - } else if ($newValue instanceof AST\Node) { - $sql .= $newValue->dispatch($this); - } else { - $sql .= $this->_conn->quote($newValue); + switch (true) { + case ($newValue instanceof AST\Node): + $sql .= $newValue->dispatch($this); + break; + + case ($newValue === null): + $sql .= 'NULL'; + break; + + default: + $sql .= $this->_conn->quote($newValue); + break; } $this->_useSqlTableAliases = $useTableAliasesBefore; @@ -1502,12 +1467,14 @@ class SqlWalker implements TreeWalker */ public function walkWhereClause($whereClause) { - $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; + $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases); if ($condSql) { return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); - } else if ($discrSql) { + } + + if ($discrSql) { return ' WHERE ' . $discrSql; } @@ -1524,11 +1491,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)); } /** @@ -1541,11 +1508,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)); } /** @@ -1573,7 +1540,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) . ')'; @@ -1607,12 +1576,12 @@ class SqlWalker implements TreeWalker $sql .= 'EXISTS (SELECT 1 FROM '; $entityExpr = $collMemberExpr->entityExpression; $collPathExpr = $collMemberExpr->collectionValuedPathExpression; - + $fieldName = $collPathExpr->field; $dqlAlias = $collPathExpr->identificationVariable; - + $class = $this->_queryComponents[$dqlAlias]['metadata']; - + if ($entityExpr instanceof AST\InputParameter) { $dqlParamKey = $entityExpr->name; $entity = $this->_query->getParameter($dqlParamKey); @@ -1620,40 +1589,39 @@ class SqlWalker implements TreeWalker //TODO throw new \BadMethodCallException("Not implemented"); } - + $assoc = $class->associationMappings[$fieldName]; - + if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); - + $sql .= $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' WHERE '; - + $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $first = true; - + foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { if ($first) $first = false; else $sql .= ' AND '; - - $sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform) - . ' = ' + + $sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform) + . ' = ' . $targetTableAlias . '.' . $sourceColumn; } - + $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']); - + $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; @@ -1661,11 +1629,11 @@ class SqlWalker implements TreeWalker $joinTableAlias = $this->getSQLTableAlias($joinTable['name']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); - + // join to target table - $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform) . ' ' . $joinTableAlias + $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform) . ' ' . $joinTableAlias . ' INNER JOIN ' . $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' ON '; - + // join conditions $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] @@ -1683,29 +1651,28 @@ class SqlWalker implements TreeWalker $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $first = true; - + foreach ($joinColumns as $joinColumn) { if ($first) $first = false; else $sql .= ' AND '; - $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' + $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $this->_platform); } - + $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 . ' = ?'; } } return $sql . ')'; } - + /** * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. * @@ -1755,11 +1722,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 .= ')'; @@ -1787,11 +1752,11 @@ class SqlWalker implements TreeWalker if ($this->_useSqlTableAliases) { $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.'; } - + $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN '); - + $sqlParameterList = array(); - + foreach ($instanceOfExpr->value as $parameter) { if ($parameter instanceof AST\InputParameter) { // We need to modify the parameter value to be its correspondent mapped value @@ -1812,7 +1777,7 @@ class SqlWalker implements TreeWalker $sqlParameterList[] = $this->_conn->quote($class->discriminatorValue); } else { $discrMap = array_flip($class->discriminatorMap); - + if (!isset($discrMap[$entityClassName])) { throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName); } @@ -1820,7 +1785,7 @@ class SqlWalker implements TreeWalker $sqlParameterList[] = $this->_conn->quote($discrMap[$entityClassName]); } } - + $sql .= '(' . implode(', ', $sqlParameterList) . ')'; return $sql; @@ -1834,9 +1799,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); } /** @@ -1850,16 +1815,16 @@ class SqlWalker implements TreeWalker switch ($literal->type) { case AST\Literal::STRING: return $this->_conn->quote($literal->value); - + case AST\Literal::BOOLEAN: $bool = strtolower($literal->value) == 'true' ? true : false; $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool); - + return $boolVal; - + case AST\Literal::NUMERIC: return $literal->value; - + default: throw QueryException::invalidLiteral($literal); } @@ -1929,23 +1894,19 @@ class SqlWalker implements TreeWalker */ public function walkComparisonExpression($compExpr) { - $sql = ''; - $leftExpr = $compExpr->leftExpression; + $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; } @@ -1984,11 +1945,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)); } /** @@ -2000,22 +1961,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)); } /** @@ -2029,13 +1986,16 @@ class SqlWalker implements TreeWalker if (is_string($factor)) { return $factor; } - + // 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); } /** @@ -2048,7 +2008,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); } diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 77106ec7d..568a960a4 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -67,7 +67,9 @@ class SchemaTool /** * Creates the database schema for the given array of ClassMetadata instances. * + * @throws ToolsException * @param array $classes + * @return void */ public function createSchema(array $classes) { @@ -75,7 +77,11 @@ class SchemaTool $conn = $this->_em->getConnection(); foreach ($createSchemaSql as $sql) { - $conn->executeQuery($sql); + try { + $conn->executeQuery($sql); + } catch(\Exception $e) { + throw ToolsException::schemaToolFailure($sql, $e); + } } } @@ -94,7 +100,7 @@ class SchemaTool /** * Some instances of ClassMetadata don't need to be processed in the SchemaTool context. This method detects them. - * + * * @param ClassMetadata $class * @param array $processedClasses * @return bool @@ -551,7 +557,7 @@ class SchemaTool try { $conn->executeQuery($sql); } catch(\Exception $e) { - + } } } @@ -589,7 +595,7 @@ class SchemaTool /** * Get SQL to drop the tables defined by the passed classes. - * + * * @param array $classes * @return array */ @@ -615,7 +621,7 @@ class SchemaTool } } } - + if ($this->_platform->supportsSequences()) { foreach ($schema->getSequences() AS $sequence) { $visitor->acceptSequence($sequence); @@ -659,7 +665,7 @@ class SchemaTool /** * Gets the sequence of SQL statements that need to be performed in order * to bring the given class mappings in-synch with the relational schema. - * If $saveMode is set to true the command is executed in the Database, + * If $saveMode is set to true the command is executed in the Database, * else SQL is returned. * * @param array $classes The classes to consider. diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index a7f8e3a1c..2ddd0bf66 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -50,7 +50,7 @@ class SchemaValidator } /** - * Checks the internal consistency of mapping files. + * Checks the internal consistency of all mapping files. * * There are several checks that can't be done at runtime or are too expensive, which can be verified * with this command. For example: @@ -69,150 +69,7 @@ class SchemaValidator $classes = $cmf->getAllMetadata(); foreach ($classes AS $class) { - $ce = array(); - /* @var $class ClassMetadata */ - foreach ($class->associationMappings AS $fieldName => $assoc) { - if (!$cmf->hasMetadataFor($assoc['targetEntity'])) { - $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; - } - - if ($assoc['mappedBy'] && $assoc['inversedBy']) { - $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning."; - } - - $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); - - /* @var $assoc AssociationMapping */ - if ($assoc['mappedBy']) { - if ($targetMetadata->hasField($assoc['mappedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which is not defined as association."; - } - if (!$targetMetadata->hasAssociation($assoc['mappedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which does not exist."; - } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] == null) { - $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". - "bi-directional relationship, but the specified mappedBy association on the target-entity ". - $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". - "'inversedBy' attribute."; - } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { - $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . - $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". - "incosistent with each other."; - } - } - - if ($assoc['inversedBy']) { - if ($targetMetadata->hasField($assoc['inversedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which is not defined as association."; - } - if (!$targetMetadata->hasAssociation($assoc['inversedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which does not exist."; - } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] == null) { - $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the owning side of a ". - "bi-directional relationship, but the specified mappedBy association on the target-entity ". - $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". - "'inversedBy' attribute."; - } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] != $fieldName) { - $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . - $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ". - "incosistent with each other."; - } - } - - if ($assoc['isOwningSide']) { - if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { - foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) { - if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $class->name . "'."; - break; - } - - $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $class->identifier)) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; - } - } - foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { - $targetClass = $cmf->getMetadataFor($assoc['targetEntity']); - if (!isset($targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']])) { - $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetClass->name . "'."; - break; - } - - $fieldName = $targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetClass->identifier)) { - $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; - } - } - - if (count($targetClass->identifier) != count($assoc['joinTable']['inverseJoinColumns'])) { - $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . - "have to match to ALL identifier columns of the target entity '". $targetClass->name . "'"; - } - - if (count($class->identifier) != count($assoc['joinTable']['joinColumns'])) { - $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . - "have to match to ALL identifier columns of the source entity '". $class->name . "'"; - } - - } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { - foreach ($assoc['joinColumns'] AS $joinColumn) { - $targetClass = $cmf->getMetadataFor($assoc['targetEntity']); - if (!isset($targetClass->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetClass->name . "'."; - break; - } - - $fieldName = $targetClass->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetClass->identifier)) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; - } - } - - if (count($class->identifier) != count($assoc['joinColumns'])) { - $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . - "have to match to ALL identifier columns of the source entity '". $class->name . "'"; - } - } - } - - if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { - $targetClass = $cmf->getMetadataFor($assoc['targetEntity']); - foreach ($assoc['orderBy'] AS $orderField => $orientation) { - if (!$targetClass->hasField($orderField)) { - $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " . - $orderField . " that is not a field on the target entity " . $targetClass->name; - } - } - } - } - - foreach ($class->reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $publicAttr) { - if ($publicAttr->isStatic()) { - continue; - } - $ce[] = "Field '".$publicAttr->getName()."' in class '".$class->name."' must be private ". - "or protected. Public fields may break lazy-loading."; - } - - foreach ($class->subClasses AS $subClass) { - if (!in_array($class->name, class_parents($subClass))) { - $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child ". - "of '" . $class->name . "' but these entities are not related through inheritance."; - } - } - - if ($ce) { + if ($ce = $this->validateClass($class)) { $errors[$class->name] = $ce; } } @@ -220,6 +77,165 @@ class SchemaValidator return $errors; } + /** + * Validate a single class of the current + * + * @param ClassMetadataInfo $class + * @return array + */ + public function validateClass(ClassMetadataInfo $class) + { + $ce = array(); + $cmf = $this->em->getMetadataFactory(); + + foreach ($class->associationMappings AS $fieldName => $assoc) { + if (!$cmf->hasMetadataFor($assoc['targetEntity'])) { + $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; + return $ce; + } + + if ($assoc['mappedBy'] && $assoc['inversedBy']) { + $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning."; + } + + $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); + + /* @var $assoc AssociationMapping */ + if ($assoc['mappedBy']) { + if ($targetMetadata->hasField($assoc['mappedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which is not defined as association."; + } + if (!$targetMetadata->hasAssociation($assoc['mappedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which does not exist."; + } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] == null) { + $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". + "bi-directional relationship, but the specified mappedBy association on the target-entity ". + $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". + "'inversedBy' attribute."; + } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { + $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . + $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". + "incosistent with each other."; + } + } + + if ($assoc['inversedBy']) { + if ($targetMetadata->hasField($assoc['inversedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which is not defined as association."; + } + if (!$targetMetadata->hasAssociation($assoc['inversedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which does not exist."; + } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] == null) { + $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the owning side of a ". + "bi-directional relationship, but the specified mappedBy association on the target-entity ". + $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". + "'inversedBy' attribute."; + } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] != $fieldName) { + $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . + $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ". + "incosistent with each other."; + } + } + + if ($assoc['isOwningSide']) { + if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { + foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) { + if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $class->name . "'."; + break; + } + + $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; + if (!in_array($fieldName, $class->identifier)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { + if (!isset($targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']])) { + $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; + break; + } + + $fieldName = $targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']]; + if (!in_array($fieldName, $targetMetadata->identifier)) { + $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + + if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) { + $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . + "have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " . + "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $assoc['relationToTargetKeyColumns'])) . + "' are missing."; + } + + if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) { + $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . + "have to contain to ALL identifier columns of the source entity '". $class->name . "', " . + "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['relationToSourceKeyColumns'])) . + "' are missing."; + } + + } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { + foreach ($assoc['joinColumns'] AS $joinColumn) { + if (!isset($targetMetadata->fieldNames[$joinColumn['referencedColumnName']])) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; + break; + } + + $fieldName = $targetMetadata->fieldNames[$joinColumn['referencedColumnName']]; + if (!in_array($fieldName, $targetMetadata->identifier)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + + if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) { + $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . + "have to match to ALL identifier columns of the source entity '". $class->name . "', " . + "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['joinColumns'])) . + "' are missing."; + } + } + } + + if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { + foreach ($assoc['orderBy'] AS $orderField => $orientation) { + if (!$targetMetadata->hasField($orderField)) { + $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " . + $orderField . " that is not a field on the target entity " . $targetMetadata->name; + } + } + } + } + + foreach ($class->reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $publicAttr) { + if ($publicAttr->isStatic()) { + continue; + } + $ce[] = "Field '".$publicAttr->getName()."' in class '".$class->name."' must be private ". + "or protected. Public fields may break lazy-loading."; + } + + foreach ($class->subClasses AS $subClass) { + if (!in_array($class->name, class_parents($subClass))) { + $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child ". + "of '" . $class->name . "' but these entities are not related through inheritance."; + } + } + + return $ce; + } + /** * Check if the Database Schema is in sync with the current metadata state. * diff --git a/lib/Doctrine/ORM/Tools/ToolsException.php b/lib/Doctrine/ORM/Tools/ToolsException.php index f7ed87105..4551d87da 100644 --- a/lib/Doctrine/ORM/Tools/ToolsException.php +++ b/lib/Doctrine/ORM/Tools/ToolsException.php @@ -1,11 +1,38 @@ . + */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\ORMException; +/** + * Tools related Exceptions + * + * @author Benjamin Eberlei + */ class ToolsException extends ORMException { + public static function schemaToolFailure($sql, \Exception $e) + { + return new self("Schema-Tool failed with Error '" . $e->getMessage() . "' while executing DDL: " . $sql, "0", $e); + } + public static function couldNotMapDoctrine1Type($type) { return new self("Could not map doctrine 1 type '$type'!"); diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 4366250dd..6add3bd6b 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -471,19 +471,21 @@ class UnitOfWork implements PropertyChangedListener */ public function computeChangeSet(ClassMetadata $class, $entity) { - if ( ! $class->isInheritanceTypeNone()) { - $class = $this->em->getClassMetadata(get_class($entity)); - } - $oid = spl_object_hash($entity); if (isset($this->readOnlyObjects[$oid])) { return; } + if ( ! $class->isInheritanceTypeNone()) { + $class = $this->em->getClassMetadata(get_class($entity)); + } + $actualData = array(); + foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); + if (isset($class->associationMappings[$name]) && ($class->associationMappings[$name]['type'] & ClassMetadata::TO_MANY) && $value !== null @@ -2054,6 +2056,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) { diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php index 51ea2278d..70b1c6353 100644 --- a/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php @@ -9,6 +9,7 @@ class DDC117Article { /** @Id @Column(type="integer", name="article_id") @GeneratedValue */ private $id; + /** @Column */ private $title; diff --git a/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php b/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php index f4f5e1fcb..3e3deedb1 100644 --- a/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php +++ b/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php @@ -21,7 +21,7 @@ class LegacyUser */ public $_username; /** - * @Column(type="string", length=255) + * @Column(type="string", length=255, name="name") */ public $_name; /** diff --git a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php index c6cd891a1..e666ae196 100644 --- a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php +++ b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php @@ -23,12 +23,12 @@ class LegacyUserReference private $_target; /** - * @column(type="string") + * @column(type="string", name="description") */ private $_description; /** - * @column(type="datetime") + * @column(type="datetime", name="created") */ private $_created; diff --git a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php index ada41d00b..72fc6d587 100644 --- a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php @@ -1,7 +1,5 @@ useModelSet('cms'); - parent::setUp(); + private $_em; + + protected function setUp() + { + $this->_em = $this->_getTestEntityManager(); } public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) @@ -70,7 +70,7 @@ class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.email_id AS email_id4 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1" ); } - + public function testSupportsQueriesWithSimpleConditionalExpression() { $this->assertSqlGeneration( @@ -94,7 +94,7 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $dqlAliases[] = $dqlAlias; } } - + // Create our conditions for all involved classes $factors = array(); foreach ($dqlAliases as $alias) { @@ -108,7 +108,7 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $factor = new Query\AST\ConditionalFactor($condPrimary); $factors[] = $factor; } - + if (($whereClause = $selectStatement->whereClause) !== null) { // There is already a WHERE clause, so append the conditions $condExpr = $whereClause->conditionalExpression; @@ -119,18 +119,18 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $whereClause->conditionalExpression = $condExpr; } - + $existingTerms = $whereClause->conditionalExpression->conditionalTerms; - + if (count($existingTerms) > 1) { // More than one term, so we need to wrap all these terms in a single root term // i.e: "WHERE u.name = :foo or u.other = :bar" => "WHERE (u.name = :foo or u.other = :bar) AND " - + $primary = new Query\AST\ConditionalPrimary; $primary->conditionalExpression = new Query\AST\ConditionalExpression($existingTerms); $existingFactor = new Query\AST\ConditionalFactor($primary); $term = new Query\AST\ConditionalTerm(array_merge(array($existingFactor), $factors)); - + $selectStatement->whereClause->conditionalExpression->conditionalTerms = array($term); } else { // Just one term so we can simply append our factors to that term diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index 4f8e11420..719a9f993 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -20,7 +20,9 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function tearDown() { - $this->_em->getConfiguration()->setEntityNamespaces(array()); + if ($this->_em) { + $this->_em->getConfiguration()->setEntityNamespaces(array()); + } parent::tearDown(); } @@ -78,7 +80,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase return array($user->id, $address->id); } - + public function buildUser($name, $username, $status, $address) { $user = new CmsUser(); @@ -89,10 +91,10 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($user); $this->_em->flush(); - + return $user; } - + public function buildAddress($country, $city, $street, $zip) { $address = new CmsAddress(); @@ -103,7 +105,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($address); $this->_em->flush(); - + return $address; } @@ -134,22 +136,22 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase { $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); - + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); - + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); - + unset($address1); unset($address2); unset($address3); - + $this->_em->clear(); - + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); $addresses = $repository->findBy(array('user' => array($user1->getId(), $user2->getId()))); - + $this->assertEquals(2, count($addresses)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); } @@ -158,22 +160,22 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase { $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); - + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); - + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); - + unset($address1); unset($address2); unset($address3); - + $this->_em->clear(); - + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); $addresses = $repository->findBy(array('user' => array($user1, $user2))); - + $this->assertEquals(2, count($addresses)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); } @@ -189,7 +191,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Guilherme', $users[0]->name); $this->assertEquals('dev', $users[0]->status); } - + public function testFindAll() { $user1Id = $this->loadFixture(); @@ -280,7 +282,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $userId = $user->id; $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId); - + $this->setExpectedException('Doctrine\ORM\OptimisticLockException'); $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC); } @@ -423,7 +425,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testFindByLimitOffset() { $this->loadFixture(); - + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $users1 = $repos->findBy(array(), null, 1, 0); @@ -451,8 +453,8 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertSame($usersAsc[0], $usersDesc[2]); $this->assertSame($usersAsc[2], $usersDesc[0]); } - - + + /** * @group DDC-753 */ @@ -465,19 +467,19 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithDefaultCustomRepository'); $this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753DefaultRepository", $repos); $this->assertTrue($repos->isDefaultRepository()); - - + + $repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithCustomRepository'); $this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753CustomRepository", $repos); $this->assertTrue($repos->isCustomRepository()); - + $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\Tests\Models\DDC753\DDC753DefaultRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\ORM\EntityRepository"); $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository"); } - - + + /** * @group DDC-753 * @expectedException Doctrine\ORM\ORMException @@ -488,6 +490,6 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository"); } - + } diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php index 4bf010602..351c1e25b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -29,7 +29,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup'); $class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY; - + $this->loadFixture(); } @@ -137,9 +137,9 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase { $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); - + $queryCount = $this->getCurrentQueryCount(); - + $someGroups = $user->groups->slice(0, 2); $this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsGroup', $someGroups); @@ -225,7 +225,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized."); $article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId); - + $queryCount = $this->getCurrentQueryCount(); $this->assertTrue($user->articles->contains($article)); $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); @@ -304,6 +304,49 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); } + /** + * @group DDC-1399 + */ + public function testCountAfterAddThenFlush() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + + $newGroup = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $newGroup->name = "Test4"; + + $user->addGroup($newGroup); + $this->_em->persist($newGroup); + + $this->assertFalse($user->groups->isInitialized()); + $this->assertEquals(4, count($user->groups)); + $this->assertFalse($user->groups->isInitialized()); + + $this->_em->flush(); + + $this->assertEquals(4, count($user->groups)); + } + + /** + * @group DDC-1462 + */ + public function testSliceOnDirtyCollection() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + /* @var $user CmsUser */ + + $newGroup = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $newGroup->name = "Test4"; + + $user->addGroup($newGroup); + $this->_em->persist($newGroup); + + $qc = $this->getCurrentQueryCount(); + $groups = $user->groups->slice(0, 10); + + $this->assertEquals(4, count($groups)); + $this->assertEquals($qc + 1, $this->getCurrentQueryCount()); + } + private function loadFixture() { $user1 = new \Doctrine\Tests\Models\CMS\CmsUser(); @@ -364,7 +407,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($article1); $this->_em->persist($article2); - + $this->_em->flush(); $this->_em->clear(); diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 3c41e0201..eb46329f1 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -35,7 +35,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $user->status = 'dev'; $this->_em->persist($user); $this->_em->flush(); - + $this->_em->clear(); $rsm = new ResultSetMapping; @@ -94,24 +94,24 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($addr->street, $addresses[0]->street); $this->assertTrue($addresses[0]->user instanceof CmsUser); } - + public function testJoinedOneToManyNativeQuery() { $user = new CmsUser; $user->name = 'Roman'; $user->username = 'romanb'; $user->status = 'dev'; - + $phone = new CmsPhonenumber; $phone->phonenumber = 424242; - + $user->addPhonenumber($phone); - + $this->_em->persist($user); $this->_em->flush(); - + $this->_em->clear(); - + $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); @@ -119,7 +119,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('status'), 'status'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', 'u', 'phonenumbers'); $rsm->addFieldResult('p', $this->platform->getSQLResultCasing('phonenumber'), 'phonenumber'); - + $query = $this->_em->createNativeQuery('SELECT id, name, status, phonenumber FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?', $rsm); $query->setParameter(1, 'romanb'); @@ -133,30 +133,30 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $phones = $users[0]->getPhonenumbers(); $this->assertEquals(424242, $phones[0]->phonenumber); $this->assertTrue($phones[0]->getUser() === $users[0]); - + } - + public function testJoinedOneToOneNativeQuery() { $user = new CmsUser; $user->name = 'Roman'; $user->username = 'romanb'; $user->status = 'dev'; - + $addr = new CmsAddress; $addr->country = 'germany'; $addr->zip = 10827; $addr->city = 'Berlin'; - - + + $user->setAddress($addr); - + $this->_em->persist($user); $this->_em->flush(); - + $this->_em->clear(); - - + + $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); @@ -167,12 +167,12 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('country'), 'country'); $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('zip'), 'zip'); $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('city'), 'city'); - + $query = $this->_em->createNativeQuery('SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', $rsm); $query->setParameter(1, 'romanb'); - + $users = $query->getResult(); - + $this->assertEquals(1, count($users)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]); $this->assertEquals('Roman', $users[0]->name); diff --git a/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php index 8a5819956..44832fc76 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php @@ -27,14 +27,14 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $readOnly->name = "Test2"; - $readOnly->number = 4321; + $readOnly->numericValue = 4321; $this->_em->flush(); $this->_em->clear(); $dbReadOnly = $this->_em->find('Doctrine\Tests\ORM\Functional\ReadOnlyEntity', $readOnly->id); $this->assertEquals("Test1", $dbReadOnly->name); - $this->assertEquals(1234, $dbReadOnly->number); + $this->assertEquals(1234, $dbReadOnly->numericValue); } } @@ -51,11 +51,11 @@ class ReadOnlyEntity /** @column(type="string") */ public $name; /** @Column(type="integer") */ - public $number; + public $numericValue; public function __construct($name, $number) { $this->name = $name; - $this->number = $number; + $this->numericValue = $number; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php index 235b4c91c..3dcae4fab 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php @@ -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 $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 $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)); } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php index 21a042187..93dc0eea6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php @@ -24,7 +24,7 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase $user->name = "John Galt"; $user->username = "jgalt"; $user->status = "inactive"; - + $article = new CmsArticle(); $article->topic = "This is John Galt speaking!"; $article->text = "Yadda Yadda!"; @@ -44,11 +44,10 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase ->setParameter('author', $user) ->getResult(); - $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = :topic AND a.user = :author AND a.user = :author AND a.text = :text"; + $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = :topic AND a.user = :author AND a.user = :author"; $farticle = $this->_em->createQuery($dql) ->setParameter('author', $user) ->setParameter('topic', 'This is John Galt speaking!') - ->setParameter('text', 'Yadda Yadda!') ->getSingleResult(); $this->assertSame($article, $farticle); @@ -70,12 +69,11 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($article); $this->_em->flush(); - $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = ?1 AND a.user = ?2 AND a.user = ?3 AND a.text = ?4"; + $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = ?1 AND a.user = ?2 AND a.user = ?3"; $farticle = $this->_em->createQuery($dql) ->setParameter(1, 'This is John Galt speaking!') ->setParameter(2, $user) ->setParameter(3, $user) - ->setParameter(4, 'Yadda Yadda!') ->getSingleResult(); $this->assertSame($article, $farticle); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php index 472978bc2..bddbbdf44 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php @@ -106,7 +106,7 @@ class DDC1209_3 { /** * @Id - * @Column(type="datetime") + * @Column(type="datetime", name="somedate") */ private $date; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php index cec258f37..95dcc2b65 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php @@ -21,10 +21,10 @@ class DDC1225Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1225_TestEntity2'), )); } catch(\PDOException $e) { - + } } - + public function testIssue() { $qb = $this->_em->createQueryBuilder(); @@ -32,10 +32,10 @@ class DDC1225Test extends \Doctrine\Tests\OrmFunctionalTestCase ->select('te1') ->where('te1.testEntity2 = ?1') ->setParameter(1, 0); - + $this->assertEquals( - 'SELECT t0_.test_entity2_id AS test_entity2_id0 FROM te1 t0_ WHERE t0_.test_entity2_id = ?', - $qb->getQuery()->getSQL() + strtolower('SELECT t0_.test_entity2_id AS test_entity2_id0 FROM te1 t0_ WHERE t0_.test_entity2_id = ?'), + strtolower($qb->getQuery()->getSQL()) ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php index 34aef78cf..6e14e2111 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php @@ -21,58 +21,58 @@ class DDC1228Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1228User'), $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1228Profile'), )); - } catch(\PDOException $e) { - + } catch(\Exception $e) { + } } - + public function testOneToOnePersist() { $user = new DDC1228User; $profile = new DDC1228Profile(); $profile->name = "Foo"; $user->profile = $profile; - + $this->_em->persist($user); $this->_em->persist($profile); $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->find(__NAMESPACE__ . '\\DDC1228User', $user->id); - + $this->assertFalse($user->getProfile()->__isInitialized__, "Proxy is not initialized"); $user->getProfile()->setName("Bar"); $this->assertTrue($user->getProfile()->__isInitialized__, "Proxy is not initialized"); - + $this->assertEquals("Bar", $user->getProfile()->getName()); $this->assertEquals(array("id" => 1, "name" => "Foo"), $this->_em->getUnitOfWork()->getOriginalEntityData($user->getProfile())); - + $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->find(__NAMESPACE__ . '\\DDC1228User', $user->id); $this->assertEquals("Bar", $user->getProfile()->getName()); } - + public function testRefresh() { $user = new DDC1228User; $profile = new DDC1228Profile(); $profile->name = "Foo"; $user->profile = $profile; - + $this->_em->persist($user); $this->_em->persist($profile); $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->getReference(__NAMESPACE__ . '\\DDC1228User', $user->id); - + $this->_em->refresh($user); $user->name = "Baz"; $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->find(__NAMESPACE__ . '\\DDC1228User', $user->id); $this->assertEquals("Baz", $user->name); } @@ -88,20 +88,20 @@ class DDC1228User * @var int */ public $id; - + /** - * @column(type="string") + * @Column(type="string") * @var string */ - public $name = ''; - + public $name = 'Bar'; + /** * @OneToOne(targetEntity="DDC1228Profile") * @var Profile */ public $profile; - - public function getProfile() + + public function getProfile() { return $this->profile; } @@ -117,13 +117,13 @@ class DDC1228Profile * @var int */ public $id; - + /** * @column(type="string") * @var string */ public $name; - + public function getName() { return $this->name; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php index 467577a43..6783928ef 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php @@ -19,49 +19,49 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_schemaTool->createSchema(array( $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1238User'), )); - } catch(\PDOException $e) { - + } catch(\Exception $e) { + } } - + public function testIssue() { $user = new DDC1238User; $user->setName("test"); - + $this->_em->persist($user); $this->_em->flush(); $this->_em->clear(); - + $userId = $user->getId(); $this->_em->clear(); - + $user = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); $this->_em->clear(); - + $userId2 = $user->getId(); $this->assertEquals($userId, $userId2, "This proxy can still be initialized."); } - + public function testIssueProxyClear() { $user = new DDC1238User; $user->setName("test"); - + $this->_em->persist($user); $this->_em->flush(); $this->_em->clear(); - + // force proxy load, getId() doesn't work anymore $user->getName(); $userId = $user->getId(); $this->_em->clear(); - + $user = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); $this->_em->clear(); - + $user2 = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); - + // force proxy load, getId() doesn't work anymore $user->getName(); $this->assertNull($user->getId(), "Now this is null, we already have a user instance of that type"); @@ -75,18 +75,18 @@ class DDC1238User { /** @Id @GeneratedValue @Column(type="integer") */ private $id; - + /** * @Column * @var string */ private $name; - + public function getId() { return $this->id; } - + public function getName() { return $this->name; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php index e12ee9ab7..e283b9f1c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -7,56 +7,56 @@ use DateTime; require_once __DIR__ . '/../../../TestInit.php'; /** - * @group DDC-1135 + * @group DDC-1335 */ -class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase +class DDC1335Test extends \Doctrine\Tests\OrmFunctionalTestCase { protected function setUp() { parent::setUp(); try { $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135User'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135Phone'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1335User'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1335Phone'), )); $this->loadFixture(); } catch(\Exception $e) { } } - - + + public function testDql() { - $dql = 'SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.id'; + $dql = 'SELECT u FROM ' . __NAMESPACE__ . '\DDC1335User u INDEX BY u.id'; $query = $this->_em->createQuery($dql); $result = $query->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey(1, $result); $this->assertArrayHasKey(2, $result); $this->assertArrayHasKey(3, $result); - - $dql = 'SELECT u, p FROM '.__NAMESPACE__ . '\DDC1135User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id'; + + $dql = 'SELECT u, p FROM '.__NAMESPACE__ . '\DDC1335User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id'; $query = $this->_em->createQuery($dql); $result = $query->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey('foo@foo.com', $result); $this->assertArrayHasKey('bar@bar.com', $result); $this->assertArrayHasKey('foobar@foobar.com', $result); - + $this->assertEquals(sizeof($result['foo@foo.com']->phones), 3); $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); - + $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); - + $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); - + $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); @@ -65,77 +65,77 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testTicket() { $builder = $this->_em->createQueryBuilder(); - $builder->select('u')->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.id'); + $builder->select('u')->from(__NAMESPACE__ . '\DDC1335User', 'u', 'u.id'); $dql = $builder->getQuery()->getDQL(); $result = $builder->getQuery()->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey(1, $result); $this->assertArrayHasKey(2, $result); $this->assertArrayHasKey(3, $result); - $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.id', $dql); + $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1335User u INDEX BY u.id', $dql); } - + public function testIndexByUnique() { $builder = $this->_em->createQueryBuilder(); - $builder->select('u')->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.email'); + $builder->select('u')->from(__NAMESPACE__ . '\DDC1335User', 'u', 'u.email'); $dql = $builder->getQuery()->getDQL(); $result = $builder->getQuery()->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey('foo@foo.com', $result); $this->assertArrayHasKey('bar@bar.com', $result); $this->assertArrayHasKey('foobar@foobar.com', $result); - $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.email', $dql); + $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1335User u INDEX BY u.email', $dql); } - + public function testIndexWithJoin() { $builder = $this->_em->createQueryBuilder(); $builder->select('u','p') - ->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.email') + ->from(__NAMESPACE__ . '\DDC1335User', 'u', 'u.email') ->join('u.phones', 'p', null, null, 'p.id'); - + $dql = $builder->getQuery()->getDQL(); $result = $builder->getQuery()->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey('foo@foo.com', $result); $this->assertArrayHasKey('bar@bar.com', $result); $this->assertArrayHasKey('foobar@foobar.com', $result); - + $this->assertEquals(sizeof($result['foo@foo.com']->phones), 3); $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); - + $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); - + $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); - + $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); - - $this->assertEquals('SELECT u, p FROM '.__NAMESPACE__ . '\DDC1135User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id', $dql); + + $this->assertEquals('SELECT u, p FROM '.__NAMESPACE__ . '\DDC1335User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id', $dql); } - + private function loadFixture() { $p1 = array('11 xxxx-xxxx','11 yyyy-yyyy','11 zzzz-zzzz'); $p2 = array('22 xxxx-xxxx','22 yyyy-yyyy','22 zzzz-zzzz'); $p3 = array('33 xxxx-xxxx','33 yyyy-yyyy','33 zzzz-zzzz'); - - $u1 = new DDC1135User("foo@foo.com", "Foo",$p1); - $u2 = new DDC1135User("bar@bar.com", "Bar",$p2); - $u3 = new DDC1135User("foobar@foobar.com", "Foo Bar",$p3); - + + $u1 = new DDC1335User("foo@foo.com", "Foo",$p1); + $u2 = new DDC1335User("bar@bar.com", "Bar",$p2); + $u3 = new DDC1335User("foobar@foobar.com", "Foo Bar",$p3); + $this->_em->persist($u1); $this->_em->persist($u2); $this->_em->persist($u3); @@ -148,7 +148,7 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase /** * @Entity */ -class DDC1135User +class DDC1335User { /** * @Id @Column(type="integer") @@ -160,25 +160,25 @@ class DDC1135User * @Column(type="string", unique=true) */ public $email; - + /** * @Column(type="string") */ public $name; - + /** - * @OneToMany(targetEntity="DDC1135Phone", mappedBy="user", cascade={"persist", "remove"}) + * @OneToMany(targetEntity="DDC1335Phone", mappedBy="user", cascade={"persist", "remove"}) */ public $phones; - + public function __construct($email, $name, array $numbers = array()) { $this->name = $name; $this->email = $email; $this->phones = new \Doctrine\Common\Collections\ArrayCollection(); - + foreach ($numbers as $number) { - $this->phones->add(new DDC1135Phone($this,$number)); + $this->phones->add(new DDC1335Phone($this,$number)); } } } @@ -186,22 +186,22 @@ class DDC1135User /** * @Entity */ -class DDC1135Phone +class DDC1335Phone { /** * @Id * @Column(name="id", type="integer") - * @GeneratedValue(strategy="AUTO") + * @GeneratedValue */ public $id; /** - * @Column(name="number", type="string", nullable = false) + * @Column(name="numericalValue", type="string", nullable = false) */ - public $number; + public $numericalValue; /** - * @ManyToOne(targetEntity="DDC1135User", inversedBy="phones") + * @ManyToOne(targetEntity="DDC1335User", inversedBy="phones") * @JoinColumn(name="user_id", referencedColumnName="id", nullable = false) */ public $user; @@ -209,6 +209,6 @@ class DDC1135Phone public function __construct($user, $number) { $this->user = $user; - $this->number = $number; + $this->numericalValue = $number; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php new file mode 100644 index 000000000..eaf9dd3f9 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php @@ -0,0 +1,69 @@ +_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; + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php index 512b1c9ea..2db32b9b8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php @@ -25,18 +25,15 @@ class DDC331Test extends \Doctrine\Tests\OrmFunctionalTestCase parent::setUp(); } + /** + * @group DDC-331 + */ public function testSelectFieldOnRootEntity() { - $employee = new CompanyEmployee; - $employee->setName('Roman S. Borschel'); - $employee->setSalary(100000); - $employee->setDepartment('IT'); - - $this->_em->persist($employee); - $this->_em->flush(); - $this->_em->clear(); - $q = $this->_em->createQuery('SELECT e.name FROM Doctrine\Tests\Models\Company\CompanyEmployee e'); - $this->assertEquals('SELECT c0_.name AS name0 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', $q->getSql()); + $this->assertEquals( + strtolower('SELECT c0_.name AS name0 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id'), + strtolower($q->getSql()) + ); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php index f0867f352..4ea830863 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php @@ -18,7 +18,10 @@ class DDC448Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testIssue() { $q = $this->_em->createQuery("select b from ".__NAMESPACE__."\\DDC448SubTable b where b.connectedClassId = ?1"); - $this->assertEquals('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.connectedClassId AS connectedClassId2 FROM SubTable s1_ INNER JOIN DDC448MainTable d0_ ON s1_.id = d0_.id WHERE d0_.connectedClassId = ?', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.connectedClassId AS connectedClassId2 FROM SubTable s1_ INNER JOIN DDC448MainTable d0_ ON s1_.id = d0_.id WHERE d0_.connectedClassId = ?'), + strtolower($q->getSQL()) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php index 25f378255..9e8d58be9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php @@ -18,7 +18,10 @@ class DDC493Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testIssue() { $q = $this->_em->createQuery("select u, c.data from ".__NAMESPACE__."\\DDC493Distributor u JOIN u.contact c"); - $this->assertEquals('SELECT d0_.id AS id0, d1_.data AS data1, d0_.discr AS discr2, d0_.contact AS contact3 FROM DDC493Distributor d2_ INNER JOIN DDC493Customer d0_ ON d2_.id = d0_.id INNER JOIN DDC493Contact d1_ ON d0_.contact = d1_.id', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT d0_.id AS id0, d1_.data AS data1, d0_.discr AS discr2, d0_.contact AS contact3 FROM DDC493Distributor d2_ INNER JOIN DDC493Customer d0_ ON d2_.id = d0_.id INNER JOIN DDC493Contact d1_ ON d0_.contact = d1_.id'), + strtolower($q->getSQL()) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php index 125f297db..b71d674cc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php @@ -18,7 +18,10 @@ class DDC513Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testIssue() { $q = $this->_em->createQuery("select u from ".__NAMESPACE__."\\DDC513OfferItem u left join u.price p"); - $this->assertEquals('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.price AS price2 FROM DDC513OfferItem d1_ INNER JOIN DDC513Item d0_ ON d1_.id = d0_.id LEFT JOIN DDC513Price d2_ ON d0_.price = d2_.id', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.price AS price2 FROM DDC513OfferItem d1_ INNER JOIN DDC513Item d0_ ON d1_.id = d0_.id LEFT JOIN DDC513Price d2_ ON d0_.price = d2_.id'), + strtolower($q->getSQL()) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php index 9fbfd01ab..4786cc176 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php @@ -30,7 +30,10 @@ class DDC698Test extends \Doctrine\Tests\OrmFunctionalTestCase $sql = $qb->getQuery()->getSQL(); - $this->assertEquals('SELECT p0_.privilegeID AS privilegeID0, p0_.name AS name1, r1_.roleID AS roleID2, r1_.name AS name3, r1_.shortName AS shortName4 FROM Privileges p0_ LEFT JOIN RolePrivileges r2_ ON p0_.privilegeID = r2_.privilegeID LEFT JOIN Roles r1_ ON r1_.roleID = r2_.roleID', $sql); + $this->assertEquals( + strtolower('SELECT p0_.privilegeID AS privilegeID0, p0_.name AS name1, r1_.roleID AS roleID2, r1_.name AS name3, r1_.shortName AS shortName4 FROM Privileges p0_ LEFT JOIN RolePrivileges r2_ ON p0_.privilegeID = r2_.privilegeID LEFT JOIN Roles r1_ ON r1_.roleID = r2_.roleID'), + strtolower($sql) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php index 82cb67223..6bd18ef98 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php @@ -19,7 +19,10 @@ class DDC719Test extends \Doctrine\Tests\OrmFunctionalTestCase { $q = $this->_em->createQuery('SELECT g, c FROM Doctrine\Tests\ORM\Functional\Ticket\DDC719Group g LEFT JOIN g.children c WHERE g.parents IS EMPTY'); - $this->assertEquals('SELECT g0_.name AS name0, g0_.description AS description1, g0_.id AS id2, g1_.name AS name3, g1_.description AS description4, g1_.id AS id5 FROM groups g0_ LEFT JOIN groups_groups g2_ ON g0_.id = g2_.parent_id LEFT JOIN groups g1_ ON g1_.id = g2_.child_id WHERE (SELECT COUNT(*) FROM groups_groups g3_ WHERE g3_.child_id = g0_.id) = 0', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT g0_.name AS name0, g0_.description AS description1, g0_.id AS id2, g1_.name AS name3, g1_.description AS description4, g1_.id AS id5 FROM groups g0_ LEFT JOIN groups_groups g2_ ON g0_.id = g2_.parent_id LEFT JOIN groups g1_ ON g1_.id = g2_.child_id WHERE (SELECT COUNT(*) FROM groups_groups g3_ WHERE g3_.child_id = g0_.id) = 0'), + strtolower($q->getSQL()) + ); } } @@ -28,12 +31,12 @@ class DDC719Test extends \Doctrine\Tests\OrmFunctionalTestCase */ class Entity { - /** + /** * @Id @GeneratedValue - * @Column(type="integer") + * @Column(type="integer") */ protected $id; - + public function getId() { return $this->id; } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php index 473a8679f..a3734b75a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php @@ -34,10 +34,10 @@ class DDC949Test extends \Doctrine\Tests\OrmFunctionalTestCase $true = $this->_em->getRepository('Doctrine\Tests\Models\Generic\BooleanModel')->findOneBy(array('booleanField' => true)); $false = $this->_em->getRepository('Doctrine\Tests\Models\Generic\BooleanModel')->findOneBy(array('booleanField' => false)); - $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $true); + $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $true, "True model not found"); $this->assertTrue($true->booleanField, "True Boolean Model should be true."); - $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $false); + $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $false, "False model not found"); $this->assertFalse($false->booleanField, "False Boolean Model should be false."); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 53002a8fc..499fb8c4c 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -17,11 +17,11 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase /** * Assert a valid SQL generation. - * + * * @param string $dqlToBeTested * @param string $sqlToBeConfirmed * @param array $queryHints - * @param array $queryParams + * @param array $queryParams */ public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints = array(), array $queryParams = array()) { @@ -34,11 +34,11 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) ->useQueryCache(false); - + foreach ($queryHints AS $name => $value) { $query->setHint($name, $value); } - + parent::assertEquals($sqlToBeConfirmed, $query->getSQL()); $query->free(); } catch (\Exception $e) { @@ -383,7 +383,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')" ); } - + public function testSupportsInstanceOfExpressionInWherePartWithMultipleValues() { $this->assertSqlGeneration( @@ -391,7 +391,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee', 'manager')" ); } - + /** * @group DDC-1194 */ @@ -402,7 +402,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')" ); } - + /** * @group DDC-1194 */ @@ -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' + . ')' ); } @@ -686,7 +703,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, (SELECT COUNT(*) FROM cms_articles c1_ WHERE c1_.user_id = c0_.id) AS sclr4 FROM cms_users c0_ ORDER BY sclr4 ASC" ); } - + public function testOrderBySupportsSingleValuedPathExpressionOwningSide() { $this->assertSqlGeneration( @@ -694,7 +711,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.topic AS topic1, c0_.text AS text2, c0_.version AS version3 FROM cms_articles c0_ ORDER BY c0_.user_id ASC" ); } - + /** * @expectedException Doctrine\ORM\Query\QueryException */ @@ -729,12 +746,12 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = true" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanfield1 FROM boolean_model b0_ WHERE b0_.booleanField = true" ); $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = false" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanfield1 FROM boolean_model b0_ WHERE b0_.booleanField = false" ); $this->_em->getConnection()->setDatabasePlatform($oldPlat); @@ -877,7 +894,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertSqlGeneration( "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'", - "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ". + "SELECT c0_.id AS ID0, c0_.status AS STATUS1, c0_.username AS USERNAME2, c0_.name AS NAME3 ". "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR UPDATE", array(Query::HINT_LOCK_MODE => \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) ); @@ -931,7 +948,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT c0_.id AS id0, c0_.name AS name1, count(c1_.id) AS sclr2 FROM cms_groups c0_ INNER JOIN cms_users_groups c2_ ON c0_.id = c2_.group_id INNER JOIN cms_users c1_ ON c1_.id = c2_.user_id GROUP BY c0_.id' ); } - + public function testCaseContainingNullIf() { $this->assertSqlGeneration( @@ -939,7 +956,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT NULLIF(c0_.id, c0_.name) AS sclr0 FROM cms_groups c0_' ); } - + public function testCaseContainingCoalesce() { $this->assertSqlGeneration( @@ -1009,248 +1026,281 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id" ); } - + public function testGeneralCaseWithSingleWhenClause() { $this->assertSqlGeneration( - "SELECT g.id, CASE WHEN ((g.id / 2) > 18) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", + "SELECT g.id, CASE WHEN ((g.id / 2) > 18) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", "SELECT c0_.id AS id0, CASE WHEN (c0_.id / 2 > 18) THEN 1 ELSE 0 END AS sclr1 FROM cms_groups c0_" ); } - + public function testGeneralCaseWithMultipleWhenClause() { $this->assertSqlGeneration( - "SELECT g.id, CASE WHEN (g.id / 2 < 10) THEN 2 WHEN ((g.id / 2) > 20) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", + "SELECT g.id, CASE WHEN (g.id / 2 < 10) THEN 2 WHEN ((g.id / 2) > 20) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", "SELECT c0_.id AS id0, CASE WHEN (c0_.id / 2 < 10) THEN 2 WHEN (c0_.id / 2 > 20) THEN 1 ELSE 0 END AS sclr1 FROM cms_groups c0_" ); } - + public function testSimpleCaseWithSingleWhenClause() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = CASE g.name WHEN 'admin' THEN 1 ELSE 2 END", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = CASE g.name WHEN 'admin' THEN 1 ELSE 2 END", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id = CASE c0_.name WHEN admin THEN 1 ELSE 2 END" ); } - + public function testSimpleCaseWithMultipleWhenClause() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = (CASE g.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = (CASE g.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id = CASE c0_.name WHEN admin THEN 1 WHEN moderator THEN 2 ELSE 3 END" ); } - + public function testGeneralCaseWithSingleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN ((g2.id / 2) > 18) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN ((g2.id / 2) > 18) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE WHEN (c1_.id / 2 > 18) THEN 2 ELSE 1 END AS sclr2 FROM cms_groups c1_)" ); } - + public function testGeneralCaseWithMultipleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN (g.id / 2 < 10) THEN 3 WHEN ((g.id / 2) > 20) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN (g.id / 2 < 10) THEN 3 WHEN ((g.id / 2) > 20) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE WHEN (c0_.id / 2 < 10) THEN 3 WHEN (c0_.id / 2 > 20) THEN 2 ELSE 1 END AS sclr2 FROM cms_groups c1_)" ); } - + public function testSimpleCaseWithSingleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 ELSE 2 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 ELSE 2 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE c1_.name WHEN admin THEN 1 ELSE 2 END AS sclr2 FROM cms_groups c1_)" ); } - + public function testSimpleCaseWithMultipleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE c1_.name WHEN admin THEN 1 WHEN moderator THEN 2 ELSE 3 END AS sclr2 FROM cms_groups c1_)" ); } - + /** * @group DDC-1339 */ public function testIdentityFunctionInSelectClause() { $this->assertSqlGeneration( - "SELECT IDENTITY(u.email) as email_id FROM Doctrine\Tests\Models\CMS\CmsUser u", + "SELECT IDENTITY(u.email) as email_id FROM Doctrine\Tests\Models\CMS\CmsUser u", "SELECT c0_.email_id AS sclr0 FROM cms_users c0_" ); } - + /** * @group DDC-1339 */ public function testIdentityFunctionDoesNotAcceptStateField() { $this->assertInvalidSqlGeneration( - "SELECT IDENTITY(u.name) as name FROM Doctrine\Tests\Models\CMS\CmsUser u", + "SELECT IDENTITY(u.name) as name FROM Doctrine\Tests\Models\CMS\CmsUser u", "Doctrine\ORM\Query\QueryException" ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInRootClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInRootClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', 'SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_', array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInChildClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c2_.car_id AS car_id6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInChildClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c0_.discr AS discr5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInLeafClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c2_.car_id AS car_id8 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInLeafClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInRootClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6, c0_.salesPerson_id AS salesPerson_id7 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInRootClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInChildClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInChildClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInLeafClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInLeafClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1161 */ public function testSelfReferenceWithOneToOneDoesNotDuplicateAlias() { $this->assertSqlGeneration( - 'SELECT p, pp FROM Doctrine\Tests\Models\Company\CompanyPerson p JOIN p.spouse pp', + 'SELECT p, pp FROM Doctrine\Tests\Models\Company\CompanyPerson p JOIN p.spouse pp', "SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c3_.id AS id7, c3_.name AS name8, c4_.title AS title9, c4_.car_id AS car_id10, c5_.salary AS salary11, c5_.department AS department12, c5_.startDate AS startDate13, c0_.discr AS discr14, c0_.spouse_id AS spouse_id15, c3_.discr AS discr16, c3_.spouse_id AS spouse_id17 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id INNER JOIN company_persons c3_ ON c0_.spouse_id = c3_.id LEFT JOIN company_managers c4_ ON c3_.id = c4_.id LEFT JOIN company_employees c5_ ON c3_.id = c5_.id", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } + + /** + * @group DDC-1384 + */ + public function testAliasDoesNotExceedPlatformDefinedLength() + { + $this->assertSqlGeneration( + 'SELECT m FROM ' . __NAMESPACE__ . '\\DDC1384Model m', + "SELECT d0_.aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo AS fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0 FROM DDC1384Model d0_" + ); + } + + /** + * @group DDC-331 + * @group DDC-1384 + */ + public function testIssue331() + { + $this->assertSqlGeneration( + 'SELECT e.name FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + '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)" + ); + } } @@ -1277,7 +1327,19 @@ class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode $parser->match(\Doctrine\ORM\Query\Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); - + $parser->match(\Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS); } } +/** + * @Entity + */ +class DDC1384Model +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue + */ + protected $aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo; +} diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php index 64bb03f36..0fc3bb636 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php @@ -71,4 +71,88 @@ class SchemaValidatorTest extends \Doctrine\Tests\OrmTestCase )); $this->validator->validateMapping(); } + + /** + * @group DDC-1439 + */ + public function testInvalidManyToManyJoinColumnSchema() + { + $class1 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity1'); + $class2 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity2'); + + $ce = $this->validator->validateClass($class1); + + $this->assertEquals( + array( + "The inverse join columns of the many-to-many table 'Entity1Entity2' have to contain to ALL identifier columns of the target entity 'Doctrine\Tests\ORM\Tools\InvalidEntity2', however 'key4' are missing.", + "The join columns of the many-to-many table 'Entity1Entity2' have to contain to ALL identifier columns of the source entity 'Doctrine\Tests\ORM\Tools\InvalidEntity1', however 'key2' are missing." + ), + $ce + ); + } + + /** + * @group DDC-1439 + */ + public function testInvalidToOneJoinColumnSchema() + { + $class1 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity1'); + $class2 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity2'); + + $ce = $this->validator->validateClass($class2); + + $this->assertEquals( + array( + "The referenced column name 'id' does not have a corresponding field with this column name on the class 'Doctrine\Tests\ORM\Tools\InvalidEntity1'.", + "The join columns of the association 'assoc' have to match to ALL identifier columns of the source entity 'Doctrine\Tests\ORM\Tools\InvalidEntity2', however 'key3, key4' are missing." + ), + $ce + ); + } +} + +/** + * @Entity + */ +class InvalidEntity1 +{ + /** + * @Id @Column + */ + protected $key1; + /** + * @Id @Column + */ + protected $key2; + /** + * @ManyToMany (targetEntity="InvalidEntity2") + * @JoinTable (name="Entity1Entity2", + * joinColumns={@JoinColumn(name="key1", referencedColumnName="key1")}, + * inverseJoinColumns={@JoinColumn(name="key3", referencedColumnName="key3")} + * ) + */ + protected $entity2; +} + +/** + * @Entity + */ +class InvalidEntity2 +{ + /** + * @Id @Column + * @GeneratedValue(strategy="AUTO") + */ + protected $key3; + + /** + * @Id @Column + * @GeneratedValue(strategy="AUTO") + */ + protected $key4; + + /** + * @ManyToOne(targetEntity="InvalidEntity1") + */ + protected $assoc; } \ No newline at end of file