1
0
mirror of synced 2025-03-05 04:13:20 +03:00

Merge branch 'master' of github.com:doctrine/doctrine2

This commit is contained in:
Benjamin Eberlei 2011-10-22 10:37:32 +02:00
commit 23560038b4
47 changed files with 1744 additions and 154 deletions

View File

@ -1,6 +1,6 @@
# Doctrine 2 ORM # 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 for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility

View File

@ -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 # Removed support for onUpdate in @JoinColumn
The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed.

View File

@ -150,7 +150,7 @@
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="CASCADE"/> <xs:enumeration value="CASCADE"/>
<xs:enumeration value="RESTRICT"/> <xs:enumeration value="RESTRICT"/>
<xs:enumeration value="SET_NULL"/> <xs:enumeration value="SET NULL"/>
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>

View File

@ -128,7 +128,7 @@ class EntityManager implements ObjectManager
$this->metadataFactory = new $metadataFactoryClassName; $this->metadataFactory = new $metadataFactoryClassName;
$this->metadataFactory->setEntityManager($this); $this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this); $this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this, $this->proxyFactory = new ProxyFactory($this,
$config->getProxyDir(), $config->getProxyDir(),
@ -203,18 +203,18 @@ class EntityManager implements ObjectManager
public function transactional(Closure $func) public function transactional(Closure $func)
{ {
$this->conn->beginTransaction(); $this->conn->beginTransaction();
try { try {
$return = $func($this); $return = $func($this);
$this->flush(); $this->flush();
$this->conn->commit(); $this->conn->commit();
return $return ?: true; return $return ?: true;
} catch (Exception $e) { } catch (Exception $e) {
$this->close(); $this->close();
$this->conn->rollback(); $this->conn->rollback();
throw $e; throw $e;
} }
} }
@ -244,7 +244,7 @@ class EntityManager implements ObjectManager
* *
* The class name must be the fully-qualified class name without a leading backslash * 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. * (as it is returned by get_class($obj)) or an aliased class name.
* *
* Examples: * Examples:
* MyProject\Domain\User * MyProject\Domain\User
* sales:PriceRequest * sales:PriceRequest
@ -413,6 +413,7 @@ class EntityManager implements ObjectManager
$entity = $class->newInstance(); $entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier); $class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array()); $this->unitOfWork->registerManaged($entity, $identifier, array());
$this->unitOfWork->markReadOnly($entity);
return $entity; return $entity;
} }
@ -421,16 +422,11 @@ class EntityManager implements ObjectManager
* Clears the EntityManager. All entities that are currently managed * Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached. * 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) public function clear($entityName = null)
{ {
if ($entityName === null) { $this->unitOfWork->clear($entityName);
$this->unitOfWork->clear();
} else {
//TODO
throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
}
} }
/** /**
@ -449,7 +445,7 @@ class EntityManager implements ObjectManager
* *
* The entity will be entered into the database at or before transaction * The entity will be entered into the database at or before transaction
* commit or as a result of the flush operation. * commit or as a result of the flush operation.
* *
* NOTE: The persist operation always considers entities that are not yet known to * 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. * 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. * Check if the Entity manager is open or closed.
* *
* @return bool * @return bool
*/ */
public function isOpen() public function isOpen()
@ -713,6 +709,18 @@ class EntityManager implements ObjectManager
return $this->proxyFactory; 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. * Factory method to create EntityManager instances.
* *

View File

@ -178,7 +178,7 @@ class EntityRepository implements ObjectRepository
*/ */
public function findOneBy(array $criteria) 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])) { if (empty($arguments)) {
// we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
throw ORMException::findByRequiresParameter($method.$by); throw ORMException::findByRequiresParameter($method.$by);
} }

View File

@ -0,0 +1,110 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Event;
use \Doctrine\Common\EventSubscriber;
use \LogicException;
/**
* Delegate events only for certain entities they are registered for.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @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);
}
}
}
}

View File

@ -36,12 +36,18 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
*/ */
private $em; private $em;
/**
* @var string
*/
private $entityClass;
/** /**
* @param \Doctrine\ORM\EntityManager $em * @param \Doctrine\ORM\EntityManager $em
*/ */
public function __construct($em) public function __construct($em, $entityClass = null)
{ {
$this->em = $em; $this->em = $em;
$this->entityClass = $entityClass;
} }
/** /**
@ -51,4 +57,24 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
{ {
return $this->em; 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;
}
} }

View File

@ -53,14 +53,14 @@ class AssignedGenerator extends AbstractIdGenerator
if (!$em->getUnitOfWork()->isInIdentityMap($value)) { if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
throw ORMException::entityMissingForeignAssignedId($entity, $value); throw ORMException::entityMissingForeignAssignedId($entity, $value);
} }
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
} else { } else {
$identifier[$idField] = $value; $identifier[$idField] = $value;
} }
} else { } else {
throw ORMException::entityMissingAssignedId($entity); throw ORMException::entityMissingAssignedIdForField($entity, $idField);
} }
} }
} else { } else {
@ -71,17 +71,17 @@ class AssignedGenerator extends AbstractIdGenerator
if (!$em->getUnitOfWork()->isInIdentityMap($value)) { if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
throw ORMException::entityMissingForeignAssignedId($entity, $value); throw ORMException::entityMissingForeignAssignedId($entity, $value);
} }
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
} else { } else {
$identifier[$idField] = $value; $identifier[$idField] = $value;
} }
} else { } else {
throw ORMException::entityMissingAssignedId($entity); throw ORMException::entityMissingAssignedIdForField($entity, $idField);
} }
} }
return $identifier; return $identifier;
} }
} }

View File

@ -164,6 +164,11 @@ abstract class AbstractHydrator
* field names during this procedure as well as any necessary conversions on * field names during this procedure as well as any necessary conversions on
* the values applied. * the values applied.
* *
* @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, * @return array An array with all the fields (name => value) of the data row,
* grouped by their component alias. * grouped by their component alias.
*/ */

View File

@ -92,6 +92,11 @@ class ArrayHydrator extends AbstractHydrator
$parent = $this->_rsm->parentAliasMap[$dqlAlias]; $parent = $this->_rsm->parentAliasMap[$dqlAlias];
$path = $parent . '.' . $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. // Get a reference to the right element in the result tree.
// This element will get the associated element attached. // This element will get the associated element attached.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
@ -154,6 +159,17 @@ class ArrayHydrator extends AbstractHydrator
// It's a root result element // It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root $this->_rootAliases[$dqlAlias] = true; // Mark as root
// 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 // Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {

View File

@ -302,6 +302,12 @@ class ObjectHydrator extends AbstractHydrator
// seen for this parent-child relationship // seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias; $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. // Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
$first = reset($this->_resultPointers); $first = reset($this->_resultPointers);
@ -408,6 +414,18 @@ class ObjectHydrator extends AbstractHydrator
// PATH C: Its a root result element // PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias $this->_rootAliases[$dqlAlias] = true; // Mark as root alias
// 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]])) { if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {

View File

@ -308,6 +308,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
if ($parent && $parent->isInheritanceTypeSingleTable()) { if ($parent && $parent->isInheritanceTypeSingleTable()) {
$class->setPrimaryTable($parent->table); $class->setPrimaryTable($parent->table);
} }
if ($parent && $parent->containsForeignIdentifier) {
$class->containsForeignIdentifier = true;
}
$class->setParentClasses($visited); $class->setParentClasses($visited);
@ -490,6 +494,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
*/ */
public function isTransient($class) public function isTransient($class)
{ {
if ( ! $this->initialized) {
$this->initialize();
}
return $this->driver->isTransient($class); return $this->driver->isTransient($class);
} }
} }

View File

@ -0,0 +1,176 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\MappingException;
/**
* XmlDriver that additionally looks for mapping information in a global file.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @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);
}
}

View File

@ -0,0 +1,182 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\MappingException;
/**
* YamlDriver that additionally looks for mapping information in a global file.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @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);
}
}

View File

@ -34,7 +34,7 @@ class ORMException extends Exception
return new self("It's a requirement to specify a Metadata Driver and pass it ". return new self("It's a requirement to specify a Metadata Driver and pass it ".
"to Doctrine\ORM\Configuration::setMetadataDriverImpl()."); "to Doctrine\ORM\Configuration::setMetadataDriverImpl().");
} }
public static function entityMissingForeignAssignedId($entity, $relatedEntity) public static function entityMissingForeignAssignedId($entity, $relatedEntity)
{ {
return new self( 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 ". "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." "you need to adjust the metadata mapping accordingly."
); );
} }
public static function unrecognizedField($field) public static function unrecognizedField($field)
{ {
return new self("Unrecognized field: $field"); return new self("Unrecognized field: $field");
@ -130,8 +129,8 @@ class ORMException extends Exception
"Unknown Entity namespace alias '$entityNamespaceAlias'." "Unknown Entity namespace alias '$entityNamespaceAlias'."
); );
} }
public static function invalidEntityRepository($className) public static function invalidEntityRepository($className)
{ {
return new self("Invalid repository class '".$className."'. ". return new self("Invalid repository class '".$className."'. ".
"it must be a Doctrine\ORM\EntityRepository."); "it must be a Doctrine\ORM\EntityRepository.");

View File

@ -580,12 +580,13 @@ class BasicEntityPersister
* @param $assoc The association that connects the entity to load to another entity, if any. * @param $assoc The association that connects the entity to load to another entity, if any.
* @param array $hints Hints for entity creation. * @param array $hints Hints for entity creation.
* @param int $lockMode * @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. * @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? * @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); list($params, $types) = $this->expandParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types); $stmt = $this->_conn->executeQuery($sql, $params, $types);
@ -823,7 +824,7 @@ class BasicEntityPersister
if (isset($sourceClass->associationMappings[$field])) { if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $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; $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value;
@ -847,7 +848,7 @@ class BasicEntityPersister
if (isset($sourceClass->associationMappings[$field])) { if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $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; $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value;
@ -1060,10 +1061,10 @@ class BasicEntityPersister
if ($columnList) $columnList .= ', '; if ($columnList) $columnList .= ', ';
$columnAlias = $srcColumn . $this->_sqlAliasCounter++; $columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) )
. '.' . $srcColumn . ' AS ' . $columnAlias;
$resultColumnName = $this->_platform->getSQLResultCasing($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])) { if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $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; $criteria[$tableAlias . "." . $targetKeyColumn] = $value;

View File

@ -209,6 +209,11 @@ class ProxyFactory
$methods .= $parameterString . ')'; $methods .= $parameterString . ')';
$methods .= "\n" . ' {' . "\n"; $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 .= ' $this->__load();' . "\n";
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');'; $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
$methods .= "\n" . ' }' . "\n"; $methods .= "\n" . ' }' . "\n";
@ -218,6 +223,23 @@ class ProxyFactory
return $methods; 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. * Generates the code for the __sleep method for a proxy class.
* *
@ -288,7 +310,7 @@ class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
unset($this->_entityPersister, $this->_identifier); unset($this->_entityPersister, $this->_identifier);
} }
} }
<methods> <methods>
public function __sleep() public function __sleep()

View File

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

View File

@ -45,12 +45,14 @@ abstract class Base
{ {
$this->addMultiple($args); $this->addMultiple($args);
} }
public function addMultiple($args = array()) public function addMultiple($args = array())
{ {
foreach ((array) $args as $arg) { foreach ((array) $args as $arg) {
$this->add($arg); $this->add($arg);
} }
return $this;
} }
public function add($arg) public function add($arg)
@ -67,6 +69,8 @@ abstract class Base
$this->_parts[] = $arg; $this->_parts[] = $arg;
} }
return $this;
} }
public function count() public function count()
@ -79,7 +83,7 @@ abstract class Base
if ($this->count() == 1) { if ($this->count() == 1) {
return (string) $this->_parts[0]; return (string) $this->_parts[0];
} }
return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator; return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator;
} }
} }

View File

@ -1328,18 +1328,18 @@ class Parser
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
$glimpse = $this->_lexer->glimpse(); $glimpse = $this->_lexer->glimpse();
if ($glimpse['type'] != Lexer::T_DOT) { if ($glimpse['type'] == Lexer::T_DOT) {
$token = $this->_lexer->lookahead; return $this->SingleValuedPathExpression();
$identVariable = $this->IdentificationVariable(); }
$token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable();
if (!isset($this->_queryComponents[$identVariable])) { if (!isset($this->_queryComponents[$identVariable])) {
$this->semanticalError('Cannot group by undefined identification variable.'); $this->semanticalError('Cannot group by undefined identification variable.');
}
return $identVariable;
} }
return $this->SingleValuedPathExpression(); return $identVariable;
} }
/** /**
@ -1354,12 +1354,9 @@ class Parser
// We need to check if we are in a ResultVariable or StateFieldPathExpression // We need to check if we are in a ResultVariable or StateFieldPathExpression
$glimpse = $this->_lexer->glimpse(); $glimpse = $this->_lexer->glimpse();
if ($glimpse['type'] != Lexer::T_DOT) { $expr = ($glimpse['type'] != Lexer::T_DOT)
$token = $this->_lexer->lookahead; ? $this->ResultVariable()
$expr = $this->ResultVariable(); : $this->SingleValuedPathExpression();
} else {
$expr = $this->SingleValuedPathExpression();
}
$item = new AST\OrderByItem($expr); $item = new AST\OrderByItem($expr);

View File

@ -1126,8 +1126,9 @@ class SqlWalker implements TreeWalker
if ( ! isset($this->_selectedClasses[$dqlAlias])) { if ( ! isset($this->_selectedClasses[$dqlAlias])) {
$this->_selectedClasses[$dqlAlias] = $class; $this->_selectedClasses[$dqlAlias] = $class;
} }
$beginning = true; $beginning = true;
// Select all fields from the queried class // Select all fields from the queried class
foreach ($class->fieldMappings as $fieldName => $mapping) { foreach ($class->fieldMappings as $fieldName => $mapping) {
if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
@ -1146,6 +1147,7 @@ class SqlWalker implements TreeWalker
. ' AS ' . $columnAlias; . ' AS ' . $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
} }
@ -1157,6 +1159,7 @@ class SqlWalker implements TreeWalker
foreach ($class->subClasses as $subClassName) { foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName); $subClass = $this->_em->getClassMetadata($subClassName);
$sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
foreach ($subClass->fieldMappings as $fieldName => $mapping) { foreach ($subClass->fieldMappings as $fieldName => $mapping) {
if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
continue; continue;
@ -1178,8 +1181,10 @@ class SqlWalker implements TreeWalker
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
if ($beginning) $beginning = false; else $sql .= ', '; if ($beginning) $beginning = false; else $sql .= ', ';
$columnAlias = $this->getSQLColumnAlias($srcColumn); $columnAlias = $this->getSQLColumnAlias($srcColumn);
$sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
} }
} }
@ -1845,12 +1850,16 @@ class SqlWalker implements TreeWalker
switch ($literal->type) { switch ($literal->type) {
case AST\Literal::STRING: case AST\Literal::STRING:
return $this->_conn->quote($literal->value); return $this->_conn->quote($literal->value);
case AST\Literal::BOOLEAN: case AST\Literal::BOOLEAN:
$bool = strtolower($literal->value) == 'true' ? true : false; $bool = strtolower($literal->value) == 'true' ? true : false;
$boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool); $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal;
return $boolVal;
case AST\Literal::NUMERIC: case AST\Literal::NUMERIC:
return $literal->value; return $literal->value;
default: default:
throw QueryException::invalidLiteral($literal); throw QueryException::invalidLiteral($literal);
} }

View File

@ -115,10 +115,12 @@ public function <methodName>()
* <description> * <description>
* *
* @param <variableType>$<variableName> * @param <variableType>$<variableName>
* @return <entity>
*/ */
public function <methodName>(<methodTypeHint>$<variableName>) public function <methodName>(<methodTypeHint>$<variableName>)
{ {
<spaces>$this-><fieldName> = $<variableName>; <spaces>$this-><fieldName> = $<variableName>;
<spaces>return $this;
}'; }';
private static $_addMethodTemplate = private static $_addMethodTemplate =
@ -150,7 +152,7 @@ public function <methodName>()
public function __construct() 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\\'; $this->_annotationsPrefix = 'ORM\\';
} }
} }
@ -302,9 +304,6 @@ public function <methodName>()
*/ */
public function setAnnotationPrefix($prefix) public function setAnnotationPrefix($prefix)
{ {
if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
return;
}
$this->_annotationsPrefix = $prefix; $this->_annotationsPrefix = $prefix;
} }
@ -737,7 +736,8 @@ public function <methodName>()
'<variableType>' => $variableType, '<variableType>' => $variableType,
'<variableName>' => Inflector::camelize($fieldName), '<variableName>' => Inflector::camelize($fieldName),
'<methodName>' => $methodName, '<methodName>' => $methodName,
'<fieldName>' => $fieldName '<fieldName>' => $fieldName,
'<entity>' => $this->_getClassName($metadata)
); );
$method = str_replace( $method = str_replace(
@ -791,7 +791,7 @@ public function <methodName>()
} }
if (isset($joinColumn['onDelete'])) { if (isset($joinColumn['onDelete'])) {
$joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false'); $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
} }
if (isset($joinColumn['columnDefinition'])) { if (isset($joinColumn['columnDefinition'])) {

View File

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

View File

@ -152,6 +152,17 @@ class SchemaValidator
"has to be a primary key column."; "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) { } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) {
foreach ($assoc['joinColumns'] AS $joinColumn) { foreach ($assoc['joinColumns'] AS $joinColumn) {
$targetClass = $cmf->getMetadataFor($assoc['targetEntity']); $targetClass = $cmf->getMetadataFor($assoc['targetEntity']);
@ -167,6 +178,11 @@ class SchemaValidator
"has to be a primary key column."; "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 . "'";
}
} }
} }

View File

@ -215,8 +215,13 @@ class UnitOfWork implements PropertyChangedListener
* @var array * @var array
*/ */
private $orphanRemovals = 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. * 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, * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
* then this collection is marked for deletion. * 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 ClassMetadata $class The class descriptor of the entity.
* @param object $entity The entity for which to compute the changes. * @param object $entity The entity for which to compute the changes.
*/ */
@ -403,6 +410,11 @@ class UnitOfWork implements PropertyChangedListener
} }
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
if (isset($this->readOnlyObjects[$oid])) {
return;
}
$actualData = array(); $actualData = array();
foreach ($class->reflFields as $name => $refProp) { foreach ($class->reflFields as $name => $refProp) {
$value = $refProp->getValue($entity); $value = $refProp->getValue($entity);
@ -458,7 +470,15 @@ class UnitOfWork implements PropertyChangedListener
$changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array(); $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array();
foreach ($actualData as $propName => $actualValue) { 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])) { if (isset($class->associationMappings[$propName])) {
$assoc = $class->associationMappings[$propName]; $assoc = $class->associationMappings[$propName];
if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) { if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) {
@ -470,8 +490,9 @@ class UnitOfWork implements PropertyChangedListener
} }
} else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) { } else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) {
// A PersistentCollection was de-referenced, so delete it. // A PersistentCollection was de-referenced, so delete it.
if ( ! in_array($orgValue, $this->collectionDeletions, true)) { $coid = spl_object_hash($orgValue);
$this->collectionDeletions[] = $orgValue; if ( ! isset($this->collectionDeletions[$coid]) ) {
$this->collectionDeletions[$coid] = $orgValue;
$changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored.
} }
} }
@ -528,7 +549,7 @@ class UnitOfWork implements PropertyChangedListener
foreach ($entitiesToProcess as $entity) { foreach ($entitiesToProcess as $entity) {
// Ignore uninitialized proxy objects // Ignore uninitialized proxy objects
if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
continue; continue;
} }
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. // 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) private function computeAssociationChanges($assoc, $value)
{ {
if ($value instanceof PersistentCollection && $value->isDirty()) { if ($value instanceof PersistentCollection && $value->isDirty()) {
$coid = spl_object_hash($value);
if ($assoc['isOwningSide']) { 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) // 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. * @param object $entity The entity for which to (re)calculate the change set.
* @throws InvalidArgumentException If the passed entity is not MANAGED. * @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); $oid = spl_object_hash($entity);
@ -853,6 +875,7 @@ class UnitOfWork implements PropertyChangedListener
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
if ( ! $calc->hasClass($targetClass->name)) { if ( ! $calc->hasClass($targetClass->name)) {
$calc->addClass($targetClass); $calc->addClass($targetClass);
$newNodes[] = $targetClass;
} }
$calc->addDependency($targetClass, $class); $calc->addDependency($targetClass, $class);
// If the target class has mapped subclasses, // 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) { if ($this->getEntityState($other, self::STATE_DETACHED) == self::STATE_MANAGED) {
$prop->setValue($managedCopy, $other); $prop->setValue($managedCopy, $other);
} else { } else {
$targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']);
$id = $targetClass->getIdentifierValues($other); $relatedId = $targetClass->getIdentifierValues($other);
$proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $id);
$prop->setValue($managedCopy, $proxy); if ($targetClass->subClasses) {
$this->registerManaged($proxy, $id, array()); $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 { } else {
@ -1538,8 +1567,9 @@ class UnitOfWork implements PropertyChangedListener
* *
* @param object $entity * @param object $entity
* @param array $visited * @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); $oid = spl_object_hash($entity);
if (isset($visited[$oid])) { if (isset($visited[$oid])) {
@ -1561,8 +1591,10 @@ class UnitOfWork implements PropertyChangedListener
case self::STATE_DETACHED: case self::STATE_DETACHED:
return; return;
} }
$this->cascadeDetach($entity, $visited); if (!$noCascade) {
$this->cascadeDetach($entity, $visited);
}
} }
/** /**
@ -1813,28 +1845,41 @@ class UnitOfWork implements PropertyChangedListener
/** /**
* Clears the UnitOfWork. * 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 = if ($entityName === null) {
$this->entityIdentifiers = $this->identityMap =
$this->originalEntityData = $this->entityIdentifiers =
$this->entityChangeSets = $this->originalEntityData =
$this->entityStates = $this->entityChangeSets =
$this->scheduledForDirtyCheck = $this->entityStates =
$this->entityInsertions = $this->scheduledForDirtyCheck =
$this->entityUpdates = $this->entityInsertions =
$this->entityDeletions = $this->entityUpdates =
$this->collectionDeletions = $this->entityDeletions =
$this->collectionUpdates = $this->collectionDeletions =
$this->extraUpdates = $this->collectionUpdates =
$this->orphanRemovals = array(); $this->extraUpdates =
if ($this->commitOrderCalculator !== null) { $this->orphanRemovals = array();
$this->commitOrderCalculator->clear(); 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)) { 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? //TODO: if $coll is already scheduled for recreation ... what to do?
// Just remove $coll from the scheduled recreations? // Just remove $coll from the scheduled recreations?
$this->collectionDeletions[] = $coll; $this->collectionDeletions[spl_object_hash($coll)] = $coll;
} }
public function isCollectionScheduledForDeletion(PersistentCollection $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); 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)]);
}
} }

View File

@ -19,7 +19,7 @@ class CmsUser
*/ */
public $id; public $id;
/** /**
* @Column(type="string", length=50) * @Column(type="string", length=50, nullable=true)
*/ */
public $status; public $status;
/** /**
@ -35,7 +35,7 @@ class CmsUser
*/ */
public $phonenumbers; public $phonenumbers;
/** /**
* @OneToMany(targetEntity="CmsArticle", mappedBy="user") * @OneToMany(targetEntity="CmsArticle", mappedBy="user", cascade={"detach"})
*/ */
public $articles; public $articles;
/** /**
@ -48,7 +48,7 @@ class CmsUser
*/ */
public $email; public $email;
/** /**
* @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge"}) * @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge", "detach"})
* @JoinTable(name="cms_users_groups", * @JoinTable(name="cms_users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}

View File

@ -7,7 +7,11 @@ namespace Doctrine\Tests\Models\Company;
* @Table(name="company_contracts") * @Table(name="company_contracts")
* @InheritanceType("SINGLE_TABLE") * @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"fix" = "CompanyFixContract", "flexible" = "CompanyFlexContract", "flexultra" = "CompanyFlexUltraContract"}) * @DiscriminatorMap({
* "fix" = "CompanyFixContract",
* "flexible" = "CompanyFlexContract",
* "flexultra" = "CompanyFlexUltraContract"
* })
*/ */
abstract class CompanyContract abstract class CompanyContract
{ {

View File

@ -0,0 +1,91 @@
<?php
namespace Doctrine\Tests\ORM\Event;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* @group DDC-1415
*/
class EntityEventDelegatorTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @var \Doctrine\ORM\Event\EntityEventDelegator
*/
private $delegator;
public function setUp()
{
$this->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');
}
}

View File

@ -863,7 +863,6 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testGetPartialReferenceToUpdateObjectWithoutLoadingIt() public function testGetPartialReferenceToUpdateObjectWithoutLoadingIt()
{ {
//$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger);
$user = new CmsUser(); $user = new CmsUser();
$user->username = "beberlei"; $user->username = "beberlei";
$user->name = "Benjamin E."; $user->name = "Benjamin E.";
@ -882,7 +881,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $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() public function testMergePersistsNewEntities()
@ -981,4 +980,54 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($article->user->__isInitialized__, "...but its initialized!"); $this->assertTrue($article->user->__isInitialized__, "...but its initialized!");
$this->assertEquals($qc+2, $this->getCurrentQueryCount()); $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));
}
} }

View File

@ -54,7 +54,30 @@ class DefaultValuesTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($userId, $a2->getUser()->getId()); $this->assertEquals($userId, $a2->getUser()->getId());
$this->assertEquals('Poweruser', $a2->getUser()->type); $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);
}
} }

View File

@ -38,18 +38,25 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user2->status = 'dev'; $user2->status = 'dev';
$this->_em->persist($user2); $this->_em->persist($user2);
$user3 = new CmsUser;
$user3->name = 'Benjamin';
$user3->username = 'beberlei';
$user3->status = null;
$this->_em->persist($user3);
$this->_em->flush(); $this->_em->flush();
$user1Id = $user->getId(); $user1Id = $user->getId();
unset($user); unset($user);
unset($user2); unset($user2);
unset($user3);
$this->_em->clear(); $this->_em->clear();
return $user1Id; return $user1Id;
} }
public function loadAssociatedFixture() public function loadAssociatedFixture()
{ {
$address = new CmsAddress(); $address = new CmsAddress();
@ -189,7 +196,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repos->findAll(); $users = $repos->findAll();
$this->assertEquals(2, count($users)); $this->assertEquals(3, count($users));
} }
public function testFindByAlias() public function testFindByAlias()
@ -202,7 +209,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$repos = $this->_em->getRepository('CMS:CmsUser'); $repos = $this->_em->getRepository('CMS:CmsUser');
$users = $repos->findAll(); $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() public function testFindMagicCallByNullValue()
{ {
$this->loadFixture(); $this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$this->setExpectedException('Doctrine\ORM\ORMException');
$users = $repos->findByStatus(null); $users = $repos->findByStatus(null);
$this->assertEquals(1, count($users));
} }
/** /**
@ -389,7 +397,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
/** /**
* @group DDC-1087 * @group DDC-1087
*/ */
public function testIsNullCriteria() public function testIsNullCriteriaDoesNotGenerateAParameter()
{ {
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repos->findBy(array('status' => null, 'username' => 'romanb')); $users = $repos->findBy(array('status' => null, 'username' => 'romanb'));
@ -399,6 +407,16 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(array('romanb'), $params); $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 * @group DDC-1094
*/ */
@ -411,7 +429,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$users1 = $repos->findBy(array(), null, 1, 0); $users1 = $repos->findBy(array(), null, 1, 0);
$users2 = $repos->findBy(array(), null, 1, 1); $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($users1));
$this->assertEquals(1, count($users2)); $this->assertEquals(1, count($users2));
$this->assertNotSame($users1[0], $users2[0]); $this->assertNotSame($users1[0], $users2[0]);
@ -428,10 +446,10 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$usersAsc = $repos->findBy(array(), array("username" => "ASC")); $usersAsc = $repos->findBy(array(), array("username" => "ASC"));
$usersDesc = $repos->findBy(array(), array("username" => "DESC")); $usersDesc = $repos->findBy(array(), array("username" => "DESC"));
$this->assertEquals(2, count($usersAsc), "Pre-condition: only two users in fixture"); $this->assertEquals(3, count($usersAsc), "Pre-condition: only three users in fixture");
$this->assertEquals(2, count($usersDesc), "Pre-condition: only two users in fixture"); $this->assertEquals(3, count($usersDesc), "Pre-condition: only three users in fixture");
$this->assertSame($usersAsc[0], $usersDesc[1]); $this->assertSame($usersAsc[0], $usersDesc[2]);
$this->assertSame($usersAsc[1], $usersDesc[0]); $this->assertSame($usersAsc[2], $usersDesc[0]);
} }

View File

@ -78,7 +78,7 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
$reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id); $reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id);
$this->assertFalse($reference->postLoadCallbackInvoked); $this->assertFalse($reference->postLoadCallbackInvoked);
$reference->getId(); // trigger proxy load $reference->getValue(); // trigger proxy load
$this->assertTrue($reference->postLoadCallbackInvoked); $this->assertTrue($reference->postLoadCallbackInvoked);
} }
@ -210,6 +210,10 @@ class LifecycleCallbackTestEntity
return $this->id; return $this->id;
} }
public function getValue() {
return $this->value;
}
/** @PrePersist */ /** @PrePersist */
public function doStuffOnPrePersist() { public function doStuffOnPrePersist() {
$this->prePersistCallbackInvoked = true; $this->prePersistCallbackInvoked = true;
@ -274,4 +278,4 @@ class LifecycleListenerPreUpdate
{ {
$eventArgs->setNewValue('name', 'Bob'); $eventArgs->setNewValue('name', 'Bob');
} }
} }

View File

@ -147,4 +147,28 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($entity->wakeUp, "Loading the proxy should call __wakeup()."); $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.");
}
} }

View File

@ -52,6 +52,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $this->_em->clear();
// force proxy load, getId() doesn't work anymore
$user->getName();
$userId = $user->getId(); $userId = $user->getId();
$this->_em->clear(); $this->_em->clear();
@ -60,6 +62,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase
$user2 = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); $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"); $this->assertNull($user->getId(), "Now this is null, we already have a user instance of that type");
} }
} }

View File

@ -0,0 +1,99 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\UnitOfWork;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1383
*/
class DDC1383Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_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;
}
}

View File

@ -31,8 +31,8 @@ class DDC381Test extends \Doctrine\Tests\OrmFunctionalTestCase
$entity = $this->_em->getReference('Doctrine\Tests\ORM\Functional\Ticket\DDC381Entity', $persistedId); $entity = $this->_em->getReference('Doctrine\Tests\ORM\Functional\Ticket\DDC381Entity', $persistedId);
// explicitly load proxy // explicitly load proxy (getId() does not trigger reload of proxy)
$id = $entity->getId(); $id = $entity->getOtherMethod();
$data = serialize($entity); $data = serialize($entity);
$entity = unserialize($data); $entity = unserialize($data);
@ -55,4 +55,9 @@ class DDC381Entity
{ {
return $this->id; return $this->id;
} }
public function getOtherMethod()
{
}
} }

View File

@ -37,6 +37,10 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0.1515, $decimal->highScale); $this->assertEquals(0.1515, $decimal->highScale);
} }
/**
* @group DDC-1394
* @return void
*/
public function testBoolean() public function testBoolean()
{ {
$bool = new BooleanModel(); $bool = new BooleanModel();
@ -46,7 +50,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $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(); $bool = $this->_em->createQuery($dql)->getSingleResult();
$this->assertTrue($bool->booleanField); $this->assertTrue($bool->booleanField);
@ -56,7 +60,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $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(); $bool = $this->_em->createQuery($dql)->getSingleResult();
$this->assertFalse($bool->booleanField); $this->assertFalse($bool->booleanField);

View File

@ -763,4 +763,58 @@ class ArrayHydratorTest extends HydrationTestCase
$this->assertEquals(1, count($result)); $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]);
}
} }

View File

@ -1008,4 +1008,165 @@ class ObjectHydratorTest extends HydrationTestCase
$this->assertEquals(4, count($result[1]->groups)); $this->assertEquals(4, count($result[1]->groups));
$this->assertEquals(2, count($result[1]->phonenumbers)); $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);
}
} }

View File

@ -0,0 +1,113 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\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);
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\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;
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\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;
}
}

View File

@ -73,12 +73,13 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
$persister = $this->_getMockPersister(); $persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); $this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier); $proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
$persister->expects($this->atLeastOnce()) $persister->expects($this->atLeastOnce())
->method('load') ->method('load')
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass)) ->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass))
->will($this->returnValue(new \stdClass())); // fake return of entity instance ->will($this->returnValue(new \stdClass())); // fake return of entity instance
$proxy->getId();
$proxy->getDescription(); $proxy->getDescription();
$proxy->getProduct();
} }
public function testReferenceProxyRespectsMethodsParametersTypeHinting() public function testReferenceProxyRespectsMethodsParametersTypeHinting()
@ -179,4 +180,4 @@ class SleepClass
{ {
return array('id'); return array('id');
} }
} }

View File

@ -33,7 +33,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
} }
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->useQueryCache(false); ->useQueryCache(false);
foreach ($queryHints AS $name => $value) { foreach ($queryHints AS $name => $value) {
$query->setHint($name, $value); $query->setHint($name, $value);
@ -65,7 +65,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
} }
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->useQueryCache(false); ->useQueryCache(false);
foreach ($queryHints AS $name => $value) { foreach ($queryHints AS $name => $value) {
$query->setHint($name, $value); $query->setHint($name, $value);
@ -729,12 +729,12 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$this->assertSqlGeneration( $this->assertSqlGeneration(
"SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true", "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( $this->assertSqlGeneration(
"SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false", "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); $this->_em->getConnection()->setDatabasePlatform($oldPlat);
@ -1095,6 +1095,162 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
"Doctrine\ORM\Query\QueryException" "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)
);
}
} }

View File

@ -21,6 +21,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->_tmpDir = \sys_get_temp_dir(); $this->_tmpDir = \sys_get_temp_dir();
\mkdir($this->_tmpDir . \DIRECTORY_SEPARATOR . $this->_namespace); \mkdir($this->_tmpDir . \DIRECTORY_SEPARATOR . $this->_namespace);
$this->_generator = new EntityGenerator(); $this->_generator = new EntityGenerator();
$this->_generator->setAnnotationPrefix("");
$this->_generator->setGenerateAnnotations(true); $this->_generator->setGenerateAnnotations(true);
$this->_generator->setGenerateStubMethods(true); $this->_generator->setGenerateStubMethods(true);
$this->_generator->setRegenerateEntityIfExists(false); $this->_generator->setRegenerateEntityIfExists(false);
@ -179,14 +180,15 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testLoadPrefixedMetadata() public function testLoadPrefixedMetadata()
{ {
$this->_generator->setAnnotationPrefix('orm:'); $this->_generator->setAnnotationPrefix('ORM\\');
$metadata = $this->generateBookEntityFixture(); $metadata = $this->generateBookEntityFixture();
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, array());
$book = $this->newInstance($metadata); $book = $this->newInstance($metadata);
$cm = new \Doctrine\ORM\Mapping\ClassMetadata($metadata->name); $cm = new \Doctrine\ORM\Mapping\ClassMetadata($metadata->name);
$driver = $this->createAnnotationDriver(array(), 'orm');
$driver->loadMetadataForClass($cm->name, $cm); $driver->loadMetadataForClass($cm->name, $cm);
$this->assertEquals($cm->columnNames, $metadata->columnNames); $this->assertEquals($cm->columnNames, $metadata->columnNames);

View File

@ -98,6 +98,8 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
public function testExportDirectoryAndFilesAreCreated() public function testExportDirectoryAndFilesAreCreated()
{ {
$this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType());
$type = $this->_getType(); $type = $this->_getType();
$metadataDriver = $this->_createMetadataDriver($type, __DIR__ . '/' . $type); $metadataDriver = $this->_createMetadataDriver($type, __DIR__ . '/' . $type);
$em = $this->_createEntityManager($metadataDriver); $em = $this->_createEntityManager($metadataDriver);
@ -113,6 +115,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
$exporter = $cme->getExporter($type, __DIR__ . '/export/' . $type); $exporter = $cme->getExporter($type, __DIR__ . '/export/' . $type);
if ($type === 'annotation') { if ($type === 'annotation') {
$entityGenerator = new EntityGenerator(); $entityGenerator = new EntityGenerator();
$entityGenerator->setAnnotationPrefix("");
$exporter->setEntityGenerator($entityGenerator); $exporter->setEntityGenerator($entityGenerator);
} }
$this->_extension = $exporter->getExtension(); $this->_extension = $exporter->getExtension();
@ -139,6 +142,8 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
$cmf = $this->_createClassMetadataFactory($em, $type); $cmf = $this->_createClassMetadataFactory($em, $type);
$metadata = $cmf->getAllMetadata(); $metadata = $cmf->getAllMetadata();
$this->assertEquals(1, count($metadata));
$class = current($metadata); $class = current($metadata);
$this->assertEquals('Doctrine\Tests\ORM\Tools\Export\ExportedUser', $class->name); $this->assertEquals('Doctrine\Tests\ORM\Tools\Export\ExportedUser', $class->name);
@ -322,8 +327,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
public function __destruct() public function __destruct()
{ {
$type = $this->_getType(); # $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType());
$this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType());
} }
protected function _deleteDirectory($path) protected function _deleteDirectory($path)

View File

@ -11,6 +11,7 @@ abstract class OrmTestCase extends DoctrineTestCase
{ {
/** The metadata cache that is shared between all ORM tests (except functional tests). */ /** The metadata cache that is shared between all ORM tests (except functional tests). */
private static $_metadataCacheImpl = null; private static $_metadataCacheImpl = null;
/** The query cache that is shared between all ORM tests (except functional tests). */ /** The query cache that is shared between all ORM tests (except functional tests). */
private static $_queryCacheImpl = null; private static $_queryCacheImpl = null;
@ -66,30 +67,31 @@ abstract class OrmTestCase extends DoctrineTestCase
*/ */
protected function _getTestEntityManager($conn = null, $conf = null, $eventManager = null, $withSharedMetadata = true) 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(); $config = new \Doctrine\ORM\Configuration();
if($withSharedMetadata) {
$config->setMetadataCacheImpl(self::getSharedMetadataCacheImpl()); $config->setMetadataCacheImpl($metadataCache);
} else {
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
}
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver()); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver());
$config->setQueryCacheImpl(self::getSharedQueryCacheImpl()); $config->setQueryCacheImpl(self::getSharedQueryCacheImpl());
$config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyDir(__DIR__ . '/Proxies');
$config->setProxyNamespace('Doctrine\Tests\Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies');
$eventManager = new \Doctrine\Common\EventManager();
if ($conn === null) { if ($conn === null) {
$conn = array( $conn = array(
'driverClass' => 'Doctrine\Tests\Mocks\DriverMock', 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock',
'wrapperClass' => 'Doctrine\Tests\Mocks\ConnectionMock', 'wrapperClass' => 'Doctrine\Tests\Mocks\ConnectionMock',
'user' => 'john', 'user' => 'john',
'password' => 'wayne' 'password' => 'wayne'
); );
} }
if (is_array($conn)) { if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager); $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager);
} }
return \Doctrine\Tests\Mocks\EntityManagerMock::create($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) { if (self::$_metadataCacheImpl === null) {
self::$_metadataCacheImpl = new \Doctrine\Common\Cache\ArrayCache; self::$_metadataCacheImpl = new \Doctrine\Common\Cache\ArrayCache;
} }
return self::$_metadataCacheImpl; return self::$_metadataCacheImpl;
} }
@ -106,6 +109,7 @@ abstract class OrmTestCase extends DoctrineTestCase
if (self::$_queryCacheImpl === null) { if (self::$_queryCacheImpl === null) {
self::$_queryCacheImpl = new \Doctrine\Common\Cache\ArrayCache; self::$_queryCacheImpl = new \Doctrine\Common\Cache\ArrayCache;
} }
return self::$_queryCacheImpl; return self::$_queryCacheImpl;
} }
} }