diff --git a/README.markdown b/README.markdown index e279f5f06..a0b5f2a20 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,6 @@ # Doctrine 2 ORM -Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2 index 76372b221..220fb39f9 100644 --- a/UPGRADE_TO_2_2 +++ b/UPGRADE_TO_2_2 @@ -1,3 +1,14 @@ +# EntityManager#getPartialReference() creates read-only entity + +Entities returned from EntityManager#getPartialReference() are now marked as read-only if they +haven't been in the identity map before. This means objects of this kind never lead to changes +in the UnitOfWork. + +# Fields omitted in a partial DQL query or a native query are never updated + +Fields of an entity that are not returned from a partial DQL Query or native SQL query +will never be updated through an UPDATE statement. + # Removed support for onUpdate in @JoinColumn The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..080885107 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "doctrine/orm", + "type": "library", + "description": "Object-Relational-Mapper for PHP", + "keywords": ["orm", "database"], + "homepage": "http://www.doctrine-project.org", + "license": "LGPL", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"} + ], + "require": { + "php": ">=5.3.2", + "ext-pdo": "*", + "doctrine/common": "master-dev", + "doctrine/dbal": "master-dev" + } +} diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 9be5b50da..04a30dc46 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -413,6 +413,7 @@ class EntityManager implements ObjectManager $entity = $class->newInstance(); $class->setIdentifierValues($entity, $identifier); $this->unitOfWork->registerManaged($entity, $identifier, array()); + $this->unitOfWork->markReadOnly($entity); return $entity; } diff --git a/lib/Doctrine/ORM/Event/EntityEventDelegator.php b/lib/Doctrine/ORM/Event/EntityEventDelegator.php new file mode 100644 index 000000000..d7c46e68e --- /dev/null +++ b/lib/Doctrine/ORM/Event/EntityEventDelegator.php @@ -0,0 +1,110 @@ +. +*/ + +namespace Doctrine\ORM\Event; + +use \Doctrine\Common\EventSubscriber; +use \LogicException; + +/** + * Delegate events only for certain entities they are registered for. + * + * @author Benjamin Eberlei + * @since 2.2 + */ +class EntityEventDelegator implements EventSubscriber +{ + /** + * Keeps track of all the event listeners. + * + * @var array + */ + private $listeners = array(); + + /** + * If frozen no new event listeners can be added. + * + * @var bool + */ + private $frozen = false; + + /** + * Adds an event listener that listens on the specified events. + * + * @param string|array $events The event(s) to listen on. + * @param string|array $entities The entities to trigger this listener for + * @param object $listener The listener object. + */ + public function addEventListener($events, $entities, $listener) + { + if ($this->frozen) { + throw new LogicException("Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " . + "is called once. This happens when you register the delegator with the event manager."); + } + + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Overrides listener if a previous one was associated already + // Prevents duplicate listeners on same event (same instance only) + $this->listeners[$event][$hash] = array('listener' => $listener, 'entities' => array_flip((array)$entities)); + } + } + + /** + * Adds an EventSubscriber. The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + * + * @param Doctrine\Common\EventSubscriber $subscriber The subscriber. + */ + public function addEventSubscriber(EventSubscriber $subscriber, $entities) + { + $this->addEventListener($subscriber->getSubscribedEvents(), $entities, $subscriber); + } + + /** + * Returns an array of events this subscriber wants to listen to. + * + * @return array + */ + public function getSubscribedEvents() + { + $this->frozen = true; + return array_keys($this->listeners); + } + + /** + * Delegate the event to an appropriate listener + * + * @param $eventName + * @param $event + * @return void + */ + public function __call($eventName, $args) + { + $event = $args[0]; + foreach ($this->listeners[$eventName] AS $listenerData) { + $class = get_class($event->getEntity()); + if (isset($listenerData['entities'][$class])) { + $listenerData['listener']->$eventName($event); + } + } + } +} diff --git a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php index c61e26d4c..ab1cc15de 100644 --- a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php +++ b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php @@ -90,7 +90,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs if (!isset($this->_entityChangeSet[$field])) { throw new \InvalidArgumentException( "Field '".$field."' is not a valid field of the entity ". - "'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs." + "'".get_class($this->getEntity())."' in PreUpdateEventArgs." ); } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php new file mode 100644 index 000000000..e60eab779 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php @@ -0,0 +1,176 @@ +. +*/ + +namespace Doctrine\ORM\Mapping\Driver; + +use Doctrine\ORM\Mapping\MappingException; + +/** + * XmlDriver that additionally looks for mapping information in a global file. + * + * @author Fabien Potencier + * @author Benjamin Eberlei + * @license MIT + */ +class SimplifiedXmlDriver extends XmlDriver +{ + protected $_prefixes = array(); + protected $_globalBasename; + protected $_classCache; + protected $_fileExtension = '.orm.xml'; + + public function __construct($prefixes) + { + $this->addNamespacePrefixes($prefixes); + } + + public function setGlobalBasename($file) + { + $this->_globalBasename = $file; + } + + public function getGlobalBasename() + { + return $this->_globalBasename; + } + + public function addNamespacePrefixes($prefixes) + { + $this->_prefixes = array_merge($this->_prefixes, $prefixes); + $this->addPaths(array_flip($prefixes)); + } + + public function getNamespacePrefixes() + { + return $this->_prefixes; + } + + public function isTransient($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + // The mapping is defined in the global mapping file + if (isset($this->_classCache[$className])) { + return false; + } + + try { + $this->_findMappingFile($className); + + return false; + } catch (MappingException $e) { + return true; + } + } + + public function getAllClassNames() + { + if (null === $this->_classCache) { + $this->initialize(); + } + + $classes = array(); + + if ($this->_paths) { + foreach ((array) $this->_paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->_fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->_prefixes[$path])) { + $classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName); + } else { + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + } + + return array_merge($classes, array_keys($this->_classCache)); + } + + public function getElement($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + if (!isset($this->_classCache[$className])) { + $this->_classCache[$className] = parent::getElement($className); + } + + return $this->_classCache[$className]; + } + + protected function initialize() + { + $this->_classCache = array(); + if (null !== $this->_globalBasename) { + foreach ($this->_paths as $path) { + if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) { + $this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file)); + } + } + } + } + + protected function _findMappingFile($className) + { + $defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension; + foreach ($this->_paths as $path) { + if (!isset($this->_prefixes[$path])) { + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return $path.DIRECTORY_SEPARATOR.$defaultFileName; + } + + continue; + } + + $prefix = $this->_prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension; + if (is_file($filename)) { + return $filename; + } + + throw MappingException::mappingFileNotFound($className, $filename); + } + + throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension); + } +} diff --git a/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php new file mode 100644 index 000000000..679ee85cc --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php @@ -0,0 +1,182 @@ +. +*/ + +namespace Doctrine\ORM\Mapping\Driver; + +use Doctrine\ORM\Mapping\MappingException; + +/** + * YamlDriver that additionally looks for mapping information in a global file. + * + * @author Fabien Potencier + * @author Benjamin Eberlei + * @license MIT + */ +class SimplifiedYamlDriver extends YamlDriver +{ + protected $_prefixes = array(); + protected $_globalBasename; + protected $_classCache; + protected $_fileExtension = '.orm.yml'; + + public function __construct($prefixes) + { + $this->addNamespacePrefixes($prefixes); + } + + public function setGlobalBasename($file) + { + $this->_globalBasename = $file; + } + + public function getGlobalBasename() + { + return $this->_globalBasename; + } + + public function addNamespacePrefixes($prefixes) + { + $this->_prefixes = array_merge($this->_prefixes, $prefixes); + $this->addPaths(array_flip($prefixes)); + } + + public function addNamespacePrefix($prefix, $path) + { + $this->_prefixes[$path] = $prefix; + } + + + public function getNamespacePrefixes() + { + return $this->_prefixes; + } + + public function isTransient($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + // The mapping is defined in the global mapping file + if (isset($this->_classCache[$className])) { + return false; + } + + try { + $this->_findMappingFile($className); + + return false; + } catch (MappingException $e) { + return true; + } + } + + public function getAllClassNames() + { + if (null === $this->_classCache) { + $this->initialize(); + } + + $classes = array(); + + if ($this->_paths) { + foreach ((array) $this->_paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->_fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->_prefixes[$path])) { + $classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName); + } else { + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + } + + return array_merge($classes, array_keys($this->_classCache)); + } + + public function getElement($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + if (!isset($this->_classCache[$className])) { + $this->_classCache[$className] = parent::getElement($className); + } + + return $this->_classCache[$className]; + } + + protected function initialize() + { + $this->_classCache = array(); + if (null !== $this->_globalBasename) { + foreach ($this->_paths as $path) { + if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) { + $this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file)); + } + } + } + } + + protected function _findMappingFile($className) + { + $defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension; + foreach ($this->_paths as $path) { + if (!isset($this->_prefixes[$path])) { + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return $path.DIRECTORY_SEPARATOR.$defaultFileName; + } + + continue; + } + + $prefix = $this->_prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension; + if (is_file($filename)) { + return $filename; + } + + throw MappingException::mappingFileNotFound($className, $filename); + } + + throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension); + } +} diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index a093f9a09..a0382a04d 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -747,6 +747,7 @@ class BasicEntityPersister * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt + * * @return array */ private function loadArrayFromStatement($assoc, $stmt) @@ -771,6 +772,8 @@ class BasicEntityPersister * @param array $assoc * @param Doctrine\DBAL\Statement $stmt * @param PersistentCollection $coll + * + * @return array */ private function loadCollectionFromStatement($assoc, $stmt, $coll) { @@ -784,7 +787,8 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - $hydrator->hydrateAll($stmt, $rsm, $hints); + + return $hydrator->hydrateAll($stmt, $rsm, $hints); } /** @@ -1056,10 +1060,10 @@ class BasicEntityPersister if ($columnList) $columnList .= ', '; $columnAlias = $srcColumn . $this->_sqlAliasCounter++; - $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) - . '.' . $srcColumn . ' AS ' . $columnAlias; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult($alias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, isset($assoc['id']) && $assoc['id'] === true); + $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) + . '.' . $srcColumn . ' AS ' . $resultColumnName; + $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); } } @@ -1179,6 +1183,7 @@ class BasicEntityPersister $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); + $this->_rsm->addFieldResult($alias, $columnAlias, $field); return $sql . ' AS ' . $columnAlias; @@ -1228,7 +1233,9 @@ class BasicEntityPersister $sql = 'SELECT 1 ' . $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode) . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql; + list($params, $types) = $this->expandParameters($criteria); + $stmt = $this->_conn->executeQuery($sql, $params, $types); } @@ -1320,7 +1327,8 @@ class BasicEntityPersister public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); - $this->loadCollectionFromStatement($assoc, $stmt, $coll); + + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } /** @@ -1478,6 +1486,7 @@ class BasicEntityPersister public function exists($entity, array $extraConditions = array()) { $criteria = $this->_class->getIdentifierValues($entity); + if ($extraConditions) { $criteria = array_merge($criteria, $extraConditions); } @@ -1485,7 +1494,9 @@ class BasicEntityPersister $sql = 'SELECT 1' . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name) . ' WHERE ' . $this->_getSelectConditionSQL($criteria); - - return (bool) $this->_conn->fetchColumn($sql, array_values($criteria)); + + list($params, $types) = $this->expandParameters($criteria); + + return (bool) $this->_conn->fetchColumn($sql, $params); } } diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index c9ab27c69..b39d3bf75 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -41,6 +41,10 @@ class SingleTablePersister extends AbstractEntityInheritancePersister /** {@inheritdoc} */ protected function _getSelectColumnListSQL() { + if ($this->_selectColumnListSql !== null) { + return $this->_selectColumnListSql; + } + $columnList = parent::_getSelectColumnListSQL(); // Append discriminator column @@ -81,7 +85,8 @@ class SingleTablePersister extends AbstractEntityInheritancePersister } } - return $columnList; + $this->_selectColumnListSql = $columnList; + return $this->_selectColumnListSql; } /** {@inheritdoc} */ diff --git a/lib/Doctrine/ORM/Query/AST/SelectExpression.php b/lib/Doctrine/ORM/Query/AST/SelectExpression.php index f1793f0c4..e3c917200 100644 --- a/lib/Doctrine/ORM/Query/AST/SelectExpression.php +++ b/lib/Doctrine/ORM/Query/AST/SelectExpression.php @@ -23,7 +23,7 @@ namespace Doctrine\ORM\Query\AST; /** * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | - * (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable] + * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable] * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org @@ -37,11 +37,13 @@ class SelectExpression extends Node { public $expression; public $fieldIdentificationVariable; + public $hiddenAliasResultVariable; - public function __construct($expression, $fieldIdentificationVariable) + public function __construct($expression, $fieldIdentificationVariable, $hiddenAliasResultVariable = false) { $this->expression = $expression; $this->fieldIdentificationVariable = $fieldIdentificationVariable; + $this->hiddenAliasResultVariable = $hiddenAliasResultVariable; } public function dispatch($sqlWalker) diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index 9d20689cf..00fcffd8c 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -76,39 +76,40 @@ class Lexer extends \Doctrine\Common\Lexer const T_FROM = 122; const T_GROUP = 123; const T_HAVING = 124; - const T_IN = 125; - const T_INDEX = 126; - const T_INNER = 127; - const T_INSTANCE = 128; - const T_IS = 129; - const T_JOIN = 130; - const T_LEADING = 131; - const T_LEFT = 132; - const T_LIKE = 133; - const T_MAX = 134; - const T_MEMBER = 135; - const T_MIN = 136; - const T_NOT = 137; - const T_NULL = 138; - const T_NULLIF = 139; - const T_OF = 140; - const T_OR = 141; - const T_ORDER = 142; - const T_OUTER = 143; - const T_SELECT = 144; - const T_SET = 145; - const T_SIZE = 146; - const T_SOME = 147; - const T_SUM = 148; - const T_THEN = 149; - const T_TRAILING = 150; - const T_TRUE = 151; - const T_UPDATE = 152; - const T_WHEN = 153; - const T_WHERE = 154; - const T_WITH = 155; - const T_PARTIAL = 156; - const T_MOD = 157; + const T_HIDDEN = 125; + const T_IN = 126; + const T_INDEX = 127; + const T_INNER = 128; + const T_INSTANCE = 129; + const T_IS = 130; + const T_JOIN = 131; + const T_LEADING = 132; + const T_LEFT = 133; + const T_LIKE = 134; + const T_MAX = 135; + const T_MEMBER = 136; + const T_MIN = 137; + const T_NOT = 138; + const T_NULL = 139; + const T_NULLIF = 140; + const T_OF = 141; + const T_OR = 142; + const T_ORDER = 143; + const T_OUTER = 144; + const T_SELECT = 145; + const T_SET = 146; + const T_SIZE = 147; + const T_SOME = 148; + const T_SUM = 149; + const T_THEN = 150; + const T_TRAILING = 151; + const T_TRUE = 152; + const T_UPDATE = 153; + const T_WHEN = 154; + const T_WHERE = 155; + const T_WITH = 156; + const T_PARTIAL = 157; + const T_MOD = 158; /** * Creates a new query scanner object. diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index d5b57a00d..08e036c1a 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1831,7 +1831,7 @@ class Parser /** * SelectExpression ::= * IdentificationVariable | StateFieldPathExpression | - * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable] + * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] ["HIDDEN"] AliasResultVariable] * * @return Doctrine\ORM\Query\AST\SelectExpression */ @@ -1839,6 +1839,7 @@ class Parser { $expression = null; $identVariable = null; + $hiddenAliasResultVariable = false; $fieldAliasIdentificationVariable = null; $peek = $this->_lexer->glimpse(); @@ -1900,6 +1901,12 @@ class Parser if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } + + if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) { + $this->match(Lexer::T_HIDDEN); + + $hiddenAliasResultVariable = true; + } if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $token = $this->_lexer->lookahead; @@ -1914,10 +1921,12 @@ class Parser } } - $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable); - if (!$supportsAlias) { + $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable, $hiddenAliasResultVariable); + + if ( ! $supportsAlias) { $this->_identVariableExpressions[$identVariable] = $expr; } + return $expr; } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 0f1c79542..188350197 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -975,8 +975,9 @@ class SqlWalker implements TreeWalker */ public function walkSelectExpression($selectExpression) { - $sql = ''; - $expr = $selectExpression->expression; + $sql = ''; + $expr = $selectExpression->expression; + $hidden = $selectExpression->hiddenAliasResultVariable; if ($expr instanceof AST\PathExpression) { if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { @@ -1006,7 +1007,10 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSQLColumnAlias($columnName); $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ($expr instanceof AST\AggregateExpression) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; @@ -1019,7 +1023,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ($expr instanceof AST\Subselect) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; @@ -1032,7 +1039,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ($expr instanceof AST\Functions\FunctionNode) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; @@ -1045,7 +1055,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ( $expr instanceof AST\SimpleArithmeticExpression || $expr instanceof AST\ArithmeticTerm || @@ -1070,7 +1083,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ( $expr instanceof AST\NullIfExpression || $expr instanceof AST\CoalesceExpression || @@ -1090,7 +1106,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else { // IdentificationVariable or PartialObjectExpression if ($expr instanceof AST\PartialObjectExpression) { @@ -1107,8 +1126,9 @@ class SqlWalker implements TreeWalker if ( ! isset($this->_selectedClasses[$dqlAlias])) { $this->_selectedClasses[$dqlAlias] = $class; } - + $beginning = true; + // Select all fields from the queried class foreach ($class->fieldMappings as $fieldName => $mapping) { if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { @@ -1127,6 +1147,7 @@ class SqlWalker implements TreeWalker . ' AS ' . $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } @@ -1138,6 +1159,7 @@ class SqlWalker implements TreeWalker foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); + foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { continue; @@ -1159,8 +1181,10 @@ class SqlWalker implements TreeWalker if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($beginning) $beginning = false; else $sql .= ', '; + $columnAlias = $this->getSQLColumnAlias($srcColumn); $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; + $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); } } @@ -1826,12 +1850,16 @@ class SqlWalker implements TreeWalker switch ($literal->type) { case AST\Literal::STRING: return $this->_conn->quote($literal->value); + case AST\Literal::BOOLEAN: $bool = strtolower($literal->value) == 'true' ? true : false; $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool); - return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal; + + return $boolVal; + case AST\Literal::NUMERIC: return $literal->value; + default: throw QueryException::invalidLiteral($literal); } diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php index ef7b02e3c..13243a0f7 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, - Symfony\Component\Console; + Symfony\Component\Console, + Doctrine\Common\Cache; /** * Command to clear the metadata cache of the various cache drivers. @@ -83,6 +84,10 @@ EOT if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.'); } + + if ($cacheDriver instanceof Cache\ApcCache) { + throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); + } $output->write('Clearing ALL Metadata cache entries' . PHP_EOL); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php index becb78a90..6451d4ad7 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, - Symfony\Component\Console; + Symfony\Component\Console, + Doctrine\Common\Cache; /** * Command to clear the query cache of the various cache drivers. @@ -83,6 +84,10 @@ EOT if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.'); } + + if ($cacheDriver instanceof Cache\ApcCache) { + throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); + } $output->write('Clearing ALL Query cache entries' . PHP_EOL); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php index 5db7fb296..1dfacb056 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, - Symfony\Component\Console; + Symfony\Component\Console, + Doctrine\Common\Cache; /** * Command to clear the result cache of the various cache drivers. @@ -78,13 +79,17 @@ EOT protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); - $cacheDriver = $em->getConfiguration()->getQueryCacheImpl(); + $cacheDriver = $em->getConfiguration()->getResultCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); } + + if ($cacheDriver instanceof Cache\ApcCache) { + throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); + } - $output->write('Clearing ALL Query cache entries' . PHP_EOL); + $output->write('Clearing ALL Result cache entries' . PHP_EOL); $result = $cacheDriver->deleteAll(); $message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index e53505f98..db353c3c1 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -150,7 +150,7 @@ public function () public function __construct() { - if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) { + if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) { $this->_annotationsPrefix = 'ORM\\'; } } @@ -399,14 +399,17 @@ public function () } $collections = array(); + foreach ($metadata->associationMappings AS $mapping) { if ($mapping['type'] & ClassMetadataInfo::TO_MANY) { $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();'; } } + if ($collections) { return $this->_prefixCodeWithSpaces(str_replace("", implode("\n", $collections), self::$_constructorMethodTemplate)); } + return ''; } @@ -788,7 +791,7 @@ public function () } if (isset($joinColumn['onDelete'])) { - $joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false'); + $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"'); } if (isset($joinColumn['columnDefinition'])) { diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ea516f3d0..df808a969 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -215,8 +215,13 @@ class UnitOfWork implements PropertyChangedListener * @var array */ private $orphanRemovals = array(); - - //private $_readOnlyObjects = array(); + + /** + * Read-Only objects are never evaluated + * + * @var array + */ + private $readOnlyObjects = array(); /** * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. @@ -393,6 +398,8 @@ class UnitOfWork implements PropertyChangedListener * If a PersistentCollection has been de-referenced in a fully MANAGED entity, * then this collection is marked for deletion. * + * @ignore + * @internal Don't call from the outside. * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to compute the changes. */ @@ -403,6 +410,11 @@ class UnitOfWork implements PropertyChangedListener } $oid = spl_object_hash($entity); + + if (isset($this->readOnlyObjects[$oid])) { + return; + } + $actualData = array(); foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); @@ -458,7 +470,15 @@ class UnitOfWork implements PropertyChangedListener $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array(); foreach ($actualData as $propName => $actualValue) { - $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + if (isset($originalData[$propName])) { + $orgValue = $originalData[$propName]; + } else if (array_key_exists($propName, $originalData)) { + $orgValue = null; + } else { + // skip field, its a partially omitted one! + continue; + } + if (isset($class->associationMappings[$propName])) { $assoc = $class->associationMappings[$propName]; if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) { @@ -528,7 +548,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects - if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) { + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { continue; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. @@ -635,7 +655,7 @@ class UnitOfWork implements PropertyChangedListener * @param object $entity The entity for which to (re)calculate the change set. * @throws InvalidArgumentException If the passed entity is not MANAGED. */ - public function recomputeSingleEntityChangeSet($class, $entity) + public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); @@ -853,6 +873,7 @@ class UnitOfWork implements PropertyChangedListener $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if ( ! $calc->hasClass($targetClass->name)) { $calc->addClass($targetClass); + $newNodes[] = $targetClass; } $calc->addDependency($targetClass, $class); // If the target class has mapped subclasses, @@ -2407,4 +2428,37 @@ class UnitOfWork implements PropertyChangedListener { return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj); } + + /** + * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit(). + * + * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information + * on this object that might be necessary to perform a correct udpate. + * + * @throws \InvalidArgumentException + * @param $object + * @return void + */ + public function markReadOnly($object) + { + if ( ! is_object($object) || ! $this->isInIdentityMap($object)) { + throw new InvalidArgumentException("Managed entity required"); + } + $this->readOnlyObjects[spl_object_hash($object)] = true; + } + + /** + * Is this entity read only? + * + * @throws \InvalidArgumentException + * @param $object + * @return void + */ + public function isReadOnly($object) + { + if ( ! is_object($object) ) { + throw new InvalidArgumentException("Managed entity required"); + } + return isset($this->readOnlyObjects[spl_object_hash($object)]); + } } diff --git a/tests/Doctrine/Tests/Models/Company/CompanyContract.php b/tests/Doctrine/Tests/Models/Company/CompanyContract.php index fd534f403..655d4fccb 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyContract.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyContract.php @@ -7,7 +7,11 @@ namespace Doctrine\Tests\Models\Company; * @Table(name="company_contracts") * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"fix" = "CompanyFixContract", "flexible" = "CompanyFlexContract", "flexultra" = "CompanyFlexUltraContract"}) + * @DiscriminatorMap({ + * "fix" = "CompanyFixContract", + * "flexible" = "CompanyFlexContract", + * "flexultra" = "CompanyFlexUltraContract" + * }) */ abstract class CompanyContract { diff --git a/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php b/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php new file mode 100644 index 000000000..5c4db0101 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php @@ -0,0 +1,91 @@ +delegator = new \Doctrine\ORM\Event\EntityEventDelegator(); + } + + public function testGetSubscribedEventsWhenEmpty() + { + $this->assertEquals(array(), $this->delegator->getSubscribedEvents()); + } + + public function testAddListener() + { + $this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener()); + $this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents()); + } + + public function testAddSubscriber() + { + $this->delegator->addEventSubscriber(new DelegateeEventListener(), 'stdClass'); + $this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents()); + } + + public function testAddListenerAfterFrozenThrowsException() + { + $this->delegator->getSubscribedEvents(); // freezes + + $this->setExpectedException("LogicException", "Cannot add event listeners aft"); + $this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener()); + } + + public function testDelegateEvent() + { + $delegatee = new DelegateeEventListener(); + $this->delegator->addEventListener('postLoad', 'stdClass', $delegatee); + + $event = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager()); + $this->delegator->postLoad($event); + $this->delegator->postLoad($event); + + $this->assertEquals(2, $delegatee->postLoad); + } + + public function testDelegatePickEntity() + { + $delegatee = new DelegateeEventListener(); + $this->delegator->addEventListener('postLoad', 'stdClass', $delegatee); + + $event1 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager()); + $event2 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \Doctrine\Tests\Models\CMS\CmsUser(), $this->_getTestEntityManager()); + $this->delegator->postLoad($event1); + $this->delegator->postLoad($event2); + + $this->assertEquals(1, $delegatee->postLoad); + } +} + +class DelegateeEventListener implements \Doctrine\Common\EventSubscriber +{ + public $postLoad = 0; + + public function postLoad($args) + { + $this->postLoad++; + } + + /** + * Returns an array of events this subscriber wants to listen to. + * + * @return array + */ + function getSubscribedEvents() + { + return array('postLoad'); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index bed8de33d..8d636dffb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -863,7 +863,6 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testGetPartialReferenceToUpdateObjectWithoutLoadingIt() { - //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); $user = new CmsUser(); $user->username = "beberlei"; $user->name = "Benjamin E."; @@ -882,7 +881,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $this->assertEquals('Stephan', $this->_em->find(get_class($user), $userId)->name); + $this->assertEquals('Benjamin E.', $this->_em->find(get_class($user), $userId)->name); } public function testMergePersistsNewEntities() diff --git a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php index 3da6bc09b..2e17ccf12 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php @@ -54,7 +54,30 @@ class DefaultValuesTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($userId, $a2->getUser()->getId()); $this->assertEquals('Poweruser', $a2->getUser()->type); } - + + /** + * @group DDC-1386 + */ + public function testGetPartialReferenceWithDefaultValueNotEvalutedInFlush() + { + $user = new DefaultValueUser; + $user->name = 'romanb'; + $user->type = 'Normaluser'; + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->getPartialReference('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id); + $this->assertTrue($this->_em->getUnitOfWork()->isReadOnly($user)); + + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->find('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id); + + $this->assertEquals('Normaluser', $user->type); + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 993ce1642..596d929f6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -533,4 +533,34 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(2, count($users)); } + + public function testQueryWithHiddenAsSelectExpression() + { + $userA = new CmsUser; + $userA->name = 'Benjamin'; + $userA->username = 'beberlei'; + $userA->status = 'developer'; + $this->_em->persist($userA); + + $userB = new CmsUser; + $userB->name = 'Roman'; + $userB->username = 'romanb'; + $userB->status = 'developer'; + $this->_em->persist($userB); + + $userC = new CmsUser; + $userC->name = 'Jonathan'; + $userC->username = 'jwage'; + $userC->status = 'developer'; + $this->_em->persist($userC); + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery("SELECT u, (SELECT COUNT(u2.id) FROM Doctrine\Tests\Models\CMS\CmsUser u2) AS HIDDEN total FROM Doctrine\Tests\Models\CMS\CmsUser u"); + $users = $query->execute(); + + $this->assertEquals(3, count($users)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/TypeTest.php b/tests/Doctrine/Tests/ORM/Functional/TypeTest.php index 25523bb80..8a2eab70e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/TypeTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/TypeTest.php @@ -37,6 +37,10 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(0.1515, $decimal->highScale); } + /** + * @group DDC-1394 + * @return void + */ public function testBoolean() { $bool = new BooleanModel(); @@ -46,7 +50,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b"; + $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true"; $bool = $this->_em->createQuery($dql)->getSingleResult(); $this->assertTrue($bool->booleanField); @@ -56,7 +60,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b"; + $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false"; $bool = $this->_em->createQuery($dql)->getSingleResult(); $this->assertFalse($bool->booleanField); diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php new file mode 100644 index 000000000..d16db4fbb --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php @@ -0,0 +1,113 @@ +. +*/ + +namespace Doctrine\Tests\ORM\Mapping\Symfony; + +/** + * @group DDC-1418 + */ +abstract class AbstractDriverTest extends \PHPUnit_Framework_TestCase +{ + public function testFindMappingFile() + { + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\EntityFoo' => 'foo', + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + touch($filename = $this->dir.'/Foo'.$this->getFileExtension()); + $this->assertEquals($filename, $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo'))); + } + + public function testFindMappingFileInSubnamespace() + { + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + touch($filename = $this->dir.'/Foo.Bar'.$this->getFileExtension()); + $this->assertEquals($filename, $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo\Bar'))); + } + + public function testFindMappingFileNamespacedFoundFileNotFound() + { + $this->setExpectedException( + 'Doctrine\ORM\Mapping\MappingException', + "No mapping file found named '".$this->dir."/Foo".$this->getFileExtension()."' for class 'MyNamespace\MySubnamespace\Entity\Foo'." + ); + + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo')); + } + + public function testFindMappingNamespaceNotFound() + { + $this->setExpectedException( + 'Doctrine\ORM\Mapping\MappingException', + "No mapping file found named 'Foo".$this->getFileExtension()."' for class 'MyOtherNamespace\MySubnamespace\Entity\Foo'." + ); + + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + $this->invoke($driver, '_findMappingFile', array('MyOtherNamespace\MySubnamespace\Entity\Foo')); + } + + protected function setUp() + { + $this->dir = sys_get_temp_dir().'/abstract_driver_test'; + @mkdir($this->dir, 0777, true); + } + + protected function tearDown() + { + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dir), \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $path) { + if ($path->isDir()) { + @rmdir($path); + } else { + @unlink($path); + } + } + + @rmdir($this->dir); + } + + abstract protected function getFileExtension(); + abstract protected function getDriver(array $paths = array()); + + private function setField($obj, $field, $value) + { + $ref = new \ReflectionProperty($obj, $field); + $ref->setAccessible(true); + $ref->setValue($obj, $value); + } + + private function invoke($obj, $method, array $args = array()) { + $ref = new \ReflectionMethod($obj, $method); + $ref->setAccessible(true); + + return $ref->invokeArgs($obj, $args); + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php new file mode 100644 index 000000000..5908b674a --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php @@ -0,0 +1,40 @@ +. +*/ + +namespace Doctrine\Tests\ORM\Mapping\Symfony; + +use \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; + +/** + * @group DDC-1418 + */ +class XmlDriverTest extends AbstractDriverTest +{ + protected function getFileExtension() + { + return '.orm.xml'; + } + + protected function getDriver(array $paths = array()) + { + $driver = new SimplifiedXmlDriver(array_flip($paths)); + + return $driver; + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php new file mode 100644 index 000000000..c5d8d1cd1 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php @@ -0,0 +1,40 @@ +. +*/ + +namespace Doctrine\Tests\ORM\Mapping\Symfony; + +use \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver; + +/** + * @group DDC-1418 + */ +class YamlDriverTest extends AbstractDriverTest +{ + protected function getFileExtension() + { + return '.orm.yml'; + } + + protected function getDriver(array $paths = array()) + { + $driver = new SimplifiedYamlDriver(array_flip($paths)); + + return $driver; + } +} diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 6ef886035..8e1585819 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -33,7 +33,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase } $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) - ->useQueryCache(false); + ->useQueryCache(false); foreach ($queryHints AS $name => $value) { $query->setHint($name, $value); @@ -65,7 +65,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase } $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) - ->useQueryCache(false); + ->useQueryCache(false); foreach ($queryHints AS $name => $value) { $query->setHint($name, $value); @@ -729,12 +729,12 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = 'true'" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = true" ); $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = 'false'" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = false" ); $this->_em->getConnection()->setDatabasePlatform($oldPlat); @@ -1095,6 +1095,150 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "Doctrine\ORM\Query\QueryException" ); } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInRootClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInRootClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_', + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInChildClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c2_.car_id AS car_id6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInChildClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c0_.discr AS discr5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInLeafClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c2_.car_id AS car_id8 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInLeafClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInRootClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6, c0_.salesPerson_id AS salesPerson_id7 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInRootClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInChildClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInChildClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInLeafClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInLeafClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } }