1
0
mirror of synced 2025-02-06 15:29:26 +03:00

Merge remote-tracking branch 'origin/master' into DDC-551

Conflicts:
	lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
	lib/Doctrine/ORM/Query.php
This commit is contained in:
Alexander 2011-11-23 22:43:42 +01:00
commit be48821e86
147 changed files with 8579 additions and 3732 deletions

3
.gitignore vendored
View File

@ -7,3 +7,6 @@ download/
lib/api/ lib/api/
lib/Doctrine/Common lib/Doctrine/Common
lib/Doctrine/DBAL lib/Doctrine/DBAL
/.settings/
.buildpath
.project

19
.travis.yml Normal file
View File

@ -0,0 +1,19 @@
language: php
php:
- 5.3
- 5.4
env:
- DB=mysql
- DB=pgsql
- DB=sqlite
before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- git submodule update --init
script: phpunit --configuration tests/travis/$DB.travis.xml

View File

@ -1,14 +1,18 @@
# Doctrine 2 ORM # Doctrine 2 ORM
Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=master)](http://travis-ci.org/doctrine/doctrine2)
2.1.x: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2)
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
without requiring unnecessary code duplication. without requiring unnecessary code duplication.
More resources: ## More resources:
* [Website](http://www.doctrine-project.org) * [Website](http://www.doctrine-project.org)
* [Documentation](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/introduction/en) * [Documentation](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/introduction/en)
* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC) * [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC)
* [Downloads](http://github.com/doctrine/doctrine2/downloads) * [Downloads](http://github.com/doctrine/doctrine2/downloads)

View File

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

View File

@ -16,5 +16,8 @@
"ext-pdo": "*", "ext-pdo": "*",
"doctrine/common": "master-dev", "doctrine/common": "master-dev",
"doctrine/dbal": "master-dev" "doctrine/dbal": "master-dev"
},
"autoload": {
"psr-0": { "Doctrine\\ORM": "lib/" }
} }
} }

View File

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

View File

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

View File

@ -207,6 +207,7 @@ class EntityManager implements ObjectManager
* the transaction is rolled back, the EntityManager closed and the exception re-thrown. * the transaction is rolled back, the EntityManager closed and the exception re-thrown.
* *
* @param Closure $func The function to execute transactionally. * @param Closure $func The function to execute transactionally.
* @return mixed Returns the non-empty value returned from the closure or true instead
*/ */
public function transactional(Closure $func) public function transactional(Closure $func)
{ {
@ -333,13 +334,17 @@ class EntityManager implements ObjectManager
* This effectively synchronizes the in-memory state of managed objects with the * This effectively synchronizes the in-memory state of managed objects with the
* database. * database.
* *
* If an entity is explicitly passed to this method only this entity and
* the cascade-persist semantics + scheduled inserts/removals are synchronized.
*
* @param object $entity
* @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails. * makes use of optimistic locking fails.
*/ */
public function flush() public function flush($entity = null)
{ {
$this->errorIfClosed(); $this->errorIfClosed();
$this->unitOfWork->commit(); $this->unitOfWork->commit($entity);
} }
/** /**

View File

@ -225,6 +225,14 @@ class EntityRepository implements ObjectRepository
return $this->_entityName; return $this->_entityName;
} }
/**
* @return string
*/
public function getClassName()
{
return $this->getEntityName();
}
/** /**
* @return EntityManager * @return EntityManager
*/ */

View File

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

View File

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

View File

@ -1,9 +1,25 @@
<?php <?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Event; namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs; use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
@ -16,18 +32,20 @@ use Doctrine\ORM\EntityManager;
class LoadClassMetadataEventArgs extends EventArgs class LoadClassMetadataEventArgs extends EventArgs
{ {
/** /**
* @var ClassMetadata * @var Doctrine\ORM\Mapping\ClassMetadata
*/ */
private $classMetadata; private $classMetadata;
/** /**
* @var EntityManager * @var Doctrine\ORM\EntityManager
*/ */
private $em; private $em;
/** /**
* @param ClassMetadataInfo $classMetadata * Constructor.
* @param EntityManager $em *
* @param Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata
* @param Doctrine\ORM\EntityManager $em
*/ */
public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em) public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em)
{ {
@ -36,7 +54,9 @@ class LoadClassMetadataEventArgs extends EventArgs
} }
/** /**
* @return ClassMetadataInfo * Retrieve associated ClassMetadata.
*
* @return Doctrine\ORM\Mapping\ClassMetadataInfo
*/ */
public function getClassMetadata() public function getClassMetadata()
{ {
@ -44,7 +64,9 @@ class LoadClassMetadataEventArgs extends EventArgs
} }
/** /**
* @return EntityManager * Retrieve associated EntityManager.
*
* @return Doctrine\ORM\EntityManager
*/ */
public function getEntityManager() public function getEntityManager()
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@
namespace Doctrine\ORM\Id; namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\ORMException; use Doctrine\ORM\ORMException;
/** /**
@ -43,44 +44,27 @@ class AssignedGenerator extends AbstractIdGenerator
public function generate(EntityManager $em, $entity) public function generate(EntityManager $em, $entity)
{ {
$class = $em->getClassMetadata(get_class($entity)); $class = $em->getClassMetadata(get_class($entity));
$identifier = array();
if ($class->isIdentifierComposite) {
$idFields = $class->getIdentifierFieldNames(); $idFields = $class->getIdentifierFieldNames();
$identifier = array();
foreach ($idFields as $idField) { foreach ($idFields as $idField) {
$value = $class->reflFields[$idField]->getValue($entity); $value = $class->reflFields[$idField]->getValue($entity);
if (isset($value)) {
if ( ! isset($value)) {
throw ORMException::entityMissingAssignedIdForField($entity, $idField);
}
if (isset($class->associationMappings[$idField])) { if (isset($class->associationMappings[$idField])) {
if ( ! $em->getUnitOfWork()->isInIdentityMap($value)) { if ( ! $em->getUnitOfWork()->isInIdentityMap($value)) {
throw ORMException::entityMissingForeignAssignedId($entity, $value); throw ORMException::entityMissingForeignAssignedId($entity, $value);
} }
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); $value = current($em->getUnitOfWork()->getEntityIdentifier($value));
} else {
$identifier[$idField] = $value;
}
} else {
throw ORMException::entityMissingAssignedIdForField($entity, $idField);
}
}
} else {
$idField = $class->identifier[0];
$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; $identifier[$idField] = $value;
} }
} else {
throw ORMException::entityMissingAssignedIdForField($entity, $idField);
}
}
return $identifier; return $identifier;
} }

View File

@ -46,7 +46,7 @@ class IdentityGenerator extends AbstractIdGenerator
*/ */
public function generate(EntityManager $em, $entity) public function generate(EntityManager $em, $entity)
{ {
return $em->getConnection()->lastInsertId($this->_seqName); return (int)$em->getConnection()->lastInsertId($this->_seqName);
} }
/** /**

View File

@ -60,9 +60,11 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
// Allocate new values // Allocate new values
$conn = $em->getConnection(); $conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName); $sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
$this->_nextValue = $conn->fetchColumn($sql);
$this->_nextValue = (int)$conn->fetchColumn($sql);
$this->_maxValue = $this->_nextValue + $this->_allocationSize; $this->_maxValue = $this->_nextValue + $this->_allocationSize;
} }
return $this->_nextValue++; return $this->_nextValue++;
} }
@ -97,6 +99,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
public function unserialize($serialized) public function unserialize($serialized)
{ {
$array = unserialize($serialized); $array = unserialize($serialized);
$this->_sequenceName = $array['sequenceName']; $this->_sequenceName = $array['sequenceName'];
$this->_allocationSize = $array['allocationSize']; $this->_allocationSize = $array['allocationSize'];
} }

View File

@ -50,11 +50,12 @@ class TableGenerator extends AbstractIdGenerator
if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) { if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) {
// Allocate new values // Allocate new values
$conn = $em->getConnection(); $conn = $em->getConnection();
if ($conn->getTransactionNestingLevel() == 0) {
if ($conn->getTransactionNestingLevel() === 0) {
// use select for update // use select for update
$sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName); $sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName);
$currentLevel = $conn->fetchColumn($sql); $currentLevel = $conn->fetchColumn($sql);
if ($currentLevel != null) { if ($currentLevel != null) {
$this->_nextValue = $currentLevel; $this->_nextValue = $currentLevel;
$this->_maxValue = $this->_nextValue + $this->_allocationSize; $this->_maxValue = $this->_nextValue + $this->_allocationSize;
@ -74,6 +75,7 @@ class TableGenerator extends AbstractIdGenerator
// or do we want to work with table locks exclusively? // or do we want to work with table locks exclusively?
} }
} }
return $this->_nextValue++; return $this->_nextValue++;
} }
} }

View File

@ -22,7 +22,8 @@ namespace Doctrine\ORM\Internal\Hydration;
use PDO, use PDO,
Doctrine\DBAL\Connection, Doctrine\DBAL\Connection,
Doctrine\DBAL\Types\Type, Doctrine\DBAL\Types\Type,
Doctrine\ORM\EntityManager; Doctrine\ORM\EntityManager,
Doctrine\ORM\Mapping\ClassMetadata;
/** /**
* Base class for all hydrators. A hydrator is a class that provides some form * Base class for all hydrators. A hydrator is a class that provides some form
@ -31,6 +32,7 @@ use PDO,
* @since 2.0 * @since 2.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi> * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
*/ */
abstract class AbstractHydrator abstract class AbstractHydrator
{ {
@ -72,6 +74,7 @@ abstract class AbstractHydrator
* *
* @param object $stmt * @param object $stmt
* @param object $resultSetMapping * @param object $resultSetMapping
*
* @return IterableResult * @return IterableResult
*/ */
public function iterate($stmt, $resultSetMapping, array $hints = array()) public function iterate($stmt, $resultSetMapping, array $hints = array())
@ -79,7 +82,9 @@ abstract class AbstractHydrator
$this->_stmt = $stmt; $this->_stmt = $stmt;
$this->_rsm = $resultSetMapping; $this->_rsm = $resultSetMapping;
$this->_hints = $hints; $this->_hints = $hints;
$this->_prepare();
$this->prepare();
return new IterableResult($this); return new IterableResult($this);
} }
@ -95,9 +100,13 @@ abstract class AbstractHydrator
$this->_stmt = $stmt; $this->_stmt = $stmt;
$this->_rsm = $resultSetMapping; $this->_rsm = $resultSetMapping;
$this->_hints = $hints; $this->_hints = $hints;
$this->_prepare();
$result = $this->_hydrateAll(); $this->prepare();
$this->_cleanup();
$result = $this->hydrateAllData();
$this->cleanup();
return $result; return $result;
} }
@ -110,12 +119,17 @@ abstract class AbstractHydrator
public function hydrateRow() public function hydrateRow()
{ {
$row = $this->_stmt->fetch(PDO::FETCH_ASSOC); $row = $this->_stmt->fetch(PDO::FETCH_ASSOC);
if ( ! $row) { if ( ! $row) {
$this->_cleanup(); $this->cleanup();
return false; return false;
} }
$result = array(); $result = array();
$this->_hydrateRow($row, $this->_cache, $result);
$this->hydrateRowData($row, $this->_cache, $result);
return $result; return $result;
} }
@ -123,16 +137,17 @@ abstract class AbstractHydrator
* Excutes one-time preparation tasks, once each time hydration is started * Excutes one-time preparation tasks, once each time hydration is started
* through {@link hydrateAll} or {@link iterate()}. * through {@link hydrateAll} or {@link iterate()}.
*/ */
protected function _prepare() protected function prepare()
{} {}
/** /**
* Excutes one-time cleanup tasks at the end of a hydration that was initiated * Excutes one-time cleanup tasks at the end of a hydration that was initiated
* through {@link hydrateAll} or {@link iterate()}. * through {@link hydrateAll} or {@link iterate()}.
*/ */
protected function _cleanup() protected function cleanup()
{ {
$this->_rsm = null; $this->_rsm = null;
$this->_stmt->closeCursor(); $this->_stmt->closeCursor();
$this->_stmt = null; $this->_stmt = null;
} }
@ -146,23 +161,24 @@ abstract class AbstractHydrator
* @param array $cache The cache to use. * @param array $cache The cache to use.
* @param mixed $result The result to fill. * @param mixed $result The result to fill.
*/ */
protected function _hydrateRow(array $data, array &$cache, array &$result) protected function hydrateRowData(array $data, array &$cache, array &$result)
{ {
throw new HydrationException("_hydrateRow() not implemented by this hydrator."); throw new HydrationException("hydrateRowData() not implemented by this hydrator.");
} }
/** /**
* Hydrates all rows from the current statement instance at once. * Hydrates all rows from the current statement instance at once.
*/ */
abstract protected function _hydrateAll(); abstract protected function hydrateAllData();
/** /**
* Processes a row of the result set. * Processes a row of the result set.
*
* Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY).
* Puts the elements of a result row into a new array, grouped by the class * Puts the elements of a result row into a new array, grouped by the dql alias
* they belong to. The column names in the result set are mapped to their * they belong to. The column names in the result set are mapped to their
* field names during this procedure as well as any necessary conversions on * field names during this procedure as well as any necessary conversions on
* the values applied. * the values applied. Scalar values are kept in a specfic key 'scalars'.
* *
* @param array $data SQL Result Row * @param array $data SQL Result Row
* @param array &$cache Cache for column to field result information * @param array &$cache Cache for column to field result information
@ -172,40 +188,51 @@ abstract class AbstractHydrator
* @return array An array with all the fields (name => value) of the data row, * @return array An array with all the fields (name => value) of the data row,
* grouped by their component alias. * grouped by their component alias.
*/ */
protected function _gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) protected function gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents)
{ {
$rowData = array(); $rowData = array();
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results. // Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) { if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) { switch (true) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; // NOTE: Most of the times it's a field mapping, so keep it first!!!
$cache[$key]['isScalar'] = true; case (isset($this->_rsm->fieldMappings[$key])):
} else if (isset($this->_rsm->fieldMappings[$key])) {
$fieldName = $this->_rsm->fieldMappings[$key]; $fieldName = $this->_rsm->fieldMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName; $cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) { break;
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. case (isset($this->_rsm->scalarMappings[$key])):
continue; $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
} else { $cache[$key]['isScalar'] = true;
break;
case (isset($this->_rsm->metaMappings[$key])):
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$fieldName = $this->_rsm->metaMappings[$key]; $fieldName = $this->_rsm->metaMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$this->_rsm->columnOwnerMap[$key]]);
$cache[$key]['isMetaColumn'] = true; $cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $fieldName; $cache[$key]['fieldName'] = $fieldName;
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$cache[$key]['dqlAlias']]);
$cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]); $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
break;
default:
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue 2;
} }
} }
if (isset($cache[$key]['isScalar'])) { if (isset($cache[$key]['isScalar'])) {
$rowData['scalars'][$cache[$key]['fieldName']] = $value; $rowData['scalars'][$cache[$key]['fieldName']] = $value;
continue; continue;
} }
@ -219,10 +246,11 @@ abstract class AbstractHydrator
if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
} }
continue; continue;
} }
// in an inheritance hierachy the same field could be defined several times. // in an inheritance hierarchy the same field could be defined several times.
// We overwrite this value so long we dont have a non-null value, that value we keep. // We overwrite this value so long we dont have a non-null value, that value we keep.
// Per definition it cannot be that a field is defined several times and has several values. // Per definition it cannot be that a field is defined several times and has several values.
if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) { if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) {
@ -241,6 +269,7 @@ abstract class AbstractHydrator
/** /**
* Processes a row of the result set. * Processes a row of the result set.
*
* Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
* simply converts column names to field names and properly converts the * simply converts column names to field names and properly converts the
* values according to their types. The resulting row has the same number * values according to their types. The resulting row has the same number
@ -248,52 +277,77 @@ abstract class AbstractHydrator
* *
* @param array $data * @param array $data
* @param array $cache * @param array $cache
*
* @return array The processed row. * @return array The processed row.
*/ */
protected function _gatherScalarRowData(&$data, &$cache) protected function gatherScalarRowData(&$data, &$cache)
{ {
$rowData = array(); $rowData = array();
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results. // Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) { if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) { switch (true) {
// NOTE: During scalar hydration, most of the times it's a scalar mapping, keep it first!!!
case (isset($this->_rsm->scalarMappings[$key])):
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$cache[$key]['isScalar'] = true; $cache[$key]['isScalar'] = true;
} else if (isset($this->_rsm->fieldMappings[$key])) { break;
case (isset($this->_rsm->fieldMappings[$key])):
$fieldName = $this->_rsm->fieldMappings[$key]; $fieldName = $this->_rsm->fieldMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName; $cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) { break;
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. case (isset($this->_rsm->metaMappings[$key])):
continue;
} else {
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['isMetaColumn'] = true; $cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
break;
default:
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue 2;
} }
} }
$fieldName = $cache[$key]['fieldName']; $fieldName = $cache[$key]['fieldName'];
if (isset($cache[$key]['isScalar'])) { switch (true) {
case (isset($cache[$key]['isScalar'])):
$rowData[$fieldName] = $value; $rowData[$fieldName] = $value;
} else if (isset($cache[$key]['isMetaColumn'])) { break;
case (isset($cache[$key]['isMetaColumn'])):
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
break;
default:
$value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
} else {
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type']
->convertToPHPValue($value, $this->_platform);
} }
} }
return $rowData; return $rowData;
} }
protected function registerManaged($class, $entity, $data) /**
* Register entity as managed in UnitOfWork.
*
* @param Doctrine\ORM\Mapping\ClassMetadata $class
* @param object $entity
* @param array $data
*
* @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow
*/
protected function registerManaged(ClassMetadata $class, $entity, array $data)
{ {
if ($class->isIdentifierComposite) { if ($class->isIdentifierComposite) {
$id = array(); $id = array();
@ -311,6 +365,7 @@ abstract class AbstractHydrator
$id = array($class->identifier[0] => $data[$class->identifier[0]]); $id = array($class->identifier[0] => $data[$class->identifier[0]]);
} }
} }
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
} }
} }

View File

@ -25,8 +25,14 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
* The ArrayHydrator produces a nested array "graph" that is often (not always) * The ArrayHydrator produces a nested array "graph" that is often (not always)
* interchangeable with the corresponding object graph for read-only access. * interchangeable with the corresponding object graph for read-only access.
* *
* @since 2.0
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @since 1.0 * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
*
* @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable.
* Example: SELECT u AS user FROM User u
* The result should contains an array where each array index is an array: array('user' => [User object])
* Problem must be solved somehow by removing the isMixed in ResultSetMapping
*/ */
class ArrayHydrator extends AbstractHydrator class ArrayHydrator extends AbstractHydrator
{ {
@ -38,14 +44,17 @@ class ArrayHydrator extends AbstractHydrator
private $_idTemplate = array(); private $_idTemplate = array();
private $_resultCounter = 0; private $_resultCounter = 0;
/** @override */ /**
protected function _prepare() * {@inheritdoc}
*/
protected function prepare()
{ {
$this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
$this->_identifierMap = array(); $this->_identifierMap = array();
$this->_resultPointers = array(); $this->_resultPointers = array();
$this->_idTemplate = array(); $this->_idTemplate = array();
$this->_resultCounter = 0; $this->_resultCounter = 0;
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array(); $this->_identifierMap[$dqlAlias] = array();
$this->_resultPointers[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array();
@ -53,30 +62,37 @@ class ArrayHydrator extends AbstractHydrator
} }
} }
/** @override */ /**
protected function _hydrateAll() * {@inheritdoc}
*/
protected function hydrateAllData()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($data, $cache, $result); $this->hydrateRowData($data, $cache, $result);
} }
return $result; return $result;
} }
/** @override */ /**
protected function _hydrateRow(array $data, array &$cache, array &$result) * {@inheritdoc}
*/
protected function hydrateRowData(array $row, array &$cache, array &$result)
{ {
// 1) Initialize // 1) Initialize
$id = $this->_idTemplate; // initialize the id-memory $id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = array(); $nonemptyComponents = array();
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents);
// Extract scalar values. They're appended at the end. // Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) { if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars']; $scalars = $rowData['scalars'];
unset($rowData['scalars']); unset($rowData['scalars']);
if (empty($rowData)) { if (empty($rowData)) {
++$this->_resultCounter; ++$this->_resultCounter;
} }
@ -111,11 +127,12 @@ class ArrayHydrator extends AbstractHydrator
} }
$relationAlias = $this->_rsm->relationMap[$dqlAlias]; $relationAlias = $this->_rsm->relationMap[$dqlAlias];
$relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
// Check the type of the relation (many or single-valued) // Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
$oneToOne = false; $oneToOne = false;
if (isset($nonemptyComponents[$dqlAlias])) { if (isset($nonemptyComponents[$dqlAlias])) {
if ( ! isset($baseElement[$relationAlias])) { if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array(); $baseElement[$relationAlias] = array();
@ -128,20 +145,21 @@ class ArrayHydrator extends AbstractHydrator
if ( ! $indexExists || ! $indexIsValid) { if ( ! $indexExists || ! $indexIsValid) {
$element = $data; $element = $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias]; $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element;
$baseElement[$relationAlias][$element[$field]] = $element;
} else { } else {
$baseElement[$relationAlias][] = $element; $baseElement[$relationAlias][] = $element;
} }
end($baseElement[$relationAlias]); end($baseElement[$relationAlias]);
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
key($baseElement[$relationAlias]); $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
} }
} else if ( ! isset($baseElement[$relationAlias])) { } else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array(); $baseElement[$relationAlias] = array();
} }
} else { } else {
$oneToOne = true; $oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) { if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = null; $baseElement[$relationAlias] = null;
} else if ( ! isset($baseElement[$relationAlias])) { } else if ( ! isset($baseElement[$relationAlias])) {
@ -159,14 +177,16 @@ class ArrayHydrator extends AbstractHydrator
// It's a root result element // It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root $this->_rootAliases[$dqlAlias] = true; // Mark as root
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result. // if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$result[] = array(0 => null); $result[] = array($entityKey => null);
} else { } else {
$result[] = null; $result[] = null;
} }
$resultKey = $this->_resultCounter;
++$this->_resultCounter; ++$this->_resultCounter;
continue; continue;
} }
@ -174,26 +194,23 @@ class ArrayHydrator extends AbstractHydrator
// Check for an existing element // Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $rowData[$dqlAlias]; $element = $rowData[$dqlAlias];
if ($this->_rsm->isMixed) {
$element = array($entityKey => $element);
}
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias]; $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
if ($this->_rsm->isMixed) { $result[$resultKey] = $element;
$result[] = array($element[$field] => $element);
++$this->_resultCounter;
} else {
$result[$element[$field]] = $element;
}
} else {
if ($this->_rsm->isMixed) {
$result[] = array($element);
++$this->_resultCounter;
} else { } else {
$resultKey = $this->_resultCounter;
$result[] = $element; $result[] = $element;
++$this->_resultCounter;
} }
}
end($result); $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
} else { } else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$resultKey = $index;
/*if ($this->_rsm->isMixed) { /*if ($this->_rsm->isMixed) {
$result[] =& $result[$index]; $result[] =& $result[$index];
++$this->_resultCounter; ++$this->_resultCounter;
@ -205,8 +222,17 @@ class ArrayHydrator extends AbstractHydrator
// Append scalar values to mixed result sets // Append scalar values to mixed result sets
if (isset($scalars)) { if (isset($scalars)) {
if ( ! isset($resultKey) ) {
// this only ever happens when no object is fetched (scalar result only)
if (isset($this->_rsm->indexByMap['scalars'])) {
$resultKey = $row[$this->_rsm->indexByMap['scalars']];
} else {
$resultKey = $this->_resultCounter - 1;
}
}
foreach ($scalars as $name => $value) { foreach ($scalars as $name => $value) {
$result[$this->_resultCounter - 1][$name] = $value; $result[$resultKey][$name] = $value;
} }
} }
} }
@ -224,28 +250,45 @@ class ArrayHydrator extends AbstractHydrator
{ {
if ($coll === null) { if ($coll === null) {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
return; return;
} }
if ($index !== false) {
$this->_resultPointers[$dqlAlias] =& $coll[$index];
return;
} else {
if ($coll) {
if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll;
} else {
end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
}
}
}
}
private function _getClassMetadata($className) if ($index !== false) {
$this->_resultPointers[$dqlAlias] =& $coll[$index];
return;
}
if ( ! $coll) {
return;
}
if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll;
return;
}
end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
return;
}
/**
* Retrieve ClassMetadata associated to entity class name.
*
* @param string $className
*
* @return Doctrine\ORM\Mapping\ClassMetadata
*/
private function getClassMetadata($className)
{ {
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className); $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
return $this->_ce[$className]; return $this->_ce[$className];
} }
} }

View File

@ -14,4 +14,13 @@ class HydrationException extends \Doctrine\ORM\ORMException
return new self("The parent object of entity result with alias '$alias' was not found." return new self("The parent object of entity result with alias '$alias' was not found."
. " The parent alias is '$parentAlias'."); . " The parent alias is '$parentAlias'.");
} }
public static function emptyDiscriminatorValue($dqlAlias)
{
return new self("The DQL alias '" . $dqlAlias . "' contains an entity ".
"of an inheritance hierachy with an empty discriminator value. This means " .
"that the database contains inconsistent data with an empty " .
"discriminator value in a table row."
);
}
} }

View File

@ -29,9 +29,16 @@ use PDO,
/** /**
* The ObjectHydrator constructs an object graph out of an SQL result set. * The ObjectHydrator constructs an object graph out of an SQL result set.
* *
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
*
* @internal Highly performance-sensitive code. * @internal Highly performance-sensitive code.
*
* @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable.
* Example: SELECT u AS user FROM User u
* The result should contains an array where each array index is an array: array('user' => [User object])
* Problem must be solved somehow by removing the isMixed in ResultSetMapping
*/ */
class ObjectHydrator extends AbstractHydrator class ObjectHydrator extends AbstractHydrator
{ {
@ -53,12 +60,14 @@ class ObjectHydrator extends AbstractHydrator
/** @override */ /** @override */
protected function _prepare() protected function prepare()
{ {
$this->_identifierMap = $this->_identifierMap =
$this->_resultPointers = $this->_resultPointers =
$this->_idTemplate = array(); $this->_idTemplate = array();
$this->_resultCounter = 0; $this->_resultCounter = 0;
if ( ! isset($this->_hints['deferEagerLoad'])) { if ( ! isset($this->_hints['deferEagerLoad'])) {
$this->_hints['deferEagerLoad'] = true; $this->_hints['deferEagerLoad'] = true;
} }
@ -66,15 +75,17 @@ class ObjectHydrator extends AbstractHydrator
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array(); $this->_identifierMap[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = ''; $this->_idTemplate[$dqlAlias] = '';
$class = $this->_em->getClassMetadata($className);
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $class; $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
// Remember which associations are "fetch joined", so that we know where to inject // Remember which associations are "fetch joined", so that we know where to inject
// collection stubs or proxies and where not. // collection stubs or proxies and where not.
if (isset($this->_rsm->relationMap[$dqlAlias])) { if ( ! isset($this->_rsm->relationMap[$dqlAlias])) {
continue;
}
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) {
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]);
} }
@ -82,30 +93,30 @@ class ObjectHydrator extends AbstractHydrator
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
$sourceClass = $this->_getClassMetadata($sourceClassName); $sourceClass = $this->_getClassMetadata($sourceClassName);
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true;
if ($sourceClass->subClasses) { $this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true;
foreach ($sourceClass->subClasses as $sourceSubclassName) {
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true; if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
continue;
} }
}
if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) {
// Mark any non-collection opposite sides as fetched, too. // Mark any non-collection opposite sides as fetched, too.
if ($assoc['mappedBy']) { if ($assoc['mappedBy']) {
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true; $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true;
} else {
continue;
}
// handle fetch-joined owning side bi-directional one-to-one associations
if ($assoc['inversedBy']) { if ($assoc['inversedBy']) {
$class = $this->_ce[$className];
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; $inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
$this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true; if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
if ($class->subClasses) { continue;
foreach ($class->subClasses as $targetSubclassName) {
$this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
}
}
}
}
}
} }
$this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true;
} }
} }
} }
@ -113,11 +124,12 @@ class ObjectHydrator extends AbstractHydrator
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function _cleanup() protected function cleanup()
{ {
$eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true;
parent::_cleanup(); parent::cleanup();
$this->_identifierMap = $this->_identifierMap =
$this->_initializedCollections = $this->_initializedCollections =
$this->_existingCollections = $this->_existingCollections =
@ -131,13 +143,13 @@ class ObjectHydrator extends AbstractHydrator
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function _hydrateAll() protected function hydrateAllData()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($row, $cache, $result); $this->hydrateRowData($row, $cache, $result);
} }
// Take snapshots from all newly initialized collections // Take snapshots from all newly initialized collections
@ -152,35 +164,40 @@ class ObjectHydrator extends AbstractHydrator
* Initializes a related collection. * Initializes a related collection.
* *
* @param object $entity The entity to which the collection belongs. * @param object $entity The entity to which the collection belongs.
* @param ClassMetadata $class
* @param string $name The name of the field on the entity that holds the collection. * @param string $name The name of the field on the entity that holds the collection.
* @param string $parentDqlAlias Alias of the parent fetch joining this collection.
*/ */
private function _initRelatedCollection($entity, $class, $fieldName) private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$relation = $class->associationMappings[$fieldName]; $relation = $class->associationMappings[$fieldName];
$value = $class->reflFields[$fieldName]->getValue($entity); $value = $class->reflFields[$fieldName]->getValue($entity);
if ($value === null) { if ($value === null) {
$value = new ArrayCollection; $value = new ArrayCollection;
} }
if ( ! $value instanceof PersistentCollection) { if ( ! $value instanceof PersistentCollection) {
$value = new PersistentCollection( $value = new PersistentCollection(
$this->_em, $this->_em, $this->_ce[$relation['targetEntity']], $value
$this->_ce[$relation['targetEntity']],
$value
); );
$value->setOwner($entity, $relation); $value->setOwner($entity, $relation);
$class->reflFields[$fieldName]->setValue($entity, $value); $class->reflFields[$fieldName]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH]) || } else if (
isset($this->_hints['fetched'][$class->name][$fieldName]) && isset($this->_hints[Query::HINT_REFRESH]) ||
! $value->isInitialized()) { isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
! $value->isInitialized()
) {
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
$value->setDirty(false); $value->setDirty(false);
$value->setInitialized(true); $value->setInitialized(true);
$value->unwrap()->clear(); $value->unwrap()->clear();
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else { } else {
// Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
@ -200,17 +217,24 @@ class ObjectHydrator extends AbstractHydrator
private function _getEntity(array $data, $dqlAlias) private function _getEntity(array $data, $dqlAlias)
{ {
$className = $this->_rsm->aliasMap[$dqlAlias]; $className = $this->_rsm->aliasMap[$dqlAlias];
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
if ($data[$discrColumn] === "") {
throw HydrationException::emptyDiscriminatorValue($dqlAlias);
}
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
unset($data[$discrColumn]); unset($data[$discrColumn]);
} }
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) {
$class = $this->_ce[$className]; $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
$this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
} }
$this->_hints['fetchAlias'] = $dqlAlias;
return $this->_uow->createEntity($className, $data, $this->_hints); return $this->_uow->createEntity($className, $data, $this->_hints);
} }
@ -218,6 +242,7 @@ class ObjectHydrator extends AbstractHydrator
{ {
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent? // TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
$class = $this->_ce[$className]; $class = $this->_ce[$className];
/* @var $class ClassMetadata */ /* @var $class ClassMetadata */
if ($class->isIdentifierComposite) { if ($class->isIdentifierComposite) {
$idHash = ''; $idHash = '';
@ -249,6 +274,7 @@ class ObjectHydrator extends AbstractHydrator
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className); $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
return $this->_ce[$className]; return $this->_ce[$className];
} }
@ -273,18 +299,20 @@ class ObjectHydrator extends AbstractHydrator
* @param array $cache The cache to use. * @param array $cache The cache to use.
* @param array $result The result array to fill. * @param array $result The result array to fill.
*/ */
protected function _hydrateRow(array $data, array &$cache, array &$result) protected function hydrateRowData(array $row, array &$cache, array &$result)
{ {
// Initialize // Initialize
$id = $this->_idTemplate; // initialize the id-memory $id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = array(); $nonemptyComponents = array();
// Split the row data into chunks of class data. // Split the row data into chunks of class data.
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents);
// Extract scalar values. They're appended at the end. // Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) { if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars']; $scalars = $rowData['scalars'];
unset($rowData['scalars']); unset($rowData['scalars']);
if (empty($rowData)) { if (empty($rowData)) {
++$this->_resultCounter; ++$this->_resultCounter;
} }
@ -311,7 +339,7 @@ class ObjectHydrator extends AbstractHydrator
// Get a reference to the parent object to which the joined element belongs. // Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
$first = reset($this->_resultPointers); $first = reset($this->_resultPointers);
$parentObject = $this->_resultPointers[$parentAlias][key($first)]; $parentObject = $first[key($first)];
} else if (isset($this->_resultPointers[$parentAlias])) { } else if (isset($this->_resultPointers[$parentAlias])) {
$parentObject = $this->_resultPointers[$parentAlias]; $parentObject = $this->_resultPointers[$parentAlias];
} else { } else {
@ -333,7 +361,7 @@ class ObjectHydrator extends AbstractHydrator
if (isset($this->_initializedCollections[$collKey])) { if (isset($this->_initializedCollections[$collKey])) {
$reflFieldValue = $this->_initializedCollections[$collKey]; $reflFieldValue = $this->_initializedCollections[$collKey];
} else if ( ! isset($this->_existingCollections[$collKey])) { } else if ( ! isset($this->_existingCollections[$collKey])) {
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
} }
$indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
@ -352,8 +380,7 @@ class ObjectHydrator extends AbstractHydrator
$element = $this->_getEntity($data, $dqlAlias); $element = $this->_getEntity($data, $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias]; $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]];
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
$reflFieldValue->hydrateSet($indexValue, $element); $reflFieldValue->hydrateSet($indexValue, $element);
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
} else { } else {
@ -369,10 +396,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
} }
} else if ( ! $reflField->getValue($parentObject)) { } else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
$coll->setOwner($parentObject, $relation);
$reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
} }
} else { } else {
// PATH B: Single-valued association // PATH B: Single-valued association
@ -383,6 +407,7 @@ class ObjectHydrator extends AbstractHydrator
$reflField->setValue($parentObject, $element); $reflField->setValue($parentObject, $element);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
$targetClass = $this->_ce[$relation['targetEntity']]; $targetClass = $this->_ce[$relation['targetEntity']];
if ($relation['isOwningSide']) { if ($relation['isOwningSide']) {
//TODO: Just check hints['fetched'] here? //TODO: Just check hints['fetched'] here?
// If there is an inverse mapping on the target class its bidirectional // If there is an inverse mapping on the target class its bidirectional
@ -413,14 +438,16 @@ class ObjectHydrator extends AbstractHydrator
} else { } else {
// PATH C: Its a root result element // PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias $this->_rootAliases[$dqlAlias] = true; // Mark as root alias
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result. // if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$result[] = array(0 => null); $result[] = array($entityKey => null);
} else { } else {
$result[] = null; $result[] = null;
} }
$resultKey = $this->_resultCounter;
++$this->_resultCounter; ++$this->_resultCounter;
continue; continue;
} }
@ -428,35 +455,31 @@ class ObjectHydrator extends AbstractHydrator
// check for existing result from the iterations before // check for existing result from the iterations before
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$field = $this->_rsm->indexByMap[$dqlAlias];
$key = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$element = array($key => $element); $element = array($entityKey => $element);
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
} else {
$result[$key] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key;
} }
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
$resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
if (isset($this->_hints['collection'])) { if (isset($this->_hints['collection'])) {
$this->_hints['collection']->hydrateSet($key, $element); $this->_hints['collection']->hydrateSet($resultKey, $element);
} }
$result[$resultKey] = $element;
} else { } else {
if ($this->_rsm->isMixed) { $resultKey = $this->_resultCounter;
$element = array(0 => $element);
}
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter; ++$this->_resultCounter;
if (isset($this->_hints['collection'])) { if (isset($this->_hints['collection'])) {
$this->_hints['collection']->hydrateAdd($element); $this->_hints['collection']->hydrateAdd($element);
} }
$result[] = $element;
} }
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
// Update result pointer // Update result pointer
$this->_resultPointers[$dqlAlias] = $element; $this->_resultPointers[$dqlAlias] = $element;
@ -464,6 +487,7 @@ class ObjectHydrator extends AbstractHydrator
// Update result pointer // Update result pointer
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$this->_resultPointers[$dqlAlias] = $result[$index]; $this->_resultPointers[$dqlAlias] = $result[$index];
$resultKey = $index;
/*if ($this->_rsm->isMixed) { /*if ($this->_rsm->isMixed) {
$result[] = $result[$index]; $result[] = $result[$index];
++$this->_resultCounter; ++$this->_resultCounter;
@ -474,8 +498,16 @@ class ObjectHydrator extends AbstractHydrator
// Append scalar values to mixed result sets // Append scalar values to mixed result sets
if (isset($scalars)) { if (isset($scalars)) {
if ( ! isset($resultKey) ) {
if (isset($this->_rsm->indexByMap['scalars'])) {
$resultKey = $row[$this->_rsm->indexByMap['scalars']];
} else {
$resultKey = $this->_resultCounter - 1;
}
}
foreach ($scalars as $name => $value) { foreach ($scalars as $name => $value) {
$result[$this->_resultCounter - 1][$name] = $value; $result[$resultKey][$name] = $value;
} }
} }
} }

View File

@ -26,25 +26,32 @@ use Doctrine\DBAL\Connection;
* The created result is almost the same as a regular SQL result set, except * The created result is almost the same as a regular SQL result set, except
* that column names are mapped to field names and data type conversions take place. * that column names are mapped to field names and data type conversions take place.
* *
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/ */
class ScalarHydrator extends AbstractHydrator class ScalarHydrator extends AbstractHydrator
{ {
/** @override */ /**
protected function _hydrateAll() * {@inheritdoc}
*/
protected function hydrateAllData()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) { while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) {
$result[] = $this->_gatherScalarRowData($data, $cache); $this->hydrateRowData($data, $cache, $result);
} }
return $result; return $result;
} }
/** @override */ /**
protected function _hydrateRow(array $data, array &$cache, array &$result) * {@inheritdoc}
*/
protected function hydrateRowData(array $data, array &$cache, array &$result)
{ {
$result[] = $this->_gatherScalarRowData($data, $cache); $result[] = $this->gatherScalarRowData($data, $cache);
} }
} }

View File

@ -17,7 +17,6 @@
* <http://www.doctrine-project.org>. * <http://www.doctrine-project.org>.
*/ */
namespace Doctrine\ORM\Internal\Hydration; namespace Doctrine\ORM\Internal\Hydration;
use \PDO; use \PDO;
@ -32,15 +31,21 @@ class SimpleObjectHydrator extends AbstractHydrator
*/ */
private $class; private $class;
/**
* @var array
*/
private $declaringClasses = array(); private $declaringClasses = array();
protected function _hydrateAll() /**
* {@inheritdoc}
*/
protected function hydrateAllData()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($row, $cache, $result); $this->hydrateRowData($row, $cache, $result);
} }
$this->_em->getUnitOfWork()->triggerEagerLoads(); $this->_em->getUnitOfWork()->triggerEagerLoads();
@ -48,79 +53,73 @@ class SimpleObjectHydrator extends AbstractHydrator
return $result; return $result;
} }
protected function _prepare() /**
* {@inheritdoc}
*/
protected function prepare()
{ {
if (count($this->_rsm->aliasMap) == 1) { if (count($this->_rsm->aliasMap) !== 1) {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result.");
}
if ($this->_rsm->scalarMappings) {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.");
}
$this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap)); $this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap));
if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
// We only need to add declaring classes if we have inheritance.
if ($this->class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_NONE) {
return;
}
foreach ($this->_rsm->declaringClasses AS $column => $class) { foreach ($this->_rsm->declaringClasses AS $column => $class) {
$this->declaringClasses[$column] = $this->_em->getClassMetadata($class); $this->declaringClasses[$column] = $this->_em->getClassMetadata($class);
} }
} }
} else {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping not containing exactly one object result.");
}
if ($this->_rsm->scalarMappings) {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.");
}
}
protected function _hydrateRow(array $sqlResult, array &$cache, array &$result) /**
* {@inheritdoc}
*/
protected function hydrateRowData(array $sqlResult, array &$cache, array &$result)
{ {
$data = array();
if ($this->class->inheritanceType == ClassMetadata::INHERITANCE_TYPE_NONE) {
foreach ($sqlResult as $column => $value) {
if (!isset($cache[$column])) {
if (isset($this->_rsm->fieldMappings[$column])) {
$cache[$column]['name'] = $this->_rsm->fieldMappings[$column];
$cache[$column]['field'] = true;
} else {
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
}
}
if (isset($cache[$column]['field'])) {
$value = Type::getType($this->class->fieldMappings[$cache[$column]['name']]['type'])
->convertToPHPValue($value, $this->_platform);
}
$data[$cache[$column]['name']] = $value;
}
$entityName = $this->class->name; $entityName = $this->class->name;
} else { $data = array();
// We need to find the correct entity class name if we have inheritance in resultset
if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
$discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']); $discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']);
if ($sqlResult[$discrColumnName] === '') {
throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap));
}
$entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]]; $entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]];
unset($sqlResult[$discrColumnName]); unset($sqlResult[$discrColumnName]);
}
foreach ($sqlResult as $column => $value) { foreach ($sqlResult as $column => $value) {
// Hydrate column information if not yet present
if ( ! isset($cache[$column])) { if ( ! isset($cache[$column])) {
if (isset($this->_rsm->fieldMappings[$column])) { if (($info = $this->hydrateColumnInfo($entityName, $column)) === null) {
$field = $this->_rsm->fieldMappings[$column]; continue;
$class = $this->declaringClasses[$column];
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
$cache[$column]['name'] = $field;
$cache[$column]['class'] = $class;
}
} else if (isset($this->_rsm->relationMap[$column])) {
if ($this->_rsm->relationMap[$column] == $entityName || is_subclass_of($entityName, $this->_rsm->relationMap[$column])) {
$cache[$column]['name'] = $field;
}
} else {
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
}
} }
if (isset($cache[$column]['class'])) { $cache[$column] = $info;
$value = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type'])
->convertToPHPValue($value, $this->_platform);
} }
// the second and part is to prevent overwrites in case of multiple // Convert field to a valid PHP value
// inheritance classes using the same property name (See AbstractHydrator) if (isset($cache[$column]['field'])) {
$type = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']);
$value = $type->convertToPHPValue($value, $this->_platform);
}
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
if (isset($cache[$column]) && ( ! isset($data[$cache[$column]['name']]) || $value !== null)) { if (isset($cache[$column]) && ( ! isset($data[$cache[$column]['name']]) || $value !== null)) {
$data[$cache[$column]['name']] = $value; $data[$cache[$column]['name']] = $value;
} }
} }
}
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) { if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); $this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
@ -128,4 +127,52 @@ class SimpleObjectHydrator extends AbstractHydrator
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints); $result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints);
} }
/**
* Retrieve column information form ResultSetMapping.
*
* @param string $entityName
* @param string $column
*
* @return array
*/
protected function hydrateColumnInfo($entityName, $column)
{
switch (true) {
case (isset($this->_rsm->fieldMappings[$column])):
$class = isset($this->declaringClasses[$column])
? $this->declaringClasses[$column]
: $this->class;
// If class is not part of the inheritance, ignore
if ( ! ($class->name === $entityName || is_subclass_of($entityName, $class->name))) {
return null;
}
return array(
'class' => $class,
'name' => $this->_rsm->fieldMappings[$column],
'field' => true,
);
case (isset($this->_rsm->relationMap[$column])):
$class = isset($this->_rsm->relationMap[$column])
? $this->_rsm->relationMap[$column]
: $this->class;
// If class is not self referencing, ignore
if ( ! ($class === $entityName || is_subclass_of($entityName, $class))) {
return null;
}
// TODO: Decide what to do with associations. It seems original code is incomplete.
// One solution is to load the association, but it might require extra efforts.
return array('name' => $column);
default:
return array(
'name' => $this->_rsm->metaMappings[$column]
);
}
}
} }

View File

@ -19,30 +19,37 @@
namespace Doctrine\ORM\Internal\Hydration; namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection,
Doctrine\ORM\NoResultException,
Doctrine\ORM\NonUniqueResultException;
/** /**
* Hydrator that hydrates a single scalar value from the result set. * Hydrator that hydrates a single scalar value from the result set.
* *
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/ */
class SingleScalarHydrator extends AbstractHydrator class SingleScalarHydrator extends AbstractHydrator
{ {
/** @override */ /**
protected function _hydrateAll() * {@inheritdoc}
*/
protected function hydrateAllData()
{ {
$cache = array(); $data = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC);
$result = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); $numRows = count($data);
$num = count($result);
if ($num == 0) { if ($numRows === 0) {
throw new \Doctrine\ORM\NoResultException; throw new NoResultException();
} else if ($num > 1 || count($result[key($result)]) > 1) {
throw new \Doctrine\ORM\NonUniqueResultException;
} }
$result = $this->_gatherScalarRowData($result[key($result)], $cache); if ($numRows > 1 || count($data[key($data)]) > 1) {
throw new NonUniqueResultException();
}
$cache = array();
$result = $this->gatherScalarRowData($data[key($data)], $cache);
return array_shift($result); return array_shift($result);
} }

View File

@ -313,6 +313,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
$class->containsForeignIdentifier = true; $class->containsForeignIdentifier = true;
} }
if ($parent && !empty ($parent->namedQueries)) {
$this->addInheritedNamedQueries($class, $parent);
}
$class->setParentClasses($visited); $class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) { if ($this->evm->hasListeners(Events::loadClassMetadata)) {
@ -429,6 +433,25 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
} }
} }
/**
* Adds inherited named queries to the subclass mapping.
*
* @since 2.2
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
*/
private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->namedQueries as $name => $query) {
if (!isset ($subClass->namedQueries[$name])) {
$subClass->addNamedQuery(array(
'name' => $query['name'],
'query' => $query['query']
));
}
}
}
/** /**
* Completes the ID generator mapping. If "auto" is specified we choose the generator * Completes the ID generator mapping. If "auto" is specified we choose the generator
* most appropriate for the targeted database platform. * most appropriate for the targeted database platform.

View File

@ -20,6 +20,7 @@
namespace Doctrine\ORM\Mapping; namespace Doctrine\ORM\Mapping;
use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\DBAL\Types\Type;
use ReflectionClass; use ReflectionClass;
/** /**
@ -136,10 +137,6 @@ class ClassMetadataInfo implements ClassMetadata
* Identifies a many-to-one association. * Identifies a many-to-one association.
*/ */
const MANY_TO_ONE = 2; const MANY_TO_ONE = 2;
/**
* Combined bitmask for to-one (single-valued) associations.
*/
const TO_ONE = 3;
/** /**
* Identifies a one-to-many association. * Identifies a one-to-many association.
*/ */
@ -148,6 +145,10 @@ class ClassMetadataInfo implements ClassMetadata
* Identifies a many-to-many association. * Identifies a many-to-many association.
*/ */
const MANY_TO_MANY = 8; const MANY_TO_MANY = 8;
/**
* Combined bitmask for to-one (single-valued) associations.
*/
const TO_ONE = 3;
/** /**
* Combined bitmask for to-many (collection-valued) associations. * Combined bitmask for to-many (collection-valued) associations.
*/ */
@ -318,7 +319,7 @@ class ClassMetadataInfo implements ClassMetadata
public $discriminatorMap = array(); public $discriminatorMap = array();
/** /**
* READ-ONLY: The definition of the descriminator column used in JOINED and SINGLE_TABLE * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
* inheritance mappings. * inheritance mappings.
* *
* @var array * @var array
@ -685,7 +686,7 @@ class ClassMetadataInfo implements ClassMetadata
if ( ! isset($this->namedQueries[$queryName])) { if ( ! isset($this->namedQueries[$queryName])) {
throw MappingException::queryNotFound($this->name, $queryName); throw MappingException::queryNotFound($this->name, $queryName);
} }
return $this->namedQueries[$queryName]; return $this->namedQueries[$queryName]['dql'];
} }
/** /**
@ -746,6 +747,14 @@ class ClassMetadataInfo implements ClassMetadata
$this->isIdentifierComposite = true; $this->isIdentifierComposite = true;
} }
} }
if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
if (isset($mapping['id']) && $mapping['id'] === true) {
throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
}
$mapping['requireSQLConversion'] = true;
}
} }
/** /**
@ -783,6 +792,13 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
} }
if ( ($mapping['type'] & (self::MANY_TO_ONE|self::MANY_TO_MANY)) > 0 &&
isset($mapping['orphanRemoval']) &&
$mapping['orphanRemoval'] == true) {
throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
}
// Complete id mapping // Complete id mapping
if (isset($mapping['id']) && $mapping['id'] === true) { if (isset($mapping['id']) && $mapping['id'] === true) {
if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) {
@ -1111,23 +1127,23 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getIdentifierColumnNames() public function getIdentifierColumnNames()
{ {
if ($this->isIdentifierComposite) {
$columnNames = array(); $columnNames = array();
foreach ($this->identifier as $idField) {
if (isset($this->associationMappings[$idField])) { foreach ($this->identifier as $idProperty) {
// no composite pk as fk entity assumption: if (isset($this->fieldMappings[$idProperty])) {
$columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name']; $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
} else {
$columnNames[] = $this->fieldMappings[$idField]['columnName']; continue;
} }
// Association defined as Id field
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
$assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
$columnNames = array_merge($columnNames, $assocColumnNames);
} }
return $columnNames; return $columnNames;
} else if(isset($this->fieldMappings[$this->identifier[0]])) {
return array($this->fieldMappings[$this->identifier[0]]['columnName']);
} else {
// no composite pk as fk entity assumption:
return array($this->associationMappings[$this->identifier[0]]['joinColumns'][0]['name']);
}
} }
/** /**
@ -1448,8 +1464,15 @@ class ClassMetadataInfo implements ClassMetadata
if (isset($this->namedQueries[$queryMapping['name']])) { if (isset($this->namedQueries[$queryMapping['name']])) {
throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
} }
$query = str_replace('__CLASS__', $this->name, $queryMapping['query']);
$this->namedQueries[$queryMapping['name']] = $query; $name = $queryMapping['name'];
$query = $queryMapping['query'];
$dql = str_replace('__CLASS__', $this->name, $query);
$this->namedQueries[$name] = array(
'name' => $name,
'query' => $query,
'dql' => $dql
);
} }
/** /**
@ -1504,14 +1527,16 @@ class ClassMetadataInfo implements ClassMetadata
/** /**
* Stores the association mapping. * Stores the association mapping.
* *
* @param AssociationMapping $assocMapping * @param array $assocMapping
*/ */
protected function _storeAssociationMapping(array $assocMapping) protected function _storeAssociationMapping(array $assocMapping)
{ {
$sourceFieldName = $assocMapping['fieldName']; $sourceFieldName = $assocMapping['fieldName'];
if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) { if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) {
throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName); throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName);
} }
$this->associationMappings[$sourceFieldName] = $assocMapping; $this->associationMappings[$sourceFieldName] = $assocMapping;
} }
@ -1904,6 +1929,42 @@ class ClassMetadataInfo implements ClassMetadata
return $this->name; return $this->name;
} }
/**
* Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
*
* @param AbstractPlatform $platform
* @return array
*/
public function getQuotedIdentifierColumnNames($platform)
{
$quotedColumnNames = array();
foreach ($this->identifier as $idProperty) {
if (isset($this->fieldMappings[$idProperty])) {
$quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
: $this->fieldMappings[$idProperty]['columnName'];
continue;
}
// Association defined as Id field
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
$assocQuotedColumnNames = array_map(
function ($joinColumn) {
return isset($joinColumn['quoted'])
? $platform->quoteIdentifier($joinColumn['name'])
: $joinColumn['name'];
},
$joinColumns
);
$quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
}
return $quotedColumnNames;
}
/** /**
* Gets the (possibly quoted) column name of a mapped field for safe use * Gets the (possibly quoted) column name of a mapped field for safe use
* in an SQL statement. * in an SQL statement.
@ -1914,7 +1975,9 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getQuotedColumnName($field, $platform) public function getQuotedColumnName($field, $platform)
{ {
return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName']; return isset($this->fieldMappings[$field]['quoted'])
? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
: $this->fieldMappings[$field]['columnName'];
} }
/** /**
@ -1939,4 +2002,22 @@ class ClassMetadataInfo implements ClassMetadata
{ {
return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name']; return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name'];
} }
/**
* @param string $fieldName
* @return bool
*/
public function isAssociationInverseSide($fieldName)
{
return isset($this->associationMappings[$fieldName]) && ! $this->associationMappings[$fieldName]['isOwningSide'];
}
/**
* @param string $fieldName
* @return string
*/
public function getAssociationMappedByTargetField($fieldName)
{
return $this->associationMappings[$fieldName]['mappedBy'];
}
} }

View File

@ -331,7 +331,7 @@ class AnnotationDriver implements Driver
$mapping['inversedBy'] = $oneToOneAnnot->inversedBy; $mapping['inversedBy'] = $oneToOneAnnot->inversedBy;
$mapping['cascade'] = $oneToOneAnnot->cascade; $mapping['cascade'] = $oneToOneAnnot->cascade;
$mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval; $mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneAnnot->fetch); $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnot->fetch);
$metadata->mapOneToOne($mapping); $metadata->mapOneToOne($mapping);
} else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) { } else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) {
$mapping['mappedBy'] = $oneToManyAnnot->mappedBy; $mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
@ -339,7 +339,7 @@ class AnnotationDriver implements Driver
$mapping['cascade'] = $oneToManyAnnot->cascade; $mapping['cascade'] = $oneToManyAnnot->cascade;
$mapping['indexBy'] = $oneToManyAnnot->indexBy; $mapping['indexBy'] = $oneToManyAnnot->indexBy;
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval; $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyAnnot->fetch); $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnot->fetch);
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
$mapping['orderBy'] = $orderByAnnot->value; $mapping['orderBy'] = $orderByAnnot->value;
@ -355,7 +355,7 @@ class AnnotationDriver implements Driver
$mapping['cascade'] = $manyToOneAnnot->cascade; $mapping['cascade'] = $manyToOneAnnot->cascade;
$mapping['inversedBy'] = $manyToOneAnnot->inversedBy; $mapping['inversedBy'] = $manyToOneAnnot->inversedBy;
$mapping['targetEntity'] = $manyToOneAnnot->targetEntity; $mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneAnnot->fetch); $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnot->fetch);
$metadata->mapManyToOne($mapping); $metadata->mapManyToOne($mapping);
} else if ($manyToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) { } else if ($manyToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) {
$joinTable = array(); $joinTable = array();
@ -395,7 +395,7 @@ class AnnotationDriver implements Driver
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy; $mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
$mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['cascade'] = $manyToManyAnnot->cascade;
$mapping['indexBy'] = $manyToManyAnnot->indexBy; $mapping['indexBy'] = $manyToManyAnnot->indexBy;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyAnnot->fetch); $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch);
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
$mapping['orderBy'] = $orderByAnnot->value; $mapping['orderBy'] = $orderByAnnot->value;
@ -446,6 +446,10 @@ class AnnotationDriver implements Driver
if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) { if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad); $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad);
} }
if (isset($annotations['Doctrine\ORM\Mapping\PreFlush'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preFlush);
}
} }
} }
} }
@ -536,6 +540,22 @@ class AnnotationDriver implements Driver
return $classes; return $classes;
} }
/**
* Attempts to resolve the fetch mode.
*
* @param string $className The class name
* @param string $fetchMode The fetch mode
* @return integer The fetch mode as defined in ClassMetadata
* @throws MappingException If the fetch mode is not valid
*/
private function getFetchMode($className, $fetchMode)
{
if(!defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
throw MappingException::invalidFetchMode($className, $fetchMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
}
/** /**
* Factory method for the Annotation Driver * Factory method for the Annotation Driver
* *

View File

@ -387,3 +387,9 @@ final class PostRemove implements Annotation {}
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PostLoad implements Annotation {} final class PostLoad implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PreFlush implements Annotation {}

View File

@ -166,9 +166,12 @@ class XmlDriver extends AbstractFileDriver
foreach ($xmlRoot->field as $fieldMapping) { foreach ($xmlRoot->field as $fieldMapping) {
$mapping = array( $mapping = array(
'fieldName' => (string)$fieldMapping['name'], 'fieldName' => (string)$fieldMapping['name'],
'type' => (string)$fieldMapping['type']
); );
if (isset($fieldMapping['type'])) {
$mapping['type'] = (string)$fieldMapping['type'];
}
if (isset($fieldMapping['column'])) { if (isset($fieldMapping['column'])) {
$mapping['columnName'] = (string)$fieldMapping['column']; $mapping['columnName'] = (string)$fieldMapping['column'];
} }
@ -219,10 +222,13 @@ class XmlDriver extends AbstractFileDriver
$mapping = array( $mapping = array(
'id' => true, 'id' => true,
'fieldName' => (string)$idElement['name'], 'fieldName' => (string)$idElement['name']
'type' => (string)$idElement['type']
); );
if (isset($idElement['type'])) {
$mapping['type'] = (string)$idElement['type'];
}
if (isset($idElement['column'])) { if (isset($idElement['column'])) {
$mapping['columnName'] = (string)$idElement['column']; $mapping['columnName'] = (string)$idElement['column'];
} }
@ -327,6 +333,8 @@ class XmlDriver extends AbstractFileDriver
if (isset($oneToManyElement['index-by'])) { if (isset($oneToManyElement['index-by'])) {
$mapping['indexBy'] = (string)$oneToManyElement['index-by']; $mapping['indexBy'] = (string)$oneToManyElement['index-by'];
} else if (isset($oneToManyElement->{'index-by'})) {
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
} }
$metadata->mapOneToMany($mapping); $metadata->mapOneToMany($mapping);
@ -369,10 +377,6 @@ class XmlDriver extends AbstractFileDriver
$mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade); $mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
} }
if (isset($manyToOneElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$manyToOneElement->{'orphan-removal'};
}
$metadata->mapManyToOne($mapping); $metadata->mapManyToOne($mapping);
} }
} }
@ -420,10 +424,6 @@ class XmlDriver extends AbstractFileDriver
$mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade); $mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade);
} }
if (isset($manyToManyElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'};
}
if (isset($manyToManyElement->{'order-by'})) { if (isset($manyToManyElement->{'order-by'})) {
$orderBy = array(); $orderBy = array();
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} AS $orderByField) { foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} AS $orderByField) {
@ -432,8 +432,10 @@ class XmlDriver extends AbstractFileDriver
$mapping['orderBy'] = $orderBy; $mapping['orderBy'] = $orderBy;
} }
if (isset($manyToManyElement->{'index-by'})) { if (isset($manyToManyElement['index-by'])) {
$mapping['indexBy'] = (string)$manyToManyElement->{'index-by'}; $mapping['indexBy'] = (string)$manyToManyElement['index-by'];
} else if (isset($manyToManyElement->{'index-by'})) {
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
} }
$metadata->mapManyToMany($mapping); $metadata->mapManyToMany($mapping);

View File

@ -165,16 +165,15 @@ class YamlDriver extends AbstractFileDriver
continue; continue;
} }
if (!isset($idElement['type'])) {
throw MappingException::propertyTypeIsRequired($className, $name);
}
$mapping = array( $mapping = array(
'id' => true, 'id' => true,
'fieldName' => $name, 'fieldName' => $name
'type' => $idElement['type']
); );
if (isset($idElement['type'])) {
$mapping['type'] = $idElement['type'];
}
if (isset($idElement['column'])) { if (isset($idElement['column'])) {
$mapping['columnName'] = $idElement['column']; $mapping['columnName'] = $idElement['column'];
} }
@ -201,19 +200,21 @@ class YamlDriver extends AbstractFileDriver
// Evaluate fields // Evaluate fields
if (isset($element['fields'])) { if (isset($element['fields'])) {
foreach ($element['fields'] as $name => $fieldMapping) { foreach ($element['fields'] as $name => $fieldMapping) {
if (!isset($fieldMapping['type'])) {
throw MappingException::propertyTypeIsRequired($className, $name);
}
$mapping = array(
'fieldName' => $name
);
if (isset($fieldMapping['type'])) {
$e = explode('(', $fieldMapping['type']); $e = explode('(', $fieldMapping['type']);
$fieldMapping['type'] = $e[0]; $fieldMapping['type'] = $e[0];
$mapping['type'] = $fieldMapping['type'];
if (isset($e[1])) { if (isset($e[1])) {
$fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1); $fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1);
} }
$mapping = array( }
'fieldName' => $name,
'type' => $fieldMapping['type']
);
if (isset($fieldMapping['id'])) { if (isset($fieldMapping['id'])) {
$mapping['id'] = true; $mapping['id'] = true;
if (isset($fieldMapping['generator']['strategy'])) { if (isset($fieldMapping['generator']['strategy'])) {
@ -378,10 +379,6 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $manyToOneElement['cascade']; $mapping['cascade'] = $manyToOneElement['cascade'];
} }
if (isset($manyToOneElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$manyToOneElement['orphanRemoval'];
}
$metadata->mapManyToOne($mapping); $metadata->mapManyToOne($mapping);
} }
} }
@ -437,10 +434,6 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $manyToManyElement['cascade']; $mapping['cascade'] = $manyToManyElement['cascade'];
} }
if (isset($manyToManyElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
}
if (isset($manyToManyElement['orderBy'])) { if (isset($manyToManyElement['orderBy'])) {
$mapping['orderBy'] = $manyToManyElement['orderBy']; $mapping['orderBy'] = $manyToManyElement['orderBy'];
} }

View File

@ -226,6 +226,11 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported."); return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported.");
} }
public static function sqlConversionNotAllowedForIdentifiers($className, $fieldName, $type)
{
return new self("It is not possible to set id field '$fieldName' to type '$type' in entity class '$className'. The type '$type' requires conversion SQL which is not allowed for identifiers.");
}
/** /**
* @param string $className * @param string $className
* @param string $columnName * @param string $columnName
@ -270,6 +275,12 @@ class MappingException extends \Doctrine\ORM\ORMException
"part of the identifier in '$className#$field'."); "part of the identifier in '$className#$field'.");
} }
public static function illegalOrphanRemoval($className, $field)
{
return new self("Orphan removal is only allowed on one-to-one and one-to-many ".
"associations, but " . $className."#" .$field . " is not.");
}
public static function illegalInverseIdentifierAssocation($className, $field) public static function illegalInverseIdentifierAssocation($className, $field)
{ {
return new self("An inverse association is not allowed to be identifier in '$className#$field'."); return new self("An inverse association is not allowed to be identifier in '$className#$field'.");
@ -288,7 +299,7 @@ class MappingException extends \Doctrine\ORM\ORMException
public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName) public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName)
{ {
return new self( return new self(
"Entity '" . $className . "' has to be part of the descriminator map of '" . $rootClassName . "' " . "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " .
"to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " . "to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " .
"to avoid this exception from occuring." "to avoid this exception from occuring."
); );
@ -298,4 +309,9 @@ class MappingException extends \Doctrine\ORM\ORMException
{ {
return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback.");
} }
public static function invalidFetchMode($className, $annotation)
{
return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'");
}
} }

View File

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

View File

@ -59,6 +59,15 @@ class ORMException extends Exception
return new self("Unrecognized field: $field"); return new self("Unrecognized field: $field");
} }
/**
* @param string $className
* @param string $field
*/
public static function invalidOrientation($className, $field)
{
return new self("Invalid order by orientation specified for " . $className . "#" . $field);
}
public static function invalidFlushMode($mode) public static function invalidFlushMode($mode)
{ {
return new self("'$mode' is an invalid flush mode."); return new self("'$mode' is an invalid flush mode.");

View File

@ -468,7 +468,7 @@ final class PersistentCollection implements Collection
if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
return $this->em->getUnitOfWork() return $this->em->getUnitOfWork()
->getCollectionPersister($this->association) ->getCollectionPersister($this->association)
->count($this) + $this->coll->count(); ->count($this) + ($this->isDirty ? $this->coll->count() : 0);
} }
$this->initialize(); $this->initialize();
@ -567,6 +567,9 @@ final class PersistentCollection implements Collection
return; return;
} }
if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
// we need to initialize here, as orphan removal acts like implicit cascadeRemove,
// hence for event listeners we need the objects in memory.
$this->initialize();
foreach ($this->coll as $element) { foreach ($this->coll as $element) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element); $this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
} }
@ -672,7 +675,10 @@ final class PersistentCollection implements Collection
*/ */
public function slice($offset, $length = null) 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() return $this->em->getUnitOfWork()
->getCollectionPersister($this->association) ->getCollectionPersister($this->association)
->slice($this, $offset, $length); ->slice($this, $offset, $length);

View File

@ -62,17 +62,21 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
{ {
$columnName = $class->columnNames[$field]; $columnName = $class->columnNames[$field];
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); $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); $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
if (isset($class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($class->getTypeOfField($field));
$sql = $type->convertToPHPValueSQL($sql, $this->_platform);
}
return $sql . ' AS ' . $columnAlias; return $sql . ' AS ' . $columnAlias;
} }
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className) protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className)
{ {
$columnAlias = $joinColumnName . $this->_sqlAliasCounter++; $columnAlias = $this->getSQLColumnAlias($joinColumnName);
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->addMetaResult('r', $columnAlias, $joinColumnName);
$this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName);
return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias;
} }

View File

@ -229,6 +229,7 @@ class BasicEntityPersister
if (isset($insertData[$tableName])) { if (isset($insertData[$tableName])) {
$paramIndex = 1; $paramIndex = 1;
foreach ($insertData[$tableName] as $column => $value) { foreach ($insertData[$tableName] as $column => $value) {
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]); $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]);
} }
@ -337,10 +338,19 @@ class BasicEntityPersister
$set = $params = $types = array(); $set = $params = $types = array();
foreach ($updateData as $columnName => $value) { foreach ($updateData as $columnName => $value) {
$set[] = (isset($this->_class->fieldNames[$columnName])) $column = $columnName;
? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?' $placeholder = '?';
: $columnName . ' = ?';
if (isset($this->_class->fieldNames[$columnName])) {
$column = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform);
if (isset($this->_class->fieldMappings[$this->_class->fieldNames[$columnName]]['requireSQLConversion'])) {
$type = Type::getType($this->_columnTypes[$columnName]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
}
}
$set[] = $column . ' = ' . $placeholder;
$params[] = $value; $params[] = $value;
$types[] = $this->_columnTypes[$columnName]; $types[] = $this->_columnTypes[$columnName];
} }
@ -627,13 +637,7 @@ class BasicEntityPersister
$hints = array(); $hints = array();
if ($isInverseSingleValued) { if ($isInverseSingleValued) {
$hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true; $hints['fetched']["r"][$assoc['inversedBy']] = true;
if ($targetClass->subClasses) {
foreach ($targetClass->subClasses as $targetSubclassName) {
$hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true;
}
}
} }
/* cascade read-only status /* cascade read-only status
@ -919,7 +923,6 @@ class BasicEntityPersister
* @param array $orderBy * @param array $orderBy
* @param string $baseTableAlias * @param string $baseTableAlias
* @return string * @return string
* @todo Rename: _getOrderBySQL
*/ */
protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) protected final function _getOrderBySQL(array $orderBy, $baseTableAlias)
{ {
@ -930,6 +933,11 @@ class BasicEntityPersister
throw ORMException::unrecognizedField($fieldName); throw ORMException::unrecognizedField($fieldName);
} }
$orientation = strtoupper(trim($orientation));
if ($orientation != 'ASC' && $orientation != 'DESC') {
throw ORMException::invalidOrientation($this->_class->name, $fieldName);
}
$tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ? $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ?
$this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited']) $this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited'])
: $baseTableAlias; : $baseTableAlias;
@ -1008,22 +1016,18 @@ class BasicEntityPersister
$columnList .= $assoc2ColumnSQL; $columnList .= $assoc2ColumnSQL;
} }
} }
$this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable.
$first = true; $first = true;
if ($assoc['isOwningSide']) { if ($assoc['isOwningSide']) {
$this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; $this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($assoc['joinColumns']);
foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
if ( ! $first) { if ( ! $first) {
$this->_selectJoinSql .= ' AND '; $this->_selectJoinSql .= ' AND ';
} }
$tableAlias = $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias); $tableAlias = $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias);
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = '
. $tableAlias . '.' . $targetCol . ' '; . $tableAlias . '.' . $targetCol . ' ';
$first = false; $first = false;
} }
@ -1035,6 +1039,7 @@ class BasicEntityPersister
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
$this->_selectJoinSql .= ' LEFT JOIN';
$this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON ';
@ -1044,7 +1049,7 @@ class BasicEntityPersister
} }
$this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = '
. $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' '; . $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol;
$first = false; $first = false;
} }
} }
@ -1074,8 +1079,7 @@ class BasicEntityPersister
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
if ($columnList) $columnList .= ', '; if ($columnList) $columnList .= ', ';
$columnAlias = $srcColumn . $this->_sqlAliasCounter++; $resultColumnName = $this->getSQLColumnAlias($srcColumn);
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
$columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) )
. '.' . $srcColumn . ' AS ' . $resultColumnName; . '.' . $srcColumn . ' AS ' . $resultColumnName;
$this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true);
@ -1139,7 +1143,19 @@ class BasicEntityPersister
); );
} else { } else {
$columns = array_unique($columns); $columns = array_unique($columns);
$values = array_fill(0, count($columns), '?');
$values = array();
foreach ($columns AS $column) {
$placeholder = '?';
if (isset($this->_columnTypes[$column]) &&
isset($this->_class->fieldMappings[$this->_class->fieldNames[$column]]['requireSQLConversion'])) {
$type = Type::getType($this->_columnTypes[$column]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
}
$values[] = $placeholder;
}
$insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform)
. ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')'; . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')';
@ -1178,6 +1194,7 @@ class BasicEntityPersister
} }
} else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) { } else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) {
$columns[] = $this->_class->getQuotedColumnName($name, $this->_platform); $columns[] = $this->_class->getQuotedColumnName($name, $this->_platform);
$this->_columnTypes[$name] = $this->_class->fieldMappings[$name]['type'];
} }
} }
@ -1194,13 +1211,17 @@ class BasicEntityPersister
*/ */
protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r')
{ {
$columnName = $class->columnNames[$field];
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias)
. '.' . $class->getQuotedColumnName($field, $this->_platform); . '.' . $class->getQuotedColumnName($field, $this->_platform);
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]);
$this->_rsm->addFieldResult($alias, $columnAlias, $field); $this->_rsm->addFieldResult($alias, $columnAlias, $field);
if (isset($class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($class->getTypeOfField($field));
$sql = $type->convertToPHPValueSQL($sql, $this->_platform);
}
return $sql . ' AS ' . $columnAlias; return $sql . ' AS ' . $columnAlias;
} }
@ -1259,10 +1280,10 @@ class BasicEntityPersister
* *
* @return string * @return string
*/ */
protected function getLockTablesSql() protected function getLockTablesSql($alias = null)
{ {
return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name); . (null !== $alias ? $alias : $this->_getSQLTableAlias($this->_class->name));
} }
/** /**
@ -1283,12 +1304,19 @@ class BasicEntityPersister
foreach ($criteria as $field => $value) { foreach ($criteria as $field => $value) {
$conditionSql .= $conditionSql ? ' AND ' : ''; $conditionSql .= $conditionSql ? ' AND ' : '';
$placeholder = '?';
if (isset($this->_class->columnNames[$field])) { if (isset($this->_class->columnNames[$field])) {
$className = (isset($this->_class->fieldMappings[$field]['inherited'])) $className = (isset($this->_class->fieldMappings[$field]['inherited']))
? $this->_class->fieldMappings[$field]['inherited'] ? $this->_class->fieldMappings[$field]['inherited']
: $this->_class->name; : $this->_class->name;
$conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform); $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform);
if (isset($this->_class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($this->_class->getTypeOfField($field));
$placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->_platform);
}
} else if (isset($this->_class->associationMappings[$field])) { } else if (isset($this->_class->associationMappings[$field])) {
if ( ! $this->_class->associationMappings[$field]['isOwningSide']) { if ( ! $this->_class->associationMappings[$field]['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field); throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
@ -1309,7 +1337,7 @@ class BasicEntityPersister
throw ORMException::unrecognizedField($field); throw ORMException::unrecognizedField($field);
} }
$conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?'); $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ' . $placeholder);
} }
return $conditionSql; return $conditionSql;
} }
@ -1509,7 +1537,7 @@ class BasicEntityPersister
$alias = $this->_getSQLTableAlias($this->_class->name); $alias = $this->_getSQLTableAlias($this->_class->name);
$sql = 'SELECT 1' $sql = 'SELECT 1'
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $alias . $this->getLockTablesSql($alias)
. ' WHERE ' . $this->_getSelectConditionSQL($criteria); . ' WHERE ' . $this->_getSelectConditionSQL($criteria);
$filterSql = $this->generateFilterConditionSQL($this->_class, $alias); $filterSql = $this->generateFilterConditionSQL($this->_class, $alias);
@ -1522,6 +1550,39 @@ class BasicEntityPersister
return (bool) $this->_conn->fetchColumn($sql, $params); return (bool) $this->_conn->fetchColumn($sql, $params);
} }
/**
* Generates the appropriate join SQL for the given join column.
*
* @param array $joinColumns The join columns definition of an association.
* @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise.
*/
protected function getJoinSQLForJoinColumns($joinColumns)
{
// if one of the join columns is nullable, return left join
foreach($joinColumns as $joinColumn) {
if(isset($joinColumn['nullable']) && $joinColumn['nullable']){
return 'LEFT JOIN';
}
}
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())
);
}
private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
{ {
$filterSql = ''; $filterSql = '';

View File

@ -165,11 +165,13 @@ class ProxyFactory
{ {
$methods = ''; $methods = '';
$methodNames = array();
foreach ($class->reflClass->getMethods() as $method) { foreach ($class->reflClass->getMethods() as $method) {
/* @var $method ReflectionMethod */ /* @var $method ReflectionMethod */
if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone"))) { if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
continue; continue;
} }
$methodNames[$method->getName()] = true;
if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
$methods .= "\n" . ' public function '; $methods .= "\n" . ' public function ';
@ -210,8 +212,12 @@ class ProxyFactory
$methods .= $parameterString . ')'; $methods .= $parameterString . ')';
$methods .= "\n" . ' {' . "\n"; $methods .= "\n" . ' {' . "\n";
if ($this->isShortIdentifierGetter($method, $class)) { if ($this->isShortIdentifierGetter($method, $class)) {
$identifier = lcfirst(substr($method->getName(), 3));
$cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : '';
$methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; $methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
$methods .= ' return $this->_identifier["' . lcfirst(substr($method->getName(), 3)) . '"];' . "\n"; $methods .= ' return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n";
$methods .= ' }' . "\n"; $methods .= ' }' . "\n";
} }
$methods .= ' $this->__load();' . "\n"; $methods .= ' $this->__load();' . "\n";
@ -237,6 +243,7 @@ class ProxyFactory
in_array($identifier, $class->identifier, true) && in_array($identifier, $class->identifier, true) &&
$class->hasField($identifier) && $class->hasField($identifier) &&
(($method->getEndLine() - $method->getStartLine()) <= 4) (($method->getEndLine() - $method->getStartLine()) <= 4)
&& in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string'))
); );
} }

View File

@ -235,6 +235,9 @@ final class Query extends AbstractQuery
protected function _doExecute() protected function _doExecute()
{ {
$executor = $this->_parse()->getSqlExecutor(); $executor = $this->_parse()->getSqlExecutor();
if ($this->_queryCacheProfile) {
$executor->setQueryCacheProfile($this->_queryCacheProfile);
}
// Prepare parameters // Prepare parameters
$paramMappings = $this->_parserResult->getParameterMappings(); $paramMappings = $this->_parserResult->getParameterMappings();
@ -282,9 +285,16 @@ final class Query extends AbstractQuery
} }
} }
if (count($sqlParams) != count($types)) {
throw QueryException::parameterTypeMissmatch();
}
if ($sqlParams) { if ($sqlParams) {
ksort($sqlParams); ksort($sqlParams);
$sqlParams = array_values($sqlParams); $sqlParams = array_values($sqlParams);
ksort($types);
$types = array_values($types);
} }
return array($sqlParams, $types); return array($sqlParams, $types);
@ -526,7 +536,7 @@ final class Query extends AbstractQuery
* *
* @param array $params The query parameters. * @param array $params The query parameters.
* @param integer $hydrationMode The hydration mode to use. * @param integer $hydrationMode The hydration mode to use.
* @return IterableResult * @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/ */
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,7 +59,7 @@ class Composite extends Base
} }
// Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND") // Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND")
if (mb_stripos($queryPart, ' OR ') !== false || mb_stripos($queryPart, ' AND ') !== false) { if (stripos($queryPart, ' OR ') !== false || stripos($queryPart, ' AND ') !== false) {
return $this->_preSeparator . $queryPart . $this->_postSeparator; return $this->_preSeparator . $queryPart . $this->_postSeparator;
} }

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,11 @@ class QueryException extends \Doctrine\ORM\ORMException
return new self('[Semantical Error] ' . $message); return new self('[Semantical Error] ' . $message);
} }
public static function invalidLockMode()
{
return new self('Invalid lock mode hint provided.');
}
public static function invalidParameterType($expected, $received) public static function invalidParameterType($expected, $received)
{ {
return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.'); return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.');
@ -72,6 +77,11 @@ class QueryException extends \Doctrine\ORM\ORMException
return new self("Invalid parameter: token ".$key." is not defined in the query."); return new self("Invalid parameter: token ".$key." is not defined in the query.");
} }
public static function parameterTypeMissmatch()
{
return new self("DQL Query parameter and type numbers missmatch, but have to be exactly equal.");
}
public static function invalidPathExpression($pathExpr) public static function invalidPathExpression($pathExpr)
{ {
return new self( return new self(

View File

@ -36,87 +36,79 @@ namespace Doctrine\ORM\Query;
class ResultSetMapping class ResultSetMapping
{ {
/** /**
* Whether the result is mixed (contains scalar values together with field values).
*
* @ignore * @ignore
* @var boolean * @var boolean Whether the result is mixed (contains scalar values together with field values).
*/ */
public $isMixed = false; public $isMixed = false;
/** /**
* Maps alias names to class names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to class names.
*/ */
public $aliasMap = array(); public $aliasMap = array();
/** /**
* Maps alias names to related association field names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to related association field names.
*/ */
public $relationMap = array(); public $relationMap = array();
/** /**
* Maps alias names to parent alias names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to parent alias names.
*/ */
public $parentAliasMap = array(); public $parentAliasMap = array();
/** /**
* Maps column names in the result set to field names for each class.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to field names for each class.
*/ */
public $fieldMappings = array(); public $fieldMappings = array();
/** /**
* Maps column names in the result set to the alias/field name to use in the mapped result.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to the alias/field name to use in the mapped result.
*/ */
public $scalarMappings = array(); public $scalarMappings = array();
/** /**
* Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
*
* @ignore * @ignore
* @var array * @var array Maps entities in the result set to the alias name to use in the mapped result.
*/
public $entityMappings = array();
/**
* @ignore
* @var array Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
*/ */
public $metaMappings = array(); public $metaMappings = array();
/** /**
* Maps column names in the result set to the alias they belong to.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to the alias they belong to.
*/ */
public $columnOwnerMap = array(); public $columnOwnerMap = array();
/** /**
* List of columns in the result set that are used as discriminator columns.
*
* @ignore * @ignore
* @var array * @var array List of columns in the result set that are used as discriminator columns.
*/ */
public $discriminatorColumns = array(); public $discriminatorColumns = array();
/** /**
* Maps alias names to field names that should be used for indexing.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to field names that should be used for indexing.
*/ */
public $indexByMap = array(); public $indexByMap = array();
/** /**
* Map from column names to class names that declare the field the column is mapped to.
*
* @ignore * @ignore
* @var array * @var array Map from column names to class names that declare the field the column is mapped to.
*/ */
public $declaringClasses = array(); public $declaringClasses = array();
/** /**
* This is necessary to hydrate derivate foreign keys correctly. * @var array This is necessary to hydrate derivate foreign keys correctly.
*
* @var array
*/ */
public $isIdentifierColumn = array(); public $isIdentifierColumn = array();
@ -126,11 +118,19 @@ class ResultSetMapping
* @param string $class The class name of the entity. * @param string $class The class name of the entity.
* @param string $alias The alias for the class. The alias must be unique among all entity * @param string $alias The alias for the class. The alias must be unique among all entity
* results or joined entity results within this ResultSetMapping. * results or joined entity results within this ResultSetMapping.
* @param string $resultAlias The result alias with which the entity result should be
* placed in the result structure.
*
* @todo Rename: addRootEntity * @todo Rename: addRootEntity
*/ */
public function addEntityResult($class, $alias) public function addEntityResult($class, $alias, $resultAlias = null)
{ {
$this->aliasMap[$alias] = $class; $this->aliasMap[$alias] = $class;
$this->entityMappings[$alias] = $resultAlias;
if ($resultAlias !== null) {
$this->isMixed = true;
}
} }
/** /**
@ -141,6 +141,7 @@ class ResultSetMapping
* @param string $alias The alias of the entity result or joined entity result the discriminator * @param string $alias The alias of the entity result or joined entity result the discriminator
* column should be used for. * column should be used for.
* @param string $discrColumn The name of the discriminator column in the SQL result set. * @param string $discrColumn The name of the discriminator column in the SQL result set.
*
* @todo Rename: addDiscriminatorColumn * @todo Rename: addDiscriminatorColumn
*/ */
public function setDiscriminatorColumn($alias, $discrColumn) public function setDiscriminatorColumn($alias, $discrColumn)
@ -157,7 +158,51 @@ class ResultSetMapping
*/ */
public function addIndexBy($alias, $fieldName) public function addIndexBy($alias, $fieldName)
{ {
$this->indexByMap[$alias] = $fieldName; $found = false;
foreach ($this->fieldMappings AS $columnName => $columnFieldName) {
if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue;
$this->addIndexByColumn($alias, $columnName);
$found = true;
break;
}
/* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals
if ( ! $found) {
$message = sprintf(
'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.',
$alias,
$fieldName
);
throw new \LogicException($message);
}
*/
}
/**
* Set to index by a scalar result column name
*
* @param $resultColumnName
* @return void
*/
public function addIndexByScalar($resultColumnName)
{
$this->indexByMap['scalars'] = $resultColumnName;
}
/**
* Sets a column to use for indexing an entity or joined entity result by the given alias name.
*
* @param $alias
* @param $resultColumnName
* @return void
*/
public function addIndexByColumn($alias, $resultColumnName)
{
$this->indexByMap[$alias] = $resultColumnName;
} }
/** /**
@ -207,6 +252,7 @@ class ResultSetMapping
$this->columnOwnerMap[$columnName] = $alias; $this->columnOwnerMap[$columnName] = $alias;
// field name => class name of declaring class // field name => class name of declaring class
$this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
if ( ! $this->isMixed && $this->scalarMappings) { if ( ! $this->isMixed && $this->scalarMappings) {
$this->isMixed = true; $this->isMixed = true;
} }
@ -238,6 +284,7 @@ class ResultSetMapping
public function addScalarResult($columnName, $alias) public function addScalarResult($columnName, $alias)
{ {
$this->scalarMappings[$columnName] = $alias; $this->scalarMappings[$columnName] = $alias;
if ( ! $this->isMixed && $this->fieldMappings) { if ( ! $this->isMixed && $this->fieldMappings) {
$this->isMixed = true; $this->isMixed = true;
} }
@ -397,6 +444,7 @@ class ResultSetMapping
{ {
$this->metaMappings[$columnName] = $fieldName; $this->metaMappings[$columnName] = $fieldName;
$this->columnOwnerMap[$columnName] = $alias; $this->columnOwnerMap[$columnName] = $alias;
if ($isIdentifierColumn) { if ($isIdentifierColumn) {
$this->isIdentifierColumn[$alias][$columnName] = true; $this->isIdentifierColumn[$alias][$columnName] = true;
} }

View File

@ -86,20 +86,22 @@ class ResultSetMappingBuilder extends ResultSetMapping
if (isset($renamedColumns[$columnName])) { if (isset($renamedColumns[$columnName])) {
$columnName = $renamedColumns[$columnName]; $columnName = $renamedColumns[$columnName];
} }
$columnName = $platform->getSQLResultCasing($columnName);
if (isset($this->fieldMappings[$columnName])) { if (isset($this->fieldMappings[$columnName])) {
throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); 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) { foreach ($classMetadata->associationMappings AS $associationMapping) {
if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
foreach ($associationMapping['joinColumns'] AS $joinColumn) { foreach ($associationMapping['joinColumns'] AS $joinColumn) {
$columnName = $joinColumn['name']; $columnName = $joinColumn['name'];
$renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName; $renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName;
$renamedColumnName = $platform->getSQLResultCasing($renamedColumnName);
if (isset($this->metaMappings[$renamedColumnName])) { if (isset($this->metaMappings[$renamedColumnName])) {
throw new \InvalidArgumentException("The column '$renamedColumnName' conflicts with another column in the mapper."); 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);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -313,7 +313,7 @@ class QueryBuilder
* ->select('u') * ->select('u')
* ->from('User', 'u') * ->from('User', 'u')
* ->where('u.id = :user_id') * ->where('u.id = :user_id')
* ->setParameter(':user_id', 1); * ->setParameter('user_id', 1);
* </code> * </code>
* *
* @param string|integer $key The parameter position or name. * @param string|integer $key The parameter position or name.
@ -344,8 +344,8 @@ class QueryBuilder
* ->from('User', 'u') * ->from('User', 'u')
* ->where('u.id = :user_id1 OR u.id = :user_id2') * ->where('u.id = :user_id1 OR u.id = :user_id2')
* ->setParameters(array( * ->setParameters(array(
* ':user_id1' => 1, * 'user_id1' => 1,
* ':user_id2' => 2 * 'user_id2' => 2
* )); * ));
* </code> * </code>
* *
@ -539,7 +539,7 @@ class QueryBuilder
* $qb = $em->createQueryBuilder() * $qb = $em->createQueryBuilder()
* ->delete('User', 'u') * ->delete('User', 'u')
* ->where('u.id = :user_id'); * ->where('u.id = :user_id');
* ->setParameter(':user_id', 1); * ->setParameter('user_id', 1);
* </code> * </code>
* *
* @param string $delete The class/type whose instances are subject to the deletion. * @param string $delete The class/type whose instances are subject to the deletion.

View File

@ -117,7 +117,7 @@ public function <methodName>()
* @param <variableType>$<variableName> * @param <variableType>$<variableName>
* @return <entity> * @return <entity>
*/ */
public function <methodName>(<methodTypeHint>$<variableName>) public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
{ {
<spaces>$this-><fieldName> = $<variableName>; <spaces>$this-><fieldName> = $<variableName>;
<spaces>return $this; <spaces>return $this;
@ -406,7 +406,7 @@ public function <methodName>()
} }
if ($collections) { if ($collections) {
return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate)); return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->_spaces, $collections), self::$_constructorMethodTemplate));
} }
return ''; return '';
@ -634,7 +634,8 @@ public function <methodName>()
foreach ($metadata->associationMappings as $associationMapping) { foreach ($metadata->associationMappings as $associationMapping) {
if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $nullable = $this->_isAssociationIsNullable($associationMapping) ? 'null' : null;
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
$methods[] = $code; $methods[] = $code;
} }
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
@ -653,6 +654,22 @@ public function <methodName>()
return implode("\n\n", $methods); return implode("\n\n", $methods);
} }
private function _isAssociationIsNullable($associationMapping)
{
if (isset($associationMapping['joinColumns'])) {
$joinColumns = $associationMapping['joinColumns'];
} else {
//@todo thereis no way to retreive targetEntity metadata
$joinColumns = array();
}
foreach ($joinColumns as $joinColumn) {
if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
return false;
}
}
return true;
}
private function _generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata) private function _generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
{ {
if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
@ -707,7 +724,7 @@ public function <methodName>()
return implode("\n", $lines); return implode("\n", $lines);
} }
private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null) private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
{ {
if ($type == "add") { if ($type == "add") {
$addMethod = explode("\\", $typeHint); $addMethod = explode("\\", $typeHint);
@ -737,6 +754,7 @@ public function <methodName>()
'<variableName>' => Inflector::camelize($fieldName), '<variableName>' => Inflector::camelize($fieldName),
'<methodName>' => $methodName, '<methodName>' => $methodName,
'<fieldName>' => $fieldName, '<fieldName>' => $fieldName,
'<variableDefault>' => ($defaultValue !== null ) ? ('='.$defaultValue) : '',
'<entity>' => $this->_getClassName($metadata) '<entity>' => $this->_getClassName($metadata)
); );
@ -805,7 +823,12 @@ public function <methodName>()
{ {
$lines = array(); $lines = array();
$lines[] = $this->_spaces . '/**'; $lines[] = $this->_spaces . '/**';
if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
$lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\ArrayCollection';
} else {
$lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity']; $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity'];
}
if ($this->_generateAnnotations) { if ($this->_generateAnnotations) {
$lines[] = $this->_spaces . ' *'; $lines[] = $this->_spaces . ' *';

View File

@ -279,6 +279,9 @@ class XmlExporter extends AbstractExporter
if ($associationMapping['isCascadeDetach']) { if ($associationMapping['isCascadeDetach']) {
$cascade[] = 'cascade-detach'; $cascade[] = 'cascade-detach';
} }
if (count($cascade) === 5) {
$cascade = array('cascade-all');
}
if ($cascade) { if ($cascade) {
$cascadeXml = $associationMappingXml->addChild('cascade'); $cascadeXml = $associationMappingXml->addChild('cascade');
foreach ($cascade as $type) { foreach ($cascade as $type) {

View File

@ -147,6 +147,9 @@ class YamlExporter extends AbstractExporter
if ($associationMapping['isCascadeDetach']) { if ($associationMapping['isCascadeDetach']) {
$cascade[] = 'detach'; $cascade[] = 'detach';
} }
if (count($cascade) === 5) {
$cascade = array('all');
}
$associationMappingArray = array( $associationMappingArray = array(
'targetEntity' => $associationMapping['targetEntity'], 'targetEntity' => $associationMapping['targetEntity'],
'cascade' => $cascade, 'cascade' => $cascade,

View File

@ -67,7 +67,9 @@ class SchemaTool
/** /**
* Creates the database schema for the given array of ClassMetadata instances. * Creates the database schema for the given array of ClassMetadata instances.
* *
* @throws ToolsException
* @param array $classes * @param array $classes
* @return void
*/ */
public function createSchema(array $classes) public function createSchema(array $classes)
{ {
@ -75,7 +77,11 @@ class SchemaTool
$conn = $this->_em->getConnection(); $conn = $this->_em->getConnection();
foreach ($createSchemaSql as $sql) { foreach ($createSchemaSql as $sql) {
try {
$conn->executeQuery($sql); $conn->executeQuery($sql);
} catch(\Exception $e) {
throw ToolsException::schemaToolFailure($sql, $e);
}
} }
} }
@ -139,7 +145,7 @@ class SchemaTool
$this->_gatherRelationsSql($class, $table, $schema); $this->_gatherRelationsSql($class, $table, $schema);
// Add the discriminator column // Add the discriminator column
$discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table); $this->addDiscriminatorColumnDefinition($class, $table);
// Aggregate all the information from all classes in the hierarchy // Aggregate all the information from all classes in the hierarchy
foreach ($class->parentClasses as $parentClassName) { foreach ($class->parentClasses as $parentClassName) {
@ -171,7 +177,7 @@ class SchemaTool
// Add the discriminator column only to the root table // Add the discriminator column only to the root table
if ($class->name == $class->rootEntityName) { if ($class->name == $class->rootEntityName) {
$discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table); $this->addDiscriminatorColumnDefinition($class, $table);
} else { } else {
// Add an ID FK column to child tables // Add an ID FK column to child tables
/* @var Doctrine\ORM\Mapping\ClassMetadata $class */ /* @var Doctrine\ORM\Mapping\ClassMetadata $class */
@ -261,7 +267,7 @@ class SchemaTool
* @return array The portable column definition of the discriminator column as required by * @return array The portable column definition of the discriminator column as required by
* the DBAL. * the DBAL.
*/ */
private function _getDiscriminatorColumnDefinition($class, $table) private function addDiscriminatorColumnDefinition($class, $table)
{ {
$discrColumn = $class->discriminatorColumn; $discrColumn = $class->discriminatorColumn;

View File

@ -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 * There are several checks that can't be done at runtime or are too expensive, which can be verified
* with this command. For example: * with this command. For example:
@ -69,11 +69,29 @@ class SchemaValidator
$classes = $cmf->getAllMetadata(); $classes = $cmf->getAllMetadata();
foreach ($classes AS $class) { foreach ($classes AS $class) {
if ($ce = $this->validateClass($class)) {
$errors[$class->name] = $ce;
}
}
return $errors;
}
/**
* Validate a single class of the current
*
* @param ClassMetadataInfo $class
* @return array
*/
public function validateClass(ClassMetadataInfo $class)
{
$ce = array(); $ce = array();
/* @var $class ClassMetadata */ $cmf = $this->em->getMetadataFactory();
foreach ($class->associationMappings AS $fieldName => $assoc) { foreach ($class->associationMappings AS $fieldName => $assoc) {
if (!$cmf->hasMetadataFor($assoc['targetEntity'])) { if (!$cmf->hasMetadataFor($assoc['targetEntity'])) {
$ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.';
return $ce;
} }
if ($assoc['mappedBy'] && $assoc['inversedBy']) { if ($assoc['mappedBy'] && $assoc['inversedBy']) {
@ -139,59 +157,67 @@ class SchemaValidator
} }
} }
foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) {
$targetClass = $cmf->getMetadataFor($assoc['targetEntity']); if (!isset($targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']])) {
if (!isset($targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']])) {
$ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " .
"have a corresponding field with this column name on the class '" . $targetClass->name . "'."; "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'.";
break; break;
} }
$fieldName = $targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']]; $fieldName = $targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']];
if (!in_array($fieldName, $targetClass->identifier)) { if (!in_array($fieldName, $targetMetadata->identifier)) {
$ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " .
"has to be a primary key column."; "has to be a primary key column.";
} }
} }
if (count($targetClass->identifier) != count($assoc['joinTable']['inverseJoinColumns'])) { if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) {
$ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . $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 . "'"; "have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " .
"however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), array_values($assoc['relationToTargetKeyColumns']))) .
"' are missing.";
} }
if (count($class->identifier) != count($assoc['joinTable']['joinColumns'])) { if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) {
$ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . $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 . "'"; "have to contain to ALL identifier columns of the source entity '". $class->name . "', " .
"however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), array_values($assoc['relationToSourceKeyColumns']))) .
"' are missing.";
} }
} else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) {
foreach ($assoc['joinColumns'] AS $joinColumn) { foreach ($assoc['joinColumns'] AS $joinColumn) {
$targetClass = $cmf->getMetadataFor($assoc['targetEntity']); if (!isset($targetMetadata->fieldNames[$joinColumn['referencedColumnName']])) {
if (!isset($targetClass->fieldNames[$joinColumn['referencedColumnName']])) {
$ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " .
"have a corresponding field with this column name on the class '" . $targetClass->name . "'."; "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'.";
break; break;
} }
$fieldName = $targetClass->fieldNames[$joinColumn['referencedColumnName']]; $fieldName = $targetMetadata->fieldNames[$joinColumn['referencedColumnName']];
if (!in_array($fieldName, $targetClass->identifier)) { if (!in_array($fieldName, $targetMetadata->identifier)) {
$ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " .
"has to be a primary key column."; "has to be a primary key column.";
} }
} }
if (count($class->identifier) != count($assoc['joinColumns'])) { if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) {
$ids = array();
foreach ($assoc['joinColumns'] AS $joinColumn) {
$ids[] = $joinColumn['name'];
}
$ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " .
"have to match to ALL identifier columns of the source entity '". $class->name . "'"; "have to match to ALL identifier columns of the source entity '". $class->name . "', " .
"however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $ids)) .
"' are missing.";
} }
} }
} }
if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) {
$targetClass = $cmf->getMetadataFor($assoc['targetEntity']);
foreach ($assoc['orderBy'] AS $orderField => $orientation) { foreach ($assoc['orderBy'] AS $orderField => $orientation) {
if (!$targetClass->hasField($orderField)) { if (!$targetMetadata->hasField($orderField)) {
$ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " . $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " .
$orderField . " that is not a field on the target entity " . $targetClass->name; $orderField . " that is not a field on the target entity " . $targetMetadata->name;
} }
} }
} }
@ -212,12 +238,7 @@ class SchemaValidator
} }
} }
if ($ce) { return $ce;
$errors[$class->name] = $ce;
}
}
return $errors;
} }
/** /**
@ -230,6 +251,6 @@ class SchemaValidator
$schemaTool = new SchemaTool($this->em); $schemaTool = new SchemaTool($this->em);
$allMetadata = $this->em->getMetadataFactory()->getAllMetadata(); $allMetadata = $this->em->getMetadataFactory()->getAllMetadata();
return (count($schemaTool->getUpdateSchemaSql($allMetadata, false)) == 0); return (count($schemaTool->getUpdateSchemaSql($allMetadata, true)) == 0);
} }
} }

View File

@ -1,11 +1,38 @@
<?php <?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Tools; namespace Doctrine\ORM\Tools;
use Doctrine\ORM\ORMException; use Doctrine\ORM\ORMException;
/**
* Tools related Exceptions
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class ToolsException extends ORMException 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) public static function couldNotMapDoctrine1Type($type)
{ {
return new self("Could not map doctrine 1 type '$type'!"); return new self("Could not map doctrine 1 type '$type'!");

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit ef431a14852d7e8f2d0ea789487509ab266e5ce2 Subproject commit 9c880cf9ae2c14102568520b5ee885b03bda93e4

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

View File

@ -0,0 +1,34 @@
<?php
namespace Doctrine\Tests\DbalTypes;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class NegativeToPositiveType extends Type
{
public function getName()
{
return 'negative_to_positive';
}
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration);
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return 'ABS(' . $sqlExpr . ')';
}
public function convertToPHPValueSQL($sqlExpr, $platform)
{
return '-(' . $sqlExpr . ')';
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Doctrine\Tests\DbalTypes;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class UpperCaseStringType extends StringType
{
public function getName()
{
return 'upper_case_string';
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return 'UPPER(' . $sqlExpr . ')';
}
public function convertToPHPValueSQL($sqlExpr, $platform)
{
return 'LOWER(' . $sqlExpr . ')';
}
}

View File

@ -8,6 +8,7 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
private $_platformMock; private $_platformMock;
private $_lastInsertId = 0; private $_lastInsertId = 0;
private $_inserts = array(); private $_inserts = array();
private $_executeUpdates = array();
public function __construct(array $params, $driver, $config = null, $eventManager = null) public function __construct(array $params, $driver, $config = null, $eventManager = null)
{ {
@ -30,11 +31,19 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
/** /**
* @override * @override
*/ */
public function insert($tableName, array $data) public function insert($tableName, array $data, array $types = array())
{ {
$this->_inserts[$tableName][] = $data; $this->_inserts[$tableName][] = $data;
} }
/**
* @override
*/
public function executeUpdate($query, array $params = array(), array $types = array())
{
$this->_executeUpdates[] = array('query' => $query, 'params' => $params, 'types' => $types);
}
/** /**
* @override * @override
*/ */
@ -84,6 +93,11 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
return $this->_inserts; return $this->_inserts;
} }
public function getExecuteUpdates()
{
return $this->_executeUpdates;
}
public function reset() public function reset()
{ {
$this->_inserts = array(); $this->_inserts = array();

View File

@ -87,4 +87,11 @@ class DatabasePlatformMock extends \Doctrine\DBAL\Platforms\AbstractPlatform
{ {
} }
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
throw DBALException::notSupported(__METHOD__);
}
} }

View File

@ -8,7 +8,7 @@ namespace Doctrine\Tests\Mocks;
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
*/ */
class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement class HydratorMockStatement implements \IteratorAggregate, \Doctrine\DBAL\Driver\Statement
{ {
private $_resultSet; private $_resultSet;
@ -98,4 +98,14 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement
public function rowCount() public function rowCount()
{ {
} }
public function getIterator()
{
return $this->_resultSet;
}
public function setFetchMode($fetchMode)
{
}
} }

View File

@ -0,0 +1,21 @@
<?php
namespace Doctrine\Tests\Models\CustomType;
/**
* @Entity
* @Table(name="customtype_children")
*/
class CustomTypeChild
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @Column(type="upper_case_string")
*/
public $lowerCaseString = 'foo';
}

View File

@ -0,0 +1,68 @@
<?php
namespace Doctrine\Tests\Models\CustomType;
/**
* @Entity
* @Table(name="customtype_parents")
*/
class CustomTypeParent
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @Column(type="negative_to_positive", nullable=true)
*/
public $customInteger;
/**
* @OneToOne(targetEntity="Doctrine\Tests\Models\CustomType\CustomTypeChild", cascade={"persist", "remove"})
*/
public $child;
/**
* @ManyToMany(targetEntity="Doctrine\Tests\Models\CustomType\CustomTypeParent", mappedBy="myFriends")
*/
private $friendsWithMe;
/**
* @ManyToMany(targetEntity="Doctrine\Tests\Models\CustomType\CustomTypeParent", inversedBy="friendsWithMe")
* @JoinTable(
* name="customtype_parent_friends",
* joinColumns={@JoinColumn(name="customtypeparent_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_customtypeparent_id", referencedColumnName="id")}
* )
*/
private $myFriends;
public function __construct()
{
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addMyFriend(CustomTypeParent $friend)
{
$this->getMyFriends()->add($friend);
$friend->addFriendWithMe($this);
}
public function getMyFriends()
{
return $this->myFriends;
}
public function addFriendWithMe(CustomTypeParent $friend)
{
$this->getFriendsWithMe()->add($friend);
}
public function getFriendsWithMe()
{
return $this->friendsWithMe;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Doctrine\Tests\Models\CustomType;
/**
* @Entity
* @Table(name="customtype_uppercases")
*/
class CustomTypeUpperCase
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @Column(type="upper_case_string")
*/
public $lowerCaseString;
}

View File

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

View File

@ -0,0 +1,76 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\Models\DDC1476;
/**
* @Entity()
*/
class DDC1476EntityWithDefaultFieldType
{
/**
* @Id
* @Column()
* @GeneratedValue("NONE")
*/
protected $id;
/** @column() */
protected $name;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
{
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
));
$metadata->mapField(array(
'fieldName' => 'name',
));
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadataInfo::GENERATOR_TYPE_NONE);
}
}

View File

@ -21,7 +21,7 @@ class LegacyUser
*/ */
public $_username; public $_username;
/** /**
* @Column(type="string", length=255) * @Column(type="string", length=255, name="name")
*/ */
public $_name; public $_name;
/** /**

View File

@ -23,12 +23,12 @@ class LegacyUserReference
private $_target; private $_target;
/** /**
* @column(type="string") * @column(type="string", name="description")
*/ */
private $_description; private $_description;
/** /**
* @column(type="datetime") * @column(type="datetime", name="created")
*/ */
private $_created; private $_created;

View File

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

View File

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -33,11 +31,13 @@ require_once __DIR__ . '/../../TestInit.php';
* @link http://www.doctrine-project.org * @link http://www.doctrine-project.org
* @since 2.0 * @since 2.0
*/ */
class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase class CustomTreeWalkersTest extends \Doctrine\Tests\OrmTestCase
{ {
protected function setUp() { private $_em;
$this->useModelSet('cms');
parent::setUp(); protected function setUp()
{
$this->_em = $this->_getTestEntityManager();
} }
public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed)

View File

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

View File

@ -20,7 +20,9 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function tearDown() public function tearDown()
{ {
if ($this->_em) {
$this->_em->getConfiguration()->setEntityNamespaces(array()); $this->_em->getConfiguration()->setEntityNamespaces(array());
}
parent::tearDown(); parent::tearDown();
} }
@ -489,5 +491,15 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository");
} }
/**
* @group DDC-1500
*/
public function testInvalidOrientation()
{
$this->setExpectedException('Doctrine\ORM\ORMException', 'Invalid order by orientation specified for Doctrine\Tests\Models\CMS\CmsUser#username');
$repo = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$repo->findBy(array('status' => 'test'), array('username' => 'INVALID'));
}
} }

View File

@ -304,6 +304,49 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); $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() private function loadFixture()
{ {
$user1 = new \Doctrine\Tests\Models\CMS\CmsUser(); $user1 = new \Doctrine\Tests\Models\CMS\CmsUser();

View File

@ -43,6 +43,29 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('changed from preUpdate callback!', $result[0]->value); $this->assertEquals('changed from preUpdate callback!', $result[0]->value);
} }
public function testPreFlushCallbacksAreInvoked()
{
$entity = new LifecycleCallbackTestEntity;
$entity->value = 'hello';
$this->_em->persist($entity);
$this->_em->flush();
$this->assertTrue($entity->prePersistCallbackInvoked);
$this->assertTrue($entity->preFlushCallbackInvoked);
$entity->preFlushCallbackInvoked = false;
$this->_em->flush();
$this->assertTrue($entity->preFlushCallbackInvoked);
$entity->value = 'bye';
$entity->preFlushCallbackInvoked = false;
$this->_em->flush();
$this->assertTrue($entity->preFlushCallbackInvoked);
}
public function testChangesDontGetLost() public function testChangesDontGetLost()
{ {
$user = new LifecycleCallbackTestUser; $user = new LifecycleCallbackTestUser;
@ -190,6 +213,8 @@ class LifecycleCallbackTestEntity
public $postPersistCallbackInvoked = false; public $postPersistCallbackInvoked = false;
public $postLoadCallbackInvoked = false; public $postLoadCallbackInvoked = false;
public $preFlushCallbackInvoked = false;
/** /**
* @Id @Column(type="integer") * @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO") * @GeneratedValue(strategy="AUTO")
@ -233,6 +258,11 @@ class LifecycleCallbackTestEntity
public function doStuffOnPreUpdate() { public function doStuffOnPreUpdate() {
$this->value = 'changed from preUpdate callback!'; $this->value = 'changed from preUpdate callback!';
} }
/** @PreFlush */
public function doStuffOnPreFlush() {
$this->preFlushCallbackInvoked = true;
}
} }
/** /**

View File

@ -39,6 +39,7 @@ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase
$cleanFile = $this->_em->find(get_class($file), $file->getId()); $cleanFile = $this->_em->find(get_class($file), $file->getId());
$this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()); $this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $cleanFile->getParent());
$this->assertEquals($directory->getId(), $cleanFile->getParent()->getId()); $this->assertEquals($directory->getId(), $cleanFile->getParent()->getId());
$this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()->getParent()); $this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()->getParent());
$this->assertEquals($root->getId(), $cleanFile->getParent()->getParent()->getId()); $this->assertEquals($root->getId(), $cleanFile->getParent()->getParent()->getId());

View File

@ -13,15 +13,14 @@ require_once __DIR__ . '/../../TestInit.php';
*/ */
class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
protected $userId;
protected function setUp() protected function setUp()
{ {
$this->useModelSet('cms'); $this->useModelSet('cms');
parent::setUp(); parent::setUp();
}
public function testOrphanRemoval()
{
$user = new CmsUser; $user = new CmsUser;
$user->status = 'dev'; $user->status = 'dev';
$user->username = 'romanb'; $user->username = 'romanb';
@ -35,11 +34,13 @@ class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($user); $this->_em->persist($user);
$this->_em->flush(); $this->_em->flush();
$userId = $user->getId(); $this->userId = $user->getId();
$this->_em->clear(); $this->_em->clear();
}
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId); public function testOrphanRemoval()
{
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->_em->remove($userProxy); $this->_em->remove($userProxy);
$this->_em->flush(); $this->_em->flush();
@ -55,4 +56,20 @@ class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval'); $this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval');
} }
/**
* @group DDC-1496
*/
public function testOrphanRemovalUnitializedCollection()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$user->phonenumbers->clear();
$this->_em->flush();
$query = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval');
}
} }

View File

@ -73,7 +73,14 @@ class OneToManyUnidirectionalAssociationTest extends \Doctrine\Tests\OrmFunction
$this->_em->persist($routeA); $this->_em->persist($routeA);
$this->_em->persist($routeB); $this->_em->persist($routeB);
$this->setExpectedException('Exception'); // depends on the underyling Database Driver $exceptionThrown = false;
$this->_em->flush(); // Exception try {
// exception depending on the underyling Database Driver
$this->_em->flush();
} catch(\Exception $e) {
$exceptionThrown = true;
}
$this->assertTrue($exceptionThrown, "The underlying database driver throws an exception.");
} }
} }

View File

@ -19,6 +19,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$schemaTool->createSchema(array( $schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Train'), $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Train'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\TrainDriver'), $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\TrainDriver'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\TrainOwner'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Waggon'), $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Waggon'),
)); ));
} catch(\Exception $e) {} } catch(\Exception $e) {}
@ -26,7 +27,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testEagerLoadOneToOneOwningSide() public function testEagerLoadOneToOneOwningSide()
{ {
$train = new Train(); $train = new Train(new TrainOwner("Alexander"));
$driver = new TrainDriver("Benjamin"); $driver = new TrainDriver("Benjamin");
$waggon = new Waggon(); $waggon = new Waggon();
@ -48,7 +49,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testEagerLoadOneToOneNullOwningSide() public function testEagerLoadOneToOneNullOwningSide()
{ {
$train = new Train(); $train = new Train(new TrainOwner("Alexander"));
$this->_em->persist($train); // cascades $this->_em->persist($train); // cascades
$this->_em->flush(); $this->_em->flush();
@ -65,9 +66,8 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testEagerLoadOneToOneInverseSide() public function testEagerLoadOneToOneInverseSide()
{ {
$train = new Train(); $owner = new TrainOwner("Alexander");
$driver = new TrainDriver("Benjamin"); $train = new Train($owner);
$train->setDriver($driver);
$this->_em->persist($train); // cascades $this->_em->persist($train); // cascades
$this->_em->flush(); $this->_em->flush();
@ -75,9 +75,9 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$sqlCount = count($this->_sqlLoggerStack->queries); $sqlCount = count($this->_sqlLoggerStack->queries);
$driver = $this->_em->find(get_class($driver), $driver->id); $driver = $this->_em->find(get_class($owner), $owner->id);
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $driver->train); $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $owner->train);
$this->assertNotNull($driver->train); $this->assertNotNull($owner->train);
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries)); $this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
} }
@ -103,7 +103,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testEagerLoadManyToOne() public function testEagerLoadManyToOne()
{ {
$train = new Train(); $train = new Train(new TrainOwner("Alexander"));
$waggon = new Waggon(); $waggon = new Waggon();
$train->addWaggon($waggon); $train->addWaggon($waggon);
@ -115,6 +115,59 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $waggon->train); $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $waggon->train);
$this->assertNotNull($waggon->train); $this->assertNotNull($waggon->train);
} }
public function testEagerLoadWithNullableColumnsGeneratesLeftJoinOnBothSides()
{
$train = new Train(new TrainOwner("Alexander"));
$driver = new TrainDriver("Benjamin");
$train->setDriver($driver);
$this->_em->persist($train);
$this->_em->flush();
$this->_em->clear();
$train = $this->_em->find(get_class($train), $train->id);
$this->assertEquals(
"SELECT t0.id AS id1, t0.driver_id AS driver_id2, t3.id AS id4, t3.name AS name5, t0.owner_id AS owner_id6, t7.id AS id8, t7.name AS name9 FROM Train t0 LEFT JOIN TrainDriver t3 ON t0.driver_id = t3.id INNER JOIN TrainOwner t7 ON t0.owner_id = t7.id WHERE t0.id = ?",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
$this->_em->clear();
$driver = $this->_em->find(get_class($driver), $driver->id);
$this->assertEquals(
"SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id IN (?)",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
}
public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoinOnOwningSide()
{
$waggon = new Waggon();
$this->_em->persist($waggon);
$this->_em->flush();
$this->_em->clear();
$waggon = $this->_em->find(get_class($waggon), $waggon->id);
$this->assertEquals(
"SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
}
public function testEagerLoadWithNonNullableColumnsGeneratesLeftJoinOnNonOwningSide()
{
$owner = new TrainOwner('Alexander');
$train = new Train($owner);
$this->_em->persist($train);
$this->_em->flush();
$this->_em->clear();
$waggon = $this->_em->find(get_class($owner), $owner->id);
$this->assertEquals(
"SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id = ?",
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
}
} }
/** /**
@ -130,16 +183,23 @@ class Train
/** /**
* Owning side * Owning side
* @OneToOne(targetEntity="TrainDriver", inversedBy="train", fetch="EAGER", cascade={"persist"}) * @OneToOne(targetEntity="TrainDriver", inversedBy="train", fetch="EAGER", cascade={"persist"})
* @JoinColumn(nullable=true)
*/ */
public $driver; public $driver;
/**
* Owning side
* @OneToOne(targetEntity="TrainOwner", inversedBy="train", fetch="EAGER", cascade={"persist"})
*/
public $owner;
/** /**
* @oneToMany(targetEntity="Waggon", mappedBy="train", cascade={"persist"}) * @oneToMany(targetEntity="Waggon", mappedBy="train", cascade={"persist"})
*/ */
public $waggons; public $waggons;
public function __construct() public function __construct(TrainOwner $owner)
{ {
$this->waggons = new \Doctrine\Common\Collections\ArrayCollection(); $this->waggons = new \Doctrine\Common\Collections\ArrayCollection();
$this->setOwner($owner);
} }
public function setDriver(TrainDriver $driver) public function setDriver(TrainDriver $driver)
@ -148,6 +208,12 @@ class Train
$driver->setTrain($this); $driver->setTrain($this);
} }
public function setOwner(TrainOwner $owner)
{
$this->owner = $owner;
$owner->setTrain($this);
}
public function addWaggon(Waggon $w) public function addWaggon(Waggon $w)
{ {
$w->setTrain($this); $w->setTrain($this);
@ -181,6 +247,32 @@ class TrainDriver
} }
} }
/**
* @Entity
*/
class TrainOwner
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @column(type="string") */
public $name;
/**
* Inverse side
* @OneToOne(targetEntity="Train", mappedBy="owner", fetch="EAGER")
*/
public $train;
public function __construct($name)
{
$this->name = $name;
}
public function setTrain(Train $t)
{
$this->train = $t;
}
}
/** /**
* @Entity * @Entity
*/ */

View File

@ -0,0 +1,105 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Common\Persistence\PersistentObject;
/**
* Test that Doctrine ORM correctly works with the ObjectManagerAware and PersistentObject
* classes from Common.
*
* @group DDC-1448
*/
class PersistentObjectTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\PersistentEntity'),
));
} catch (\Exception $e) {
}
PersistentObject::setObjectManager($this->_em);
}
public function testPersist()
{
$entity = new PersistentEntity();
$entity->setName("test");
$this->_em->persist($entity);
$this->_em->flush();
}
public function testFind()
{
$entity = new PersistentEntity();
$entity->setName("test");
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->find(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
$this->assertEquals('test', $entity->getName());
$entity->setName('foobar');
$this->_em->flush();
}
public function testGetReference()
{
$entity = new PersistentEntity();
$entity->setName("test");
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->getReference(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
$this->assertEquals('test', $entity->getName());
}
public function testSetAssociation()
{
$entity = new PersistentEntity();
$entity->setName("test");
$entity->setParent($entity);
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->getReference(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
$this->assertSame($entity, $entity->getParent());
}
}
/**
* @Entity
*/
class PersistentEntity extends PersistentObject
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
protected $id;
/**
* @Column(type="string")
* @var string
*/
protected $name;
/**
* @ManyToOne(targetEntity="PersistentEntity")
* @var PersistentEntity
*/
protected $parent;
}

View File

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

View File

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

View File

@ -457,6 +457,42 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNull($query->getOneOrNullResult(Query::HYDRATE_SCALAR)); $this->assertNull($query->getOneOrNullResult(Query::HYDRATE_SCALAR));
} }
/**
* @group DBAL-171
*/
public function testParameterOrder()
{
$user1 = new CmsUser;
$user1->name = 'Benjamin';
$user1->username = 'beberlei';
$user1->status = 'developer';
$this->_em->persist($user1);
$user2 = new CmsUser;
$user2->name = 'Roman';
$user2->username = 'romanb';
$user2->status = 'developer';
$this->_em->persist($user2);
$user3 = new CmsUser;
$user3->name = 'Jonathan';
$user3->username = 'jwage';
$user3->status = 'developer';
$this->_em->persist($user3);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.status = :a AND u.id IN (:b)");
$query->setParameters(array(
'b' => array($user1->id, $user2->id, $user3->id),
'a' => 'developer',
));
$result = $query->getResult();
$this->assertEquals(3, count($result));
}
public function testDqlWithAutoInferOfParameters() public function testDqlWithAutoInferOfParameters()
{ {
$user = new CmsUser; $user = new CmsUser;

View File

@ -27,14 +27,14 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$readOnly->name = "Test2"; $readOnly->name = "Test2";
$readOnly->number = 4321; $readOnly->numericValue = 4321;
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $this->_em->clear();
$dbReadOnly = $this->_em->find('Doctrine\Tests\ORM\Functional\ReadOnlyEntity', $readOnly->id); $dbReadOnly = $this->_em->find('Doctrine\Tests\ORM\Functional\ReadOnlyEntity', $readOnly->id);
$this->assertEquals("Test1", $dbReadOnly->name); $this->assertEquals("Test1", $dbReadOnly->name);
$this->assertEquals(1234, $dbReadOnly->number); $this->assertEquals(1234, $dbReadOnly->numericValue);
} }
} }
@ -51,11 +51,11 @@ class ReadOnlyEntity
/** @column(type="string") */ /** @column(type="string") */
public $name; public $name;
/** @Column(type="integer") */ /** @Column(type="integer") */
public $number; public $numericValue;
public function __construct($name, $number) public function __construct($name, $number)
{ {
$this->name = $name; $this->name = $name;
$this->number = $number; $this->numericValue = $number;
} }
} }

View File

@ -5,6 +5,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Proxy\ProxyClassGenerator; use Doctrine\ORM\Proxy\ProxyClassGenerator;
use Doctrine\Tests\Models\ECommerce\ECommerceProduct; use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
@ -160,6 +161,29 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy."); $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy.");
} }
public function testDoNotInitializeProxyOnGettingTheIdentifierAndReturnTheRightType()
{
$product = new ECommerceProduct();
$product->setName('Doctrine Cookbook');
$shipping = new ECommerceShipping();
$shipping->setDays(1);
$product->setShipping($shipping);
$this->_em->persist($product);
$this->_em->flush();
$this->_em->clear();
$id = $shipping->getId();
$product = $this->_em->getRepository('Doctrine\Tests\Models\ECommerce\ECommerceProduct')->find($product->getId());
$entity = $product->getShipping();
$this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
$this->assertEquals($id, $entity->getId());
$this->assertSame($id, $entity->getId(), "Check that the id's are the same value, and type.");
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy.");
}
public function testInitializeProxyOnGettingSomethingOtherThanTheIdentifier() public function testInitializeProxyOnGettingSomethingOtherThanTheIdentifier()
{ {
$id = $this->createProduct(); $id = $this->createProduct();

View File

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

View File

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

View File

@ -32,24 +32,28 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
$tool = new SchemaTool($this->_em); $tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes); $sql = $tool->getCreateSchemaSql($classes);
$sqlCount = count($sql);
$this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id))", $sql[0]); $this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id))", array_shift($sql));
$this->assertEquals("CREATE UNIQUE INDEX UNIQ_ACAC157BA76ED395 ON cms_addresses (user_id)", $sql[1]); $this->assertEquals("CREATE UNIQUE INDEX UNIQ_ACAC157BA76ED395 ON cms_addresses (user_id)", array_shift($sql));
$this->assertEquals("CREATE TABLE cms_users (id INT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", $sql[2]); $this->assertEquals("CREATE TABLE cms_users (id INT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", array_shift($sql));
$this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5F85E0677 ON cms_users (username)", $sql[3]); $this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5F85E0677 ON cms_users (username)", array_shift($sql));
$this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id))", $sql[4]); $this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 ON cms_users (email_id)", array_shift($sql));
$this->assertEquals("CREATE INDEX IDX_7EA9409AA76ED395 ON cms_users_groups (user_id)", $sql[5]); $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id))", array_shift($sql));
$this->assertEquals("CREATE INDEX IDX_7EA9409AFE54D947 ON cms_users_groups (group_id)", $sql[6]); $this->assertEquals("CREATE INDEX IDX_7EA9409AA76ED395 ON cms_users_groups (user_id)", array_shift($sql));
$this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber))", $sql[7]); $this->assertEquals("CREATE INDEX IDX_7EA9409AFE54D947 ON cms_users_groups (group_id)", array_shift($sql));
$this->assertEquals("CREATE INDEX IDX_F21F790FA76ED395 ON cms_phonenumbers (user_id)", $sql[8]); $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber))", array_shift($sql));
$this->assertEquals("CREATE SEQUENCE cms_addresses_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[9]); $this->assertEquals("CREATE INDEX IDX_F21F790FA76ED395 ON cms_phonenumbers (user_id)", array_shift($sql));
$this->assertEquals("CREATE SEQUENCE cms_users_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[10]); $this->assertEquals("CREATE SEQUENCE cms_addresses_id_seq INCREMENT BY 1 MINVALUE 1 START 1", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_addresses ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[11]); $this->assertEquals("CREATE SEQUENCE cms_users_id_seq INCREMENT BY 1 MINVALUE 1 START 1", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[12]); $this->assertEquals("ALTER TABLE cms_addresses ADD CONSTRAINT FK_ACAC157BA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (group_id) REFERENCES cms_groups(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[13]); $this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[14]); $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals(count($sql), 15); $this->assertEquals(array(), $sql, "SQL Array should be empty now.");
$this->assertEquals(17, $sqlCount, "Total of 17 queries should be executed");
} }
public function testGetCreateSchemaSql2() public function testGetCreateSchemaSql2()
@ -92,7 +96,7 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
$tool = new SchemaTool($this->_em); $tool = new SchemaTool($this->_em);
$sql = $tool->getDropSchemaSQL($classes); $sql = $tool->getDropSchemaSQL($classes);
$this->assertEquals(13, count($sql)); $this->assertEquals(14, count($sql));
$dropSequenceSQLs = 0; $dropSequenceSQLs = 0;
foreach ($sql AS $stmt) { foreach ($sql AS $stmt) {
if (strpos($stmt, "DROP SEQUENCE") === 0) { if (strpos($stmt, "DROP SEQUENCE") === 0) {

View File

@ -44,11 +44,10 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase
->setParameter('author', $user) ->setParameter('author', $user)
->getResult(); ->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) $farticle = $this->_em->createQuery($dql)
->setParameter('author', $user) ->setParameter('author', $user)
->setParameter('topic', 'This is John Galt speaking!') ->setParameter('topic', 'This is John Galt speaking!')
->setParameter('text', 'Yadda Yadda!')
->getSingleResult(); ->getSingleResult();
$this->assertSame($article, $farticle); $this->assertSame($article, $farticle);
@ -70,12 +69,11 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($article); $this->_em->persist($article);
$this->_em->flush(); $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) $farticle = $this->_em->createQuery($dql)
->setParameter(1, 'This is John Galt speaking!') ->setParameter(1, 'This is John Galt speaking!')
->setParameter(2, $user) ->setParameter(2, $user)
->setParameter(3, $user) ->setParameter(3, $user)
->setParameter(4, 'Yadda Yadda!')
->getSingleResult(); ->getSingleResult();
$this->assertSame($article, $farticle); $this->assertSame($article, $farticle);

View File

@ -27,8 +27,8 @@ class DDC1151Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals("CREATE TABLE \"Group\" (id INT NOT NULL, PRIMARY KEY(id))", $sql[4]); $this->assertEquals("CREATE TABLE \"Group\" (id INT NOT NULL, PRIMARY KEY(id))", $sql[4]);
$this->assertEquals("CREATE SEQUENCE User_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[5]); $this->assertEquals("CREATE SEQUENCE User_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[5]);
$this->assertEquals("CREATE SEQUENCE Group_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[6]); $this->assertEquals("CREATE SEQUENCE Group_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[6]);
$this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD FOREIGN KEY (ddc1151user_id) REFERENCES \"User\"(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[7]); $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD CONSTRAINT FK_88A3259AC5AD08A FOREIGN KEY (ddc1151user_id) REFERENCES \"User\" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[7]);
$this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD FOREIGN KEY (ddc1151group_id) REFERENCES \"Group\"(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[8]); $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD CONSTRAINT FK_88A32597357E0B1 FOREIGN KEY (ddc1151group_id) REFERENCES \"Group\" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[8]);
} }
} }

View File

@ -209,8 +209,15 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->article1->addTranslation('en', 'Bar'); $this->article1->addTranslation('en', 'Bar');
$this->article1->addTranslation('en', 'Baz'); $this->article1->addTranslation('en', 'Baz');
$this->setExpectedException('Exception'); $exceptionThrown = false;
try {
// exception depending on the underyling Database Driver
$this->_em->flush(); $this->_em->flush();
} catch(\Exception $e) {
$exceptionThrown = true;
}
$this->assertTrue($exceptionThrown, "The underlying database driver throws an exception.");
} }
/** /**

View File

@ -106,7 +106,7 @@ class DDC1209_3
{ {
/** /**
* @Id * @Id
* @Column(type="datetime") * @Column(type="datetime", name="somedate")
*/ */
private $date; private $date;

Some files were not shown because too many files have changed in this diff Show More