1
0
mirror of synced 2025-01-31 04:21:44 +03:00

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

Conflicts:
	lib/Doctrine/ORM/Persisters/ManyToManyPersister.php
	lib/Doctrine/ORM/Persisters/OneToManyPersister.php
This commit is contained in:
Alexander 2011-12-05 21:53:34 +01:00
commit 04635ad4ff
75 changed files with 3159 additions and 1411 deletions

View File

@ -1,6 +1,6 @@
# ResultCache implementation rewritten # ResultCache implementation rewritten
The result cache is completly rewritten and now works on the database result level, not inside the ORM AbstractQuery The result cache is completely 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 anymore. This means that for result cached queries the hydration will now always be performed again, regardless of
the hydration mode. Affected areas are: the hydration mode. Affected areas are:
@ -12,20 +12,24 @@ The API is backwards compatible however most of the getter methods on the `Abstr
deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` 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. 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
haven't been in the identity map before. This means objects of this kind never lead to changes haven't been in the identity map before. This means objects of this kind never lead to changes
in the UnitOfWork. in the UnitOfWork.
# Fields omitted in a partial DQL query or a native query are never updated # Fields omitted in a partial DQL query or a native query are never updated
Fields of an entity that are not returned from a partial DQL Query or native SQL query Fields of an entity that are not returned from a partial DQL Query or native SQL query
will never be updated through an UPDATE statement. will never be updated through an UPDATE statement.
# Removed support for onUpdate in @JoinColumn # Removed support for onUpdate in @JoinColumn
The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. The onUpdate foreign key handling makes absolutely no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed.
# Changes in Annotation Handling # Changes in Annotation Handling
@ -42,3 +46,31 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio
$driver = new AnnotationDriver($reader, (array)$paths); $driver = new AnnotationDriver($reader, (array)$paths);
$config->setMetadataDriverImpl($driver); $config->setMetadataDriverImpl($driver);
# Scalar mappings can now be ommitted from DQL result
You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore.
Example:
SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10
Your result will be a collection of Users, and not an array with key 0 as User object instance and "numArticles" as the number of articles per user
# Map entities as scalars in DQL result
When hydrating to array or even a mixed result in object hydrator, previously you had the 0 index holding you entity instance.
You are now allowed to alias this, providing more flexibility for you code.
Example:
SELECT u AS user FROM User u
Will now return a collection of arrays with index "user" pointing to the User object instance.
# Performance optimizations
Thousands of lines were completely reviewed and optimized for best performance.
Removed redundancy and improved code readability made now internal Doctrine code easier to understand.
Also, Doctrine 2.2 now is around 10-15% faster than 2.1.

View File

@ -163,7 +163,11 @@ abstract class AbstractQuery
*/ */
public function getParameter($key) public function getParameter($key)
{ {
return isset($this->_params[$key]) ? $this->_params[$key] : null; if (isset($this->_params[$key])) {
return $this->_params[$key];
}
return null;
} }
/** /**
@ -174,7 +178,11 @@ abstract class AbstractQuery
*/ */
public function getParameterType($key) public function getParameterType($key)
{ {
return isset($this->_paramTypes[$key]) ? $this->_paramTypes[$key] : null; if (isset($this->_paramTypes[$key])) {
return $this->_paramTypes[$key];
}
return null;
} }
/** /**
@ -220,12 +228,9 @@ abstract class AbstractQuery
public function setParameters(array $params, array $types = array()) public function setParameters(array $params, array $types = array())
{ {
foreach ($params as $key => $value) { foreach ($params as $key => $value) {
if (isset($types[$key])) { $this->setParameter($key, $value, isset($types[$key]) ? $types[$key] : null);
$this->setParameter($key, $value, $types[$key]);
} else {
$this->setParameter($key, $value);
}
} }
return $this; return $this;
} }
@ -238,6 +243,7 @@ abstract class AbstractQuery
public function setResultSetMapping(Query\ResultSetMapping $rsm) public function setResultSetMapping(Query\ResultSetMapping $rsm)
{ {
$this->_resultSetMapping = $rsm; $this->_resultSetMapping = $rsm;
return $this; return $this;
} }
@ -252,11 +258,11 @@ 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();
} }
if ($this->_queryCacheProfile) {
$this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver); $this->_queryCacheProfile = $this->_queryCacheProfile
} else { ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
$this->_queryCacheProfile = new QueryCacheProfile(0, null, $resultCacheDriver); : new QueryCacheProfile(0, null, $resultCacheDriver);
}
return $this; return $this;
} }
@ -270,9 +276,9 @@ abstract class AbstractQuery
{ {
if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) { if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
return $this->_queryCacheProfile->getResultCacheDriver(); return $this->_queryCacheProfile->getResultCacheDriver();
} else {
return $this->_em->getConfiguration()->getResultCacheImpl();
} }
return $this->_em->getConfiguration()->getResultCacheImpl();
} }
/** /**
@ -289,9 +295,12 @@ abstract class AbstractQuery
if ($bool) { if ($bool) {
$this->setResultCacheLifetime($lifetime); $this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId); $this->setResultCacheId($resultCacheId);
} else {
$this->_queryCacheProfile = null; return $this;
} }
$this->_queryCacheProfile = null;
return $this; return $this;
} }
@ -303,16 +312,12 @@ abstract class AbstractQuery
*/ */
public function setResultCacheLifetime($lifetime) public function setResultCacheLifetime($lifetime)
{ {
if ($lifetime === null) { $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
$lifetime = 0;
} else { $this->_queryCacheProfile = $this->_queryCacheProfile
$lifetime = (int)$lifetime; ? $this->_queryCacheProfile->setLifetime($lifetime)
} : new QueryCacheProfile($lifetime);
if ($this->_queryCacheProfile) {
$this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime);
} else {
$this->_queryCacheProfile = new QueryCacheProfile($lifetime);
}
return $this; return $this;
} }
@ -336,6 +341,7 @@ abstract class AbstractQuery
public function expireResultCache($expire = true) public function expireResultCache($expire = true)
{ {
$this->_expireResultCache = $expire; $this->_expireResultCache = $expire;
return $this; return $this;
} }
@ -374,6 +380,7 @@ abstract class AbstractQuery
} }
$this->_hints['fetchMode'][$class][$assocName] = $fetchMode; $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
return $this; return $this;
} }
@ -387,6 +394,7 @@ abstract class AbstractQuery
public function setHydrationMode($hydrationMode) public function setHydrationMode($hydrationMode)
{ {
$this->_hydrationMode = $hydrationMode; $this->_hydrationMode = $hydrationMode;
return $this; return $this;
} }
@ -451,14 +459,15 @@ abstract class AbstractQuery
return null; return null;
} }
if (is_array($result)) { if ( ! is_array($result)) {
if (count($result) > 1) { return $result;
throw new NonUniqueResultException;
}
return array_shift($result);
} }
return $result; if (count($result) > 1) {
throw new NonUniqueResultException;
}
return array_shift($result);
} }
/** /**
@ -482,14 +491,15 @@ abstract class AbstractQuery
throw new NoResultException; throw new NoResultException;
} }
if (is_array($result)) { if ( ! is_array($result)) {
if (count($result) > 1) { return $result;
throw new NonUniqueResultException;
}
return array_shift($result);
} }
return $result; if (count($result) > 1) {
throw new NonUniqueResultException;
}
return array_shift($result);
} }
/** /**
@ -515,6 +525,7 @@ abstract class AbstractQuery
public function setHint($name, $value) public function setHint($name, $value)
{ {
$this->_hints[$name] = $value; $this->_hints[$name] = $value;
return $this; return $this;
} }
@ -588,8 +599,8 @@ abstract class AbstractQuery
} }
return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints $stmt, $this->_resultSetMapping, $this->_hints
); );
} }
/** /**
@ -602,11 +613,10 @@ abstract class AbstractQuery
*/ */
public function setResultCacheId($id) public function setResultCacheId($id)
{ {
if ($this->_queryCacheProfile) { $this->_queryCacheProfile = $this->_queryCacheProfile
$this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id); ? $this->_queryCacheProfile->setCacheKey($id)
} else { : new QueryCacheProfile(0, $id);
$this->_queryCacheProfile = new QueryCacheProfile(0, $id);
}
return $this; return $this;
} }

View File

@ -138,10 +138,12 @@ class EntityManager implements ObjectManager
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this); $this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this, $this->proxyFactory = new ProxyFactory(
$config->getProxyDir(), $this,
$config->getProxyNamespace(), $config->getProxyDir(),
$config->getAutoGenerateProxyClasses()); $config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses()
);
} }
/** /**
@ -183,6 +185,7 @@ class EntityManager implements ObjectManager
if ($this->expressionBuilder === null) { if ($this->expressionBuilder === null) {
$this->expressionBuilder = new Query\Expr; $this->expressionBuilder = new Query\Expr;
} }
return $this->expressionBuilder; return $this->expressionBuilder;
} }
@ -275,9 +278,11 @@ class EntityManager implements ObjectManager
public function createQuery($dql = "") public function createQuery($dql = "")
{ {
$query = new Query($this); $query = new Query($this);
if ( ! empty($dql)) { if ( ! empty($dql)) {
$query->setDql($dql); $query->setDql($dql);
} }
return $query; return $query;
} }
@ -304,6 +309,7 @@ class EntityManager implements ObjectManager
$query = new NativeQuery($this); $query = new NativeQuery($this);
$query->setSql($sql); $query->setSql($sql);
$query->setResultSetMapping($rsm); $query->setResultSetMapping($rsm);
return $query; return $query;
} }
@ -316,6 +322,7 @@ class EntityManager implements ObjectManager
public function createNamedNativeQuery($name) public function createNamedNativeQuery($name)
{ {
list($sql, $rsm) = $this->config->getNamedNativeQuery($name); list($sql, $rsm) = $this->config->getNamedNativeQuery($name);
return $this->createNativeQuery($sql, $rsm); return $this->createNativeQuery($sql, $rsm);
} }
@ -344,6 +351,7 @@ class EntityManager implements ObjectManager
public function flush($entity = null) public function flush($entity = null)
{ {
$this->errorIfClosed(); $this->errorIfClosed();
$this->unitOfWork->commit($entity); $this->unitOfWork->commit($entity);
} }
@ -368,27 +376,39 @@ class EntityManager implements ObjectManager
* without actually loading it, if the entity is not yet loaded. * without actually loading it, if the entity is not yet loaded.
* *
* @param string $entityName The name of the entity type. * @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier. * @param mixed $id The entity identifier.
* @return object The entity reference. * @return object The entity reference.
*/ */
public function getReference($entityName, $identifier) public function getReference($entityName, $id)
{ {
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
if ( ! is_array($id)) {
$id = array($class->identifier[0] => $id);
}
$sortedId = array();
foreach ($class->identifier as $identifier) {
if (!isset($id[$identifier])) {
throw ORMException::missingIdentifierField($class->name, $identifier);
}
$sortedId[$identifier] = $id[$identifier];
}
// Check identity map first, if its already in there just return it. // Check identity map first, if its already in there just return it.
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) { if ($entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName)) {
return ($entity instanceof $class->name) ? $entity : null; return ($entity instanceof $class->name) ? $entity : null;
} }
if ($class->subClasses) { if ($class->subClasses) {
$entity = $this->find($entityName, $identifier); return $this->find($entityName, $sortedId);
} else {
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $this->proxyFactory->getProxy($class->name, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
} }
if ( ! is_array($sortedId)) {
$sortedId = array($class->identifier[0] => $sortedId);
}
$entity = $this->proxyFactory->getProxy($class->name, $sortedId);
$this->unitOfWork->registerManaged($entity, $sortedId, array());
return $entity; return $entity;
} }
@ -419,6 +439,7 @@ class EntityManager implements ObjectManager
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) { if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return ($entity instanceof $class->name) ? $entity : null; return ($entity instanceof $class->name) ? $entity : null;
} }
if ( ! is_array($identifier)) { if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier); $identifier = array($class->identifier[0] => $identifier);
} }
@ -469,7 +490,9 @@ class EntityManager implements ObjectManager
if ( ! is_object($entity)) { if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity)); throw new \InvalidArgumentException(gettype($entity));
} }
$this->errorIfClosed(); $this->errorIfClosed();
$this->unitOfWork->persist($entity); $this->unitOfWork->persist($entity);
} }
@ -486,7 +509,9 @@ class EntityManager implements ObjectManager
if ( ! is_object($entity)) { if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity)); throw new \InvalidArgumentException(gettype($entity));
} }
$this->errorIfClosed(); $this->errorIfClosed();
$this->unitOfWork->remove($entity); $this->unitOfWork->remove($entity);
} }
@ -501,7 +526,9 @@ class EntityManager implements ObjectManager
if ( ! is_object($entity)) { if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity)); throw new \InvalidArgumentException(gettype($entity));
} }
$this->errorIfClosed(); $this->errorIfClosed();
$this->unitOfWork->refresh($entity); $this->unitOfWork->refresh($entity);
} }
@ -519,6 +546,7 @@ class EntityManager implements ObjectManager
if ( ! is_object($entity)) { if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity)); throw new \InvalidArgumentException(gettype($entity));
} }
$this->unitOfWork->detach($entity); $this->unitOfWork->detach($entity);
} }
@ -535,7 +563,9 @@ class EntityManager implements ObjectManager
if ( ! is_object($entity)) { if ( ! is_object($entity)) {
throw new \InvalidArgumentException(gettype($entity)); throw new \InvalidArgumentException(gettype($entity));
} }
$this->errorIfClosed(); $this->errorIfClosed();
return $this->unitOfWork->merge($entity); return $this->unitOfWork->merge($entity);
} }
@ -575,20 +605,20 @@ class EntityManager implements ObjectManager
public function getRepository($entityName) public function getRepository($entityName)
{ {
$entityName = ltrim($entityName, '\\'); $entityName = ltrim($entityName, '\\');
if (isset($this->repositories[$entityName])) { if (isset($this->repositories[$entityName])) {
return $this->repositories[$entityName]; return $this->repositories[$entityName];
} }
$metadata = $this->getClassMetadata($entityName); $metadata = $this->getClassMetadata($entityName);
$customRepositoryClassName = $metadata->customRepositoryClassName; $repositoryClassName = $metadata->customRepositoryClassName;
if ($customRepositoryClassName !== null) { if ($repositoryClassName === null) {
$repository = new $customRepositoryClassName($this, $metadata); $repositoryClassName = $this->config->getDefaultRepositoryClassName();
} else {
$repositoryClass = $this->config->getDefaultRepositoryClassName();
$repository = new $repositoryClass($this, $metadata);
} }
$repository = new $repositoryClassName($this, $metadata);
$this->repositories[$entityName] = $repository; $this->repositories[$entityName] = $repository;
return $repository; return $repository;
@ -602,9 +632,9 @@ class EntityManager implements ObjectManager
*/ */
public function contains($entity) public function contains($entity)
{ {
return $this->unitOfWork->isScheduledForInsert($entity) || return $this->unitOfWork->isScheduledForInsert($entity)
$this->unitOfWork->isInIdentityMap($entity) && || $this->unitOfWork->isInIdentityMap($entity)
! $this->unitOfWork->isScheduledForDelete($entity); && ! $this->unitOfWork->isScheduledForDelete($entity);
} }
/** /**
@ -687,29 +717,27 @@ class EntityManager implements ObjectManager
{ {
switch ($hydrationMode) { switch ($hydrationMode) {
case Query::HYDRATE_OBJECT: case Query::HYDRATE_OBJECT:
$hydrator = new Internal\Hydration\ObjectHydrator($this); return new Internal\Hydration\ObjectHydrator($this);
break;
case Query::HYDRATE_ARRAY: case Query::HYDRATE_ARRAY:
$hydrator = new Internal\Hydration\ArrayHydrator($this); return new Internal\Hydration\ArrayHydrator($this);
break;
case Query::HYDRATE_SCALAR: case Query::HYDRATE_SCALAR:
$hydrator = new Internal\Hydration\ScalarHydrator($this); return new Internal\Hydration\ScalarHydrator($this);
break;
case Query::HYDRATE_SINGLE_SCALAR: case Query::HYDRATE_SINGLE_SCALAR:
$hydrator = new Internal\Hydration\SingleScalarHydrator($this); return new Internal\Hydration\SingleScalarHydrator($this);
break;
case Query::HYDRATE_SIMPLEOBJECT: case Query::HYDRATE_SIMPLEOBJECT:
$hydrator = new Internal\Hydration\SimpleObjectHydrator($this); return new Internal\Hydration\SimpleObjectHydrator($this);
break;
default: default:
if ($class = $this->config->getCustomHydrationMode($hydrationMode)) { if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
$hydrator = new $class($this); return new $class($this);
break;
} }
throw ORMException::invalidHydrationMode($hydrationMode);
} }
return $hydrator; throw ORMException::invalidHydrationMode($hydrationMode);
} }
/** /**
@ -745,18 +773,25 @@ class EntityManager implements ObjectManager
*/ */
public static function create($conn, Configuration $config, EventManager $eventManager = null) public static function create($conn, Configuration $config, EventManager $eventManager = null)
{ {
if (!$config->getMetadataDriverImpl()) { if ( ! $config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl(); throw ORMException::missingMappingDriverImpl();
} }
if (is_array($conn)) { switch (true) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager())); case (is_array($conn)):
} else if ($conn instanceof Connection) { $conn = \Doctrine\DBAL\DriverManager::getConnection(
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) { $conn, $config, ($eventManager ?: new EventManager())
throw ORMException::mismatchedEventManager(); );
} break;
} else {
throw new \InvalidArgumentException("Invalid argument: " . $conn); case ($conn instanceof Connection):
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
break;
default:
throw new \InvalidArgumentException("Invalid argument: " . $conn);
} }
return new EntityManager($conn, $config, $conn->getEventManager()); return new EntityManager($conn, $config, $conn->getEventManager());

View File

@ -107,42 +107,51 @@ class EntityRepository implements ObjectRepository
*/ */
public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
{ {
if ( ! is_array($id)) {
$id = array($this->_class->identifier[0] => $id);
}
$sortedId = array();
foreach ($this->_class->identifier as $identifier) {
if (!isset($id[$identifier])) {
throw ORMException::missingIdentifierField($this->_class->name, $identifier);
}
$sortedId[$identifier] = $id[$identifier];
}
// Check identity map first // Check identity map first
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) { if ($entity = $this->_em->getUnitOfWork()->tryGetById($sortedId, $this->_class->rootEntityName)) {
if (!($entity instanceof $this->_class->name)) { if ( ! ($entity instanceof $this->_class->name)) {
return null; return null;
} }
if ($lockMode != LockMode::NONE) { if ($lockMode !== LockMode::NONE) {
$this->_em->lock($entity, $lockMode, $lockVersion); $this->_em->lock($entity, $lockMode, $lockVersion);
} }
return $entity; // Hit! return $entity; // Hit!
} }
if ( ! is_array($id) || count($id) <= 1) { switch ($lockMode) {
// @todo FIXME: Not correct. Relies on specific order. case LockMode::NONE:
$value = is_array($id) ? array_values($id) : array($id); return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($sortedId);
$id = array_combine($this->_class->identifier, $value);
}
if ($lockMode == LockMode::NONE) { case LockMode::OPTIMISTIC:
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); if ( ! $this->_class->isVersioned) {
} else if ($lockMode == LockMode::OPTIMISTIC) { throw OptimisticLockException::notVersioned($this->_entityName);
if (!$this->_class->isVersioned) { }
throw OptimisticLockException::notVersioned($this->_entityName);
}
$entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
$this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion); $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($sortedId);
return $entity; $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion);
} else {
if (!$this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode); return $entity;
default:
if ( ! $this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($sortedId, null, null, array(), $lockMode);
} }
} }
@ -191,30 +200,35 @@ class EntityRepository implements ObjectRepository
*/ */
public function __call($method, $arguments) public function __call($method, $arguments)
{ {
if (substr($method, 0, 6) == 'findBy') { switch (true) {
$by = substr($method, 6, strlen($method)); case (substr($method, 0, 6) == 'findBy'):
$method = 'findBy'; $by = substr($method, 6, strlen($method));
} else if (substr($method, 0, 9) == 'findOneBy') { $method = 'findBy';
$by = substr($method, 9, strlen($method)); break;
$method = 'findOneBy';
} else { case (substr($method, 0, 9) == 'findOneBy'):
throw new \BadMethodCallException( $by = substr($method, 9, strlen($method));
"Undefined method '$method'. The method name must start with ". $method = 'findOneBy';
"either findBy or findOneBy!" break;
);
default:
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy or findOneBy!"
);
} }
if (empty($arguments)) { if (empty($arguments)) {
throw ORMException::findByRequiresParameter($method.$by); throw ORMException::findByRequiresParameter($method . $by);
} }
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) { if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
return $this->$method(array($fieldName => $arguments[0])); return $this->$method(array($fieldName => $arguments[0]));
} else {
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
} }
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
} }
/** /**

View File

@ -23,8 +23,9 @@ namespace Doctrine\ORM\Internal;
* The CommitOrderCalculator is used by the UnitOfWork to sort out the * The CommitOrderCalculator is used by the UnitOfWork to sort out the
* correct order in which changes to entities need to be persisted. * correct order in which changes to entities need to be persisted.
* *
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/ */
class CommitOrderCalculator class CommitOrderCalculator
{ {
@ -60,10 +61,9 @@ class CommitOrderCalculator
{ {
// Check whether we need to do anything. 0 or 1 node is easy. // Check whether we need to do anything. 0 or 1 node is easy.
$nodeCount = count($this->_classes); $nodeCount = count($this->_classes);
if ($nodeCount == 0) {
return array(); if ($nodeCount <= 1) {
} else if ($nodeCount == 1) { return ($nodeCount == 1) ? array_values($this->_classes) : array();
return array_values($this->_classes);
} }
// Init // Init

View File

@ -28,11 +28,6 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com> * @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
{ {

View File

@ -23,6 +23,8 @@ use PDO,
Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\PersistentCollection, Doctrine\ORM\PersistentCollection,
Doctrine\ORM\Query, Doctrine\ORM\Query,
Doctrine\ORM\Event\LifecycleEventArgs,
Doctrine\ORM\Events,
Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\ArrayCollection,
Doctrine\Common\Collections\Collection; Doctrine\Common\Collections\Collection;
@ -34,11 +36,6 @@ use PDO,
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com> * @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
{ {
@ -235,7 +232,21 @@ class ObjectHydrator extends AbstractHydrator
} }
$this->_hints['fetchAlias'] = $dqlAlias; $this->_hints['fetchAlias'] = $dqlAlias;
return $this->_uow->createEntity($className, $data, $this->_hints);
$entity = $this->_uow->createEntity($className, $data, $this->_hints);
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
if (isset($this->_ce[$className]->lifecycleCallbacks[Events::postLoad])) {
$this->_ce[$className]->invokeLifecycleCallbacks(Events::postLoad, $entity);
}
$evm = $this->_em->getEventManager();
if ($evm->hasListeners(Events::postLoad)) {
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
}
return $entity;
} }
private function _getEntityFromIdentityMap($className, array $data) private function _getEntityFromIdentityMap($className, array $data)

View File

@ -19,10 +19,12 @@
namespace Doctrine\ORM\Internal\Hydration; namespace Doctrine\ORM\Internal\Hydration;
use \PDO; use \PDO,
use Doctrine\ORM\Mapping\ClassMetadata; Doctrine\DBAL\Types\Type,
use Doctrine\DBAL\Types\Type; Doctrine\ORM\Mapping\ClassMetadata,
use Doctrine\ORM\Query; Doctrine\ORM\Event\LifecycleEventArgs,
Doctrine\ORM\Events,
Doctrine\ORM\Query;
class SimpleObjectHydrator extends AbstractHydrator class SimpleObjectHydrator extends AbstractHydrator
{ {
@ -125,7 +127,21 @@ class SimpleObjectHydrator extends AbstractHydrator
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); $this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
} }
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints); $uow = $this->_em->getUnitOfWork();
$entity = $uow->createEntity($entityName, $data, $this->_hints);
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
if (isset($this->class->lifecycleCallbacks[Events::postLoad])) {
$this->class->invokeLifecycleCallbacks(Events::postLoad, $entity);
}
$evm = $this->_em->getEventManager();
if ($evm->hasListeners(Events::postLoad)) {
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
}
$result[] = $entity;
} }
/** /**

View File

@ -0,0 +1,24 @@
<?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\Mapping;
interface Annotation
{
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class ChangeTrackingPolicy implements Annotation
{
/** @var string */
public $value;
}

View File

@ -135,20 +135,25 @@ class ClassMetadata extends ClassMetadataInfo
{ {
if ($this->isIdentifierComposite) { if ($this->isIdentifierComposite) {
$id = array(); $id = array();
foreach ($this->identifier as $idField) { foreach ($this->identifier as $idField) {
$value = $this->reflFields[$idField]->getValue($entity); $value = $this->reflFields[$idField]->getValue($entity);
if ($value !== null) { if ($value !== null) {
$id[$idField] = $value; $id[$idField] = $value;
} }
} }
return $id; return $id;
} else {
$value = $this->reflFields[$this->identifier[0]]->getValue($entity);
if ($value !== null) {
return array($this->identifier[0] => $value);
}
return array();
} }
$value = $this->reflFields[$this->identifier[0]]->getValue($entity);
if ($value !== null) {
return array($this->identifier[0] => $value);
}
return array();
} }
/** /**
@ -310,21 +315,18 @@ class ClassMetadata extends ClassMetadataInfo
$this->reflClass = new ReflectionClass($this->name); $this->reflClass = new ReflectionClass($this->name);
foreach ($this->fieldMappings as $field => $mapping) { foreach ($this->fieldMappings as $field => $mapping) {
if (isset($mapping['declared'])) { $reflField = isset($mapping['declared'])
$reflField = new ReflectionProperty($mapping['declared'], $field); ? new ReflectionProperty($mapping['declared'], $field)
} else { : $this->reflClass->getProperty($field);
$reflField = $this->reflClass->getProperty($field);
}
$reflField->setAccessible(true); $reflField->setAccessible(true);
$this->reflFields[$field] = $reflField; $this->reflFields[$field] = $reflField;
} }
foreach ($this->associationMappings as $field => $mapping) { foreach ($this->associationMappings as $field => $mapping) {
if (isset($mapping['declared'])) { $reflField = isset($mapping['declared'])
$reflField = new ReflectionProperty($mapping['declared'], $field); ? new ReflectionProperty($mapping['declared'], $field)
} else { : $this->reflClass->getProperty($field);
$reflField = $this->reflClass->getProperty($field);
}
$reflField->setAccessible(true); $reflField->setAccessible(true);
$this->reflFields[$field] = $reflField; $this->reflFields[$field] = $reflField;
@ -341,6 +343,7 @@ class ClassMetadata extends ClassMetadataInfo
if ($this->_prototype === null) { if ($this->_prototype === null) {
$this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name)); $this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
} }
return clone $this->_prototype; return clone $this->_prototype;
} }
@ -354,6 +357,7 @@ class ClassMetadata extends ClassMetadataInfo
($this->reflClass->getMethod($callback)->getModifiers() & \ReflectionMethod::IS_PUBLIC) == 0) { ($this->reflClass->getMethod($callback)->getModifiers() & \ReflectionMethod::IS_PUBLIC) == 0) {
throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callback); throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callback);
} }
return parent::addLifecycleCallback($callback, $event); return parent::addLifecycleCallback($callback, $event);
} }
} }

View File

@ -521,6 +521,12 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
$this->initialize(); $this->initialize();
} }
// Check for namespace alias
if (strpos($class, ':') !== false) {
list($namespaceAlias, $simpleClassName) = explode(':', $class);
$class = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
}
return $this->driver->isTransient($class); return $this->driver->isTransient($class);
} }
} }

View File

@ -0,0 +1,46 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Column implements Annotation
{
/** @var string */
public $name;
/** @var mixed */
public $type = 'string';
/** @var integer */
public $length;
/** @var integer */
public $precision = 0; // The precision for a decimal (exact numeric) column (Applies only for decimal column)
/** @var integer */
public $scale = 0; // The scale for a decimal (exact numeric) column (Applies only for decimal column)
/** @var boolean */
public $unique = false;
/** @var boolean */
public $nullable = false;
/** @var array */
public $options = array();
/** @var string */
public $columnDefinition;
}

View File

@ -0,0 +1,36 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class DiscriminatorColumn implements Annotation
{
/** @var string */
public $name;
/** @var string */
public $type;
/** @var integer */
public $length;
/** @var mixed */
public $fieldName; // field name used in non-object hydration (array/scalar)
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class DiscriminatorMap implements Annotation
{
/** @var array<string> */
public $value;
}

View File

@ -17,379 +17,38 @@
* <http://www.doctrine-project.org>. * <http://www.doctrine-project.org>.
*/ */
namespace Doctrine\ORM\Mapping; require_once __DIR__.'/../Annotation.php';
require_once __DIR__.'/../Entity.php';
interface Annotation {} require_once __DIR__.'/../MappedSuperclass.php';
require_once __DIR__.'/../InheritanceType.php';
require_once __DIR__.'/../DiscriminatorColumn.php';
/* Annotations */ require_once __DIR__.'/../DiscriminatorMap.php';
require_once __DIR__.'/../Id.php';
/** require_once __DIR__.'/../GeneratedValue.php';
* @Annotation require_once __DIR__.'/../Version.php';
* @Target("CLASS") require_once __DIR__.'/../JoinColumn.php';
*/ require_once __DIR__.'/../JoinColumns.php';
final class Entity implements Annotation { require_once __DIR__.'/../Column.php';
/** @var string */ require_once __DIR__.'/../OneToOne.php';
public $repositoryClass; require_once __DIR__.'/../OneToMany.php';
/** @var boolean */ require_once __DIR__.'/../ManyToOne.php';
public $readOnly = false; require_once __DIR__.'/../ManyToMany.php';
} require_once __DIR__.'/../ElementCollection.php';
require_once __DIR__.'/../Table.php';
/** require_once __DIR__.'/../UniqueConstraint.php';
* @Annotation require_once __DIR__.'/../Index.php';
* @Target("CLASS") require_once __DIR__.'/../JoinTable.php';
*/ require_once __DIR__.'/../SequenceGenerator.php';
final class MappedSuperclass implements Annotation { require_once __DIR__.'/../ChangeTrackingPolicy.php';
/** @var string */ require_once __DIR__.'/../OrderBy.php';
public $repositoryClass; require_once __DIR__.'/../NamedQueries.php';
} require_once __DIR__.'/../NamedQuery.php';
require_once __DIR__.'/../HasLifecycleCallbacks.php';
/** require_once __DIR__.'/../PrePersist.php';
* @Annotation require_once __DIR__.'/../PostPersist.php';
* @Target("CLASS") require_once __DIR__.'/../PreUpdate.php';
*/ require_once __DIR__.'/../PostUpdate.php';
final class InheritanceType implements Annotation { require_once __DIR__.'/../PreRemove.php';
/** @var string */ require_once __DIR__.'/../PostRemove.php';
public $value; require_once __DIR__.'/../PostLoad.php';
} require_once __DIR__.'/../PreFlush.php';
/**
* @Annotation
* @Target("CLASS")
*/
final class DiscriminatorColumn implements Annotation {
/** @var string */
public $name;
/** @var string */
public $type;
/** @var integer */
public $length;
/** @var mixed */
public $fieldName; // field name used in non-object hydration (array/scalar)
}
/**
* @Annotation
* @Target("CLASS")
*/
final class DiscriminatorMap implements Annotation {
/** @var array<string> */
public $value;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Id implements Annotation {}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class GeneratedValue implements Annotation {
/** @var string */
public $strategy = 'AUTO';
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Version implements Annotation {}
/**
* @Annotation
* @Target({"PROPERTY","ANNOTATION"})
*/
final class JoinColumn implements Annotation {
/** @var string */
public $name;
/** @var string */
public $referencedColumnName = 'id';
/** @var boolean */
public $unique = false;
/** @var boolean */
public $nullable = true;
/** @var mixed */
public $onDelete;
/** @var string */
public $columnDefinition;
/** @var string */
public $fieldName; // field name used in non-object hydration (array/scalar)
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class JoinColumns implements Annotation {
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $value;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Column implements Annotation {
/** @var string */
public $name;
/** @var mixed */
public $type = 'string';
/** @var integer */
public $length;
/** @var integer */
public $precision = 0; // The precision for a decimal (exact numeric) column (Applies only for decimal column)
/** @var integer */
public $scale = 0; // The scale for a decimal (exact numeric) column (Applies only for decimal column)
/** @var boolean */
public $unique = false;
/** @var boolean */
public $nullable = false;
/** @var array */
public $options = array();
/** @var string */
public $columnDefinition;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class OneToOne implements Annotation {
/** @var string */
public $targetEntity;
/** @var string */
public $mappedBy;
/** @var string */
public $inversedBy;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var boolean */
public $orphanRemoval = false;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class OneToMany implements Annotation {
/** @var string */
public $mappedBy;
/** @var string */
public $targetEntity;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var boolean */
public $orphanRemoval = false;
/** @var string */
public $indexBy;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class ManyToOne implements Annotation {
/** @var string */
public $targetEntity;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var string */
public $inversedBy;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class ManyToMany implements Annotation {
/** @var string */
public $targetEntity;
/** @var string */
public $mappedBy;
/** @var string */
public $inversedBy;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var string */
public $indexBy;
}
/**
* @Annotation
* @Target("ALL")
* @todo check available targets
*/
final class ElementCollection implements Annotation {
/** @var string */
public $tableName;
}
/**
* @Annotation
* @Target("CLASS")
*/
final class Table implements Annotation {
/** @var string */
public $name;
/** @var string */
public $schema;
/** @var array<Doctrine\ORM\Mapping\Index> */
public $indexes;
/** @var array<Doctrine\ORM\Mapping\UniqueConstraint> */
public $uniqueConstraints;
}
/**
* @Annotation
* @Target("ANNOTATION")
*/
final class UniqueConstraint implements Annotation {
/** @var string */
public $name;
/** @var array<string> */
public $columns;
}
/**
* @Annotation
* @Target("ANNOTATION")
*/
final class Index implements Annotation {
/** @var string */
public $name;
/** @var array<string> */
public $columns;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class JoinTable implements Annotation {
/** @var string */
public $name;
/** @var string */
public $schema;
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $joinColumns = array();
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $inverseJoinColumns = array();
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class SequenceGenerator implements Annotation {
/** @var string */
public $sequenceName;
/** @var integer */
public $allocationSize = 1;
/** @var integer */
public $initialValue = 1;
}
/**
* @Annotation
* @Target("CLASS")
*/
final class ChangeTrackingPolicy implements Annotation {
/** @var string */
public $value;
}
/**
* @Annotation
* @Target("PROPERTY")
*/
final class OrderBy implements Annotation {
/** @var array<string> */
public $value;
}
/**
* @Annotation
* @Target("CLASS")
*/
final class NamedQueries implements Annotation {
/** @var array<Doctrine\ORM\Mapping\NamedQuery> */
public $value;
}
/**
* @Annotation
* @Target("ANNOTATION")
*/
final class NamedQuery implements Annotation {
/** @var string */
public $name;
/** @var string */
public $query;
}
/* Annotations for lifecycle callbacks */
/**
* @Annotation
* @Target("CLASS")
*/
final class HasLifecycleCallbacks implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PrePersist implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PostPersist implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PreUpdate implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PostUpdate implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PreRemove implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PostRemove implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PostLoad implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PreFlush implements Annotation {}

View File

@ -233,6 +233,10 @@ class XmlDriver extends AbstractFileDriver
$mapping['columnName'] = (string)$idElement['column']; $mapping['columnName'] = (string)$idElement['column'];
} }
if (isset($idElement['column-definition'])) {
$mapping['columnDefinition'] = (string)$idElement['column-definition'];
}
$metadata->mapField($mapping); $metadata->mapField($mapping);
if (isset($idElement->generator)) { if (isset($idElement->generator)) {

View File

@ -182,6 +182,10 @@ class YamlDriver extends AbstractFileDriver
$mapping['length'] = $idElement['length']; $mapping['length'] = $idElement['length'];
} }
if (isset($idElement['columnDefinition'])) {
$mapping['columnDefinition'] = $idElement['columnDefinition'];
}
$metadata->mapField($mapping); $metadata->mapField($mapping);
if (isset($idElement['generator'])) { if (isset($idElement['generator'])) {

View File

@ -0,0 +1,31 @@
<?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\Mapping;
/**
* @Annotation
* @Target("ALL")
* @todo check available targets
*/
final class ElementCollection implements Annotation
{
/** @var string */
public $tableName;
}

View File

@ -0,0 +1,32 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class Entity implements Annotation
{
/** @var string */
public $repositoryClass;
/** @var boolean */
public $readOnly = false;
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class GeneratedValue implements Annotation
{
/** @var string */
public $strategy = 'AUTO';
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class HasLifecycleCallbacks implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Id implements Annotation
{
}

View File

@ -0,0 +1,32 @@
<?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\Mapping;
/**
* @Annotation
* @Target("ANNOTATION")
*/
final class Index implements Annotation
{
/** @var string */
public $name;
/** @var array<string> */
public $columns;
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class InheritanceType implements Annotation
{
/** @var string */
public $value;
}

View File

@ -0,0 +1,42 @@
<?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\Mapping;
/**
* @Annotation
* @Target({"PROPERTY","ANNOTATION"})
*/
final class JoinColumn implements Annotation
{
/** @var string */
public $name;
/** @var string */
public $referencedColumnName = 'id';
/** @var boolean */
public $unique = false;
/** @var boolean */
public $nullable = true;
/** @var mixed */
public $onDelete;
/** @var string */
public $columnDefinition;
/** @var string */
public $fieldName; // field name used in non-object hydration (array/scalar)
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class JoinColumns implements Annotation
{
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $value;
}

View File

@ -0,0 +1,36 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class JoinTable implements Annotation
{
/** @var string */
public $name;
/** @var string */
public $schema;
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $joinColumns = array();
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $inverseJoinColumns = array();
}

View File

@ -0,0 +1,40 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class ManyToMany implements Annotation
{
/** @var string */
public $targetEntity;
/** @var string */
public $mappedBy;
/** @var string */
public $inversedBy;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var string */
public $indexBy;
}

View File

@ -0,0 +1,36 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class ManyToOne implements Annotation
{
/** @var string */
public $targetEntity;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var string */
public $inversedBy;
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class MappedSuperclass implements Annotation
{
/** @var string */
public $repositoryClass;
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class NamedQueries implements Annotation
{
/** @var array<Doctrine\ORM\Mapping\NamedQuery> */
public $value;
}

View File

@ -0,0 +1,32 @@
<?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\Mapping;
/**
* @Annotation
* @Target("ANNOTATION")
*/
final class NamedQuery implements Annotation
{
/** @var string */
public $name;
/** @var string */
public $query;
}

View File

@ -0,0 +1,40 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class OneToMany implements Annotation
{
/** @var string */
public $mappedBy;
/** @var string */
public $targetEntity;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var boolean */
public $orphanRemoval = false;
/** @var string */
public $indexBy;
}

View File

@ -0,0 +1,40 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class OneToOne implements Annotation
{
/** @var string */
public $targetEntity;
/** @var string */
public $mappedBy;
/** @var string */
public $inversedBy;
/** @var array<string> */
public $cascade;
/** @var string */
public $fetch = 'LAZY';
/** @var boolean */
public $orphanRemoval = false;
}

View File

@ -0,0 +1,30 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class OrderBy implements Annotation
{
/** @var array<string> */
public $value;
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PostLoad implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PostPersist implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PostRemove implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PostUpdate implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PreFlush implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PrePersist implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PreRemove implements Annotation
{
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("METHOD")
*/
final class PreUpdate implements Annotation
{
}

View File

@ -0,0 +1,34 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class SequenceGenerator implements Annotation
{
/** @var string */
public $sequenceName;
/** @var integer */
public $allocationSize = 1;
/** @var integer */
public $initialValue = 1;
}

View File

@ -0,0 +1,36 @@
<?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\Mapping;
/**
* @Annotation
* @Target("CLASS")
*/
final class Table implements Annotation
{
/** @var string */
public $name;
/** @var string */
public $schema;
/** @var array<Doctrine\ORM\Mapping\Index> */
public $indexes;
/** @var array<Doctrine\ORM\Mapping\UniqueConstraint> */
public $uniqueConstraints;
}

View File

@ -0,0 +1,32 @@
<?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\Mapping;
/**
* @Annotation
* @Target("ANNOTATION")
*/
final class UniqueConstraint implements Annotation
{
/** @var string */
public $name;
/** @var array<string> */
public $columns;
}

View File

@ -0,0 +1,28 @@
<?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\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Version implements Annotation
{
}

View File

@ -38,6 +38,7 @@ final class NativeQuery extends AbstractQuery
public function setSQL($sql) public function setSQL($sql)
{ {
$this->_sql = $sql; $this->_sql = $sql;
return $this; return $this;
} }
@ -58,16 +59,18 @@ final class NativeQuery extends AbstractQuery
protected function _doExecute() protected function _doExecute()
{ {
$params = $this->_params; $params = $this->_params;
$types = $this->_paramTypes; $types = $this->_paramTypes;
if ($params) {
if (is_int(key($params))) { if ($params && is_int(key($params))) {
ksort($params); ksort($params);
ksort($types); ksort($types);
$params = array_values($params);
$types = array_values($types); $params = array_values($params);
} $types = array_values($types);
} }
return $this->_em->getConnection()->executeQuery($this->_sql, $params, $types, $this->_queryCacheProfile); return $this->_em->getConnection()->executeQuery(
$this->_sql, $params, $types, $this->_queryCacheProfile
);
} }
} }

View File

@ -144,4 +144,9 @@ class ORMException extends Exception
return new self("Invalid repository class '".$className."'. ". return new self("Invalid repository class '".$className."'. ".
"it must be a Doctrine\ORM\EntityRepository."); "it must be a Doctrine\ORM\EntityRepository.");
} }
public static function missingIdentifierField($className, $fieldName)
{
return new self("The identifier $fieldName is missing for a query of " . $className);
}
} }

View File

@ -21,6 +21,7 @@ namespace Doctrine\ORM;
use Doctrine\ORM\Mapping\ClassMetadata, use Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\Common\Collections\Collection, Doctrine\Common\Collections\Collection,
Doctrine\Common\Collections\ArrayCollection,
Closure; Closure;
/** /**
@ -114,8 +115,8 @@ final class PersistentCollection implements Collection
*/ */
public function __construct(EntityManager $em, $class, $coll) public function __construct(EntityManager $em, $class, $coll)
{ {
$this->coll = $coll; $this->coll = $coll;
$this->em = $em; $this->em = $em;
$this->typeClass = $class; $this->typeClass = $class;
} }
@ -129,8 +130,8 @@ final class PersistentCollection implements Collection
*/ */
public function setOwner($entity, array $assoc) public function setOwner($entity, array $assoc)
{ {
$this->owner = $entity; $this->owner = $entity;
$this->association = $assoc; $this->association = $assoc;
$this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy']; $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy'];
} }
@ -160,16 +161,18 @@ final class PersistentCollection implements Collection
public function hydrateAdd($element) public function hydrateAdd($element)
{ {
$this->coll->add($element); $this->coll->add($element);
// If _backRefFieldName is set and its a one-to-many association, // If _backRefFieldName is set and its a one-to-many association,
// we need to set the back reference. // we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) { if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
// Set back reference to owner // Set back reference to owner
$this->typeClass->reflFields[$this->backRefFieldName] $this->typeClass->reflFields[$this->backRefFieldName]->setValue(
->setValue($element, $this->owner); $element, $this->owner
);
$this->em->getUnitOfWork()->setOriginalEntityProperty( $this->em->getUnitOfWork()->setOriginalEntityProperty(
spl_object_hash($element), spl_object_hash($element), $this->backRefFieldName, $this->owner
$this->backRefFieldName, );
$this->owner);
} }
} }
@ -183,12 +186,14 @@ final class PersistentCollection implements Collection
public function hydrateSet($key, $element) public function hydrateSet($key, $element)
{ {
$this->coll->set($key, $element); $this->coll->set($key, $element);
// If _backRefFieldName is set, then the association is bidirectional // If _backRefFieldName is set, then the association is bidirectional
// and we need to set the back reference. // and we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) { if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
// Set back reference to owner // Set back reference to owner
$this->typeClass->reflFields[$this->backRefFieldName] $this->typeClass->reflFields[$this->backRefFieldName]->setValue(
->setValue($element, $this->owner); $element, $this->owner
);
} }
} }
@ -198,23 +203,31 @@ final class PersistentCollection implements Collection
*/ */
public function initialize() public function initialize()
{ {
if ( ! $this->initialized && $this->association) { if ($this->initialized || ! $this->association) {
if ($this->isDirty) { return;
// Has NEW objects added through add(). Remember them.
$newObjects = $this->coll->toArray();
}
$this->coll->clear();
$this->em->getUnitOfWork()->loadCollection($this);
$this->takeSnapshot();
// Reattach NEW objects added through add(), if any.
if (isset($newObjects)) {
foreach ($newObjects as $obj) {
$this->coll->add($obj);
}
$this->isDirty = true;
}
$this->initialized = true;
} }
// Has NEW objects added through add(). Remember them.
$newObjects = array();
if ($this->isDirty) {
$newObjects = $this->coll->toArray();
}
$this->coll->clear();
$this->em->getUnitOfWork()->loadCollection($this);
$this->takeSnapshot();
// Reattach NEW objects added through add(), if any.
if ($newObjects) {
foreach ($newObjects as $obj) {
$this->coll->add($obj);
}
$this->isDirty = true;
}
$this->initialized = true;
} }
/** /**
@ -224,7 +237,7 @@ final class PersistentCollection implements Collection
public function takeSnapshot() public function takeSnapshot()
{ {
$this->snapshot = $this->coll->toArray(); $this->snapshot = $this->coll->toArray();
$this->isDirty = false; $this->isDirty = false;
} }
/** /**
@ -246,8 +259,11 @@ final class PersistentCollection implements Collection
*/ */
public function getDeleteDiff() public function getDeleteDiff()
{ {
return array_udiff_assoc($this->snapshot, $this->coll->toArray(), return array_udiff_assoc(
function($a, $b) {return $a === $b ? 0 : 1;}); $this->snapshot,
$this->coll->toArray(),
function($a, $b) { return $a === $b ? 0 : 1; }
);
} }
/** /**
@ -258,8 +274,11 @@ final class PersistentCollection implements Collection
*/ */
public function getInsertDiff() public function getInsertDiff()
{ {
return array_udiff_assoc($this->coll->toArray(), $this->snapshot, return array_udiff_assoc(
function($a, $b) {return $a === $b ? 0 : 1;}); $this->coll->toArray(),
$this->snapshot,
function($a, $b) { return $a === $b ? 0 : 1; }
);
} }
/** /**
@ -277,12 +296,17 @@ final class PersistentCollection implements Collection
*/ */
private function changed() private function changed()
{ {
if ( ! $this->isDirty) { if ($this->isDirty) {
$this->isDirty = true; return;
if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] == ClassMetadata::MANY_TO_MANY && }
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
$this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner); $this->isDirty = true;
}
if ($this->association !== null &&
$this->association['isOwningSide'] &&
$this->association['type'] === ClassMetadata::MANY_TO_MANY &&
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
$this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
} }
} }
@ -331,6 +355,7 @@ final class PersistentCollection implements Collection
public function first() public function first()
{ {
$this->initialize(); $this->initialize();
return $this->coll->first(); return $this->coll->first();
} }
@ -338,6 +363,7 @@ final class PersistentCollection implements Collection
public function last() public function last()
{ {
$this->initialize(); $this->initialize();
return $this->coll->last(); return $this->coll->last();
} }
@ -351,13 +377,19 @@ final class PersistentCollection implements Collection
// not used we can issue a straight SQL delete/update on the // not used we can issue a straight SQL delete/update on the
// association (table). Without initializing the collection. // association (table). Without initializing the collection.
$this->initialize(); $this->initialize();
$removed = $this->coll->remove($key); $removed = $this->coll->remove($key);
if ($removed) {
$this->changed(); if ( ! $removed) {
if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY && return $removed;
$this->association['orphanRemoval']) { }
$this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
} $this->changed();
if ($this->association !== null &&
$this->association['type'] == ClassMetadata::ONE_TO_MANY &&
$this->association['orphanRemoval']) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
} }
return $removed; return $removed;
@ -368,25 +400,36 @@ final class PersistentCollection implements Collection
*/ */
public function removeElement($element) public function removeElement($element)
{ {
// TODO: Assuming the identity of entities in a collection is always based if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
// on their primary key (there is no equals/hashCode in PHP), if ($this->coll->contains($element)) {
// if the collection is not initialized, we could issue a straight return $this->coll->removeElement($element);
// SQL DELETE/UPDATE on the association (table) without initializing }
// the collection.
/*if ( ! $this->initialized) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
$this->em->getUnitOfWork()->getCollectionPersister($this->association)
->deleteRows($this, $element); if ($persister->removeElement($this, $element)) {
}*/ return $element;
}
return null;
}
$this->initialize(); $this->initialize();
$removed = $this->coll->removeElement($element); $removed = $this->coll->removeElement($element);
if ($removed) {
$this->changed(); if ( ! $removed) {
if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY && return $removed;
$this->association['orphanRemoval']) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
}
} }
$this->changed();
if ($this->association !== null &&
$this->association['type'] === ClassMetadata::ONE_TO_MANY &&
$this->association['orphanRemoval']) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
}
return $removed; return $removed;
} }
@ -396,6 +439,7 @@ final class PersistentCollection implements Collection
public function containsKey($key) public function containsKey($key)
{ {
$this->initialize(); $this->initialize();
return $this->coll->containsKey($key); return $this->coll->containsKey($key);
} }
@ -404,14 +448,14 @@ final class PersistentCollection implements Collection
*/ */
public function contains($element) public function contains($element)
{ {
if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
return $this->coll->contains($element) || $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
$this->em->getUnitOfWork()
->getCollectionPersister($this->association) return $this->coll->contains($element) || $persister->contains($this, $element);
->contains($this, $element);
} }
$this->initialize(); $this->initialize();
return $this->coll->contains($element); return $this->coll->contains($element);
} }
@ -421,6 +465,7 @@ final class PersistentCollection implements Collection
public function exists(Closure $p) public function exists(Closure $p)
{ {
$this->initialize(); $this->initialize();
return $this->coll->exists($p); return $this->coll->exists($p);
} }
@ -430,6 +475,7 @@ final class PersistentCollection implements Collection
public function indexOf($element) public function indexOf($element)
{ {
$this->initialize(); $this->initialize();
return $this->coll->indexOf($element); return $this->coll->indexOf($element);
} }
@ -439,6 +485,7 @@ final class PersistentCollection implements Collection
public function get($key) public function get($key)
{ {
$this->initialize(); $this->initialize();
return $this->coll->get($key); return $this->coll->get($key);
} }
@ -448,6 +495,7 @@ final class PersistentCollection implements Collection
public function getKeys() public function getKeys()
{ {
$this->initialize(); $this->initialize();
return $this->coll->getKeys(); return $this->coll->getKeys();
} }
@ -457,6 +505,7 @@ final class PersistentCollection implements Collection
public function getValues() public function getValues()
{ {
$this->initialize(); $this->initialize();
return $this->coll->getValues(); return $this->coll->getValues();
} }
@ -465,13 +514,14 @@ final class PersistentCollection implements Collection
*/ */
public function count() public function count()
{ {
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() $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
->getCollectionPersister($this->association)
->count($this) + ($this->isDirty ? $this->coll->count() : 0); return $persister->count($this) + ($this->isDirty ? $this->coll->count() : 0);
} }
$this->initialize(); $this->initialize();
return $this->coll->count(); return $this->coll->count();
} }
@ -481,7 +531,9 @@ final class PersistentCollection implements Collection
public function set($key, $value) public function set($key, $value)
{ {
$this->initialize(); $this->initialize();
$this->coll->set($key, $value); $this->coll->set($key, $value);
$this->changed(); $this->changed();
} }
@ -491,7 +543,9 @@ final class PersistentCollection implements Collection
public function add($value) public function add($value)
{ {
$this->coll->add($value); $this->coll->add($value);
$this->changed(); $this->changed();
return true; return true;
} }
@ -501,6 +555,7 @@ final class PersistentCollection implements Collection
public function isEmpty() public function isEmpty()
{ {
$this->initialize(); $this->initialize();
return $this->coll->isEmpty(); return $this->coll->isEmpty();
} }
@ -510,6 +565,7 @@ final class PersistentCollection implements Collection
public function getIterator() public function getIterator()
{ {
$this->initialize(); $this->initialize();
return $this->coll->getIterator(); return $this->coll->getIterator();
} }
@ -519,6 +575,7 @@ final class PersistentCollection implements Collection
public function map(Closure $func) public function map(Closure $func)
{ {
$this->initialize(); $this->initialize();
return $this->coll->map($func); return $this->coll->map($func);
} }
@ -528,6 +585,7 @@ final class PersistentCollection implements Collection
public function filter(Closure $p) public function filter(Closure $p)
{ {
$this->initialize(); $this->initialize();
return $this->coll->filter($p); return $this->coll->filter($p);
} }
@ -537,6 +595,7 @@ final class PersistentCollection implements Collection
public function forAll(Closure $p) public function forAll(Closure $p)
{ {
$this->initialize(); $this->initialize();
return $this->coll->forAll($p); return $this->coll->forAll($p);
} }
@ -546,6 +605,7 @@ final class PersistentCollection implements Collection
public function partition(Closure $p) public function partition(Closure $p)
{ {
$this->initialize(); $this->initialize();
return $this->coll->partition($p); return $this->coll->partition($p);
} }
@ -555,6 +615,7 @@ final class PersistentCollection implements Collection
public function toArray() public function toArray()
{ {
$this->initialize(); $this->initialize();
return $this->coll->toArray(); return $this->coll->toArray();
} }
@ -566,19 +627,28 @@ final class PersistentCollection implements Collection
if ($this->initialized && $this->isEmpty()) { if ($this->initialized && $this->isEmpty()) {
return; return;
} }
if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
$uow = $this->em->getUnitOfWork();
if ($this->association['type'] === ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
// we need to initialize here, as orphan removal acts like implicit cascadeRemove, // we need to initialize here, as orphan removal acts like implicit cascadeRemove,
// hence for event listeners we need the objects in memory. // hence for event listeners we need the objects in memory.
$this->initialize(); $this->initialize();
foreach ($this->coll as $element) { foreach ($this->coll as $element) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element); $uow->scheduleOrphanRemoval($element);
} }
} }
$this->coll->clear(); $this->coll->clear();
$this->initialized = true; // direct call, {@link initialize()} is too expensive $this->initialized = true; // direct call, {@link initialize()} is too expensive
if ($this->association['isOwningSide']) { if ($this->association['isOwningSide']) {
$this->changed(); $this->changed();
$this->em->getUnitOfWork()->scheduleCollectionDeletion($this);
$uow->scheduleCollectionDeletion($this);
$this->takeSnapshot(); $this->takeSnapshot();
} }
} }
@ -622,6 +692,7 @@ final class PersistentCollection implements Collection
if ( ! isset($offset)) { if ( ! isset($offset)) {
return $this->add($value); return $this->add($value);
} }
return $this->set($offset, $value); return $this->set($offset, $value);
} }
@ -656,6 +727,8 @@ final class PersistentCollection implements Collection
/** /**
* Retrieves the wrapped Collection instance. * Retrieves the wrapped Collection instance.
*
* @return Doctrine\Common\Collections\Collection
*/ */
public function unwrap() public function unwrap()
{ {
@ -671,20 +744,19 @@ final class PersistentCollection implements Collection
* *
* @param int $offset * @param int $offset
* @param int $length * @param int $length
*
* @return array * @return array
*/ */
public function slice($offset, $length = null) public function slice($offset, $length = null)
{ {
if ( ! $this->initialized && if ( ! $this->initialized && ! $this->isDirty && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
! $this->isDirty && $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
$this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
return $this->em->getUnitOfWork() return $persister->slice($this, $offset, $length);
->getCollectionPersister($this->association)
->slice($this, $offset, $length);
} }
$this->initialize(); $this->initialize();
return $this->coll->slice($offset, $length); return $this->coll->slice($offset, $length);
} }
} }

View File

@ -151,6 +151,16 @@ abstract class AbstractCollectionPersister
throw new \BadMethodCallException("Checking for existance of a key is not supported by this CollectionPersister."); throw new \BadMethodCallException("Checking for existance of a key is not supported by this CollectionPersister.");
} }
public function removeElement(PersistentCollection $coll, $element)
{
throw new \BadMethodCallException("Removing an element is not supported by this CollectionPersister.");
}
public function removeKey(PersistentCollection $coll, $key)
{
throw new \BadMethodCallException("Removing a key is not supported by this CollectionPersister.");
}
public function get(PersistentCollection $coll, $index) public function get(PersistentCollection $coll, $index)
{ {
throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister."); throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister.");

View File

@ -28,8 +28,9 @@ use Doctrine\ORM\Mapping\ClassMetadata,
/** /**
* Persister for many-to-many collections. * Persister for many-to-many collections.
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.0
*/ */
class ManyToManyPersister extends AbstractCollectionPersister class ManyToManyPersister extends AbstractCollectionPersister
{ {
@ -80,8 +81,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
$columns = $mapping['joinTableColumns']; $columns = $mapping['joinTableColumns'];
$class = $this->_em->getClassMetadata(get_class($coll->getOwner())); $class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
return 'INSERT INTO ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) $joinTable = $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform());
. ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
return 'INSERT INTO ' . $joinTable . ' (' . implode(', ', $columns) . ')'
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
} }
/** /**
@ -119,27 +122,21 @@ class ManyToManyPersister extends AbstractCollectionPersister
} }
foreach ($mapping['joinTableColumns'] as $joinTableColumn) { foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) { $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]);
if ($isComposite) {
if ($class1->containsForeignIdentifier) { if ( ! $isComposite) {
$params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2);
} else {
$params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; continue;
}
} else {
$params[] = array_pop($identifier1);
}
} else {
if ($isComposite) {
if ($class2->containsForeignIdentifier) {
$params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
} else {
$params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
}
} else {
$params[] = array_pop($identifier2);
}
} }
if ($isRelationToSource) {
$params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
continue;
}
$params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
} }
return $params; return $params;
@ -152,19 +149,11 @@ class ManyToManyPersister extends AbstractCollectionPersister
*/ */
protected function _getDeleteSQL(PersistentCollection $coll) protected function _getDeleteSQL(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
$class = $this->_em->getClassMetadata(get_class($coll->getOwner())); $mapping = $coll->getMapping();
$joinTable = $mapping['joinTable'];
$whereClause = '';
foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) {
if ($whereClause !== '') $whereClause .= ' AND ';
$whereClause .= $relationColumn . ' = ?';
}
return 'DELETE FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) return 'DELETE FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' WHERE ' . $whereClause; . ' WHERE ' . implode(' = ? AND ', array_keys($mapping['relationToSourceKeyColumns'])) . ' = ?';
} }
/** /**
@ -176,18 +165,22 @@ class ManyToManyPersister extends AbstractCollectionPersister
*/ */
protected function _getDeleteSQLParameters(PersistentCollection $coll) protected function _getDeleteSQLParameters(PersistentCollection $coll)
{ {
$params = array();
$mapping = $coll->getMapping();
$identifier = $this->_uow->getEntityIdentifier($coll->getOwner()); $identifier = $this->_uow->getEntityIdentifier($coll->getOwner());
$mapping = $coll->getMapping();
$params = array();
if (count($mapping['relationToSourceKeyColumns']) > 1) { // Optimization for single column identifier
$sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner())); if (count($mapping['relationToSourceKeyColumns']) === 1) {
$params[] = array_pop($identifier);
foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) { return $params;
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]]; }
}
} else { // Composite identifier
$params[] = array_pop($identifier); $sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
} }
return $params; return $params;
@ -198,7 +191,6 @@ class ManyToManyPersister extends AbstractCollectionPersister
*/ */
public function count(PersistentCollection $coll) public function count(PersistentCollection $coll)
{ {
$params = array();
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$class = $this->_em->getClassMetadata($mapping['sourceEntity']); $class = $this->_em->getClassMetadata($mapping['sourceEntity']);
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
@ -210,28 +202,30 @@ class ManyToManyPersister extends AbstractCollectionPersister
$joinColumns = $mapping['relationToTargetKeyColumns']; $joinColumns = $mapping['relationToTargetKeyColumns'];
} }
$whereClause = ''; $whereClauses = array();
$params = array();
foreach ($mapping['joinTableColumns'] as $joinTableColumn) { foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($joinColumns[$joinTableColumn])) { if ( ! isset($joinColumns[$joinTableColumn])) {
if ($whereClause !== '') { continue;
$whereClause .= ' AND ';
}
$whereClause .= "t.$joinTableColumn = ?";
$params[] = ($class->containsForeignIdentifier)
? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])]
: $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
} }
$whereClauses[] = $joinTableColumn . ' = ?';
$params[] = ($class->containsForeignIdentifier)
? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])]
: $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
} }
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping); list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
if('' !== $filterSql) {
$whereClauses[] = $filterSql;
}
$sql = 'SELECT COUNT(*)' $sql = 'SELECT COUNT(*)'
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) . ' t' . ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) . ' t'
. $joinTargetEntitySQL . $joinTargetEntitySQL
. ' WHERE ' . $whereClause . ' WHERE ' . implode(' AND ', $whereClauses);
. $filterSql;
return $this->_conn->fetchColumn($sql, $params); return $this->_conn->fetchColumn($sql, $params);
} }
@ -252,6 +246,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
/** /**
* @param PersistentCollection $coll * @param PersistentCollection $coll
* @param object $element * @param object $element
* @return boolean
*/ */
public function contains(PersistentCollection $coll, $element) public function contains(PersistentCollection $coll, $element)
{ {
@ -262,7 +257,43 @@ class ManyToManyPersister extends AbstractCollectionPersister
return false; return false;
} }
$params = array(); list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, true);
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->_conn->fetchColumn($sql, $params);
}
/**
* @param PersistentCollection $coll
* @param object $element
* @return boolean
*/
public function removeElement(PersistentCollection $coll, $element)
{
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
return false;
}
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, false);
$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->_conn->executeUpdate($sql, $params);
}
/**
* @param Doctrine\ORM\PersistentCollection $coll
* @param object $element
* @param boolean $addFilters Whether the filter SQL should be included or not.
* @return array
*/
private function getJoinTableRestrictions(PersistentCollection $coll, $element, $addFilters)
{
$uow = $this->_em->getUnitOfWork();
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
if ( ! $mapping['isOwningSide']) { if ( ! $mapping['isOwningSide']) {
@ -279,41 +310,35 @@ class ManyToManyPersister extends AbstractCollectionPersister
$targetId = $uow->getEntityIdentifier($element); $targetId = $uow->getEntityIdentifier($element);
} }
$whereClause = ''; $quotedJoinTable = $sourceClass->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform());
$whereClauses = array();
$params = array();
foreach ($mapping['joinTableColumns'] as $joinTableColumn) { foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
$whereClauses[] = $joinTableColumn . ' = ?';
if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
if ($whereClause !== '') {
$whereClause .= ' AND ';
}
$whereClause .= $joinTableColumn . ' = ?';
$params[] = ($targetClass->containsForeignIdentifier) $params[] = ($targetClass->containsForeignIdentifier)
? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])] ? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]
: $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; : $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
} else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) { continue;
if ($whereClause !== '') { }
$whereClause .= ' AND ';
}
$whereClause .= $joinTableColumn . ' = ?'; // relationToSourceKeyColumns
$params[] = ($sourceClass->containsForeignIdentifier)
? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
}
$params[] = ($sourceClass->containsForeignIdentifier) if($addFilters) {
? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])] list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; if('' !== $filterSql) {
$quotedJoinTable .= ' t ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
} }
} }
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping); return array($quotedJoinTable, $whereClauses, $params);
$sql = 'SELECT 1'
. ' FROM ' . $sourceClass->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) . ' t'
. $joinTargetEntitySQL
. ' WHERE ' . $whereClause
. $filterSql;
return (bool) $this->_conn->fetchColumn($sql, $params);
} }
/** /**
@ -356,9 +381,11 @@ class ManyToManyPersister extends AbstractCollectionPersister
{ {
$filterSql = ''; $filterSql = '';
$first = true;
foreach($this->_em->getFilters()->getEnabledFilters() as $filter) { foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { if('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
$filterSql .= 'AND (' . $filterExpr . ')'; if($first) $first = false; else $filterSql .= ' AND ';
$filterSql .= '(' . $filterExpr . ')';
} }
} }

View File

@ -27,13 +27,9 @@ use Doctrine\ORM\PersistentCollection,
/** /**
* Persister for one-to-many collections. * Persister for one-to-many collections.
* *
* IMPORTANT: * @author Roman Borschel <roman@code-factory.org>
* This persister is only used for uni-directional one-to-many mappings on a foreign key * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* (which are not yet supported). So currently this persister is not used. * @since 2.0
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @todo Remove
*/ */
class OneToManyPersister extends AbstractCollectionPersister class OneToManyPersister extends AbstractCollectionPersister
{ {
@ -48,24 +44,19 @@ class OneToManyPersister extends AbstractCollectionPersister
protected function _getDeleteRowSQL(PersistentCollection $coll) protected function _getDeleteRowSQL(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$targetClass = $this->_em->getClassMetadata($mapping->getTargetEntityName()); $class = $this->_em->getClassMetadata($mapping['targetEntity']);
$table = $targetClass->getTableName();
$ownerMapping = $targetClass->getAssociationMapping($mapping['mappedBy']); return 'DELETE FROM ' . $class->getQuotedTableName($this->_conn->getDatabasePlatform())
. ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?';
}
$setClause = ''; /**
foreach ($ownerMapping->sourceToTargetKeyColumns as $sourceCol => $targetCol) { * {@inheritdoc}
if ($setClause != '') $setClause .= ', '; *
$setClause .= "$sourceCol = NULL"; */
} protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element)
{
$whereClause = ''; return array_values($this->_uow->getEntityIdentifier($element));
foreach ($targetClass->getIdentifierColumnNames() as $idColumn) {
if ($whereClause != '') $whereClause .= ' AND ';
$whereClause .= "$idColumn = ?";
}
return array("UPDATE $table SET $setClause WHERE $whereClause", $this->_uow->getEntityIdentifier($element));
} }
protected function _getInsertRowSQL(PersistentCollection $coll) protected function _getInsertRowSQL(PersistentCollection $coll)
@ -73,6 +64,16 @@ class OneToManyPersister extends AbstractCollectionPersister
return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz"; return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz";
} }
/**
* Gets the SQL parameters for the corresponding SQL statement to insert the given
* element of the given collection into the database.
*
* @param PersistentCollection $coll
* @param mixed $element
*/
protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element)
{}
/* Not used for OneToManyPersister */ /* Not used for OneToManyPersister */
protected function _getUpdateRowSQL(PersistentCollection $coll) protected function _getUpdateRowSQL(PersistentCollection $coll)
{ {
@ -98,60 +99,37 @@ class OneToManyPersister extends AbstractCollectionPersister
protected function _getDeleteSQLParameters(PersistentCollection $coll) protected function _getDeleteSQLParameters(PersistentCollection $coll)
{} {}
/**
* Gets the SQL parameters for the corresponding SQL statement to insert the given
* element of the given collection into the database.
*
* @param PersistentCollection $coll
* @param mixed $element
*/
protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element)
{}
/**
* Gets the SQL parameters for the corresponding SQL statement to delete the given
* element from the given collection.
*
* @param PersistentCollection $coll
* @param mixed $element
*/
protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element)
{}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function count(PersistentCollection $coll) public function count(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']); $targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
$sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']); $sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
$params = array(); $whereClauses = array();
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $params = array();
$where = '';
foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) { foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) {
if ($where != '') { $whereClauses[] = $joinColumn['name'] . ' = ?';
$where .= ' AND ';
} $params[] = ($targetClass->containsForeignIdentifier)
$where .= "t." . $joinColumn['name'] . " = ?"; ? $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])]
if ($targetClass->containsForeignIdentifier) { : $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
$params[] = $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
} else {
$params[] = $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
}
} }
$sql = "SELECT count(*) FROM " . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . " t WHERE " . $where;
// Apply the filters
foreach($this->_em->getFilters()->getEnabledFilters() as $filter) { foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if("" !== $filterExpr = $filter->addFilterConstraint($targetClass, 't')) { if("" !== $filterExpr = $filter->addFilterConstraint($targetClass, 't')) {
$sql .= ' AND (' . $filterExpr . ')'; $whereClauses[] = '(' . $filterExpr . ')';
} }
} }
$sql = 'SELECT count(*)'
. ' FROM ' . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . ' t'
. ' WHERE ' . implode(' AND ', $whereClauses);
return $this->_conn->fetchColumn($sql, $params); return $this->_conn->fetchColumn($sql, $params);
} }
@ -163,19 +141,45 @@ class OneToManyPersister extends AbstractCollectionPersister
*/ */
public function slice(PersistentCollection $coll, $offset, $length = null) public function slice(PersistentCollection $coll, $offset, $length = null)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
return $this->_em->getUnitOfWork() $uow = $this->_em->getUnitOfWork();
->getEntityPersister($mapping['targetEntity']) $persister = $uow->getEntityPersister($mapping['targetEntity']);
->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length);
return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length);
} }
/** /**
* @param PersistentCollection $coll * @param PersistentCollection $coll
* @param object $element * @param object $element
* @return boolean
*/ */
public function contains(PersistentCollection $coll, $element) public function contains(PersistentCollection $coll, $element)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$uow = $this->_em->getUnitOfWork();
// shortcut for new entities
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
return false;
}
$persister = $uow->getEntityPersister($mapping['targetEntity']);
// only works with single id identifier entities. Will throw an
// exception in Entity Persisters if that is not the case for the
// 'mappedBy' field.
$id = current( $uow->getEntityIdentifier($coll->getOwner()) );
return $persister->exists($element, array($mapping['mappedBy'] => $id));
}
/**
* @param PersistentCollection $coll
* @param object $element
* @return boolean
*/
public function removeElement(PersistentCollection $coll, $element)
{
$uow = $this->_em->getUnitOfWork(); $uow = $this->_em->getUnitOfWork();
// shortcut for new entities // shortcut for new entities
@ -183,11 +187,11 @@ class OneToManyPersister extends AbstractCollectionPersister
return false; return false;
} }
// only works with single id identifier entities. Will throw an exception in Entity Persisters $mapping = $coll->getMapping();
// if that is not the case for the 'mappedBy' field. $class = $this->_em->getClassMetadata($mapping['targetEntity']);
$id = current( $uow->getEntityIdentifier($coll->getOwner()) ); $sql = 'DELETE FROM ' . $class->getQuotedTableName($this->_conn->getDatabasePlatform())
. ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?';
return $uow->getEntityPersister($mapping['targetEntity']) return (bool) $this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element));
->exists($element, array($mapping['mappedBy'] => $id));
} }
} }

View File

@ -205,26 +205,30 @@ final class Query extends AbstractQuery
return $this->_parserResult; return $this->_parserResult;
} }
// Check query cache. $this->_state = self::STATE_CLEAN;
if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) {
$hash = $this->_getQueryCacheId();
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
if ($cached === false) { // Check query cache.
// Cache miss. if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) {
$parser = new Parser($this);
$this->_parserResult = $parser->parse();
$queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
} else {
// Cache hit.
$this->_parserResult = $cached;
}
} else {
$parser = new Parser($this); $parser = new Parser($this);
$this->_parserResult = $parser->parse(); $this->_parserResult = $parser->parse();
return $this->_parserResult;
} }
$this->_state = self::STATE_CLEAN; $hash = $this->_getQueryCacheId();
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
if ($cached !== false) {
// Cache hit.
$this->_parserResult = $cached;
return $this->_parserResult;
}
// Cache miss.
$parser = new Parser($this);
$this->_parserResult = $parser->parse();
$queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
return $this->_parserResult; return $this->_parserResult;
} }
@ -235,6 +239,7 @@ final class Query extends AbstractQuery
protected function _doExecute() protected function _doExecute()
{ {
$executor = $this->_parse()->getSqlExecutor(); $executor = $this->_parse()->getSqlExecutor();
if ($this->_queryCacheProfile) { if ($this->_queryCacheProfile) {
$executor->setQueryCacheProfile($this->_queryCacheProfile); $executor->setQueryCacheProfile($this->_queryCacheProfile);
} }
@ -342,6 +347,7 @@ final class Query extends AbstractQuery
public function setQueryCacheDriver($queryCache) public function setQueryCacheDriver($queryCache)
{ {
$this->_queryCache = $queryCache; $this->_queryCache = $queryCache;
return $this; return $this;
} }
@ -354,6 +360,7 @@ final class Query extends AbstractQuery
public function useQueryCache($bool) public function useQueryCache($bool)
{ {
$this->_useQueryCache = $bool; $this->_useQueryCache = $bool;
return $this; return $this;
} }
@ -367,9 +374,9 @@ final class Query extends AbstractQuery
{ {
if ($this->_queryCache) { if ($this->_queryCache) {
return $this->_queryCache; return $this->_queryCache;
} else {
return $this->_em->getConfiguration()->getQueryCacheImpl();
} }
return $this->_em->getConfiguration()->getQueryCacheImpl();
} }
/** /**
@ -383,6 +390,7 @@ final class Query extends AbstractQuery
if ($timeToLive !== null) { if ($timeToLive !== null) {
$timeToLive = (int) $timeToLive; $timeToLive = (int) $timeToLive;
} }
$this->_queryCacheTTL = $timeToLive; $this->_queryCacheTTL = $timeToLive;
return $this; return $this;
@ -427,6 +435,7 @@ final class Query extends AbstractQuery
public function free() public function free()
{ {
parent::free(); parent::free();
$this->_dql = null; $this->_dql = null;
$this->_state = self::STATE_CLEAN; $this->_state = self::STATE_CLEAN;
} }
@ -443,6 +452,7 @@ final class Query extends AbstractQuery
$this->_dql = $dqlQuery; $this->_dql = $dqlQuery;
$this->_state = self::STATE_DIRTY; $this->_state = self::STATE_DIRTY;
} }
return $this; return $this;
} }
@ -492,6 +502,7 @@ final class Query extends AbstractQuery
{ {
$this->_firstResult = $firstResult; $this->_firstResult = $firstResult;
$this->_state = self::STATE_DIRTY; $this->_state = self::STATE_DIRTY;
return $this; return $this;
} }
@ -516,6 +527,7 @@ final class Query extends AbstractQuery
{ {
$this->_maxResults = $maxResults; $this->_maxResults = $maxResults;
$this->_state = self::STATE_DIRTY; $this->_state = self::STATE_DIRTY;
return $this; return $this;
} }
@ -541,6 +553,7 @@ final class Query extends AbstractQuery
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
{ {
$this->setHint(self::HINT_INTERNAL_ITERATION, true); $this->setHint(self::HINT_INTERNAL_ITERATION, true);
return parent::iterate($params, $hydrationMode); return parent::iterate($params, $hydrationMode);
} }
@ -550,6 +563,7 @@ final class Query extends AbstractQuery
public function setHint($name, $value) public function setHint($name, $value)
{ {
$this->_state = self::STATE_DIRTY; $this->_state = self::STATE_DIRTY;
return parent::setHint($name, $value); return parent::setHint($name, $value);
} }
@ -559,6 +573,7 @@ final class Query extends AbstractQuery
public function setHydrationMode($hydrationMode) public function setHydrationMode($hydrationMode)
{ {
$this->_state = self::STATE_DIRTY; $this->_state = self::STATE_DIRTY;
return parent::setHydrationMode($hydrationMode); return parent::setHydrationMode($hydrationMode);
} }
@ -571,13 +586,14 @@ final class Query extends AbstractQuery
*/ */
public function setLockMode($lockMode) public function setLockMode($lockMode)
{ {
if ($lockMode == LockMode::PESSIMISTIC_READ || $lockMode == LockMode::PESSIMISTIC_WRITE) { if ($lockMode === LockMode::PESSIMISTIC_READ || $lockMode === LockMode::PESSIMISTIC_WRITE) {
if (!$this->_em->getConnection()->isTransactionActive()) { if ( ! $this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired(); throw TransactionRequiredException::transactionRequired();
} }
} }
$this->setHint(self::HINT_LOCK_MODE, $lockMode); $this->setHint(self::HINT_LOCK_MODE, $lockMode);
return $this; return $this;
} }
@ -589,9 +605,11 @@ final class Query extends AbstractQuery
public function getLockMode() public function getLockMode()
{ {
$lockMode = $this->getHint(self::HINT_LOCK_MODE); $lockMode = $this->getHint(self::HINT_LOCK_MODE);
if (!$lockMode) {
if ( ! $lockMode) {
return LockMode::NONE; return LockMode::NONE;
} }
return $lockMode; return $lockMode;
} }
@ -622,6 +640,7 @@ final class Query extends AbstractQuery
public function __clone() public function __clone()
{ {
parent::__clone(); parent::__clone();
$this->_state = self::STATE_DIRTY; $this->_state = self::STATE_DIRTY;
} }
} }

View File

@ -35,13 +35,13 @@ namespace Doctrine\ORM\Query\AST;
class InExpression extends Node class InExpression extends Node
{ {
public $not; public $not;
public $pathExpression; public $expression;
public $literals = array(); public $literals = array();
public $subselect; public $subselect;
public function __construct($pathExpression) public function __construct($expression)
{ {
$this->pathExpression = $pathExpression; $this->expression = $expression;
} }
public function dispatch($sqlWalker) public function dispatch($sqlWalker)

View File

@ -149,50 +149,58 @@ class Lexer extends \Doctrine\Common\Lexer
{ {
$type = self::T_NONE; $type = self::T_NONE;
// Recognizing numeric values switch (true) {
if (is_numeric($value)) { // Recognize numeric values
return (strpos($value, '.') !== false || stripos($value, 'e') !== false) case (is_numeric($value)):
? self::T_FLOAT : self::T_INTEGER; if (strpos($value, '.') !== false || stripos($value, 'e') !== false) {
} return self::T_FLOAT;
// Differentiate between quoted names, identifiers, input parameters and symbols
if ($value[0] === "'") {
$value = str_replace("''", "'", substr($value, 1, strlen($value) - 2));
return self::T_STRING;
} else if (ctype_alpha($value[0]) || $value[0] === '_') {
$name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value);
if (defined($name)) {
$type = constant($name);
if ($type > 100) {
return $type;
} }
}
return self::T_IDENTIFIER; return self::T_INTEGER;
} else if ($value[0] === '?' || $value[0] === ':') {
return self::T_INPUT_PARAMETER; // Recognize quoted strings
} else { case ($value[0] === "'"):
switch ($value) { $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2));
case '.': return self::T_DOT;
case ',': return self::T_COMMA; return self::T_STRING;
case '(': return self::T_OPEN_PARENTHESIS;
case ')': return self::T_CLOSE_PARENTHESIS; // Recognize identifiers
case '=': return self::T_EQUALS; case (ctype_alpha($value[0]) || $value[0] === '_'):
case '>': return self::T_GREATER_THAN; $name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value);
case '<': return self::T_LOWER_THAN;
case '+': return self::T_PLUS; if (defined($name)) {
case '-': return self::T_MINUS; $type = constant($name);
case '*': return self::T_MULTIPLY;
case '/': return self::T_DIVIDE; if ($type > 100) {
case '!': return self::T_NEGATE; return $type;
case '{': return self::T_OPEN_CURLY_BRACE; }
case '}': return self::T_CLOSE_CURLY_BRACE; }
default:
// Do nothing return self::T_IDENTIFIER;
break;
} // Recognize input parameters
case ($value[0] === '?' || $value[0] === ':'):
return self::T_INPUT_PARAMETER;
// Recognize symbols
case ($value === '.'): return self::T_DOT;
case ($value === ','): return self::T_COMMA;
case ($value === '('): return self::T_OPEN_PARENTHESIS;
case ($value === ')'): return self::T_CLOSE_PARENTHESIS;
case ($value === '='): return self::T_EQUALS;
case ($value === '>'): return self::T_GREATER_THAN;
case ($value === '<'): return self::T_LOWER_THAN;
case ($value === '+'): return self::T_PLUS;
case ($value === '-'): return self::T_MINUS;
case ($value === '*'): return self::T_MULTIPLY;
case ($value === '/'): return self::T_DIVIDE;
case ($value === '!'): return self::T_NEGATE;
case ($value === '{'): return self::T_OPEN_CURLY_BRACE;
case ($value === '}'): return self::T_CLOSE_CURLY_BRACE;
// Default
default:
// Do nothing
} }
return $type; return $type;

View File

@ -2,7 +2,7 @@
/* /*
* 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 MERCHARNTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
@ -677,13 +677,10 @@ class Parser
} }
// Build the error message // Build the error message
$semanticalError = 'Invalid PathExpression. '; $semanticalError = 'Invalid PathExpression. ';
$semanticalError .= (count($expectedStringTypes) == 1)
if (count($expectedStringTypes) == 1) { ? 'Must be a ' . $expectedStringTypes[0] . '.'
$semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.'; : implode(' or ', $expectedStringTypes) . ' expected.';
} else {
$semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
}
$this->semanticalError($semanticalError, $deferredItem['token']); $this->semanticalError($semanticalError, $deferredItem['token']);
} }
@ -1308,7 +1305,7 @@ class Parser
} }
/** /**
* GroupByItem ::= IdentificationVariable | SingleValuedPathExpression * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
* *
* @return string | \Doctrine\ORM\Query\AST\PathExpression * @return string | \Doctrine\ORM\Query\AST\PathExpression
*/ */
@ -1317,18 +1314,20 @@ class Parser
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
$glimpse = $this->_lexer->glimpse(); $glimpse = $this->_lexer->glimpse();
if ($glimpse['type'] == Lexer::T_DOT) { if ($glimpse['type'] === Lexer::T_DOT) {
return $this->SingleValuedPathExpression(); return $this->SingleValuedPathExpression();
} }
$token = $this->_lexer->lookahead; // Still need to decide between IdentificationVariable or ResultVariable
$identVariable = $this->IdentificationVariable(); $lookaheadValue = $this->_lexer->lookahead['value'];
if ( ! isset($this->_queryComponents[$identVariable])) { if ( ! isset($this->_queryComponents[$lookaheadValue])) {
$this->semanticalError('Cannot group by undefined identification variable.'); $this->semanticalError('Cannot group by undefined identification or result variable.');
} }
return $identVariable; return (isset($this->_queryComponents[$lookaheadValue]['metadata']))
? $this->IdentificationVariable()
: $this->ResultVariable();
} }
/** /**
@ -1866,53 +1865,78 @@ class Parser
$expression = null; $expression = null;
$identVariable = null; $identVariable = null;
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
$lookaheadType = $this->_lexer->lookahead['type'];
if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT) { switch (true) {
// ScalarExpression (u.name) // ScalarExpression (u.name)
$expression = $this->ScalarExpression(); case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
} else if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS) {
// IdentificationVariable (u)
$expression = $identVariable = $this->IdentificationVariable();
} else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) {
// CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
$expression = $this->CaseExpression();
} else if ($this->_isFunction()) {
// DQL Function (SUM(u.value) or SUM(u.value) + 1)
$this->_lexer->peek(); // "("
$lookaheadType = $this->_lexer->lookahead['type'];
$beyond = $this->_peekBeyondClosingParenthesis();
if ($this->_isMathOperator($beyond)) {
// SUM(u.id) + COUNT(u.id)
$expression = $this->ScalarExpression(); $expression = $this->ScalarExpression();
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { break;
// COUNT(u.id)
$expression = $this->AggregateExpression(); // IdentificationVariable (u)
} else { case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
// SUM(u.id) $expression = $identVariable = $this->IdentificationVariable();
$expression = $this->FunctionDeclaration(); break;
}
} else if ($this->_lexer->lookahead['type'] === Lexer::T_PARTIAL) { // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
case ($lookaheadType === Lexer::T_CASE):
case ($lookaheadType === Lexer::T_COALESCE):
case ($lookaheadType === Lexer::T_NULLIF):
$expression = $this->CaseExpression();
break;
// DQL Function (SUM(u.value) or SUM(u.value) + 1)
case ($this->_isFunction()):
$this->_lexer->peek(); // "("
switch (true) {
case ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())):
// SUM(u.id) + COUNT(u.id)
$expression = $this->ScalarExpression();
break;
case ($this->_isAggregateFunction($lookaheadType)):
// COUNT(u.id)
$expression = $this->AggregateExpression();
break;
default:
// IDENTITY(u)
$expression = $this->FunctionDeclaration();
break;
}
break;
// PartialObjectExpression (PARTIAL u.{id, name}) // PartialObjectExpression (PARTIAL u.{id, name})
$expression = $this->PartialObjectExpression(); case ($lookaheadType === Lexer::T_PARTIAL):
$identVariable = $expression->identificationVariable; $expression = $this->PartialObjectExpression();
} else if ($this->_lexer->lookahead['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) { $identVariable = $expression->identificationVariable;
break;
// Subselect // Subselect
$this->match(Lexer::T_OPEN_PARENTHESIS); case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
$expression = $this->Subselect(); $this->match(Lexer::T_OPEN_PARENTHESIS);
$this->match(Lexer::T_CLOSE_PARENTHESIS); $expression = $this->Subselect();
} else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_OPEN_PARENTHESIS, Lexer::T_INTEGER, Lexer::T_FLOAT, Lexer::T_STRING))) { $this->match(Lexer::T_CLOSE_PARENTHESIS);
break;
// Shortcut: ScalarExpression => SimpleArithmeticExpression // Shortcut: ScalarExpression => SimpleArithmeticExpression
$expression = $this->SimpleArithmeticExpression(); case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
} else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_PLUS, Lexer::T_MINUS))) { case ($lookaheadType === Lexer::T_INTEGER):
// SimpleArithmeticExpression : (- u.value ) or ( + u.value ) case ($lookaheadType === Lexer::T_STRING):
$expression = $this->SimpleArithmeticExpression(); case ($lookaheadType === Lexer::T_FLOAT):
} else { // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
$this->syntaxError( case ($lookaheadType === Lexer::T_MINUS):
'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', case ($lookaheadType === Lexer::T_PLUS):
$this->_lexer->lookahead $expression = $this->SimpleArithmeticExpression();
); break;
default:
$this->syntaxError(
'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
$this->_lexer->lookahead
);
} }
// [["AS"] ["HIDDEN"] AliasResultVariable] // [["AS"] ["HIDDEN"] AliasResultVariable]
@ -1965,25 +1989,41 @@ class Parser
{ {
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { switch ($this->_lexer->lookahead['type']) {
// SingleValuedPathExpression | IdentificationVariable case Lexer::T_IDENTIFIER:
$expression = ($peek['value'] == '.') switch (true) {
? $this->StateFieldPathExpression() case ($peek['type'] === Lexer::T_DOT):
: $this->IdentificationVariable(); $expression = $this->StateFieldPathExpression();
return new AST\SimpleSelectExpression($expression);
case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
$expression = $this->IdentificationVariable();
return new AST\SimpleSelectExpression($expression);
default:
// Do nothing
}
break;
case Lexer::T_OPEN_PARENTHESIS:
if ($peek['type'] !== Lexer::T_SELECT) {
// Shortcut: ScalarExpression => SimpleArithmeticExpression
$expression = $this->SimpleArithmeticExpression();
return new AST\SimpleSelectExpression($expression);
}
return new AST\SimpleSelectExpression($expression);
} else if ($this->_lexer->lookahead['value'] == '(') {
if ($peek['type'] == Lexer::T_SELECT) {
// Subselect // Subselect
$this->match(Lexer::T_OPEN_PARENTHESIS); $this->match(Lexer::T_OPEN_PARENTHESIS);
$expression = $this->Subselect(); $expression = $this->Subselect();
$this->match(Lexer::T_CLOSE_PARENTHESIS); $this->match(Lexer::T_CLOSE_PARENTHESIS);
} else {
// Shortcut: ScalarExpression => SimpleArithmeticExpression
$expression = $this->SimpleArithmeticExpression();
}
return new AST\SimpleSelectExpression($expression); return new AST\SimpleSelectExpression($expression);
default:
// Do nothing
} }
$this->_lexer->peek(); $this->_lexer->peek();
@ -2099,27 +2139,26 @@ class Parser
{ {
$condPrimary = new AST\ConditionalPrimary; $condPrimary = new AST\ConditionalPrimary;
if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { if ( ! $this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
// Peek beyond the matching closing paranthesis ')'
$peek = $this->_peekBeyondClosingParenthesis();
if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) ||
$peek['type'] === Lexer::T_NOT ||
$peek['type'] === Lexer::T_BETWEEN ||
$peek['type'] === Lexer::T_LIKE ||
$peek['type'] === Lexer::T_IN ||
$peek['type'] === Lexer::T_IS ||
$peek['type'] === Lexer::T_EXISTS) {
$condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
} else {
$this->match(Lexer::T_OPEN_PARENTHESIS);
$condPrimary->conditionalExpression = $this->ConditionalExpression();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
}
} else {
$condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
return $condPrimary;
} }
// Peek beyond the matching closing paranthesis ')'
$peek = $this->_peekBeyondClosingParenthesis();
if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) ||
in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS))) {
$condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
return $condPrimary;
}
$this->match(Lexer::T_OPEN_PARENTHESIS);
$condPrimary->conditionalExpression = $this->ConditionalExpression();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
return $condPrimary; return $condPrimary;
} }
@ -2132,10 +2171,10 @@ class Parser
*/ */
public function SimpleConditionalExpression() public function SimpleConditionalExpression()
{ {
$token = $this->_lexer->lookahead;
if ($this->_lexer->isNextToken(Lexer::T_NOT)) { if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$token = $this->_lexer->glimpse(); $token = $this->_lexer->glimpse();
} else {
$token = $this->_lexer->lookahead;
} }
if ($token['type'] === Lexer::T_EXISTS) { if ($token['type'] === Lexer::T_EXISTS) {
@ -2464,9 +2503,9 @@ class Parser
} }
return $this->FunctionDeclaration(); return $this->FunctionDeclaration();
} else {
return $this->Literal();
} }
return $this->Literal();
} }
} }
@ -2498,30 +2537,46 @@ class Parser
*/ */
public function StringPrimary() public function StringPrimary()
{ {
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $lookaheadType = $this->_lexer->lookahead['type'];
$peek = $this->_lexer->glimpse();
switch ($lookaheadType) {
case Lexer::T_IDENTIFIER:
$peek = $this->_lexer->glimpse();
if ($peek['value'] == '.') {
return $this->StateFieldPathExpression();
}
if ($peek['value'] == '(') {
// do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions.
return $this->FunctionDeclaration();
}
if ($peek['value'] == '.') {
return $this->StateFieldPathExpression();
} else if ($peek['value'] == '(') {
// do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions.
return $this->FunctionDeclaration();
} else {
$this->syntaxError("'.' or '('"); $this->syntaxError("'.' or '('");
} break;
} else if ($this->_lexer->isNextToken(Lexer::T_STRING)) {
$this->match(Lexer::T_STRING);
return $this->_lexer->token['value']; case Lexer::T_STRING:
} else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_STRING);
return $this->InputParameter();
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { return $this->_lexer->token['value'];
return $this->AggregateExpression();
} else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) { case Lexer::T_INPUT_PARAMETER:
return $this->CaseExpression(); return $this->InputParameter();
case Lexer::T_CASE:
case Lexer::T_COALESCE:
case Lexer::T_NULLIF:
return $this->CaseExpression();
default:
if ($this->_isAggregateFunction($lookaheadType)) {
return $this->AggregateExpression();
}
} }
$this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'); $this->syntaxError(
'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
);
} }
/** /**
@ -2552,7 +2607,7 @@ class Parser
return $this->InputParameter(); return $this->InputParameter();
} }
return $this->IdentificationVariable(); return $this->StateFieldPathExpression();
} }
/** /**
@ -2564,40 +2619,28 @@ class Parser
*/ */
public function AggregateExpression() public function AggregateExpression()
{ {
$lookaheadType = $this->_lexer->lookahead['type'];
$isDistinct = false; $isDistinct = false;
$functionName = '';
if ($this->_lexer->isNextToken(Lexer::T_COUNT)) { if ( ! in_array($lookaheadType, array(Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM))) {
$this->match(Lexer::T_COUNT); $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
$functionName = $this->_lexer->token['value'];
$this->match(Lexer::T_OPEN_PARENTHESIS);
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$isDistinct = true;
}
$pathExp = $this->SingleValuedPathExpression();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
} else {
if ($this->_lexer->isNextToken(Lexer::T_AVG)) {
$this->match(Lexer::T_AVG);
} else if ($this->_lexer->isNextToken(Lexer::T_MAX)) {
$this->match(Lexer::T_MAX);
} else if ($this->_lexer->isNextToken(Lexer::T_MIN)) {
$this->match(Lexer::T_MIN);
} else if ($this->_lexer->isNextToken(Lexer::T_SUM)) {
$this->match(Lexer::T_SUM);
} else {
$this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
}
$functionName = $this->_lexer->token['value'];
$this->match(Lexer::T_OPEN_PARENTHESIS);
$pathExp = $this->SimpleArithmeticExpression();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
} }
$this->match($lookaheadType);
$functionName = $this->_lexer->token['value'];
$this->match(Lexer::T_OPEN_PARENTHESIS);
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$isDistinct = true;
}
$pathExp = ($lookaheadType === Lexer::T_COUNT)
? $this->SingleValuedPathExpression()
: $this->SimpleArithmeticExpression();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
} }
@ -2608,24 +2651,19 @@ class Parser
*/ */
public function QuantifiedExpression() public function QuantifiedExpression()
{ {
$type = ''; $lookaheadType = $this->_lexer->lookahead['type'];
$value = $this->_lexer->lookahead['value'];
if ($this->_lexer->isNextToken(Lexer::T_ALL)) { if ( ! in_array($lookaheadType, array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME))) {
$this->match(Lexer::T_ALL);
$type = 'ALL';
} else if ($this->_lexer->isNextToken(Lexer::T_ANY)) {
$this->match(Lexer::T_ANY);
$type = 'ANY';
} else if ($this->_lexer->isNextToken(Lexer::T_SOME)) {
$this->match(Lexer::T_SOME);
$type = 'SOME';
} else {
$this->syntaxError('ALL, ANY or SOME'); $this->syntaxError('ALL, ANY or SOME');
} }
$this->match($lookaheadType);
$this->match(Lexer::T_OPEN_PARENTHESIS); $this->match(Lexer::T_OPEN_PARENTHESIS);
$qExpr = new AST\QuantifiedExpression($this->Subselect()); $qExpr = new AST\QuantifiedExpression($this->Subselect());
$qExpr->type = $type; $qExpr->type = $value;
$this->match(Lexer::T_CLOSE_PARENTHESIS); $this->match(Lexer::T_CLOSE_PARENTHESIS);
return $qExpr; return $qExpr;
@ -2666,14 +2704,11 @@ class Parser
{ {
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
$leftExpr = $this->ArithmeticExpression(); $leftExpr = $this->ArithmeticExpression();
$operator = $this->ComparisonOperator(); $operator = $this->ComparisonOperator();
$rightExpr = ($this->_isNextAllAnySome())
if ($this->_isNextAllAnySome()) { ? $this->QuantifiedExpression()
$rightExpr = $this->QuantifiedExpression(); : $this->ArithmeticExpression();
} else {
$rightExpr = $this->ArithmeticExpression();
}
return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
} }
@ -2685,7 +2720,7 @@ class Parser
*/ */
public function InExpression() public function InExpression()
{ {
$inExpression = new AST\InExpression($this->SingleValuedPathExpression()); $inExpression = new AST\InExpression($this->ArithmeticExpression());
if ($this->_lexer->isNextToken(Lexer::T_NOT)) { if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT); $this->match(Lexer::T_NOT);

View File

@ -435,7 +435,8 @@ class SqlWalker implements TreeWalker
{ {
$this->_useSqlTableAliases = false; $this->_useSqlTableAliases = false;
return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause); return $this->walkUpdateClause($AST->updateClause)
. $this->walkWhereClause($AST->whereClause);
} }
/** /**
@ -448,9 +449,29 @@ class SqlWalker implements TreeWalker
{ {
$this->_useSqlTableAliases = false; $this->_useSqlTableAliases = false;
return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause); return $this->walkDeleteClause($AST->deleteClause)
. $this->walkWhereClause($AST->whereClause);
} }
/**
* Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
* This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
*
* @param string $identVariable
* @return string
*/
public function walkEntityIdentificationVariable($identVariable)
{
$class = $this->_queryComponents[$identVariable]['metadata'];
$tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
$sqlParts = array();
foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) {
$sqlParts[] = $tableAlias . '.' . $columnName;
}
return implode(', ', $sqlParts);
}
/** /**
* Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
@ -713,7 +734,7 @@ class SqlWalker implements TreeWalker
$expr = $orderByItem->expression; $expr = $orderByItem->expression;
$sql = ($expr instanceof AST\PathExpression) $sql = ($expr instanceof AST\PathExpression)
? $this->walkPathExpression($expr) ? $this->walkPathExpression($expr)
: $this->_scalarResultAliasMap[$this->_queryComponents[$expr]['token']['value']]; : $this->walkResultVariable($this->_queryComponents[$expr]['token']['value']);
return $sql . ' ' . strtoupper($orderByItem->type); return $sql . ' ' . strtoupper($orderByItem->type);
} }
@ -1050,6 +1071,8 @@ class SqlWalker implements TreeWalker
$sql .= $col . ' AS ' . $columnAlias; $sql .= $col . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
if ( ! $hidden) { if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
$this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias;
@ -1139,6 +1162,8 @@ class SqlWalker implements TreeWalker
$sqlParts[] = $col . ' AS '. $columnAlias; $sqlParts[] = $col . ' AS '. $columnAlias;
$this->_scalarResultAliasMap[$resultAlias][] = $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
} }
@ -1168,6 +1193,8 @@ class SqlWalker implements TreeWalker
$sqlParts[] = $col . ' AS ' . $columnAlias; $sqlParts[] = $col . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias][] = $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
} }
} }
@ -1317,15 +1344,7 @@ class SqlWalker implements TreeWalker
break; break;
default: // IdentificationVariable default: // IdentificationVariable
$class = $this->_queryComponents[$expr]['metadata']; $sql .= $this->walkEntityIdentificationVariable($expr);
$tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr);
$sqlParts = array();
foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) {
$sqlParts[] = $tableAlias . '.' . $columnName;
}
$sql .= implode(', ', $sqlParts);
break; break;
} }
@ -1355,25 +1374,7 @@ class SqlWalker implements TreeWalker
$sqlParts = array(); $sqlParts = array();
foreach ($groupByClause->groupByItems AS $groupByItem) { foreach ($groupByClause->groupByItems AS $groupByItem) {
if ( ! is_string($groupByItem)) { $sqlParts[] = $this->walkGroupByItem($groupByItem);
$sqlParts[] = $this->walkGroupByItem($groupByItem);
continue;
}
foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
$item->type = AST\PathExpression::TYPE_STATE_FIELD;
$sqlParts[] = $this->walkGroupByItem($item);
}
foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) {
if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
$item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
$sqlParts[] = $this->walkGroupByItem($item);
}
}
} }
return ' GROUP BY ' . implode(', ', $sqlParts); return ' GROUP BY ' . implode(', ', $sqlParts);
@ -1385,9 +1386,38 @@ class SqlWalker implements TreeWalker
* @param GroupByItem * @param GroupByItem
* @return string The SQL. * @return string The SQL.
*/ */
public function walkGroupByItem(AST\PathExpression $pathExpr) public function walkGroupByItem($groupByItem)
{ {
return $this->walkPathExpression($pathExpr); // StateFieldPathExpression
if ( ! is_string($groupByItem)) {
return $this->walkPathExpression($groupByItem);
}
// ResultVariable
if (isset($this->_queryComponents[$groupByItem]['resultVariable'])) {
return $this->walkResultVariable($groupByItem);
}
// IdentificationVariable
$sqlParts = array();
foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
$item->type = AST\PathExpression::TYPE_STATE_FIELD;
$sqlParts[] = $this->walkPathExpression($item);
}
foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) {
if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
$item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
$sqlParts[] = $this->walkPathExpression($item);
}
}
return implode(', ', $sqlParts);
} }
/** /**
@ -1597,20 +1627,30 @@ class SqlWalker implements TreeWalker
{ {
$sql = $collMemberExpr->not ? 'NOT ' : ''; $sql = $collMemberExpr->not ? 'NOT ' : '';
$sql .= 'EXISTS (SELECT 1 FROM '; $sql .= 'EXISTS (SELECT 1 FROM ';
$entityExpr = $collMemberExpr->entityExpression;
$entityExpr = $collMemberExpr->entityExpression;
$collPathExpr = $collMemberExpr->collectionValuedPathExpression; $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
$fieldName = $collPathExpr->field; $fieldName = $collPathExpr->field;
$dqlAlias = $collPathExpr->identificationVariable; $dqlAlias = $collPathExpr->identificationVariable;
$class = $this->_queryComponents[$dqlAlias]['metadata']; $class = $this->_queryComponents[$dqlAlias]['metadata'];
if ($entityExpr instanceof AST\InputParameter) { switch (true) {
$dqlParamKey = $entityExpr->name; // InputParameter
$entity = $this->_query->getParameter($dqlParamKey); case ($entityExpr instanceof AST\InputParameter):
} else { $dqlParamKey = $entityExpr->name;
//TODO $entity = $this->_query->getParameter($dqlParamKey);
throw new \BadMethodCallException("Not implemented"); $entitySql = '?';
break;
// SingleValuedAssociationPathExpression | IdentificationVariable
case ($entityExpr instanceof AST\PathExpression):
$entitySql = $this->walkPathExpression($entityExpr);
break;
default:
throw new \BadMethodCallException("Not implemented");
} }
$assoc = $class->associationMappings[$fieldName]; $assoc = $class->associationMappings[$fieldName];
@ -1623,25 +1663,23 @@ class SqlWalker implements TreeWalker
$sql .= $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' WHERE '; $sql .= $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' WHERE ';
$owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
$first = true; $sqlParts = array();
foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
if ($first) $first = false; else $sql .= ' AND '; $targetColumn = $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform);
$sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform) $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
. ' = '
. $targetTableAlias . '.' . $sourceColumn;
} }
$sql .= ' AND ';
$first = true;
foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) {
if ($first) $first = false; else $sql .= ' AND '; if (isset($dqlParamKey)) {
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
}
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql;
$sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?';
} }
$sql .= implode(' AND ', $sqlParts);
} else { // many-to-many } else { // many-to-many
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
@ -1658,39 +1696,42 @@ class SqlWalker implements TreeWalker
. ' INNER JOIN ' . $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' ON '; . ' INNER JOIN ' . $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' ON ';
// join conditions // join conditions
$joinColumns = $assoc['isOwningSide'] $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
? $joinTable['inverseJoinColumns'] $joinSqlParts = array();
: $joinTable['joinColumns'];
$first = true;
foreach ($joinColumns as $joinColumn) { foreach ($joinColumns as $joinColumn) {
if ($first) $first = false; else $sql .= ' AND '; $targetColumn = $targetClass->getQuotedColumnName(
$targetClass->fieldNames[$joinColumn['referencedColumnName']],
$this->_platform
);
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
. $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $this->_platform);
} }
$sql .= implode(' AND ', $joinSqlParts);
$sql .= ' WHERE '; $sql .= ' WHERE ';
$joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
$first = true; $sqlParts = array();
foreach ($joinColumns as $joinColumn) { foreach ($joinColumns as $joinColumn) {
if ($first) $first = false; else $sql .= ' AND '; $targetColumn = $class->getQuotedColumnName(
$class->fieldNames[$joinColumn['referencedColumnName']],
$this->_platform
);
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
. $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $this->_platform);
} }
$sql .= ' AND ';
$first = true;
foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) {
if ($first) $first = false; else $sql .= ' AND '; if (isset($dqlParamKey)) {
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
}
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql;
$sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?';
} }
$sql .= implode(' AND ', $sqlParts);
} }
return $sql . ')'; return $sql . ')';
@ -1742,8 +1783,7 @@ class SqlWalker implements TreeWalker
*/ */
public function walkInExpression($inExpr) public function walkInExpression($inExpr)
{ {
$sql = $this->walkPathExpression($inExpr->pathExpression) $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
. ($inExpr->not ? ' NOT' : '') . ' IN (';
$sql .= ($inExpr->subselect) $sql .= ($inExpr->subselect)
? $this->walkSubselect($inExpr->subselect) ? $this->walkSubselect($inExpr->subselect)
@ -1985,7 +2025,7 @@ class SqlWalker implements TreeWalker
{ {
if (is_string($term)) { if (is_string($term)) {
return (isset($this->_queryComponents[$term])) return (isset($this->_queryComponents[$term]))
? $this->_scalarResultAliasMap[$this->_queryComponents[$term]['token']['value']] ? $this->walkResultVariable($this->_queryComponents[$term]['token']['value'])
: $term; : $term;
} }
@ -2037,8 +2077,7 @@ class SqlWalker implements TreeWalker
return $primary->dispatch($this); return $primary->dispatch($this);
} }
// TODO: We need to deal with IdentificationVariable here return $this->walkEntityIdentificationVariable($primary);
return '';
} }
/** /**
@ -2053,4 +2092,21 @@ class SqlWalker implements TreeWalker
? $this->_conn->quote($stringPrimary) ? $this->_conn->quote($stringPrimary)
: $stringPrimary->dispatch($this); : $stringPrimary->dispatch($this);
} }
/**
* Walks down a ResultVriable that represents an AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
public function walkResultVariable($resultVariable)
{
$resultAlias = $this->_scalarResultAliasMap[$resultVariable];
if (is_array($resultAlias)) {
return implode(', ', $resultAlias);
}
return $resultAlias;
}
} }

View File

@ -168,7 +168,7 @@ interface TreeWalker
* @param GroupByItem * @param GroupByItem
* @return string The SQL. * @return string The SQL.
*/ */
function walkGroupByItem(AST\PathExpression $pathExpr); function walkGroupByItem($groupByItem);
/** /**
* Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
@ -394,6 +394,14 @@ interface TreeWalker
*/ */
function walkPathExpression($pathExpr); function walkPathExpression($pathExpr);
/**
* Walks down an ResultVariable AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
function walkResultVariable($resultVariable);
/** /**
* Gets an executor that can be used to execute the result of this walker. * Gets an executor that can be used to execute the result of this walker.
* *

View File

@ -202,7 +202,7 @@ abstract class TreeWalkerAdapter implements TreeWalker
* @param GroupByItem * @param GroupByItem
* @return string The SQL. * @return string The SQL.
*/ */
public function walkGroupByItem(AST\PathExpression $pathExpr) {} public function walkGroupByItem($groupByItem) {}
/** /**
* Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
@ -428,6 +428,14 @@ abstract class TreeWalkerAdapter implements TreeWalker
*/ */
public function walkPathExpression($pathExpr) {} public function walkPathExpression($pathExpr) {}
/**
* Walks down an ResultVariable AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
public function walkResultVariable($resultVariable) {}
/** /**
* Gets an executor that can be used to execute the result of this walker. * Gets an executor that can be used to execute the result of this walker.
* *

View File

@ -270,10 +270,10 @@ class TreeWalkerChain implements TreeWalker
* @param GroupByItem * @param GroupByItem
* @return string The SQL. * @return string The SQL.
*/ */
public function walkGroupByItem(AST\PathExpression $pathExpr) public function walkGroupByItem($groupByItem)
{ {
foreach ($this->_walkers as $walker) { foreach ($this->_walkers as $walker) {
$walker->walkGroupByItem($pathExpr); $walker->walkGroupByItem($groupByItem);
} }
} }
@ -641,6 +641,19 @@ class TreeWalkerChain implements TreeWalker
} }
} }
/**
* Walks down an ResultVariable AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
public function walkResultVariable($resultVariable)
{
foreach ($this->_walkers as $walker) {
$walker->walkResultVariable($resultVariable);
}
}
/** /**
* Gets an executor that can be used to execute the result of this walker. * Gets an executor that can be used to execute the result of this walker.
* *

View File

@ -355,12 +355,9 @@ class QueryBuilder
public function setParameters(array $params, array $types = array()) public function setParameters(array $params, array $types = array())
{ {
foreach ($params as $key => $value) { foreach ($params as $key => $value) {
if (isset($types[$key])) { $this->setParameter($key, $value, isset($types[$key]) ? $types[$key] : null);
$this->setParameter($key, $value, $types[$key]);
} else {
$this->setParameter($key, $value);
}
} }
return $this; return $this;
} }
@ -394,6 +391,7 @@ class QueryBuilder
public function setFirstResult($firstResult) public function setFirstResult($firstResult)
{ {
$this->_firstResult = $firstResult; $this->_firstResult = $firstResult;
return $this; return $this;
} }
@ -417,6 +415,7 @@ class QueryBuilder
public function setMaxResults($maxResults) public function setMaxResults($maxResults)
{ {
$this->_maxResults = $maxResults; $this->_maxResults = $maxResults;
return $this; return $this;
} }
@ -652,13 +651,16 @@ class QueryBuilder
public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{ {
$rootAlias = substr($join, 0, strpos($join, '.')); $rootAlias = substr($join, 0, strpos($join, '.'));
if (!in_array($rootAlias, $this->getRootAliases())) {
if ( ! in_array($rootAlias, $this->getRootAliases())) {
$rootAlias = $this->getRootAlias(); $rootAlias = $this->getRootAlias();
} }
return $this->add('join', array( $join = new Expr\Join(
$rootAlias => new Expr\Join(Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy) Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
), true); );
return $this->add('join', array($rootAlias => $join), true);
} }
/** /**
@ -685,13 +687,16 @@ class QueryBuilder
public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{ {
$rootAlias = substr($join, 0, strpos($join, '.')); $rootAlias = substr($join, 0, strpos($join, '.'));
if (!in_array($rootAlias, $this->getRootAliases())) {
if ( ! in_array($rootAlias, $this->getRootAliases())) {
$rootAlias = $this->getRootAlias(); $rootAlias = $this->getRootAlias();
} }
return $this->add('join', array( $join = new Expr\Join(
$rootAlias => new Expr\Join(Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy) Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
), true); );
return $this->add('join', array($rootAlias => $join), true);
} }
/** /**

View File

@ -196,7 +196,7 @@ public function <methodName>()
if ($this->_backupExisting && file_exists($path)) { if ($this->_backupExisting && file_exists($path)) {
$backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~"; $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
if (!copy($path, $backupPath)) { if (!copy($path, $backupPath)) {
throw new \RuntimeException("Attempt to backup overwritten entitiy file but copy operation failed."); throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
} }
} }
@ -754,7 +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) : '', '<variableDefault>' => ($defaultValue !== null ) ? (' = '.$defaultValue) : '',
'<entity>' => $this->_getClassName($metadata) '<entity>' => $this->_getClassName($metadata)
); );

View File

@ -1700,21 +1700,18 @@ class UnitOfWork implements PropertyChangedListener
// do not merge fields marked lazy that have not been fetched. // do not merge fields marked lazy that have not been fetched.
continue; continue;
} else if ( ! $assoc2['isCascadeMerge']) { } else if ( ! $assoc2['isCascadeMerge']) {
if ($this->getEntityState($other, self::STATE_DETACHED) == self::STATE_MANAGED) { if ($this->getEntityState($other, self::STATE_DETACHED) !== self::STATE_MANAGED) {
$prop->setValue($managedCopy, $other);
} else {
$targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']);
$relatedId = $targetClass->getIdentifierValues($other); $relatedId = $targetClass->getIdentifierValues($other);
if ($targetClass->subClasses) { if ($targetClass->subClasses) {
$entity = $this->em->find($targetClass->name, $relatedId); $other = $this->em->find($targetClass->name, $relatedId);
} else { } else {
$proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); $other = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId);
$prop->setValue($managedCopy, $proxy); $this->registerManaged($other, $relatedId, array());
$this->registerManaged($proxy, $relatedId, array());
} }
} }
$prop->setValue($managedCopy, $other);
} }
} else { } else {
$mergeCol = $prop->getValue($entity); $mergeCol = $prop->getValue($entity);
@ -1793,6 +1790,7 @@ class UnitOfWork implements PropertyChangedListener
public function detach($entity) public function detach($entity)
{ {
$visited = array(); $visited = array();
$this->doDetach($entity, $visited); $this->doDetach($entity, $visited);
} }
@ -1806,6 +1804,7 @@ class UnitOfWork implements PropertyChangedListener
private function doDetach($entity, array &$visited, $noCascade = false) private function doDetach($entity, array &$visited, $noCascade = false)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
if (isset($visited[$oid])) { if (isset($visited[$oid])) {
return; // Prevent infinite recursion return; // Prevent infinite recursion
} }
@ -1817,16 +1816,22 @@ class UnitOfWork implements PropertyChangedListener
if ($this->isInIdentityMap($entity)) { if ($this->isInIdentityMap($entity)) {
$this->removeFromIdentityMap($entity); $this->removeFromIdentityMap($entity);
} }
unset($this->entityInsertions[$oid], $this->entityUpdates[$oid],
$this->entityDeletions[$oid], $this->entityIdentifiers[$oid], unset(
$this->entityStates[$oid], $this->originalEntityData[$oid]); $this->entityInsertions[$oid],
$this->entityUpdates[$oid],
$this->entityDeletions[$oid],
$this->entityIdentifiers[$oid],
$this->entityStates[$oid],
$this->originalEntityData[$oid]
);
break; break;
case self::STATE_NEW: case self::STATE_NEW:
case self::STATE_DETACHED: case self::STATE_DETACHED:
return; return;
} }
if (!$noCascade) { if ( ! $noCascade) {
$this->cascadeDetach($entity, $visited); $this->cascadeDetach($entity, $visited);
} }
} }
@ -1841,6 +1846,7 @@ class UnitOfWork implements PropertyChangedListener
public function refresh($entity) public function refresh($entity)
{ {
$visited = array(); $visited = array();
$this->doRefresh($entity, $visited); $this->doRefresh($entity, $visited);
} }
@ -1854,6 +1860,7 @@ class UnitOfWork implements PropertyChangedListener
private function doRefresh($entity, array &$visited) private function doRefresh($entity, array &$visited)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
if (isset($visited[$oid])) { if (isset($visited[$oid])) {
return; // Prevent infinite recursion return; // Prevent infinite recursion
} }
@ -1861,15 +1868,16 @@ class UnitOfWork implements PropertyChangedListener
$visited[$oid] = $entity; // mark visited $visited[$oid] = $entity; // mark visited
$class = $this->em->getClassMetadata(get_class($entity)); $class = $this->em->getClassMetadata(get_class($entity));
if ($this->getEntityState($entity) == self::STATE_MANAGED) {
$this->getEntityPersister($class->name)->refresh( if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
$entity
);
} else {
throw new InvalidArgumentException("Entity is not MANAGED."); throw new InvalidArgumentException("Entity is not MANAGED.");
} }
$this->getEntityPersister($class->name)->refresh(
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
$entity
);
$this->cascadeRefresh($entity, $visited); $this->cascadeRefresh($entity, $visited);
} }
@ -1882,21 +1890,34 @@ class UnitOfWork implements PropertyChangedListener
private function cascadeRefresh($entity, array &$visited) private function cascadeRefresh($entity, array &$visited)
{ {
$class = $this->em->getClassMetadata(get_class($entity)); $class = $this->em->getClassMetadata(get_class($entity));
foreach ($class->associationMappings as $assoc) {
if ( ! $assoc['isCascadeRefresh']) { $associationMappings = array_filter(
continue; $class->associationMappings,
} function ($assoc) { return $assoc['isCascadeRefresh']; }
);
foreach ($associationMappings as $assoc) {
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
if ($relatedEntities instanceof Collection) {
if ($relatedEntities instanceof PersistentCollection) { switch (true) {
case ($relatedEntities instanceof PersistentCollection):
// Unwrap so that foreach() does not initialize // Unwrap so that foreach() does not initialize
$relatedEntities = $relatedEntities->unwrap(); $relatedEntities = $relatedEntities->unwrap();
} // break; is commented intentionally!
foreach ($relatedEntities as $relatedEntity) {
$this->doRefresh($relatedEntity, $visited); case ($relatedEntities instanceof Collection):
} case (is_array($relatedEntities)):
} else if ($relatedEntities !== null) { foreach ($relatedEntities as $relatedEntity) {
$this->doRefresh($relatedEntities, $visited); $this->doRefresh($relatedEntity, $visited);
}
break;
case ($relatedEntities !== null):
$this->doRefresh($relatedEntities, $visited);
break;
default:
// Do nothing
} }
} }
} }
@ -1910,21 +1931,34 @@ class UnitOfWork implements PropertyChangedListener
private function cascadeDetach($entity, array &$visited) private function cascadeDetach($entity, array &$visited)
{ {
$class = $this->em->getClassMetadata(get_class($entity)); $class = $this->em->getClassMetadata(get_class($entity));
foreach ($class->associationMappings as $assoc) {
if ( ! $assoc['isCascadeDetach']) { $associationMappings = array_filter(
continue; $class->associationMappings,
} function ($assoc) { return $assoc['isCascadeDetach']; }
);
foreach ($associationMappings as $assoc) {
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
if ($relatedEntities instanceof Collection) {
if ($relatedEntities instanceof PersistentCollection) { switch (true) {
case ($relatedEntities instanceof PersistentCollection):
// Unwrap so that foreach() does not initialize // Unwrap so that foreach() does not initialize
$relatedEntities = $relatedEntities->unwrap(); $relatedEntities = $relatedEntities->unwrap();
} // break; is commented intentionally!
foreach ($relatedEntities as $relatedEntity) {
$this->doDetach($relatedEntity, $visited); case ($relatedEntities instanceof Collection):
} case (is_array($relatedEntities)):
} else if ($relatedEntities !== null) { foreach ($relatedEntities as $relatedEntity) {
$this->doDetach($relatedEntities, $visited); $this->doDetach($relatedEntity, $visited);
}
break;
case ($relatedEntities !== null):
$this->doDetach($relatedEntities, $visited);
break;
default:
// Do nothing
} }
} }
} }
@ -1939,11 +1973,15 @@ class UnitOfWork implements PropertyChangedListener
private function cascadeMerge($entity, $managedCopy, array &$visited) private function cascadeMerge($entity, $managedCopy, array &$visited)
{ {
$class = $this->em->getClassMetadata(get_class($entity)); $class = $this->em->getClassMetadata(get_class($entity));
foreach ($class->associationMappings as $assoc) {
if ( ! $assoc['isCascadeMerge']) { $associationMappings = array_filter(
continue; $class->associationMappings,
} function ($assoc) { return $assoc['isCascadeMerge']; }
);
foreach ($associationMappings as $assoc) {
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
if ($relatedEntities instanceof Collection) { if ($relatedEntities instanceof Collection) {
if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) { if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
continue; continue;
@ -1953,6 +1991,7 @@ class UnitOfWork implements PropertyChangedListener
// Unwrap so that foreach() does not initialize // Unwrap so that foreach() does not initialize
$relatedEntities = $relatedEntities->unwrap(); $relatedEntities = $relatedEntities->unwrap();
} }
foreach ($relatedEntities as $relatedEntity) { foreach ($relatedEntities as $relatedEntity) {
$this->doMerge($relatedEntity, $visited, $managedCopy, $assoc); $this->doMerge($relatedEntity, $visited, $managedCopy, $assoc);
} }
@ -1972,22 +2011,34 @@ class UnitOfWork implements PropertyChangedListener
private function cascadePersist($entity, array &$visited) private function cascadePersist($entity, array &$visited)
{ {
$class = $this->em->getClassMetadata(get_class($entity)); $class = $this->em->getClassMetadata(get_class($entity));
foreach ($class->associationMappings as $assoc) {
if ( ! $assoc['isCascadePersist']) {
continue;
}
$associationMappings = array_filter(
$class->associationMappings,
function ($assoc) { return $assoc['isCascadePersist']; }
);
foreach ($associationMappings as $assoc) {
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
if (($relatedEntities instanceof Collection || is_array($relatedEntities))) {
if ($relatedEntities instanceof PersistentCollection) { switch (true) {
case ($relatedEntities instanceof PersistentCollection):
// Unwrap so that foreach() does not initialize // Unwrap so that foreach() does not initialize
$relatedEntities = $relatedEntities->unwrap(); $relatedEntities = $relatedEntities->unwrap();
} // break; is commented intentionally!
foreach ($relatedEntities as $relatedEntity) {
$this->doPersist($relatedEntity, $visited); case ($relatedEntities instanceof Collection):
} case (is_array($relatedEntities)):
} else if ($relatedEntities !== null) { foreach ($relatedEntities as $relatedEntity) {
$this->doPersist($relatedEntities, $visited); $this->doPersist($relatedEntity, $visited);
}
break;
case ($relatedEntities !== null):
$this->doPersist($relatedEntities, $visited);
break;
default:
// Do nothing
} }
} }
} }
@ -2002,24 +2053,33 @@ class UnitOfWork implements PropertyChangedListener
{ {
$class = $this->em->getClassMetadata(get_class($entity)); $class = $this->em->getClassMetadata(get_class($entity));
foreach ($class->associationMappings as $assoc) { $associationMappings = array_filter(
if ( ! $assoc['isCascadeRemove']) { $class->associationMappings,
continue; function ($assoc) { return $assoc['isCascadeRemove']; }
} );
foreach ($associationMappings as $assoc) {
if ($entity instanceof Proxy && !$entity->__isInitialized__) { if ($entity instanceof Proxy && !$entity->__isInitialized__) {
$entity->__load(); $entity->__load();
} }
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { switch (true) {
// If its a PersistentCollection initialization is intended! No unwrap! case ($relatedEntities instanceof Collection):
foreach ($relatedEntities as $relatedEntity) { case (is_array($relatedEntities)):
$this->doRemove($relatedEntity, $visited); // If its a PersistentCollection initialization is intended! No unwrap!
} foreach ($relatedEntities as $relatedEntity) {
} else if ($relatedEntities !== null) { $this->doRemove($relatedEntity, $visited);
$this->doRemove($relatedEntities, $visited); }
break;
case ($relatedEntities !== null):
$this->doRemove($relatedEntities, $visited);
break;
default:
// Do nothing
} }
} }
} }
@ -2040,29 +2100,40 @@ class UnitOfWork implements PropertyChangedListener
$entityName = get_class($entity); $entityName = get_class($entity);
$class = $this->em->getClassMetadata($entityName); $class = $this->em->getClassMetadata($entityName);
if ($lockMode == \Doctrine\DBAL\LockMode::OPTIMISTIC) { switch ($lockMode) {
if (!$class->isVersioned) { case \Doctrine\DBAL\LockMode::OPTIMISTIC;
throw OptimisticLockException::notVersioned($entityName); if ( ! $class->isVersioned) {
} throw OptimisticLockException::notVersioned($entityName);
}
if ($lockVersion === null) {
return;
}
if ($lockVersion != null) {
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
if ($entityVersion != $lockVersion) { if ($entityVersion != $lockVersion) {
throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion); throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion);
} }
}
} else if (in_array($lockMode, array(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE))) {
if (!$this->em->getConnection()->isTransactionActive()) { break;
throw TransactionRequiredException::transactionRequired();
}
$oid = spl_object_hash($entity); case \Doctrine\DBAL\LockMode::PESSIMISTIC_READ:
case \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE:
if (!$this->em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
$this->getEntityPersister($class->name)->lock( $oid = spl_object_hash($entity);
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
$lockMode $this->getEntityPersister($class->name)->lock(
); array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
$lockMode
);
break;
default:
// Do nothing
} }
} }
@ -2076,6 +2147,7 @@ class UnitOfWork implements PropertyChangedListener
if ($this->commitOrderCalculator === null) { if ($this->commitOrderCalculator === null) {
$this->commitOrderCalculator = new Internal\CommitOrderCalculator; $this->commitOrderCalculator = new Internal\CommitOrderCalculator;
} }
return $this->commitOrderCalculator; return $this->commitOrderCalculator;
} }
@ -2164,9 +2236,11 @@ class UnitOfWork implements PropertyChangedListener
private function newInstance($class) private function newInstance($class)
{ {
$entity = $class->newInstance(); $entity = $class->newInstance();
if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) { if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) {
$entity->injectObjectManager($this->em, $class); $entity->injectObjectManager($this->em, $class);
} }
return $entity; return $entity;
} }
@ -2190,35 +2264,30 @@ class UnitOfWork implements PropertyChangedListener
if ($class->isIdentifierComposite) { if ($class->isIdentifierComposite) {
$id = array(); $id = array();
foreach ($class->identifier as $fieldName) { foreach ($class->identifier as $fieldName) {
if (isset($class->associationMappings[$fieldName])) { $id[$fieldName] = isset($class->associationMappings[$fieldName])
$id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]; ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
} else { : $data[$fieldName];
$id[$fieldName] = $data[$fieldName];
}
} }
$idHash = implode(' ', $id); $idHash = implode(' ', $id);
} else { } else {
if (isset($class->associationMappings[$class->identifier[0]])) { $idHash = isset($class->associationMappings[$class->identifier[0]])
$idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]; ? $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]
} else { : $data[$class->identifier[0]];
/*echo $className;
\Doctrine\Common\Util\Debug::dump($data);
\Doctrine\Common\Util\Debug::dump($class->identifier);
ob_end_flush();
ob_start();*/
$idHash = $data[$class->identifier[0]];
}
$id = array($class->identifier[0] => $idHash); $id = array($class->identifier[0] => $idHash);
} }
if (isset($this->identityMap[$class->rootEntityName][$idHash])) { if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
$entity = $this->identityMap[$class->rootEntityName][$idHash]; $entity = $this->identityMap[$class->rootEntityName][$idHash];
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
if ($entity instanceof Proxy && ! $entity->__isInitialized__) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
$entity->__isInitialized__ = true; $entity->__isInitialized__ = true;
$overrideLocalValues = true; $overrideLocalValues = true;
if ($entity instanceof NotifyPropertyChanged) { if ($entity instanceof NotifyPropertyChanged) {
$entity->addPropertyChangedListener($this); $entity->addPropertyChangedListener($this);
} }
@ -2242,156 +2311,175 @@ class UnitOfWork implements PropertyChangedListener
} else { } else {
$entity = $this->newInstance($class); $entity = $this->newInstance($class);
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$this->entityIdentifiers[$oid] = $id; $this->entityIdentifiers[$oid] = $id;
$this->entityStates[$oid] = self::STATE_MANAGED; $this->entityStates[$oid] = self::STATE_MANAGED;
$this->originalEntityData[$oid] = $data; $this->originalEntityData[$oid] = $data;
$this->identityMap[$class->rootEntityName][$idHash] = $entity; $this->identityMap[$class->rootEntityName][$idHash] = $entity;
if ($entity instanceof NotifyPropertyChanged) { if ($entity instanceof NotifyPropertyChanged) {
$entity->addPropertyChangedListener($this); $entity->addPropertyChangedListener($this);
} }
$overrideLocalValues = true; $overrideLocalValues = true;
} }
if ($overrideLocalValues) { if ( ! $overrideLocalValues) {
foreach ($data as $field => $value) { return $entity;
if (isset($class->fieldMappings[$field])) { }
$class->reflFields[$field]->setValue($entity, $value);
} foreach ($data as $field => $value) {
if (isset($class->fieldMappings[$field])) {
$class->reflFields[$field]->setValue($entity, $value);
}
}
// Loading the entity right here, if its in the eager loading map get rid of it there.
unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) {
unset($this->eagerLoadingEntities[$class->rootEntityName]);
}
// Properly initialize any unfetched associations, if partial objects are not allowed.
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
return $entity;
}
foreach ($class->associationMappings as $field => $assoc) {
// Check if the association is not among the fetch-joined associations already.
if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
continue;
} }
// Loading the entity right here, if its in the eager loading map get rid of it there. $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && switch (true) {
! $this->eagerLoadingEntities[$class->rootEntityName]) { case ($assoc['type'] & ClassMetadata::TO_ONE):
unset($this->eagerLoadingEntities[$class->rootEntityName]); if ( ! $assoc['isOwningSide']) {
} // Inverse side of x-to-one can never be lazy
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity));
continue 2;
}
$associatedId = array();
// TODO: Is this even computed right in all cases of composite keys?
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
if ($joinColumnValue !== null) {
if ($targetClass->containsForeignIdentifier) {
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
} else {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
}
}
if ( ! $associatedId) {
// Foreign key is NULL
$class->reflFields[$field]->setValue($entity, null);
$this->originalEntityData[$oid][$field] = null;
// Properly initialize any unfetched associations, if partial objects are not allowed.
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
foreach ($class->associationMappings as $field => $assoc) {
// Check if the association is not among the fetch-joined associations already.
if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
continue; continue;
} }
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if ( ! isset($hints['fetchMode'][$class->name][$field])) {
$hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if ($assoc['isOwningSide']) {
$associatedId = array();
// TODO: Is this even computed right in all cases of composite keys?
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
if ($joinColumnValue !== null) {
if ($targetClass->containsForeignIdentifier) {
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
} else {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
}
}
if ( ! $associatedId) {
// Foreign key is NULL
$class->reflFields[$field]->setValue($entity, null);
$this->originalEntityData[$oid][$field] = null;
} else {
if (!isset($hints['fetchMode'][$class->name][$field])) {
$hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
}
// Foreign key is set
// Check identity map first
// FIXME: Can break easily with composite keys if join column values are in
// wrong order. The correct order is the one in ClassMetadata#identifier.
$relatedIdHash = implode(' ', $associatedId);
if (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])) {
$newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
// if this is an uninitialized proxy, we are deferring eager loads,
// this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
// then we cann append this entity for eager loading!
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER &&
isset($hints['deferEagerLoad']) &&
!$targetClass->isIdentifierComposite &&
$newValue instanceof Proxy &&
$newValue->__isInitialized__ === false) {
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
}
} else {
if ($targetClass->subClasses) {
// If it might be a subtype, it can not be lazy. There isn't even
// a way to solve this with deferred eager loading, which means putting
// an entity with subclasses at a *-to-one location is really bad! (performance-wise)
$newValue = $this->getEntityPersister($assoc['targetEntity'])
->loadOneToOneEntity($assoc, $entity, $associatedId);
} else {
// Deferred eager load only works for single identifier classes
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER) {
if (isset($hints['deferEagerLoad']) && !$targetClass->isIdentifierComposite) {
// TODO: Is there a faster approach?
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
} else {
// TODO: This is very imperformant, ignore it?
$newValue = $this->em->find($assoc['targetEntity'], $associatedId);
}
} else {
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
}
// PERF: Inlined & optimized code from UnitOfWork#registerManaged()
$newValueOid = spl_object_hash($newValue);
$this->entityIdentifiers[$newValueOid] = $associatedId;
$this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
$this->entityStates[$newValueOid] = self::STATE_MANAGED;
// make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
}
}
$this->originalEntityData[$oid][$field] = $newValue;
$class->reflFields[$field]->setValue($entity, $newValue);
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity);
}
}
} else {
// Inverse side of x-to-one can never be lazy
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])
->loadOneToOneEntity($assoc, $entity));
}
} else {
// Inject collection
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
$pColl->setOwner($entity, $assoc);
$reflField = $class->reflFields[$field];
$reflField->setValue($entity, $pColl);
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
} else {
$pColl->setInitialized(false);
}
$this->originalEntityData[$oid][$field] = $pColl;
} }
}
// Foreign key is set
// Check identity map first
// FIXME: Can break easily with composite keys if join column values are in
// wrong order. The correct order is the one in ClassMetadata#identifier.
$relatedIdHash = implode(' ', $associatedId);
switch (true) {
case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])):
$newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
// if this is an uninitialized proxy, we are deferring eager loads,
// this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
// then we cann append this entity for eager loading!
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER &&
isset($hints['deferEagerLoad']) &&
!$targetClass->isIdentifierComposite &&
$newValue instanceof Proxy &&
$newValue->__isInitialized__ === false) {
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
}
break;
case ($targetClass->subClasses):
// If it might be a subtype, it can not be lazy. There isn't even
// a way to solve this with deferred eager loading, which means putting
// an entity with subclasses at a *-to-one location is really bad! (performance-wise)
$newValue = $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity, $associatedId);
break;
default:
switch (true) {
// We are negating the condition here. Other cases will assume it is valid!
case ($hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER):
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
break;
// Deferred eager load only works for single identifier classes
case (isset($hints['deferEagerLoad']) && ! $targetClass->isIdentifierComposite):
// TODO: Is there a faster approach?
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
break;
default:
// TODO: This is very imperformant, ignore it?
$newValue = $this->em->find($assoc['targetEntity'], $associatedId);
break;
}
// PERF: Inlined & optimized code from UnitOfWork#registerManaged()
$newValueOid = spl_object_hash($newValue);
$this->entityIdentifiers[$newValueOid] = $associatedId;
$this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
$this->entityStates[$newValueOid] = self::STATE_MANAGED;
// make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
break;
}
$this->originalEntityData[$oid][$field] = $newValue;
$class->reflFields[$field]->setValue($entity, $newValue);
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity);
}
break;
default:
// Inject collection
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
$pColl->setOwner($entity, $assoc);
$pColl->setInitialized(false);
$reflField = $class->reflFields[$field];
$reflField->setValue($entity, $pColl);
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
}
$this->originalEntityData[$oid][$field] = $pColl;
break;
} }
} }
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
if (isset($class->lifecycleCallbacks[Events::postLoad])) {
$class->invokeLifecycleCallbacks(Events::postLoad, $entity);
}
if ($this->evm->hasListeners(Events::postLoad)) {
$this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em));
}
return $entity; return $entity;
} }
@ -2564,7 +2652,7 @@ class UnitOfWork implements PropertyChangedListener
* *
* @param string $entityName The name of the Entity. * @param string $entityName The name of the Entity.
* *
* @return Doctrine\ORM\Persisters\AbstractEntityPersister * @return Doctrine\ORM\Persisters\BasicEntityPersister
*/ */
public function getEntityPersister($entityName) public function getEntityPersister($entityName)
{ {

View File

@ -92,4 +92,10 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(1, count($tours)); $this->assertEquals(1, count($tours));
} }
public function testSpecifiyUnknownIdentifierPrimaryKeyFails()
{
$this->setExpectedException('Doctrine\ORM\ORMException', 'The identifier long is missing for a query of Doctrine\Tests\Models\Navigation\NavPointOfInterest');
$poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('key1' => 100));
}
} }

View File

@ -29,7 +29,6 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup'); $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup');
$class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY; $class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY;
$this->loadFixture(); $this->loadFixture();
} }
@ -256,17 +255,18 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized.");
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount(); $queryCount = $this->getCurrentQueryCount();
$this->assertTrue($user->groups->contains($group)); $this->assertTrue($user->groups->contains($group));
$this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
$group = new \Doctrine\Tests\Models\CMS\CmsGroup(); $group = new \Doctrine\Tests\Models\CMS\CmsGroup();
$group->name = "A New group!"; $group->name = "A New group!";
$queryCount = $this->getCurrentQueryCount(); $queryCount = $this->getCurrentQueryCount();
$this->assertFalse($user->groups->contains($group)); $this->assertFalse($user->groups->contains($group));
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of new entity should cause no query to be executed."); $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of new entity should cause no query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
@ -275,8 +275,9 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$queryCount = $this->getCurrentQueryCount(); $queryCount = $this->getCurrentQueryCount();
$this->assertFalse($user->groups->contains($group)); $this->assertFalse($user->groups->contains($group));
$this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
} }
@ -304,6 +305,107 @@ 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.");
} }
/**
*
*/
public function testRemoveElementOneToMany()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized.");
$article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId);
$queryCount = $this->getCurrentQueryCount();
$user->articles->removeElement($article);
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$article = new \Doctrine\Tests\Models\CMS\CmsArticle();
$article->topic = "Testnew";
$article->text = "blub";
$queryCount = $this->getCurrentQueryCount();
$user->articles->removeElement($article);
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a new entity should cause no query to be executed.");
$this->_em->persist($article);
$this->_em->flush();
$queryCount = $this->getCurrentQueryCount();
$user->articles->removeElement($article);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed.");
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
}
/**
*
*/
public function testRemoveElementManyToMany()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized.");
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount();
$user->groups->removeElement($group);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
$group = new \Doctrine\Tests\Models\CMS\CmsGroup();
$group->name = "A New group!";
$queryCount = $this->getCurrentQueryCount();
$user->groups->removeElement($group);
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing new entity should cause no query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
$this->_em->persist($group);
$this->_em->flush();
$queryCount = $this->getCurrentQueryCount();
$user->groups->removeElement($group);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
}
/**
*
*/
public function testRemoveElementManyToManyInverse()
{
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$this->assertFalse($group->users->isInitialized(), "Pre-Condition: Collection is not initialized.");
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$queryCount = $this->getCurrentQueryCount();
$group->users->removeElement($user);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
$newUser = new \Doctrine\Tests\Models\CMS\CmsUser();
$newUser->name = "A New group!";
$queryCount = $this->getCurrentQueryCount();
$group->users->removeElement($newUser);
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a new entity should cause no query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
}
/** /**
* @group DDC-1399 * @group DDC-1399
*/ */

View File

@ -0,0 +1,146 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\UnitOfWork;
/**
* @group DDC-1509
*/
class DDC1509Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1509AbstractFile'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1509File'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1509Picture'),
));
} catch (\Exception $ignored) {
}
}
public function testFailingCase()
{
$file = new DDC1509File;
$thumbnail = new DDC1509File;
$picture = new DDC1509Picture;
$picture->setFile($file);
$picture->setThumbnail($thumbnail);
/* @var $em \Doctrine\ORM\EntityManager */
$em = $this->_em;
$em->persist($picture);
$em->flush();
$em->clear();
$id = $picture->getPictureId();
$pic = $em->merge($picture);
/* @var $pic DDC1509Picture */
$this->assertNotNull($pic->getThumbnail());
$this->assertNotNull($pic->getFile());
}
}
/**
* @Entity
*/
class DDC1509Picture
{
/**
* @Column(type="integer")
* @Id
* @GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ManyToOne(targetEntity="DDC1509AbstractFile", cascade={"persist", "remove"})
*/
private $thumbnail;
/**
* @ManyToOne(targetEntity="DDC1509AbstractFile", cascade={"persist", "remove"})
*/
private $file;
/**
* Get pictureId
*/
public function getPictureId()
{
return $this->id;
}
/**
* Set file
*/
public function setFile($value = null)
{
$this->file = $value;
}
/**
* Get file
*/
public function getFile()
{
return $this->file;
}
public function getThumbnail()
{
return $this->thumbnail;
}
public function setThumbnail($thumbnail)
{
$this->thumbnail = $thumbnail;
}
}
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"file" = "DDC1509File"})
*/
class DDC1509AbstractFile
{
/**
* @Column(type="integer")
* @Id
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* Get fileId
*/
public function getFileId()
{
return $this->id;
}
}
/**
* @Entity
*/
class DDC1509File extends DDC1509AbstractFile
{
}

View File

@ -373,6 +373,25 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
$this->assertEquals(ClassMetadataInfo::GENERATOR_TYPE_NONE, $class->generatorType); $this->assertEquals(ClassMetadataInfo::GENERATOR_TYPE_NONE, $class->generatorType);
} }
/**
* @group DDC-1170
*/
public function testIdentifierColumnDefinition()
{
$class = $this->createClassMetadata(__NAMESPACE__ . '\DDC1170Entity');
$this->assertArrayHasKey('id', $class->fieldMappings);
$this->assertArrayHasKey('value', $class->fieldMappings);
$this->assertArrayHasKey('columnDefinition', $class->fieldMappings['id']);
$this->assertArrayHasKey('columnDefinition', $class->fieldMappings['value']);
$this->assertEquals("INT unsigned NOT NULL", $class->fieldMappings['id']['columnDefinition']);
$this->assertEquals("VARCHAR(255) NOT NULL", $class->fieldMappings['value']['columnDefinition']);
}
} }
/** /**
@ -598,3 +617,64 @@ class Dog extends Animal
} }
} }
/**
* @Entity
*/
class DDC1170Entity
{
/**
* @param string $value
*/
function __construct($value = null)
{
$this->value = $value;
}
/**
* @Id
* @GeneratedValue(strategy="NONE")
* @Column(type="integer", columnDefinition = "INT unsigned NOT NULL")
**/
private $id;
/**
* @Column(columnDefinition = "VARCHAR(255) NOT NULL")
*/
private $value;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getValue()
{
return $this->value;
}
public static function loadMetadata(ClassMetadataInfo $metadata)
{
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'columnDefinition' => 'INT unsigned NOT NULL',
));
$metadata->mapField(array(
'fieldName' => 'value',
'columnDefinition' => 'VARCHAR(255) NOT NULL'
));
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);
}
}

View File

@ -9,6 +9,7 @@ use Doctrine\Tests\Mocks\ConnectionMock;
use Doctrine\Tests\Mocks\DriverMock; use Doctrine\Tests\Mocks\DriverMock;
use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Common\EventManager; use Doctrine\Common\EventManager;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
@ -82,6 +83,51 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase
$this->assertTrue($h1); $this->assertTrue($h1);
} }
/**
* @group DDC-1512
*/
public function testIsTransient()
{
$cmf = new ClassMetadataFactory();
$driver = $this->getMock('Doctrine\ORM\Mapping\Driver\Driver');
$driver->expects($this->at(0))
->method('isTransient')
->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsUser'))
->will($this->returnValue(true));
$driver->expects($this->at(1))
->method('isTransient')
->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsArticle'))
->will($this->returnValue(false));
$em = $this->_createEntityManager($driver);
$this->assertTrue($em->getMetadataFactory()->isTransient('Doctrine\Tests\Models\CMS\CmsUser'));
$this->assertFalse($em->getMetadataFactory()->isTransient('Doctrine\Tests\Models\CMS\CmsArticle'));
}
/**
* @group DDC-1512
*/
public function testIsTransientEntityNamespace()
{
$cmf = new ClassMetadataFactory();
$driver = $this->getMock('Doctrine\ORM\Mapping\Driver\Driver');
$driver->expects($this->at(0))
->method('isTransient')
->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsUser'))
->will($this->returnValue(true));
$driver->expects($this->at(1))
->method('isTransient')
->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsArticle'))
->will($this->returnValue(false));
$em = $this->_createEntityManager($driver);
$em->getConfiguration()->addEntityNamespace('CMS', 'Doctrine\Tests\Models\CMS');
$this->assertTrue($em->getMetadataFactory()->isTransient('CMS:CmsUser'));
$this->assertFalse($em->getMetadataFactory()->isTransient('CMS:CmsArticle'));
}
protected function _createEntityManager($metadataDriver) protected function _createEntityManager($metadataDriver)
{ {
$driverMock = new DriverMock(); $driverMock = new DriverMock();

View File

@ -0,0 +1,16 @@
<?php
use Doctrine\ORM\Mapping\ClassMetadataInfo;
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'columnDefinition' => 'INT unsigned NOT NULL',
));
$metadata->mapField(array(
'fieldName' => 'value',
'columnDefinition' => 'VARCHAR(255) NOT NULL'
));
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\ORM\Mapping\DDC1170Entity">
<id name="id" column-definition="INT unsigned NOT NULL">
<generator strategy="NONE"/>
</id>
<field name="value" column-definition="VARCHAR(255) NOT NULL"/>
</entity>
</doctrine-mapping>

View File

@ -0,0 +1,10 @@
Doctrine\Tests\ORM\Mapping\DDC1170Entity:
type: entity
id:
id:
columnDefinition: INT unsigned NOT NULL
generator:
strategy: NONE
fields:
value:
columnDefinition: VARCHAR(255) NOT NULL

View File

@ -40,7 +40,14 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$query->setHint($name, $value); $query->setHint($name, $value);
} }
parent::assertEquals($sqlToBeConfirmed, $query->getSQL()); $sqlGenerated = $query->getSQL();
parent::assertEquals(
$sqlToBeConfirmed,
$sqlGenerated,
sprintf('"%s" is not equal of "%s"', $sqlGenerated, $sqlToBeConfirmed)
);
$query->free(); $query->free();
} catch (\Exception $e) { } catch (\Exception $e) {
$this->fail($e->getMessage() ."\n".$e->getTraceAsString()); $this->fail($e->getMessage() ."\n".$e->getTraceAsString());
@ -444,8 +451,8 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
public function testSupportsSingleValuedInExpressionWithoutSpacesInWherePart() public function testSupportsSingleValuedInExpressionWithoutSpacesInWherePart()
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
"SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id IN(46)", "SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE IDENTITY(u.email) IN(46)",
"SELECT c0_.name AS name0 FROM cms_users c0_ WHERE c0_.id IN (46)" "SELECT c0_.name AS name0 FROM cms_users c0_ WHERE c0_.email_id IN (46)"
); );
} }
@ -460,8 +467,8 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
public function testSupportsNotInExpressionInWherePart() public function testSupportsNotInExpressionInWherePart()
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN (1)', 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :id NOT IN (1)',
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id NOT IN (1)' 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE ? NOT IN (1)'
); );
} }
@ -529,43 +536,71 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
); );
} }
public function testSupportsMemberOfExpression() public function testSupportsMemberOfExpressionOneToMany()
{ {
// "Get all users who have $phone as a phonenumber." (*cough* doesnt really make sense...) // "Get all users who have $phone as a phonenumber." (*cough* doesnt really make sense...)
$q1 = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.phonenumbers'); $q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.phonenumbers');
$q1->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
$phone = new \Doctrine\Tests\Models\CMS\CmsPhonenumber; $phone = new \Doctrine\Tests\Models\CMS\CmsPhonenumber;
$phone->phonenumber = 101; $phone->phonenumber = 101;
$q1->setParameter('param', $phone); $q->setParameter('param', $phone);
$this->assertEquals( $this->assertEquals(
'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_phonenumbers c1_ WHERE c0_.id = c1_.user_id AND c1_.phonenumber = ?)', 'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_phonenumbers c1_ WHERE c0_.id = c1_.user_id AND c1_.phonenumber = ?)',
$q1->getSql() $q->getSql()
); );
}
public function testSupportsMemberOfExpressionManyToMany()
{
// "Get all users who are members of $group." // "Get all users who are members of $group."
$q2 = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.groups'); $q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.groups');
$q2->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
$group = new \Doctrine\Tests\Models\CMS\CmsGroup; $group = new \Doctrine\Tests\Models\CMS\CmsGroup;
$group->id = 101; $group->id = 101;
$q2->setParameter('param', $group); $q->setParameter('param', $group);
$this->assertEquals( $this->assertEquals(
'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_users_groups c1_ INNER JOIN cms_groups c2_ ON c1_.group_id = c2_.id WHERE c1_.user_id = c0_.id AND c2_.id = ?)', 'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_users_groups c1_ INNER JOIN cms_groups c2_ ON c1_.group_id = c2_.id WHERE c1_.user_id = c0_.id AND c2_.id = ?)',
$q2->getSql() $q->getSql()
); );
}
public function testSupportsMemberOfExpressionSelfReferencing()
{
// "Get all persons who have $person as a friend." // "Get all persons who have $person as a friend."
// Tough one: Many-many self-referencing ("friends") with class table inheritance // Tough one: Many-many self-referencing ("friends") with class table inheritance
$q3 = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p WHERE :param MEMBER OF p.friends'); $q = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p WHERE :param MEMBER OF p.friends');
$person = new \Doctrine\Tests\Models\Company\CompanyPerson; $person = new \Doctrine\Tests\Models\Company\CompanyPerson;
$this->_em->getClassMetadata(get_class($person))->setIdentifierValues($person, array('id' => 101)); $this->_em->getClassMetadata(get_class($person))->setIdentifierValues($person, array('id' => 101));
$q3->setParameter('param', $person); $q->setParameter('param', $person);
$this->assertEquals( $this->assertEquals(
'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c2_.salary AS salary3, c2_.department AS department4, c2_.startDate AS startDate5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c1_.car_id AS car_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id WHERE EXISTS (SELECT 1 FROM company_persons_friends c3_ INNER JOIN company_persons c4_ ON c3_.friend_id = c4_.id WHERE c3_.person_id = c0_.id AND c4_.id = ?)', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c2_.salary AS salary3, c2_.department AS department4, c2_.startDate AS startDate5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c1_.car_id AS car_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id WHERE EXISTS (SELECT 1 FROM company_persons_friends c3_ INNER JOIN company_persons c4_ ON c3_.friend_id = c4_.id WHERE c3_.person_id = c0_.id AND c4_.id = ?)',
$q3->getSql() $q->getSql()
);
}
public function testSupportsMemberOfWithSingleValuedAssociation()
{
// Impossible example, but it illustrates the purpose
$q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.email MEMBER OF u.groups');
$this->assertEquals(
'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_users_groups c1_ INNER JOIN cms_groups c2_ ON c1_.group_id = c2_.id WHERE c1_.user_id = c0_.id AND c2_.id = c0_.email_id)',
$q->getSql()
);
}
public function testSupportsMemberOfWithIdentificationVariable()
{
// Impossible example, but it illustrates the purpose
$q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u MEMBER OF u.groups');
$this->assertEquals(
'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_users_groups c1_ INNER JOIN cms_groups c2_ ON c1_.group_id = c2_.id WHERE c1_.user_id = c0_.id AND c2_.id = c0_.id)',
$q->getSql()
); );
} }
@ -1335,6 +1370,28 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
); );
} }
/**
* @group DDC-1236
*/
public function testGroupBySupportsResultVariable()
{
$this->assertSqlGeneration(
'SELECT u, u.status AS st FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY st',
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.status AS status4 FROM cms_users c0_ GROUP BY status4'
);
}
/**
* @group DDC-1236
*/
public function testGroupBySupportsIdentificationVariable()
{
$this->assertSqlGeneration(
'SELECT u AS user FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY user',
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ GROUP BY id0, status1, username2, name3'
);
}
public function testCustomTypeValueSql() public function testCustomTypeValueSql()
{ {
if (DBALType::hasType('negative_to_positive')) { if (DBALType::hasType('negative_to_positive')) {

View File

@ -52,6 +52,8 @@ abstract class OrmTestCase extends DoctrineTestCase
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
} }
} }
\Doctrine\Common\Annotations\AnnotationRegistry::registerFile(
__DIR__ . "/../../../lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php");
return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (array)$paths); return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (array)$paths);
} }