diff --git a/README.markdown b/README.markdown
index e279f5f06..a0b5f2a20 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,6 +1,6 @@
# Doctrine 2 ORM
-Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence
+Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2
index 76372b221..220fb39f9 100644
--- a/UPGRADE_TO_2_2
+++ b/UPGRADE_TO_2_2
@@ -1,3 +1,14 @@
+# EntityManager#getPartialReference() creates read-only entity
+
+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
+in the UnitOfWork.
+
+# 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
+will never be updated through an UPDATE statement.
+
# 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.
diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd
index 627ddf460..e8e21f193 100644
--- a/doctrine-mapping.xsd
+++ b/doctrine-mapping.xsd
@@ -150,7 +150,7 @@
-
+
diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php
index 9be5b50da..b951e355e 100644
--- a/lib/Doctrine/ORM/EntityManager.php
+++ b/lib/Doctrine/ORM/EntityManager.php
@@ -128,7 +128,7 @@ class EntityManager implements ObjectManager
$this->metadataFactory = new $metadataFactoryClassName;
$this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
-
+
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this,
$config->getProxyDir(),
@@ -203,18 +203,18 @@ class EntityManager implements ObjectManager
public function transactional(Closure $func)
{
$this->conn->beginTransaction();
-
+
try {
$return = $func($this);
-
+
$this->flush();
$this->conn->commit();
-
+
return $return ?: true;
} catch (Exception $e) {
$this->close();
$this->conn->rollback();
-
+
throw $e;
}
}
@@ -244,7 +244,7 @@ class EntityManager implements ObjectManager
*
* The class name must be the fully-qualified class name without a leading backslash
* (as it is returned by get_class($obj)) or an aliased class name.
- *
+ *
* Examples:
* MyProject\Domain\User
* sales:PriceRequest
@@ -413,6 +413,7 @@ class EntityManager implements ObjectManager
$entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
+ $this->unitOfWork->markReadOnly($entity);
return $entity;
}
@@ -421,16 +422,11 @@ class EntityManager implements ObjectManager
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*
- * @param string $entityName
+ * @param string $entityName if given, only entities of this type will get detached
*/
public function clear($entityName = null)
{
- if ($entityName === null) {
- $this->unitOfWork->clear();
- } else {
- //TODO
- throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
- }
+ $this->unitOfWork->clear($entityName);
}
/**
@@ -449,7 +445,7 @@ class EntityManager implements ObjectManager
*
* The entity will be entered into the database at or before transaction
* commit or as a result of the flush operation.
- *
+ *
* NOTE: The persist operation always considers entities that are not yet known to
* this EntityManager as NEW. Do not pass detached entities to the persist operation.
*
@@ -632,7 +628,7 @@ class EntityManager implements ObjectManager
/**
* Check if the Entity manager is open or closed.
- *
+ *
* @return bool
*/
public function isOpen()
@@ -713,6 +709,18 @@ class EntityManager implements ObjectManager
return $this->proxyFactory;
}
+ /**
+ * Helper method to initialize a lazy loading proxy or persistent collection.
+ *
+ * This method is a no-op for other objects
+ *
+ * @param object $obj
+ */
+ public function initializeObject($obj)
+ {
+ $this->unitOfWork->initializeObject($obj);
+ }
+
/**
* Factory method to create EntityManager instances.
*
diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php
index de2689db2..9760a1c42 100644
--- a/lib/Doctrine/ORM/EntityRepository.php
+++ b/lib/Doctrine/ORM/EntityRepository.php
@@ -178,7 +178,7 @@ class EntityRepository implements ObjectRepository
*/
public function findOneBy(array $criteria)
{
- return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria);
+ return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria, null, null, array(), 0, 1);
}
/**
@@ -204,8 +204,7 @@ class EntityRepository implements ObjectRepository
);
}
- if ( !isset($arguments[0])) {
- // we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
+ if (empty($arguments)) {
throw ORMException::findByRequiresParameter($method.$by);
}
diff --git a/lib/Doctrine/ORM/Event/EntityEventDelegator.php b/lib/Doctrine/ORM/Event/EntityEventDelegator.php
new file mode 100644
index 000000000..d7c46e68e
--- /dev/null
+++ b/lib/Doctrine/ORM/Event/EntityEventDelegator.php
@@ -0,0 +1,110 @@
+.
+*/
+
+namespace Doctrine\ORM\Event;
+
+use \Doctrine\Common\EventSubscriber;
+use \LogicException;
+
+/**
+ * Delegate events only for certain entities they are registered for.
+ *
+ * @author Benjamin Eberlei
+ * @since 2.2
+ */
+class EntityEventDelegator implements EventSubscriber
+{
+ /**
+ * Keeps track of all the event listeners.
+ *
+ * @var array
+ */
+ private $listeners = array();
+
+ /**
+ * If frozen no new event listeners can be added.
+ *
+ * @var bool
+ */
+ private $frozen = false;
+
+ /**
+ * Adds an event listener that listens on the specified events.
+ *
+ * @param string|array $events The event(s) to listen on.
+ * @param string|array $entities The entities to trigger this listener for
+ * @param object $listener The listener object.
+ */
+ public function addEventListener($events, $entities, $listener)
+ {
+ if ($this->frozen) {
+ throw new LogicException("Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " .
+ "is called once. This happens when you register the delegator with the event manager.");
+ }
+
+ // Picks the hash code related to that listener
+ $hash = spl_object_hash($listener);
+
+ foreach ((array) $events as $event) {
+ // Overrides listener if a previous one was associated already
+ // Prevents duplicate listeners on same event (same instance only)
+ $this->listeners[$event][$hash] = array('listener' => $listener, 'entities' => array_flip((array)$entities));
+ }
+ }
+
+ /**
+ * Adds an EventSubscriber. The subscriber is asked for all the events he is
+ * interested in and added as a listener for these events.
+ *
+ * @param Doctrine\Common\EventSubscriber $subscriber The subscriber.
+ */
+ public function addEventSubscriber(EventSubscriber $subscriber, $entities)
+ {
+ $this->addEventListener($subscriber->getSubscribedEvents(), $entities, $subscriber);
+ }
+
+ /**
+ * Returns an array of events this subscriber wants to listen to.
+ *
+ * @return array
+ */
+ public function getSubscribedEvents()
+ {
+ $this->frozen = true;
+ return array_keys($this->listeners);
+ }
+
+ /**
+ * Delegate the event to an appropriate listener
+ *
+ * @param $eventName
+ * @param $event
+ * @return void
+ */
+ public function __call($eventName, $args)
+ {
+ $event = $args[0];
+ foreach ($this->listeners[$eventName] AS $listenerData) {
+ $class = get_class($event->getEntity());
+ if (isset($listenerData['entities'][$class])) {
+ $listenerData['listener']->$eventName($event);
+ }
+ }
+ }
+}
diff --git a/lib/Doctrine/ORM/Event/OnClearEventArgs.php b/lib/Doctrine/ORM/Event/OnClearEventArgs.php
index ad89fbc90..60ce4b3eb 100644
--- a/lib/Doctrine/ORM/Event/OnClearEventArgs.php
+++ b/lib/Doctrine/ORM/Event/OnClearEventArgs.php
@@ -36,12 +36,18 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
*/
private $em;
+ /**
+ * @var string
+ */
+ private $entityClass;
+
/**
* @param \Doctrine\ORM\EntityManager $em
*/
- public function __construct($em)
+ public function __construct($em, $entityClass = null)
{
$this->em = $em;
+ $this->entityClass = $entityClass;
}
/**
@@ -51,4 +57,24 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
{
return $this->em;
}
+
+ /**
+ * Name of the entity class that is cleared, or empty if all are cleared.
+ *
+ * @return string
+ */
+ public function getEntityClass()
+ {
+ return $this->entityClass;
+ }
+
+ /**
+ * Check if event clears all entities.
+ *
+ * @return bool
+ */
+ public function clearsAllEntities()
+ {
+ return $this->entityClass === null;
+ }
}
\ No newline at end of file
diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php
index 05c3790af..90a35fa12 100644
--- a/lib/Doctrine/ORM/Id/AssignedGenerator.php
+++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php
@@ -53,14 +53,14 @@ class AssignedGenerator extends AbstractIdGenerator
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
throw ORMException::entityMissingForeignAssignedId($entity, $value);
}
-
+
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
} else {
$identifier[$idField] = $value;
}
} else {
- throw ORMException::entityMissingAssignedId($entity);
+ throw ORMException::entityMissingAssignedIdForField($entity, $idField);
}
}
} else {
@@ -71,17 +71,17 @@ class AssignedGenerator extends AbstractIdGenerator
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
throw ORMException::entityMissingForeignAssignedId($entity, $value);
}
-
+
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
} else {
$identifier[$idField] = $value;
}
} else {
- throw ORMException::entityMissingAssignedId($entity);
+ throw ORMException::entityMissingAssignedIdForField($entity, $idField);
}
}
-
+
return $identifier;
}
-}
\ No newline at end of file
+}
diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php
index 181854e36..5899a69ca 100644
--- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php
+++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php
@@ -164,6 +164,11 @@ abstract class AbstractHydrator
* field names during this procedure as well as any necessary conversions on
* the values applied.
*
+ * @param array $data SQL Result Row
+ * @param array &$cache Cache for column to field result information
+ * @param array &$id Dql-Alias => ID-Hash
+ * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
+ *
* @return array An array with all the fields (name => value) of the data row,
* grouped by their component alias.
*/
diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
index 92eb45c5c..4b1c21c6f 100644
--- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
+++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
@@ -92,6 +92,11 @@ class ArrayHydrator extends AbstractHydrator
$parent = $this->_rsm->parentAliasMap[$dqlAlias];
$path = $parent . '.' . $dqlAlias;
+ // missing parent data, skipping as RIGHT JOIN hydration is not supported.
+ if ( ! isset($nonemptyComponents[$parent]) ) {
+ continue;
+ }
+
// Get a reference to the right element in the result tree.
// This element will get the associated element attached.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
@@ -154,6 +159,17 @@ class ArrayHydrator extends AbstractHydrator
// It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root
+
+ // if this row has a NULL value for the root result id then make it a null result.
+ if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
+ if ($this->_rsm->isMixed) {
+ $result[] = array(0 => null);
+ } else {
+ $result[] = null;
+ }
+ ++$this->_resultCounter;
+ continue;
+ }
// Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
index bb11a7431..1287a138b 100644
--- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
+++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
@@ -302,6 +302,12 @@ class ObjectHydrator extends AbstractHydrator
// seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias;
+ // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
+ if (!isset($nonemptyComponents[$parentAlias])) {
+ // TODO: Add special case code where we hydrate the right join objects into identity map at least
+ continue;
+ }
+
// Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
$first = reset($this->_resultPointers);
@@ -408,6 +414,18 @@ class ObjectHydrator extends AbstractHydrator
// PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias
+ // if this row has a NULL value for the root result id then make it a null result.
+ if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
+ if ($this->_rsm->isMixed) {
+ $result[] = array(0 => null);
+ } else {
+ $result[] = null;
+ }
+ ++$this->_resultCounter;
+ continue;
+ }
+
+ // check for existing result from the iterations before
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
index 4b43e3070..239022b0f 100644
--- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
+++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
@@ -308,6 +308,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
if ($parent && $parent->isInheritanceTypeSingleTable()) {
$class->setPrimaryTable($parent->table);
}
+
+ if ($parent && $parent->containsForeignIdentifier) {
+ $class->containsForeignIdentifier = true;
+ }
$class->setParentClasses($visited);
@@ -490,6 +494,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
*/
public function isTransient($class)
{
+ if ( ! $this->initialized) {
+ $this->initialize();
+ }
+
return $this->driver->isTransient($class);
}
}
diff --git a/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php
new file mode 100644
index 000000000..e60eab779
--- /dev/null
+++ b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php
@@ -0,0 +1,176 @@
+.
+*/
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\ORM\Mapping\MappingException;
+
+/**
+ * XmlDriver that additionally looks for mapping information in a global file.
+ *
+ * @author Fabien Potencier
+ * @author Benjamin Eberlei
+ * @license MIT
+ */
+class SimplifiedXmlDriver extends XmlDriver
+{
+ protected $_prefixes = array();
+ protected $_globalBasename;
+ protected $_classCache;
+ protected $_fileExtension = '.orm.xml';
+
+ public function __construct($prefixes)
+ {
+ $this->addNamespacePrefixes($prefixes);
+ }
+
+ public function setGlobalBasename($file)
+ {
+ $this->_globalBasename = $file;
+ }
+
+ public function getGlobalBasename()
+ {
+ return $this->_globalBasename;
+ }
+
+ public function addNamespacePrefixes($prefixes)
+ {
+ $this->_prefixes = array_merge($this->_prefixes, $prefixes);
+ $this->addPaths(array_flip($prefixes));
+ }
+
+ public function getNamespacePrefixes()
+ {
+ return $this->_prefixes;
+ }
+
+ public function isTransient($className)
+ {
+ if (null === $this->_classCache) {
+ $this->initialize();
+ }
+
+ // The mapping is defined in the global mapping file
+ if (isset($this->_classCache[$className])) {
+ return false;
+ }
+
+ try {
+ $this->_findMappingFile($className);
+
+ return false;
+ } catch (MappingException $e) {
+ return true;
+ }
+ }
+
+ public function getAllClassNames()
+ {
+ if (null === $this->_classCache) {
+ $this->initialize();
+ }
+
+ $classes = array();
+
+ if ($this->_paths) {
+ foreach ((array) $this->_paths as $path) {
+ if (!is_dir($path)) {
+ throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
+ }
+
+ $iterator = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($path),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($iterator as $file) {
+ $fileName = $file->getBasename($this->_fileExtension);
+
+ if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) {
+ continue;
+ }
+
+ // NOTE: All files found here means classes are not transient!
+ if (isset($this->_prefixes[$path])) {
+ $classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName);
+ } else {
+ $classes[] = str_replace('.', '\\', $fileName);
+ }
+ }
+ }
+ }
+
+ return array_merge($classes, array_keys($this->_classCache));
+ }
+
+ public function getElement($className)
+ {
+ if (null === $this->_classCache) {
+ $this->initialize();
+ }
+
+ if (!isset($this->_classCache[$className])) {
+ $this->_classCache[$className] = parent::getElement($className);
+ }
+
+ return $this->_classCache[$className];
+ }
+
+ protected function initialize()
+ {
+ $this->_classCache = array();
+ if (null !== $this->_globalBasename) {
+ foreach ($this->_paths as $path) {
+ if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) {
+ $this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file));
+ }
+ }
+ }
+ }
+
+ protected function _findMappingFile($className)
+ {
+ $defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension;
+ foreach ($this->_paths as $path) {
+ if (!isset($this->_prefixes[$path])) {
+ if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) {
+ return $path.DIRECTORY_SEPARATOR.$defaultFileName;
+ }
+
+ continue;
+ }
+
+ $prefix = $this->_prefixes[$path];
+
+ if (0 !== strpos($className, $prefix.'\\')) {
+ continue;
+ }
+
+ $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension;
+ if (is_file($filename)) {
+ return $filename;
+ }
+
+ throw MappingException::mappingFileNotFound($className, $filename);
+ }
+
+ throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension);
+ }
+}
diff --git a/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php
new file mode 100644
index 000000000..679ee85cc
--- /dev/null
+++ b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php
@@ -0,0 +1,182 @@
+.
+*/
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\ORM\Mapping\MappingException;
+
+/**
+ * YamlDriver that additionally looks for mapping information in a global file.
+ *
+ * @author Fabien Potencier
+ * @author Benjamin Eberlei
+ * @license MIT
+ */
+class SimplifiedYamlDriver extends YamlDriver
+{
+ protected $_prefixes = array();
+ protected $_globalBasename;
+ protected $_classCache;
+ protected $_fileExtension = '.orm.yml';
+
+ public function __construct($prefixes)
+ {
+ $this->addNamespacePrefixes($prefixes);
+ }
+
+ public function setGlobalBasename($file)
+ {
+ $this->_globalBasename = $file;
+ }
+
+ public function getGlobalBasename()
+ {
+ return $this->_globalBasename;
+ }
+
+ public function addNamespacePrefixes($prefixes)
+ {
+ $this->_prefixes = array_merge($this->_prefixes, $prefixes);
+ $this->addPaths(array_flip($prefixes));
+ }
+
+ public function addNamespacePrefix($prefix, $path)
+ {
+ $this->_prefixes[$path] = $prefix;
+ }
+
+
+ public function getNamespacePrefixes()
+ {
+ return $this->_prefixes;
+ }
+
+ public function isTransient($className)
+ {
+ if (null === $this->_classCache) {
+ $this->initialize();
+ }
+
+ // The mapping is defined in the global mapping file
+ if (isset($this->_classCache[$className])) {
+ return false;
+ }
+
+ try {
+ $this->_findMappingFile($className);
+
+ return false;
+ } catch (MappingException $e) {
+ return true;
+ }
+ }
+
+ public function getAllClassNames()
+ {
+ if (null === $this->_classCache) {
+ $this->initialize();
+ }
+
+ $classes = array();
+
+ if ($this->_paths) {
+ foreach ((array) $this->_paths as $path) {
+ if (!is_dir($path)) {
+ throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
+ }
+
+ $iterator = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($path),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($iterator as $file) {
+ $fileName = $file->getBasename($this->_fileExtension);
+
+ if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) {
+ continue;
+ }
+
+ // NOTE: All files found here means classes are not transient!
+ if (isset($this->_prefixes[$path])) {
+ $classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName);
+ } else {
+ $classes[] = str_replace('.', '\\', $fileName);
+ }
+ }
+ }
+ }
+
+ return array_merge($classes, array_keys($this->_classCache));
+ }
+
+ public function getElement($className)
+ {
+ if (null === $this->_classCache) {
+ $this->initialize();
+ }
+
+ if (!isset($this->_classCache[$className])) {
+ $this->_classCache[$className] = parent::getElement($className);
+ }
+
+ return $this->_classCache[$className];
+ }
+
+ protected function initialize()
+ {
+ $this->_classCache = array();
+ if (null !== $this->_globalBasename) {
+ foreach ($this->_paths as $path) {
+ if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) {
+ $this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file));
+ }
+ }
+ }
+ }
+
+ protected function _findMappingFile($className)
+ {
+ $defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension;
+ foreach ($this->_paths as $path) {
+ if (!isset($this->_prefixes[$path])) {
+ if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) {
+ return $path.DIRECTORY_SEPARATOR.$defaultFileName;
+ }
+
+ continue;
+ }
+
+ $prefix = $this->_prefixes[$path];
+
+ if (0 !== strpos($className, $prefix.'\\')) {
+ continue;
+ }
+
+ $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension;
+ if (is_file($filename)) {
+ return $filename;
+ }
+
+ throw MappingException::mappingFileNotFound($className, $filename);
+ }
+
+ throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension);
+ }
+}
diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php
index aa9657552..c156893c5 100644
--- a/lib/Doctrine/ORM/ORMException.php
+++ b/lib/Doctrine/ORM/ORMException.php
@@ -34,7 +34,7 @@ class ORMException extends Exception
return new self("It's a requirement to specify a Metadata Driver and pass it ".
"to Doctrine\ORM\Configuration::setMetadataDriverImpl().");
}
-
+
public static function entityMissingForeignAssignedId($entity, $relatedEntity)
{
return new self(
@@ -46,15 +46,14 @@ class ORMException extends Exception
);
}
- public static function entityMissingAssignedId($entity)
+ public static function entityMissingAssignedIdForField($entity, $field)
{
- return new self("Entity of type " . get_class($entity) . " is missing an assigned ID. " .
+ return new self("Entity of type " . get_class($entity) . " is missing an assigned ID for field '" . $field . "'. " .
"The identifier generation strategy for this entity requires the ID field to be populated before ".
- "EntityManager#persist() is called. If you want automatically generated identifiers instead " .
+ "EntityManager#persist() is called. If you want automatically generated identifiers instead " .
"you need to adjust the metadata mapping accordingly."
);
}
-
public static function unrecognizedField($field)
{
return new self("Unrecognized field: $field");
@@ -130,8 +129,8 @@ class ORMException extends Exception
"Unknown Entity namespace alias '$entityNamespaceAlias'."
);
}
-
- public static function invalidEntityRepository($className)
+
+ public static function invalidEntityRepository($className)
{
return new self("Invalid repository class '".$className."'. ".
"it must be a Doctrine\ORM\EntityRepository.");
diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
index 700532e3f..0379ccf56 100644
--- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
+++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -580,12 +580,13 @@ class BasicEntityPersister
* @param $assoc The association that connects the entity to load to another entity, if any.
* @param array $hints Hints for entity creation.
* @param int $lockMode
+ * @param int $limit Limit number of results
* @return object The loaded and managed entity instance or NULL if the entity can not be found.
* @todo Check identity map? loadById method? Try to guess whether $criteria is the id?
*/
- public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0)
+ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null)
{
- $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode);
+ $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode, $limit);
list($params, $types) = $this->expandParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types);
@@ -823,7 +824,7 @@ class BasicEntityPersister
if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
- $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]];
+ $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]];
}
$criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value;
@@ -847,7 +848,7 @@ class BasicEntityPersister
if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
- $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]];
+ $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]];
}
$criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value;
@@ -1060,10 +1061,10 @@ class BasicEntityPersister
if ($columnList) $columnList .= ', ';
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
- $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) )
- . '.' . $srcColumn . ' AS ' . $columnAlias;
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
- $this->_rsm->addMetaResult($alias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, isset($assoc['id']) && $assoc['id'] === true);
+ $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) )
+ . '.' . $srcColumn . ' AS ' . $resultColumnName;
+ $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true);
}
}
@@ -1355,7 +1356,7 @@ class BasicEntityPersister
if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
- $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]];
+ $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]];
}
$criteria[$tableAlias . "." . $targetKeyColumn] = $value;
diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php
index 8d62ca7ab..490c3a119 100644
--- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php
+++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php
@@ -209,6 +209,11 @@ class ProxyFactory
$methods .= $parameterString . ')';
$methods .= "\n" . ' {' . "\n";
+ if ($this->isShortIdentifierGetter($method, $class)) {
+ $methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
+ $methods .= ' return $this->_identifier["' . lcfirst(substr($method->getName(), 3)) . '"];' . "\n";
+ $methods .= ' }' . "\n";
+ }
$methods .= ' $this->__load();' . "\n";
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
$methods .= "\n" . ' }' . "\n";
@@ -218,6 +223,23 @@ class ProxyFactory
return $methods;
}
+ /**
+ * @param ReflectionMethod $method
+ * @param ClassMetadata $class
+ * @return bool
+ */
+ private function isShortIdentifierGetter($method, $class)
+ {
+ $identifier = lcfirst(substr($method->getName(), 3));
+ return (
+ $method->getNumberOfParameters() == 0 &&
+ substr($method->getName(), 0, 3) == "get" &&
+ in_array($identifier, $class->identifier, true) &&
+ $class->hasField($identifier) &&
+ (($method->getEndLine() - $method->getStartLine()) <= 4)
+ );
+ }
+
/**
* Generates the code for the __sleep method for a proxy class.
*
@@ -288,7 +310,7 @@ class extends \ implements \Doctrine\ORM\Proxy\Proxy
unset($this->_entityPersister, $this->_identifier);
}
}
-
+
public function __sleep()
diff --git a/lib/Doctrine/ORM/Query/Expr.php b/lib/Doctrine/ORM/Query/Expr.php
index 246f92717..6ba6a5d73 100644
--- a/lib/Doctrine/ORM/Query/Expr.php
+++ b/lib/Doctrine/ORM/Query/Expr.php
@@ -40,10 +40,12 @@ class Expr
*
* [php]
* // (u.type = ?1) AND (u.role = ?2)
- * $expr->andX('u.type = ?1', 'u.role = ?2'));
+ * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2'));
*
- * @param mixed $x Optional clause. Defaults = null, but requires
- * at least one defined when converting to string.
+ * @param Doctrine\ORM\Query\Expr\Comparison |
+ * Doctrine\ORM\Query\Expr\Func |
+ * Doctrine\ORM\Query\Expr\Orx
+ * $x Optional clause. Defaults = null, but requires at least one defined when converting to string.
* @return Expr\Andx
*/
public function andX($x = null)
diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php
index abe7e54be..abc2c210e 100644
--- a/lib/Doctrine/ORM/Query/Expr/Base.php
+++ b/lib/Doctrine/ORM/Query/Expr/Base.php
@@ -45,12 +45,14 @@ abstract class Base
{
$this->addMultiple($args);
}
-
+
public function addMultiple($args = array())
{
foreach ((array) $args as $arg) {
$this->add($arg);
}
+
+ return $this;
}
public function add($arg)
@@ -67,6 +69,8 @@ abstract class Base
$this->_parts[] = $arg;
}
+
+ return $this;
}
public function count()
@@ -79,7 +83,7 @@ abstract class Base
if ($this->count() == 1) {
return (string) $this->_parts[0];
}
-
+
return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator;
}
-}
\ No newline at end of file
+}
diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php
index 08e036c1a..9fc30bb8c 100644
--- a/lib/Doctrine/ORM/Query/Parser.php
+++ b/lib/Doctrine/ORM/Query/Parser.php
@@ -1328,18 +1328,18 @@ class Parser
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
$glimpse = $this->_lexer->glimpse();
- if ($glimpse['type'] != Lexer::T_DOT) {
- $token = $this->_lexer->lookahead;
- $identVariable = $this->IdentificationVariable();
+ if ($glimpse['type'] == Lexer::T_DOT) {
+ return $this->SingleValuedPathExpression();
+ }
+
+ $token = $this->_lexer->lookahead;
+ $identVariable = $this->IdentificationVariable();
- if (!isset($this->_queryComponents[$identVariable])) {
- $this->semanticalError('Cannot group by undefined identification variable.');
- }
-
- return $identVariable;
+ if (!isset($this->_queryComponents[$identVariable])) {
+ $this->semanticalError('Cannot group by undefined identification variable.');
}
- return $this->SingleValuedPathExpression();
+ return $identVariable;
}
/**
@@ -1354,12 +1354,9 @@ class Parser
// We need to check if we are in a ResultVariable or StateFieldPathExpression
$glimpse = $this->_lexer->glimpse();
- if ($glimpse['type'] != Lexer::T_DOT) {
- $token = $this->_lexer->lookahead;
- $expr = $this->ResultVariable();
- } else {
- $expr = $this->SingleValuedPathExpression();
- }
+ $expr = ($glimpse['type'] != Lexer::T_DOT)
+ ? $this->ResultVariable()
+ : $this->SingleValuedPathExpression();
$item = new AST\OrderByItem($expr);
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php
index 9a929c6d6..188350197 100644
--- a/lib/Doctrine/ORM/Query/SqlWalker.php
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php
@@ -1126,8 +1126,9 @@ class SqlWalker implements TreeWalker
if ( ! isset($this->_selectedClasses[$dqlAlias])) {
$this->_selectedClasses[$dqlAlias] = $class;
}
-
+
$beginning = true;
+
// Select all fields from the queried class
foreach ($class->fieldMappings as $fieldName => $mapping) {
if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
@@ -1146,6 +1147,7 @@ class SqlWalker implements TreeWalker
. ' AS ' . $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
}
@@ -1157,6 +1159,7 @@ class SqlWalker implements TreeWalker
foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName);
$sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
+
foreach ($subClass->fieldMappings as $fieldName => $mapping) {
if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
continue;
@@ -1178,8 +1181,10 @@ class SqlWalker implements TreeWalker
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
if ($beginning) $beginning = false; else $sql .= ', ';
+
$columnAlias = $this->getSQLColumnAlias($srcColumn);
$sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
+
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
}
}
@@ -1845,12 +1850,16 @@ class SqlWalker implements TreeWalker
switch ($literal->type) {
case AST\Literal::STRING:
return $this->_conn->quote($literal->value);
+
case AST\Literal::BOOLEAN:
$bool = strtolower($literal->value) == 'true' ? true : false;
$boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
- return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal;
+
+ return $boolVal;
+
case AST\Literal::NUMERIC:
return $literal->value;
+
default:
throw QueryException::invalidLiteral($literal);
}
diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php
index cc08d944f..62efbac85 100644
--- a/lib/Doctrine/ORM/Tools/EntityGenerator.php
+++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php
@@ -115,10 +115,12 @@ public function ()
*
*
* @param $
+ * @return
*/
public function ($)
{
$this-> = $;
+return $this;
}';
private static $_addMethodTemplate =
@@ -150,7 +152,7 @@ public function ()
public function __construct()
{
- if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
+ if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
$this->_annotationsPrefix = 'ORM\\';
}
}
@@ -302,9 +304,6 @@ public function ()
*/
public function setAnnotationPrefix($prefix)
{
- if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
- return;
- }
$this->_annotationsPrefix = $prefix;
}
@@ -737,7 +736,8 @@ public function ()
'' => $variableType,
'' => Inflector::camelize($fieldName),
'' => $methodName,
- '' => $fieldName
+ '' => $fieldName,
+ '' => $this->_getClassName($metadata)
);
$method = str_replace(
@@ -791,7 +791,7 @@ public function ()
}
if (isset($joinColumn['onDelete'])) {
- $joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false');
+ $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
}
if (isset($joinColumn['columnDefinition'])) {
diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
index f9668c78f..01309475b 100644
--- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
+++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
@@ -139,6 +139,9 @@ class XmlExporter extends AbstractExporter
if (isset($field['columnName'])) {
$idXml->addAttribute('column', $field['columnName']);
}
+ if (isset($field['associationKey']) && $field['associationKey']) {
+ $idXml->addAttribute('association-key', 'true');
+ }
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$generatorXml = $idXml->addChild('generator');
$generatorXml->addAttribute('strategy', $idGeneratorType);
diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php
index 8acaa0b54..a7f8e3a1c 100644
--- a/lib/Doctrine/ORM/Tools/SchemaValidator.php
+++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php
@@ -152,6 +152,17 @@ class SchemaValidator
"has to be a primary key column.";
}
}
+
+ if (count($targetClass->identifier) != count($assoc['joinTable']['inverseJoinColumns'])) {
+ $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " .
+ "have to match to ALL identifier columns of the target entity '". $targetClass->name . "'";
+ }
+
+ if (count($class->identifier) != count($assoc['joinTable']['joinColumns'])) {
+ $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " .
+ "have to match to ALL identifier columns of the source entity '". $class->name . "'";
+ }
+
} else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) {
foreach ($assoc['joinColumns'] AS $joinColumn) {
$targetClass = $cmf->getMetadataFor($assoc['targetEntity']);
@@ -167,6 +178,11 @@ class SchemaValidator
"has to be a primary key column.";
}
}
+
+ if (count($class->identifier) != count($assoc['joinColumns'])) {
+ $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " .
+ "have to match to ALL identifier columns of the source entity '". $class->name . "'";
+ }
}
}
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index ea516f3d0..8f5977e5d 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -215,8 +215,13 @@ class UnitOfWork implements PropertyChangedListener
* @var array
*/
private $orphanRemovals = array();
-
- //private $_readOnlyObjects = array();
+
+ /**
+ * Read-Only objects are never evaluated
+ *
+ * @var array
+ */
+ private $readOnlyObjects = array();
/**
* Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
@@ -393,6 +398,8 @@ class UnitOfWork implements PropertyChangedListener
* If a PersistentCollection has been de-referenced in a fully MANAGED entity,
* then this collection is marked for deletion.
*
+ * @ignore
+ * @internal Don't call from the outside.
* @param ClassMetadata $class The class descriptor of the entity.
* @param object $entity The entity for which to compute the changes.
*/
@@ -403,6 +410,11 @@ class UnitOfWork implements PropertyChangedListener
}
$oid = spl_object_hash($entity);
+
+ if (isset($this->readOnlyObjects[$oid])) {
+ return;
+ }
+
$actualData = array();
foreach ($class->reflFields as $name => $refProp) {
$value = $refProp->getValue($entity);
@@ -458,7 +470,15 @@ class UnitOfWork implements PropertyChangedListener
$changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array();
foreach ($actualData as $propName => $actualValue) {
- $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
+ if (isset($originalData[$propName])) {
+ $orgValue = $originalData[$propName];
+ } else if (array_key_exists($propName, $originalData)) {
+ $orgValue = null;
+ } else {
+ // skip field, its a partially omitted one!
+ continue;
+ }
+
if (isset($class->associationMappings[$propName])) {
$assoc = $class->associationMappings[$propName];
if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) {
@@ -470,8 +490,9 @@ class UnitOfWork implements PropertyChangedListener
}
} else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) {
// A PersistentCollection was de-referenced, so delete it.
- if ( ! in_array($orgValue, $this->collectionDeletions, true)) {
- $this->collectionDeletions[] = $orgValue;
+ $coid = spl_object_hash($orgValue);
+ if ( ! isset($this->collectionDeletions[$coid]) ) {
+ $this->collectionDeletions[$coid] = $orgValue;
$changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored.
}
}
@@ -528,7 +549,7 @@ class UnitOfWork implements PropertyChangedListener
foreach ($entitiesToProcess as $entity) {
// Ignore uninitialized proxy objects
- if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) {
+ if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
continue;
}
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here.
@@ -549,10 +570,11 @@ class UnitOfWork implements PropertyChangedListener
private function computeAssociationChanges($assoc, $value)
{
if ($value instanceof PersistentCollection && $value->isDirty()) {
+ $coid = spl_object_hash($value);
if ($assoc['isOwningSide']) {
- $this->collectionUpdates[] = $value;
+ $this->collectionUpdates[$coid] = $value;
}
- $this->visitedCollections[] = $value;
+ $this->visitedCollections[$coid] = $value;
}
// Look through the entities, and in any of their associations, for transient (new)
@@ -635,7 +657,7 @@ class UnitOfWork implements PropertyChangedListener
* @param object $entity The entity for which to (re)calculate the change set.
* @throws InvalidArgumentException If the passed entity is not MANAGED.
*/
- public function recomputeSingleEntityChangeSet($class, $entity)
+ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
{
$oid = spl_object_hash($entity);
@@ -853,6 +875,7 @@ class UnitOfWork implements PropertyChangedListener
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
if ( ! $calc->hasClass($targetClass->name)) {
$calc->addClass($targetClass);
+ $newNodes[] = $targetClass;
}
$calc->addDependency($targetClass, $class);
// If the target class has mapped subclasses,
@@ -1452,11 +1475,17 @@ class UnitOfWork implements PropertyChangedListener
if ($this->getEntityState($other, self::STATE_DETACHED) == self::STATE_MANAGED) {
$prop->setValue($managedCopy, $other);
} else {
+
$targetClass = $this->em->getClassMetadata($assoc2['targetEntity']);
- $id = $targetClass->getIdentifierValues($other);
- $proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $id);
- $prop->setValue($managedCopy, $proxy);
- $this->registerManaged($proxy, $id, array());
+ $relatedId = $targetClass->getIdentifierValues($other);
+
+ if ($targetClass->subClasses) {
+ $entity = $this->em->find($targetClass->name, $relatedId);
+ } else {
+ $proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId);
+ $prop->setValue($managedCopy, $proxy);
+ $this->registerManaged($proxy, $relatedId, array());
+ }
}
}
} else {
@@ -1538,8 +1567,9 @@ class UnitOfWork implements PropertyChangedListener
*
* @param object $entity
* @param array $visited
+ * @param boolean $noCascade if true, don't cascade detach operation
*/
- private function doDetach($entity, array &$visited)
+ private function doDetach($entity, array &$visited, $noCascade = false)
{
$oid = spl_object_hash($entity);
if (isset($visited[$oid])) {
@@ -1561,8 +1591,10 @@ class UnitOfWork implements PropertyChangedListener
case self::STATE_DETACHED:
return;
}
-
- $this->cascadeDetach($entity, $visited);
+
+ if (!$noCascade) {
+ $this->cascadeDetach($entity, $visited);
+ }
}
/**
@@ -1813,28 +1845,41 @@ class UnitOfWork implements PropertyChangedListener
/**
* Clears the UnitOfWork.
+ *
+ * @param string $entityName if given, only entities of this type will get detached
*/
- public function clear()
+ public function clear($entityName = null)
{
- $this->identityMap =
- $this->entityIdentifiers =
- $this->originalEntityData =
- $this->entityChangeSets =
- $this->entityStates =
- $this->scheduledForDirtyCheck =
- $this->entityInsertions =
- $this->entityUpdates =
- $this->entityDeletions =
- $this->collectionDeletions =
- $this->collectionUpdates =
- $this->extraUpdates =
- $this->orphanRemovals = array();
- if ($this->commitOrderCalculator !== null) {
- $this->commitOrderCalculator->clear();
+ if ($entityName === null) {
+ $this->identityMap =
+ $this->entityIdentifiers =
+ $this->originalEntityData =
+ $this->entityChangeSets =
+ $this->entityStates =
+ $this->scheduledForDirtyCheck =
+ $this->entityInsertions =
+ $this->entityUpdates =
+ $this->entityDeletions =
+ $this->collectionDeletions =
+ $this->collectionUpdates =
+ $this->extraUpdates =
+ $this->orphanRemovals = array();
+ if ($this->commitOrderCalculator !== null) {
+ $this->commitOrderCalculator->clear();
+ }
+ } else {
+ $visited = array();
+ foreach ($this->identityMap as $className => $entities) {
+ if ($className === $entityName) {
+ foreach ($entities as $entity) {
+ $this->doDetach($entity, $visited, true);
+ }
+ }
+ }
}
if ($this->evm->hasListeners(Events::onClear)) {
- $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em));
+ $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName));
}
}
@@ -1862,12 +1907,12 @@ class UnitOfWork implements PropertyChangedListener
{
//TODO: if $coll is already scheduled for recreation ... what to do?
// Just remove $coll from the scheduled recreations?
- $this->collectionDeletions[] = $coll;
+ $this->collectionDeletions[spl_object_hash($coll)] = $coll;
}
public function isCollectionScheduledForDeletion(PersistentCollection $coll)
{
- return in_array($coll, $this->collectionsDeletions, true);
+ return isset( $this->collectionsDeletions[spl_object_hash($coll)] );
}
/**
@@ -2407,4 +2452,37 @@ class UnitOfWork implements PropertyChangedListener
{
return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj);
}
+
+ /**
+ * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
+ *
+ * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
+ * on this object that might be necessary to perform a correct udpate.
+ *
+ * @throws \InvalidArgumentException
+ * @param $object
+ * @return void
+ */
+ public function markReadOnly($object)
+ {
+ if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
+ throw new InvalidArgumentException("Managed entity required");
+ }
+ $this->readOnlyObjects[spl_object_hash($object)] = true;
+ }
+
+ /**
+ * Is this entity read only?
+ *
+ * @throws \InvalidArgumentException
+ * @param $object
+ * @return void
+ */
+ public function isReadOnly($object)
+ {
+ if ( ! is_object($object) ) {
+ throw new InvalidArgumentException("Managed entity required");
+ }
+ return isset($this->readOnlyObjects[spl_object_hash($object)]);
+ }
}
diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php
index f02d0441b..3296fffc8 100644
--- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php
+++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php
@@ -19,7 +19,7 @@ class CmsUser
*/
public $id;
/**
- * @Column(type="string", length=50)
+ * @Column(type="string", length=50, nullable=true)
*/
public $status;
/**
@@ -35,7 +35,7 @@ class CmsUser
*/
public $phonenumbers;
/**
- * @OneToMany(targetEntity="CmsArticle", mappedBy="user")
+ * @OneToMany(targetEntity="CmsArticle", mappedBy="user", cascade={"detach"})
*/
public $articles;
/**
@@ -48,7 +48,7 @@ class CmsUser
*/
public $email;
/**
- * @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge"})
+ * @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge", "detach"})
* @JoinTable(name="cms_users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
diff --git a/tests/Doctrine/Tests/Models/Company/CompanyContract.php b/tests/Doctrine/Tests/Models/Company/CompanyContract.php
index fd534f403..655d4fccb 100644
--- a/tests/Doctrine/Tests/Models/Company/CompanyContract.php
+++ b/tests/Doctrine/Tests/Models/Company/CompanyContract.php
@@ -7,7 +7,11 @@ namespace Doctrine\Tests\Models\Company;
* @Table(name="company_contracts")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
- * @DiscriminatorMap({"fix" = "CompanyFixContract", "flexible" = "CompanyFlexContract", "flexultra" = "CompanyFlexUltraContract"})
+ * @DiscriminatorMap({
+ * "fix" = "CompanyFixContract",
+ * "flexible" = "CompanyFlexContract",
+ * "flexultra" = "CompanyFlexUltraContract"
+ * })
*/
abstract class CompanyContract
{
diff --git a/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php b/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php
new file mode 100644
index 000000000..5c4db0101
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php
@@ -0,0 +1,91 @@
+delegator = new \Doctrine\ORM\Event\EntityEventDelegator();
+ }
+
+ public function testGetSubscribedEventsWhenEmpty()
+ {
+ $this->assertEquals(array(), $this->delegator->getSubscribedEvents());
+ }
+
+ public function testAddListener()
+ {
+ $this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener());
+ $this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents());
+ }
+
+ public function testAddSubscriber()
+ {
+ $this->delegator->addEventSubscriber(new DelegateeEventListener(), 'stdClass');
+ $this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents());
+ }
+
+ public function testAddListenerAfterFrozenThrowsException()
+ {
+ $this->delegator->getSubscribedEvents(); // freezes
+
+ $this->setExpectedException("LogicException", "Cannot add event listeners aft");
+ $this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener());
+ }
+
+ public function testDelegateEvent()
+ {
+ $delegatee = new DelegateeEventListener();
+ $this->delegator->addEventListener('postLoad', 'stdClass', $delegatee);
+
+ $event = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager());
+ $this->delegator->postLoad($event);
+ $this->delegator->postLoad($event);
+
+ $this->assertEquals(2, $delegatee->postLoad);
+ }
+
+ public function testDelegatePickEntity()
+ {
+ $delegatee = new DelegateeEventListener();
+ $this->delegator->addEventListener('postLoad', 'stdClass', $delegatee);
+
+ $event1 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager());
+ $event2 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \Doctrine\Tests\Models\CMS\CmsUser(), $this->_getTestEntityManager());
+ $this->delegator->postLoad($event1);
+ $this->delegator->postLoad($event2);
+
+ $this->assertEquals(1, $delegatee->postLoad);
+ }
+}
+
+class DelegateeEventListener implements \Doctrine\Common\EventSubscriber
+{
+ public $postLoad = 0;
+
+ public function postLoad($args)
+ {
+ $this->postLoad++;
+ }
+
+ /**
+ * Returns an array of events this subscriber wants to listen to.
+ *
+ * @return array
+ */
+ function getSubscribedEvents()
+ {
+ return array('postLoad');
+ }
+}
\ No newline at end of file
diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
index bed8de33d..a2e2d05dd 100644
--- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
@@ -863,7 +863,6 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testGetPartialReferenceToUpdateObjectWithoutLoadingIt()
{
- //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger);
$user = new CmsUser();
$user->username = "beberlei";
$user->name = "Benjamin E.";
@@ -882,7 +881,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
- $this->assertEquals('Stephan', $this->_em->find(get_class($user), $userId)->name);
+ $this->assertEquals('Benjamin E.', $this->_em->find(get_class($user), $userId)->name);
}
public function testMergePersistsNewEntities()
@@ -981,4 +980,54 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($article->user->__isInitialized__, "...but its initialized!");
$this->assertEquals($qc+2, $this->getCurrentQueryCount());
}
+
+ /**
+ * @group DDC-1278
+ */
+ public function testClearWithEntityName()
+ {
+ $user = new CmsUser;
+ $user->name = 'Dominik';
+ $user->username = 'domnikl';
+ $user->status = 'developer';
+
+ $address = new CmsAddress();
+ $address->city = "Springfield";
+ $address->zip = "12354";
+ $address->country = "Germany";
+ $address->street = "Foo Street";
+ $address->user = $user;
+ $user->address = $address;
+
+ $article1 = new CmsArticle();
+ $article1->topic = 'Foo';
+ $article1->text = 'Foo Text';
+
+ $article2 = new CmsArticle();
+ $article2->topic = 'Bar';
+ $article2->text = 'Bar Text';
+
+ $user->addArticle($article1);
+ $user->addArticle($article2);
+
+ $this->_em->persist($article1);
+ $this->_em->persist($article2);
+ $this->_em->persist($address);
+ $this->_em->persist($user);
+ $this->_em->flush();
+
+ $unitOfWork = $this->_em->getUnitOfWork();
+
+ $this->_em->clear('Doctrine\Tests\Models\CMS\CmsUser');
+
+ $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($user));
+
+ $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($address));
+ $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($article1));
+ $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($article2));
+
+ $this->_em->clear();
+
+ $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address));
+ }
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php
index 3da6bc09b..2e17ccf12 100644
--- a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php
@@ -54,7 +54,30 @@ class DefaultValuesTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($userId, $a2->getUser()->getId());
$this->assertEquals('Poweruser', $a2->getUser()->type);
}
-
+
+ /**
+ * @group DDC-1386
+ */
+ public function testGetPartialReferenceWithDefaultValueNotEvalutedInFlush()
+ {
+ $user = new DefaultValueUser;
+ $user->name = 'romanb';
+ $user->type = 'Normaluser';
+
+ $this->_em->persist($user);
+ $this->_em->flush();
+ $this->_em->clear();
+
+ $user = $this->_em->getPartialReference('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id);
+ $this->assertTrue($this->_em->getUnitOfWork()->isReadOnly($user));
+
+ $this->_em->flush();
+ $this->_em->clear();
+
+ $user = $this->_em->find('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id);
+
+ $this->assertEquals('Normaluser', $user->type);
+ }
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
index c5216fb94..4f8e11420 100644
--- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
@@ -38,18 +38,25 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user2->status = 'dev';
$this->_em->persist($user2);
+ $user3 = new CmsUser;
+ $user3->name = 'Benjamin';
+ $user3->username = 'beberlei';
+ $user3->status = null;
+ $this->_em->persist($user3);
+
$this->_em->flush();
-
+
$user1Id = $user->getId();
-
+
unset($user);
unset($user2);
-
+ unset($user3);
+
$this->_em->clear();
return $user1Id;
}
-
+
public function loadAssociatedFixture()
{
$address = new CmsAddress();
@@ -189,7 +196,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repos->findAll();
- $this->assertEquals(2, count($users));
+ $this->assertEquals(3, count($users));
}
public function testFindByAlias()
@@ -202,7 +209,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$repos = $this->_em->getRepository('CMS:CmsUser');
$users = $repos->findAll();
- $this->assertEquals(2, count($users));
+ $this->assertEquals(3, count($users));
}
/**
@@ -284,10 +291,11 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testFindMagicCallByNullValue()
{
$this->loadFixture();
+
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
- $this->setExpectedException('Doctrine\ORM\ORMException');
$users = $repos->findByStatus(null);
+ $this->assertEquals(1, count($users));
}
/**
@@ -389,7 +397,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
/**
* @group DDC-1087
*/
- public function testIsNullCriteria()
+ public function testIsNullCriteriaDoesNotGenerateAParameter()
{
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repos->findBy(array('status' => null, 'username' => 'romanb'));
@@ -399,6 +407,16 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(array('romanb'), $params);
}
+ public function testIsNullCriteria()
+ {
+ $this->loadFixture();
+
+ $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
+
+ $users = $repos->findBy(array('status' => null));
+ $this->assertEquals(1, count($users));
+ }
+
/**
* @group DDC-1094
*/
@@ -411,7 +429,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$users1 = $repos->findBy(array(), null, 1, 0);
$users2 = $repos->findBy(array(), null, 1, 1);
- $this->assertEquals(2, count($repos->findBy(array())));
+ $this->assertEquals(3, count($repos->findBy(array())));
$this->assertEquals(1, count($users1));
$this->assertEquals(1, count($users2));
$this->assertNotSame($users1[0], $users2[0]);
@@ -428,10 +446,10 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$usersAsc = $repos->findBy(array(), array("username" => "ASC"));
$usersDesc = $repos->findBy(array(), array("username" => "DESC"));
- $this->assertEquals(2, count($usersAsc), "Pre-condition: only two users in fixture");
- $this->assertEquals(2, count($usersDesc), "Pre-condition: only two users in fixture");
- $this->assertSame($usersAsc[0], $usersDesc[1]);
- $this->assertSame($usersAsc[1], $usersDesc[0]);
+ $this->assertEquals(3, count($usersAsc), "Pre-condition: only three users in fixture");
+ $this->assertEquals(3, count($usersDesc), "Pre-condition: only three users in fixture");
+ $this->assertSame($usersAsc[0], $usersDesc[2]);
+ $this->assertSame($usersAsc[2], $usersDesc[0]);
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php
index 1ddd7a8ff..2d71541d2 100644
--- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php
@@ -78,7 +78,7 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
$reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id);
$this->assertFalse($reference->postLoadCallbackInvoked);
- $reference->getId(); // trigger proxy load
+ $reference->getValue(); // trigger proxy load
$this->assertTrue($reference->postLoadCallbackInvoked);
}
@@ -210,6 +210,10 @@ class LifecycleCallbackTestEntity
return $this->id;
}
+ public function getValue() {
+ return $this->value;
+ }
+
/** @PrePersist */
public function doStuffOnPrePersist() {
$this->prePersistCallbackInvoked = true;
@@ -274,4 +278,4 @@ class LifecycleListenerPreUpdate
{
$eventArgs->setNewValue('name', 'Bob');
}
-}
\ No newline at end of file
+}
diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php
index 8ecb389af..b89f3d04e 100644
--- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php
@@ -147,4 +147,28 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($entity->wakeUp, "Loading the proxy should call __wakeup().");
}
+
+ public function testDoNotInitializeProxyOnGettingTheIdentifier()
+ {
+ $id = $this->createProduct();
+
+ /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */
+ $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id);
+
+ $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
+ $this->assertEquals($id, $entity->getId());
+ $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy.");
+ }
+
+ public function testInitializeProxyOnGettingSomethingOtherThanTheIdentifier()
+ {
+ $id = $this->createProduct();
+
+ /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */
+ $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id);
+
+ $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
+ $this->assertEquals('Doctrine Cookbook', $entity->getName());
+ $this->assertTrue($entity->__isInitialized__, "Getting something other than the identifier initializes the proxy.");
+ }
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php
index 93a89818e..467577a43 100644
--- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php
@@ -52,6 +52,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
+ // force proxy load, getId() doesn't work anymore
+ $user->getName();
$userId = $user->getId();
$this->_em->clear();
@@ -60,6 +62,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase
$user2 = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId);
+ // force proxy load, getId() doesn't work anymore
+ $user->getName();
$this->assertNull($user->getId(), "Now this is null, we already have a user instance of that type");
}
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1383Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1383Test.php
new file mode 100644
index 000000000..6a8cf483a
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1383Test.php
@@ -0,0 +1,99 @@
+_schemaTool->createSchema(array(
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1383AbstractEntity'),
+ $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1383Entity'),
+ ));
+ } catch(\Exception $ignored) {}
+ }
+
+ public function testFailingCase()
+ {
+ $parent = new DDC1383Entity();
+ $child = new DDC1383Entity();
+
+ $child->setReference($parent);
+
+ $this->_em->persist($parent);
+ $this->_em->persist($child);
+
+ $id = $child->getId();
+
+ $this->_em->flush();
+ $this->_em->clear();
+
+ // Try merging the parent entity
+ $child = $this->_em->merge($child);
+ $parent = $child->getReference();
+
+ // Parent is not instance of the abstract class
+ self::assertTrue($parent instanceof DDC1383AbstractEntity,
+ "Entity class is " . get_class($parent) . ', "DDC1383AbstractEntity" was expected');
+
+ // Parent is NOT instance of entity
+ self::assertTrue($parent instanceof DDC1383Entity,
+ "Entity class is " . get_class($parent) . ', "DDC1383Entity" was expected');
+ }
+}
+
+/**
+ * @Entity
+ * @InheritanceType("JOINED")
+ * @DiscriminatorColumn(name="discr", type="integer")
+ * @DiscriminatorMap({1 = "DDC1383Entity"})
+ */
+abstract class DDC1383AbstractEntity
+{
+ /**
+ * @Id
+ * @Column(type="integer")
+ * @GeneratedValue
+ */
+ protected $id;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id)
+ {
+ $this->id = $id;
+ }
+}
+
+/**
+ * @Entity
+ */
+class DDC1383Entity extends DDC1383AbstractEntity
+{
+ /**
+ * @ManyToOne(targetEntity="DDC1383AbstractEntity")
+ */
+ protected $reference;
+
+ public function getReference()
+ {
+ return $this->reference;
+ }
+
+ public function setReference(DDC1383AbstractEntity $reference)
+ {
+ $this->reference = $reference;
+ }
+}
\ No newline at end of file
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php
index c25a8aa75..678135daf 100644
--- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php
@@ -31,8 +31,8 @@ class DDC381Test extends \Doctrine\Tests\OrmFunctionalTestCase
$entity = $this->_em->getReference('Doctrine\Tests\ORM\Functional\Ticket\DDC381Entity', $persistedId);
- // explicitly load proxy
- $id = $entity->getId();
+ // explicitly load proxy (getId() does not trigger reload of proxy)
+ $id = $entity->getOtherMethod();
$data = serialize($entity);
$entity = unserialize($data);
@@ -55,4 +55,9 @@ class DDC381Entity
{
return $this->id;
}
+
+ public function getOtherMethod()
+ {
+
+ }
}
\ No newline at end of file
diff --git a/tests/Doctrine/Tests/ORM/Functional/TypeTest.php b/tests/Doctrine/Tests/ORM/Functional/TypeTest.php
index 25523bb80..8a2eab70e 100644
--- a/tests/Doctrine/Tests/ORM/Functional/TypeTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/TypeTest.php
@@ -37,6 +37,10 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0.1515, $decimal->highScale);
}
+ /**
+ * @group DDC-1394
+ * @return void
+ */
public function testBoolean()
{
$bool = new BooleanModel();
@@ -46,7 +50,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
- $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b";
+ $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true";
$bool = $this->_em->createQuery($dql)->getSingleResult();
$this->assertTrue($bool->booleanField);
@@ -56,7 +60,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
- $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b";
+ $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false";
$bool = $this->_em->createQuery($dql)->getSingleResult();
$this->assertFalse($bool->booleanField);
diff --git a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php
index 4fb5b92ac..a318f78af 100644
--- a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php
+++ b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php
@@ -763,4 +763,58 @@ class ArrayHydratorTest extends HydrationTestCase
$this->assertEquals(1, count($result));
}
+
+ /**
+ * @group DDC-1358
+ */
+ public function testMissingIdForRootEntity()
+ {
+ $rsm = new ResultSetMapping;
+ $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
+ $rsm->addFieldResult('u', 'u__id', 'id');
+ $rsm->addFieldResult('u', 'u__status', 'status');
+ $rsm->addScalarResult('sclr0', 'nameUpper');
+
+ // Faked result set
+ $resultSet = array(
+ //row1
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'sclr0' => 'ROMANB',
+ ),
+ array(
+ 'u__id' => null,
+ 'u__status' => null,
+ 'sclr0' => 'ROMANB',
+ ),
+ array(
+ 'u__id' => '2',
+ 'u__status' => 'developer',
+ 'sclr0' => 'JWAGE',
+ ),
+ array(
+ 'u__id' => null,
+ 'u__status' => null,
+ 'sclr0' => 'JWAGE',
+ ),
+ );
+
+ $stmt = new HydratorMockStatement($resultSet);
+ $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
+
+ $result = $hydrator->hydrateAll($stmt, $rsm);
+
+ $this->assertEquals(4, count($result), "Should hydrate four results.");
+
+ $this->assertEquals('ROMANB', $result[0]['nameUpper']);
+ $this->assertEquals('ROMANB', $result[1]['nameUpper']);
+ $this->assertEquals('JWAGE', $result[2]['nameUpper']);
+ $this->assertEquals('JWAGE', $result[3]['nameUpper']);
+
+ $this->assertEquals(array('id' => 1, 'status' => 'developer'), $result[0][0]);
+ $this->assertNull($result[1][0]);
+ $this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][0]);
+ $this->assertNull($result[3][0]);
+ }
}
\ No newline at end of file
diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php
index f2673ac70..581a3504a 100644
--- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php
+++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php
@@ -1008,4 +1008,165 @@ class ObjectHydratorTest extends HydrationTestCase
$this->assertEquals(4, count($result[1]->groups));
$this->assertEquals(2, count($result[1]->phonenumbers));
}
+
+ /**
+ * @group DDC-1358
+ */
+ public function testMissingIdForRootEntity()
+ {
+ $rsm = new ResultSetMapping;
+ $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
+ $rsm->addFieldResult('u', 'u__id', 'id');
+ $rsm->addFieldResult('u', 'u__status', 'status');
+ $rsm->addScalarResult('sclr0', 'nameUpper');
+
+ // Faked result set
+ $resultSet = array(
+ //row1
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'sclr0' => 'ROMANB',
+ ),
+ array(
+ 'u__id' => null,
+ 'u__status' => null,
+ 'sclr0' => 'ROMANB',
+ ),
+ array(
+ 'u__id' => '2',
+ 'u__status' => 'developer',
+ 'sclr0' => 'JWAGE',
+ ),
+ array(
+ 'u__id' => null,
+ 'u__status' => null,
+ 'sclr0' => 'JWAGE',
+ ),
+ );
+
+ $stmt = new HydratorMockStatement($resultSet);
+ $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em);
+
+ $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true));
+
+ $this->assertEquals(4, count($result), "Should hydrate four results.");
+
+ $this->assertEquals('ROMANB', $result[0]['nameUpper']);
+ $this->assertEquals('ROMANB', $result[1]['nameUpper']);
+ $this->assertEquals('JWAGE', $result[2]['nameUpper']);
+ $this->assertEquals('JWAGE', $result[3]['nameUpper']);
+
+ $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]);
+ $this->assertNull($result[1][0]);
+ $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]);
+ $this->assertNull($result[3][0]);
+ }
+
+ /**
+ * @group DDC-1358
+ * @return void
+ */
+ public function testMissingIdForCollectionValuedChildEntity()
+ {
+ $rsm = new ResultSetMapping;
+ $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
+ $rsm->addJoinedEntityResult(
+ 'Doctrine\Tests\Models\CMS\CmsPhonenumber',
+ 'p',
+ 'u',
+ 'phonenumbers'
+ );
+ $rsm->addFieldResult('u', 'u__id', 'id');
+ $rsm->addFieldResult('u', 'u__status', 'status');
+ $rsm->addScalarResult('sclr0', 'nameUpper');
+ $rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber');
+
+ // Faked result set
+ $resultSet = array(
+ //row1
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'sclr0' => 'ROMANB',
+ 'p__phonenumber' => '42',
+ ),
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'sclr0' => 'ROMANB',
+ 'p__phonenumber' => null
+ ),
+ array(
+ 'u__id' => '2',
+ 'u__status' => 'developer',
+ 'sclr0' => 'JWAGE',
+ 'p__phonenumber' => '91'
+ ),
+ array(
+ 'u__id' => '2',
+ 'u__status' => 'developer',
+ 'sclr0' => 'JWAGE',
+ 'p__phonenumber' => null
+ )
+ );
+
+ $stmt = new HydratorMockStatement($resultSet);
+ $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em);
+
+ $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true));
+
+ $this->assertEquals(2, count($result));
+ $this->assertEquals(1, $result[0][0]->phonenumbers->count());
+ $this->assertEquals(1, $result[1][0]->phonenumbers->count());
+ }
+
+ /**
+ * @group DDC-1358
+ */
+ public function testMissingIdForSingleValuedChildEntity()
+ {
+ $rsm = new ResultSetMapping;
+ $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
+ $rsm->addJoinedEntityResult(
+ 'Doctrine\Tests\Models\CMS\CmsAddress',
+ 'a',
+ 'u',
+ 'address'
+ );
+ $rsm->addFieldResult('u', 'u__id', 'id');
+ $rsm->addFieldResult('u', 'u__status', 'status');
+ $rsm->addScalarResult('sclr0', 'nameUpper');
+ $rsm->addFieldResult('a', 'a__id', 'id');
+ $rsm->addFieldResult('a', 'a__city', 'city');
+ $rsm->addMetaResult('a', 'user_id', 'user_id');
+
+ // Faked result set
+ $resultSet = array(
+ //row1
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'sclr0' => 'ROMANB',
+ 'a__id' => 1,
+ 'a__city' => 'Berlin',
+ ),
+ array(
+ 'u__id' => '2',
+ 'u__status' => 'developer',
+ 'sclr0' => 'BENJAMIN',
+ 'a__id' => null,
+ 'a__city' => null,
+ ),
+ );
+
+ $stmt = new HydratorMockStatement($resultSet);
+ $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em);
+
+ $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true));
+
+ $this->assertEquals(2, count($result));
+ $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0][0]->address);
+ $this->assertNull($result[1][0]->address);
+ }
}
diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php
new file mode 100644
index 000000000..d16db4fbb
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php
@@ -0,0 +1,113 @@
+.
+*/
+
+namespace Doctrine\Tests\ORM\Mapping\Symfony;
+
+/**
+ * @group DDC-1418
+ */
+abstract class AbstractDriverTest extends \PHPUnit_Framework_TestCase
+{
+ public function testFindMappingFile()
+ {
+ $driver = $this->getDriver(array(
+ 'MyNamespace\MySubnamespace\EntityFoo' => 'foo',
+ 'MyNamespace\MySubnamespace\Entity' => $this->dir,
+ ));
+
+ touch($filename = $this->dir.'/Foo'.$this->getFileExtension());
+ $this->assertEquals($filename, $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo')));
+ }
+
+ public function testFindMappingFileInSubnamespace()
+ {
+ $driver = $this->getDriver(array(
+ 'MyNamespace\MySubnamespace\Entity' => $this->dir,
+ ));
+
+ touch($filename = $this->dir.'/Foo.Bar'.$this->getFileExtension());
+ $this->assertEquals($filename, $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo\Bar')));
+ }
+
+ public function testFindMappingFileNamespacedFoundFileNotFound()
+ {
+ $this->setExpectedException(
+ 'Doctrine\ORM\Mapping\MappingException',
+ "No mapping file found named '".$this->dir."/Foo".$this->getFileExtension()."' for class 'MyNamespace\MySubnamespace\Entity\Foo'."
+ );
+
+ $driver = $this->getDriver(array(
+ 'MyNamespace\MySubnamespace\Entity' => $this->dir,
+ ));
+
+ $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo'));
+ }
+
+ public function testFindMappingNamespaceNotFound()
+ {
+ $this->setExpectedException(
+ 'Doctrine\ORM\Mapping\MappingException',
+ "No mapping file found named 'Foo".$this->getFileExtension()."' for class 'MyOtherNamespace\MySubnamespace\Entity\Foo'."
+ );
+
+ $driver = $this->getDriver(array(
+ 'MyNamespace\MySubnamespace\Entity' => $this->dir,
+ ));
+
+ $this->invoke($driver, '_findMappingFile', array('MyOtherNamespace\MySubnamespace\Entity\Foo'));
+ }
+
+ protected function setUp()
+ {
+ $this->dir = sys_get_temp_dir().'/abstract_driver_test';
+ @mkdir($this->dir, 0777, true);
+ }
+
+ protected function tearDown()
+ {
+ $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dir), \RecursiveIteratorIterator::CHILD_FIRST);
+
+ foreach ($iterator as $path) {
+ if ($path->isDir()) {
+ @rmdir($path);
+ } else {
+ @unlink($path);
+ }
+ }
+
+ @rmdir($this->dir);
+ }
+
+ abstract protected function getFileExtension();
+ abstract protected function getDriver(array $paths = array());
+
+ private function setField($obj, $field, $value)
+ {
+ $ref = new \ReflectionProperty($obj, $field);
+ $ref->setAccessible(true);
+ $ref->setValue($obj, $value);
+ }
+
+ private function invoke($obj, $method, array $args = array()) {
+ $ref = new \ReflectionMethod($obj, $method);
+ $ref->setAccessible(true);
+
+ return $ref->invokeArgs($obj, $args);
+ }
+}
diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php
new file mode 100644
index 000000000..5908b674a
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php
@@ -0,0 +1,40 @@
+.
+*/
+
+namespace Doctrine\Tests\ORM\Mapping\Symfony;
+
+use \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
+
+/**
+ * @group DDC-1418
+ */
+class XmlDriverTest extends AbstractDriverTest
+{
+ protected function getFileExtension()
+ {
+ return '.orm.xml';
+ }
+
+ protected function getDriver(array $paths = array())
+ {
+ $driver = new SimplifiedXmlDriver(array_flip($paths));
+
+ return $driver;
+ }
+}
diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php
new file mode 100644
index 000000000..c5d8d1cd1
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php
@@ -0,0 +1,40 @@
+.
+*/
+
+namespace Doctrine\Tests\ORM\Mapping\Symfony;
+
+use \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver;
+
+/**
+ * @group DDC-1418
+ */
+class YamlDriverTest extends AbstractDriverTest
+{
+ protected function getFileExtension()
+ {
+ return '.orm.yml';
+ }
+
+ protected function getDriver(array $paths = array())
+ {
+ $driver = new SimplifiedYamlDriver(array_flip($paths));
+
+ return $driver;
+ }
+}
diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php
index 6257dbe70..39bdcfe5a 100644
--- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php
+++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php
@@ -73,12 +73,13 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
+
$persister->expects($this->atLeastOnce())
->method('load')
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass))
->will($this->returnValue(new \stdClass())); // fake return of entity instance
- $proxy->getId();
$proxy->getDescription();
+ $proxy->getProduct();
}
public function testReferenceProxyRespectsMethodsParametersTypeHinting()
@@ -179,4 +180,4 @@ class SleepClass
{
return array('id');
}
-}
\ No newline at end of file
+}
diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
index 6ef886035..53002a8fc 100644
--- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
@@ -33,7 +33,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
}
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
- ->useQueryCache(false);
+ ->useQueryCache(false);
foreach ($queryHints AS $name => $value) {
$query->setHint($name, $value);
@@ -65,7 +65,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
}
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
- ->useQueryCache(false);
+ ->useQueryCache(false);
foreach ($queryHints AS $name => $value) {
$query->setHint($name, $value);
@@ -729,12 +729,12 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$this->assertSqlGeneration(
"SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true",
- "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = 'true'"
+ "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = true"
);
$this->assertSqlGeneration(
"SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false",
- "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = 'false'"
+ "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = false"
);
$this->_em->getConnection()->setDatabasePlatform($oldPlat);
@@ -1095,6 +1095,162 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
"Doctrine\ORM\Query\QueryException"
);
}
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeJoinInRootClassWithDisabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p',
+ 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id',
+ array(Query::HINT_FORCE_PARTIAL_LOAD => false)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeJoinInRootClassWithEnabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p',
+ 'SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_',
+ array(Query::HINT_FORCE_PARTIAL_LOAD => true)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeJoinInChildClassWithDisabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e',
+ 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c2_.car_id AS car_id6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id',
+ array(Query::HINT_FORCE_PARTIAL_LOAD => false)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeJoinInChildClassWithEnabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e',
+ 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c0_.discr AS discr5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id',
+ array(Query::HINT_FORCE_PARTIAL_LOAD => true)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeJoinInLeafClassWithDisabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m',
+ 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c2_.car_id AS car_id8 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id',
+ array(Query::HINT_FORCE_PARTIAL_LOAD => false)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeJoinInLeafClassWithEnabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m',
+ 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id',
+ array(Query::HINT_FORCE_PARTIAL_LOAD => true)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeSingleTableInRootClassWithDisabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c',
+ "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6, c0_.salesPerson_id AS salesPerson_id7 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')",
+ array(Query::HINT_FORCE_PARTIAL_LOAD => false)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeSingleTableInRootClassWithEnabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c',
+ "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')",
+ array(Query::HINT_FORCE_PARTIAL_LOAD => true)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeSingleTableInChildClassWithDisabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc',
+ "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')",
+ array(Query::HINT_FORCE_PARTIAL_LOAD => false)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeSingleTableInChildClassWithEnabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc',
+ "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')",
+ array(Query::HINT_FORCE_PARTIAL_LOAD => true)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeSingleTableInLeafClassWithDisabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc',
+ "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')",
+ array(Query::HINT_FORCE_PARTIAL_LOAD => false)
+ );
+ }
+
+ /**
+ * @group DDC-1389
+ */
+ public function testInheritanceTypeSingleTableInLeafClassWithEnabledForcePartialLoad()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc',
+ "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')",
+ array(Query::HINT_FORCE_PARTIAL_LOAD => true)
+ );
+ }
+
+ /**
+ * @group DDC-1161
+ */
+ public function testSelfReferenceWithOneToOneDoesNotDuplicateAlias()
+ {
+ $this->assertSqlGeneration(
+ 'SELECT p, pp FROM Doctrine\Tests\Models\Company\CompanyPerson p JOIN p.spouse pp',
+ "SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c3_.id AS id7, c3_.name AS name8, c4_.title AS title9, c4_.car_id AS car_id10, c5_.salary AS salary11, c5_.department AS department12, c5_.startDate AS startDate13, c0_.discr AS discr14, c0_.spouse_id AS spouse_id15, c3_.discr AS discr16, c3_.spouse_id AS spouse_id17 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id INNER JOIN company_persons c3_ ON c0_.spouse_id = c3_.id LEFT JOIN company_managers c4_ ON c3_.id = c4_.id LEFT JOIN company_employees c5_ ON c3_.id = c5_.id",
+ array(Query::HINT_FORCE_PARTIAL_LOAD => false)
+ );
+ }
}
diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php
index fce7d4c20..f2b298159 100644
--- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php
+++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php
@@ -21,6 +21,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->_tmpDir = \sys_get_temp_dir();
\mkdir($this->_tmpDir . \DIRECTORY_SEPARATOR . $this->_namespace);
$this->_generator = new EntityGenerator();
+ $this->_generator->setAnnotationPrefix("");
$this->_generator->setGenerateAnnotations(true);
$this->_generator->setGenerateStubMethods(true);
$this->_generator->setRegenerateEntityIfExists(false);
@@ -179,14 +180,15 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testLoadPrefixedMetadata()
{
- $this->_generator->setAnnotationPrefix('orm:');
+ $this->_generator->setAnnotationPrefix('ORM\\');
$metadata = $this->generateBookEntityFixture();
+ $reader = new \Doctrine\Common\Annotations\AnnotationReader();
+ $driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, array());
$book = $this->newInstance($metadata);
$cm = new \Doctrine\ORM\Mapping\ClassMetadata($metadata->name);
- $driver = $this->createAnnotationDriver(array(), 'orm');
$driver->loadMetadataForClass($cm->name, $cm);
$this->assertEquals($cm->columnNames, $metadata->columnNames);
diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php
index c9bfdccf0..2571a1b98 100644
--- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php
+++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php
@@ -98,6 +98,8 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
public function testExportDirectoryAndFilesAreCreated()
{
+ $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType());
+
$type = $this->_getType();
$metadataDriver = $this->_createMetadataDriver($type, __DIR__ . '/' . $type);
$em = $this->_createEntityManager($metadataDriver);
@@ -113,6 +115,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
$exporter = $cme->getExporter($type, __DIR__ . '/export/' . $type);
if ($type === 'annotation') {
$entityGenerator = new EntityGenerator();
+ $entityGenerator->setAnnotationPrefix("");
$exporter->setEntityGenerator($entityGenerator);
}
$this->_extension = $exporter->getExtension();
@@ -139,6 +142,8 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
$cmf = $this->_createClassMetadataFactory($em, $type);
$metadata = $cmf->getAllMetadata();
+ $this->assertEquals(1, count($metadata));
+
$class = current($metadata);
$this->assertEquals('Doctrine\Tests\ORM\Tools\Export\ExportedUser', $class->name);
@@ -322,8 +327,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
public function __destruct()
{
- $type = $this->_getType();
- $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType());
+# $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType());
}
protected function _deleteDirectory($path)
diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php
index 678478633..fa9938d09 100644
--- a/tests/Doctrine/Tests/OrmTestCase.php
+++ b/tests/Doctrine/Tests/OrmTestCase.php
@@ -11,6 +11,7 @@ abstract class OrmTestCase extends DoctrineTestCase
{
/** The metadata cache that is shared between all ORM tests (except functional tests). */
private static $_metadataCacheImpl = null;
+
/** The query cache that is shared between all ORM tests (except functional tests). */
private static $_queryCacheImpl = null;
@@ -66,30 +67,31 @@ abstract class OrmTestCase extends DoctrineTestCase
*/
protected function _getTestEntityManager($conn = null, $conf = null, $eventManager = null, $withSharedMetadata = true)
{
+ $metadataCache = $withSharedMetadata
+ ? self::getSharedMetadataCacheImpl()
+ : new \Doctrine\Common\Cache\ArrayCache;
+
$config = new \Doctrine\ORM\Configuration();
- if($withSharedMetadata) {
- $config->setMetadataCacheImpl(self::getSharedMetadataCacheImpl());
- } else {
- $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
- }
-
+
+ $config->setMetadataCacheImpl($metadataCache);
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver());
-
$config->setQueryCacheImpl(self::getSharedQueryCacheImpl());
$config->setProxyDir(__DIR__ . '/Proxies');
$config->setProxyNamespace('Doctrine\Tests\Proxies');
- $eventManager = new \Doctrine\Common\EventManager();
+
if ($conn === null) {
$conn = array(
- 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock',
+ 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock',
'wrapperClass' => 'Doctrine\Tests\Mocks\ConnectionMock',
- 'user' => 'john',
- 'password' => 'wayne'
+ 'user' => 'john',
+ 'password' => 'wayne'
);
}
+
if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager);
}
+
return \Doctrine\Tests\Mocks\EntityManagerMock::create($conn, $config, $eventManager);
}
@@ -98,6 +100,7 @@ abstract class OrmTestCase extends DoctrineTestCase
if (self::$_metadataCacheImpl === null) {
self::$_metadataCacheImpl = new \Doctrine\Common\Cache\ArrayCache;
}
+
return self::$_metadataCacheImpl;
}
@@ -106,6 +109,7 @@ abstract class OrmTestCase extends DoctrineTestCase
if (self::$_queryCacheImpl === null) {
self::$_queryCacheImpl = new \Doctrine\Common\Cache\ArrayCache;
}
+
return self::$_queryCacheImpl;
}
}