diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index a5eae8b72..5e30c825d 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -234,6 +234,25 @@ abstract class AbstractHydrator // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. continue 2; } + + if (isset($this->_rsm->newObjectMappings[$key])) { + $mapping = $this->_rsm->newObjectMappings[$key]; + + $cache[$key]['isNewObjectParameter'] = true; + $cache[$key]['argIndex'] = $mapping['argIndex']; + $cache[$key]['objIndex'] = $mapping['objIndex']; + $cache[$key]['class'] = new \ReflectionClass($mapping['className']); + } + } + + if (isset($cache[$key]['isNewObjectParameter'])) { + $class = $cache[$key]['class']; + $argIndex = $cache[$key]['argIndex']; + $objIndex = $cache[$key]['objIndex']; + $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); + + $rowData['newObjects'][$objIndex]['class'] = $class; + $rowData['newObjects'][$objIndex]['args'][$argIndex] = $value; } if (isset($cache[$key]['isScalar'])) { diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 3f48f8607..1dd18566e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -19,15 +19,12 @@ namespace Doctrine\ORM\Internal\Hydration; -use PDO, - Doctrine\ORM\Mapping\ClassMetadata, - Doctrine\ORM\PersistentCollection, - Doctrine\ORM\Query, - Doctrine\ORM\Event\LifecycleEventArgs, - Doctrine\ORM\Events, - Doctrine\Common\Collections\ArrayCollection, - Doctrine\Common\Collections\Collection, - Doctrine\ORM\Proxy\Proxy; +use PDO; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Query; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\Proxy\Proxy; /** * The ObjectHydrator constructs an object graph out of an SQL result set. @@ -35,6 +32,7 @@ use PDO, * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco + * @author Fabio B. Silva * * @internal Highly performance-sensitive code. */ @@ -43,38 +41,67 @@ class ObjectHydrator extends AbstractHydrator /* Local ClassMetadata cache to avoid going to the EntityManager all the time. * This local cache is maintained between hydration runs and not cleared. */ - private $_ce = array(); + private $ce = array(); /* The following parts are reinitialized on every hydration run. */ - private $_identifierMap; - private $_resultPointers; - private $_idTemplate; - private $_resultCounter; - private $_rootAliases = array(); - private $_initializedCollections = array(); - private $_existingCollections = array(); + /** + * @var array + */ + private $identifierMap; + + /** + * @var array + */ + private $resultPointers; + + /** + * @var array + */ + private $idTemplate; + + /** + * @var integer + */ + private $resultCounter; + + /** + * @var array + */ + private $rootAliases = array(); + + /** + * @var array + */ + private $initializedCollections = array(); + + /** + * @var array + */ + private $existingCollections = array(); - /** @override */ + /** + * {@inheritdoc} + */ protected function prepare() { - $this->_identifierMap = - $this->_resultPointers = - $this->_idTemplate = array(); + $this->identifierMap = + $this->resultPointers = + $this->idTemplate = array(); - $this->_resultCounter = 0; + $this->resultCounter = 0; if ( ! isset($this->_hints['deferEagerLoad'])) { $this->_hints['deferEagerLoad'] = true; } foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { - $this->_identifierMap[$dqlAlias] = array(); - $this->_idTemplate[$dqlAlias] = ''; + $this->identifierMap[$dqlAlias] = array(); + $this->idTemplate[$dqlAlias] = ''; - if ( ! isset($this->_ce[$className])) { - $this->_ce[$className] = $this->_em->getClassMetadata($className); + if ( ! isset($this->ce[$className])) { + $this->ce[$className] = $this->_em->getClassMetadata($className); } // Remember which associations are "fetch joined", so that we know where to inject @@ -88,7 +115,7 @@ class ObjectHydrator extends AbstractHydrator } $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; - $sourceClass = $this->_getClassMetadata($sourceClassName); + $sourceClass = $this->getClassMetadata($sourceClassName); $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; $this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true; @@ -106,7 +133,7 @@ class ObjectHydrator extends AbstractHydrator // handle fetch-joined owning side bi-directional one-to-one associations if ($assoc['inversedBy']) { - $class = $this->_ce[$className]; + $class = $this->ce[$className]; $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) { @@ -127,10 +154,10 @@ class ObjectHydrator extends AbstractHydrator parent::cleanup(); - $this->_identifierMap = - $this->_initializedCollections = - $this->_existingCollections = - $this->_resultPointers = array(); + $this->identifierMap = + $this->initializedCollections = + $this->existingCollections = + $this->resultPointers = array(); if ($eagerLoad) { $this->_em->getUnitOfWork()->triggerEagerLoads(); @@ -150,7 +177,7 @@ class ObjectHydrator extends AbstractHydrator } // Take snapshots from all newly initialized collections - foreach ($this->_initializedCollections as $coll) { + foreach ($this->initializedCollections as $coll) { $coll->takeSnapshot(); } @@ -165,7 +192,7 @@ class ObjectHydrator extends AbstractHydrator * @param string $fieldName The name of the field on the entity that holds the collection. * @param string $parentDqlAlias Alias of the parent fetch joining this collection. */ - private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias) + private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias) { $oid = spl_object_hash($entity); $relation = $class->associationMappings[$fieldName]; @@ -177,14 +204,14 @@ class ObjectHydrator extends AbstractHydrator if ( ! $value instanceof PersistentCollection) { $value = new PersistentCollection( - $this->_em, $this->_ce[$relation['targetEntity']], $value + $this->_em, $this->ce[$relation['targetEntity']], $value ); $value->setOwner($entity, $relation); $class->reflFields[$fieldName]->setValue($entity, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); - $this->_initializedCollections[$oid . $fieldName] = $value; + $this->initializedCollections[$oid . $fieldName] = $value; } else if ( isset($this->_hints[Query::HINT_REFRESH]) || isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) && @@ -195,10 +222,10 @@ class ObjectHydrator extends AbstractHydrator $value->setInitialized(true); $value->unwrap()->clear(); - $this->_initializedCollections[$oid . $fieldName] = $value; + $this->initializedCollections[$oid . $fieldName] = $value; } else { // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! - $this->_existingCollections[$oid . $fieldName] = $value; + $this->existingCollections[$oid . $fieldName] = $value; } return $value; @@ -211,7 +238,7 @@ class ObjectHydrator extends AbstractHydrator * @param string $dqlAlias The DQL alias of the entity's class. * @return object The entity. */ - private function _getEntity(array $data, $dqlAlias) + private function getEntity(array $data, $dqlAlias) { $className = $this->_rsm->aliasMap[$dqlAlias]; @@ -231,13 +258,13 @@ class ObjectHydrator extends AbstractHydrator throw HydrationException::emptyDiscriminatorValue($dqlAlias); } - $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; + $className = $this->ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } - if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { - $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); + if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->rootAliases[$dqlAlias])) { + $this->registerManaged($this->ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } $this->_hints['fetchAlias'] = $dqlAlias; @@ -250,10 +277,10 @@ class ObjectHydrator extends AbstractHydrator * @param array $data * @return mixed */ - private function _getEntityFromIdentityMap($className, array $data) + private function getEntityFromIdentityMap($className, array $data) { // TODO: Abstract this code and UnitOfWork::createEntity() equivalent? - $class = $this->_ce[$className]; + $class = $this->ce[$className]; /* @var $class ClassMetadata */ if ($class->isIdentifierComposite) { @@ -281,13 +308,13 @@ class ObjectHydrator extends AbstractHydrator * @param string $className The name of the class. * @return ClassMetadata */ - private function _getClassMetadata($className) + private function getClassMetadata($className) { - if ( ! isset($this->_ce[$className])) { - $this->_ce[$className] = $this->_em->getClassMetadata($className); + if ( ! isset($this->ce[$className])) { + $this->ce[$className] = $this->_em->getClassMetadata($className); } - return $this->_ce[$className]; + return $this->ce[$className]; } /** @@ -314,7 +341,7 @@ class ObjectHydrator extends AbstractHydrator protected function hydrateRowData(array $row, array &$cache, array &$result) { // Initialize - $id = $this->_idTemplate; // initialize the id-memory + $id = $this->idTemplate; // initialize the id-memory $nonemptyComponents = array(); // Split the row data into chunks of class data. $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); @@ -326,7 +353,18 @@ class ObjectHydrator extends AbstractHydrator unset($rowData['scalars']); if (empty($rowData)) { - ++$this->_resultCounter; + ++$this->resultCounter; + } + } + + // Extract "new" object constructor arguments. They're appended at the end. + if (isset($rowData['newObjects'])) { + $newObjects = $rowData['newObjects']; + + unset($rowData['newObjects']); + + if (empty($rowData)) { + ++$this->resultCounter; } } @@ -349,21 +387,21 @@ class ObjectHydrator extends AbstractHydrator } // Get a reference to the parent object to which the joined element belongs. - if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { - $first = reset($this->_resultPointers); + if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) { + $first = reset($this->resultPointers); $parentObject = $first[key($first)]; - } else if (isset($this->_resultPointers[$parentAlias])) { - $parentObject = $this->_resultPointers[$parentAlias]; + } else if (isset($this->resultPointers[$parentAlias])) { + $parentObject = $this->resultPointers[$parentAlias]; } else { // Parent object of relation not found, so skip it. continue; } - $parentClass = $this->_ce[$this->_rsm->aliasMap[$parentAlias]]; - $oid = spl_object_hash($parentObject); - $relationField = $this->_rsm->relationMap[$dqlAlias]; - $relation = $parentClass->associationMappings[$relationField]; - $reflField = $parentClass->reflFields[$relationField]; + $parentClass = $this->ce[$this->_rsm->aliasMap[$parentAlias]]; + $oid = spl_object_hash($parentObject); + $relationField = $this->_rsm->relationMap[$dqlAlias]; + $relation = $parentClass->associationMappings[$relationField]; + $reflField = $parentClass->reflFields[$relationField]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { @@ -371,45 +409,45 @@ class ObjectHydrator extends AbstractHydrator // PATH A: Collection-valued association if (isset($nonemptyComponents[$dqlAlias])) { $collKey = $oid . $relationField; - if (isset($this->_initializedCollections[$collKey])) { - $reflFieldValue = $this->_initializedCollections[$collKey]; - } else if ( ! isset($this->_existingCollections[$collKey])) { - $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); + if (isset($this->initializedCollections[$collKey])) { + $reflFieldValue = $this->initializedCollections[$collKey]; + } else if ( ! isset($this->existingCollections[$collKey])) { + $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } - $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); - $index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; - $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; + $indexExists = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); + $index = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; + $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; if ( ! $indexExists || ! $indexIsValid) { - if (isset($this->_existingCollections[$collKey])) { + if (isset($this->existingCollections[$collKey])) { // Collection exists, only look for the element in the identity map. - if ($element = $this->_getEntityFromIdentityMap($entityName, $data)) { - $this->_resultPointers[$dqlAlias] = $element; + if ($element = $this->getEntityFromIdentityMap($entityName, $data)) { + $this->resultPointers[$dqlAlias] = $element; } else { - unset($this->_resultPointers[$dqlAlias]); + unset($this->resultPointers[$dqlAlias]); } } else { - $element = $this->_getEntity($data, $dqlAlias); + $element = $this->getEntity($data, $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]]; $reflFieldValue->hydrateSet($indexValue, $element); - $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; + $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; } else { $reflFieldValue->hydrateAdd($element); $reflFieldValue->last(); - $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key(); + $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key(); } // Update result pointer - $this->_resultPointers[$dqlAlias] = $element; + $this->resultPointers[$dqlAlias] = $element; } } else { // Update result pointer - $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; + $this->resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } else if ( ! $reflFieldValue) { - $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); + $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) { $reflFieldValue->setInitialized(true); } @@ -421,10 +459,10 @@ class ObjectHydrator extends AbstractHydrator // we only need to take action if this value is null, // we refresh the entity or its an unitialized proxy. if (isset($nonemptyComponents[$dqlAlias])) { - $element = $this->_getEntity($data, $dqlAlias); + $element = $this->getEntity($data, $dqlAlias); $reflField->setValue($parentObject, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element); - $targetClass = $this->_ce[$relation['targetEntity']]; + $targetClass = $this->ce[$relation['targetEntity']]; if ($relation['isOwningSide']) { //TODO: Just check hints['fetched'] here? @@ -445,7 +483,7 @@ class ObjectHydrator extends AbstractHydrator $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject); } // Update result pointer - $this->_resultPointers[$dqlAlias] = $element; + $this->resultPointers[$dqlAlias] = $element; } else { $this->_uow->setOriginalEntityProperty($oid, $relationField, null); $reflField->setValue($parentObject, null); @@ -453,12 +491,12 @@ class ObjectHydrator extends AbstractHydrator // else leave $reflFieldValue null for single-valued associations } else { // Update result pointer - $this->_resultPointers[$dqlAlias] = $reflFieldValue; + $this->resultPointers[$dqlAlias] = $reflFieldValue; } } } else { // PATH C: Its a root result element - $this->_rootAliases[$dqlAlias] = true; // Mark as root alias + $this->rootAliases[$dqlAlias] = true; // Mark as root alias $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. @@ -468,14 +506,15 @@ class ObjectHydrator extends AbstractHydrator } else { $result[] = null; } - $resultKey = $this->_resultCounter; - ++$this->_resultCounter; + $resultKey = $this->resultCounter; + ++$this->resultCounter; continue; } // check for existing result from the iterations before - if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { - $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); + if ( ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) { + $element = $this->getEntity($rowData[$dqlAlias], $dqlAlias); + if ($this->_rsm->isMixed) { $element = array($entityKey => $element); } @@ -489,8 +528,8 @@ class ObjectHydrator extends AbstractHydrator $result[$resultKey] = $element; } else { - $resultKey = $this->_resultCounter; - ++$this->_resultCounter; + $resultKey = $this->resultCounter; + ++$this->resultCounter; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateAdd($element); @@ -499,15 +538,15 @@ class ObjectHydrator extends AbstractHydrator $result[] = $element; } - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; + $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; // Update result pointer - $this->_resultPointers[$dqlAlias] = $element; + $this->resultPointers[$dqlAlias] = $element; } else { // Update result pointer - $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; - $this->_resultPointers[$dqlAlias] = $result[$index]; + $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]]; + $this->resultPointers[$dqlAlias] = $result[$index]; $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] = $result[$index]; @@ -523,7 +562,7 @@ class ObjectHydrator extends AbstractHydrator if (isset($this->_rsm->indexByMap['scalars'])) { $resultKey = $row[$this->_rsm->indexByMap['scalars']]; } else { - $resultKey = $this->_resultCounter - 1; + $resultKey = $this->resultCounter - 1; } } @@ -531,6 +570,30 @@ class ObjectHydrator extends AbstractHydrator $result[$resultKey][$name] = $value; } } + + // Append new object to mixed result sets + if (isset($newObjects)) { + if ( ! isset($resultKey) ) { + $resultKey = $this->resultCounter - 1; + } + + $count = count($newObjects); + + foreach ($newObjects as $objIndex => $newObject) { + $class = $newObject['class']; + $args = $newObject['args']; + $obj = $class->newInstanceArgs($args); + + if ($count === 1) { + $result[$resultKey] = $obj; + + continue; + } + + $result[$resultKey][$objIndex] = $obj; + } + } + } /** @@ -541,11 +604,11 @@ class ObjectHydrator extends AbstractHydrator { parent::onClear($eventArgs); - $aliases = array_keys($this->_identifierMap); - $this->_identifierMap = array(); + $aliases = array_keys($this->identifierMap); + $this->identifierMap = array(); foreach ($aliases as $alias) { - $this->_identifierMap[$alias] = array(); + $this->identifierMap[$alias] = array(); } } } diff --git a/lib/Doctrine/ORM/Query/AST/NewObjectExpression.php b/lib/Doctrine/ORM/Query/AST/NewObjectExpression.php new file mode 100644 index 000000000..caf7a80b7 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/NewObjectExpression.php @@ -0,0 +1,60 @@ +. + */ + +namespace Doctrine\ORM\Query\AST; + +/** + * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")" + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.3 + * @author Fabio B. Silva + */ +class NewObjectExpression extends Node +{ + /** + * @var string + */ + public $className; + + /** + * @var array + */ + public $args; + + /** + * @param type $className + * @param array $args + */ + public function __construct($className, array $args) + { + $this->className = $className; + $this->args = $args; + } + + /** + * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker + * @return string + */ + public function dispatch($sqlWalker) + { + return $sqlWalker->walkNewObject($this); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index beafa7dee..4692b6ae8 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -108,6 +108,7 @@ class Lexer extends \Doctrine\Common\Lexer const T_WHERE = 154; const T_WITH = 155; const T_PARTIAL = 156; + const T_NEW = 157; /** * Creates a new query scanner object. diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index ac6ad6f3e..0b9bb5bd3 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -31,6 +31,7 @@ use Doctrine\ORM\Mapping\ClassMetadata; * @author Jonathan Wage * @author Roman Borschel * @author Janne Vanhala + * @author Fabio B. Silva */ class Parser { @@ -66,75 +67,96 @@ class Parser 'date_sub' => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction', ); - /** + /* * Expressions that were encountered during parsing of identifiers and expressions * and still need to be validated. */ - private $_deferredIdentificationVariables = array(); - private $_deferredPartialObjectExpressions = array(); - private $_deferredPathExpressions = array(); - private $_deferredResultVariables = array(); + + /** + * @var array + */ + private $deferredIdentificationVariables = array(); + + /** + * @var array + */ + private $deferredPartialObjectExpressions = array(); + + /** + * @var array + */ + private $deferredPathExpressions = array(); + + /** + * @var array + */ + private $deferredResultVariables = array(); + + /** + * @var array + */ + private $deferredNewObjectExpressions = array(); /** * The lexer. * * @var \Doctrine\ORM\Query\Lexer */ - private $_lexer; + private $lexer; /** * The parser result. * * @var \Doctrine\ORM\Query\ParserResult */ - private $_parserResult; + private $parserResult; /** * The EntityManager. * * @var \Doctrine\ORM\EntityManager */ - private $_em; + private $em; /** * The Query to parse. * * @var Query */ - private $_query; + private $query; /** * Map of declared query components in the parsed query. * * @var array */ - private $_queryComponents = array(); + private $queryComponents = array(); /** * Keeps the nesting level of defined ResultVariables * * @var integer */ - private $_nestingLevel = 0; + private $nestingLevel = 0; /** * Any additional custom tree walkers that modify the AST. * * @var array */ - private $_customTreeWalkers = array(); + private $customTreeWalkers = array(); /** * The custom last tree walker, if any, that is responsible for producing the output. * * @var TreeWalker */ - private $_customOutputWalker; + private $customOutputWalker; /** * @var array */ - private $_identVariableExpressions = array(); + private $identVariableExpressions = array(); /** * Check if a function is internally defined. Used to prevent overwriting @@ -159,10 +181,10 @@ class Parser */ public function __construct(Query $query) { - $this->_query = $query; - $this->_em = $query->getEntityManager(); - $this->_lexer = new Lexer($query->getDql()); - $this->_parserResult = new ParserResult(); + $this->query = $query; + $this->em = $query->getEntityManager(); + $this->lexer = new Lexer($query->getDql()); + $this->parserResult = new ParserResult(); } /** @@ -173,7 +195,7 @@ class Parser */ public function setCustomOutputTreeWalker($className) { - $this->_customOutputWalker = $className; + $this->customOutputWalker = $className; } /** @@ -183,7 +205,7 @@ class Parser */ public function addCustomTreeWalker($className) { - $this->_customTreeWalkers[] = $className; + $this->customTreeWalkers[] = $className; } /** @@ -193,7 +215,7 @@ class Parser */ public function getLexer() { - return $this->_lexer; + return $this->lexer; } /** @@ -203,7 +225,7 @@ class Parser */ public function getParserResult() { - return $this->_parserResult; + return $this->parserResult; } /** @@ -213,7 +235,7 @@ class Parser */ public function getEntityManager() { - return $this->_em; + return $this->em; } /** @@ -230,21 +252,25 @@ class Parser // Process any deferred validations of some nodes in the AST. // This also allows post-processing of the AST for modification purposes. - $this->_processDeferredIdentificationVariables(); + $this->processDeferredIdentificationVariables(); - if ($this->_deferredPartialObjectExpressions) { - $this->_processDeferredPartialObjectExpressions(); + if ($this->deferredPartialObjectExpressions) { + $this->processDeferredPartialObjectExpressions(); } - if ($this->_deferredPathExpressions) { - $this->_processDeferredPathExpressions($AST); + if ($this->deferredPathExpressions) { + $this->processDeferredPathExpressions($AST); } - if ($this->_deferredResultVariables) { - $this->_processDeferredResultVariables(); + if ($this->deferredResultVariables) { + $this->processDeferredResultVariables(); } - $this->_processRootEntityAliasSelected(); + if ($this->deferredNewObjectExpressions) { + $this->processDeferredNewObjectExpressions($AST); + } + + $this->processRootEntityAliasSelected(); // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot! $this->fixIdentificationVariableOrder($AST); @@ -264,14 +290,14 @@ class Parser */ public function match($token) { - $lookaheadType = $this->_lexer->lookahead['type']; + $lookaheadType = $this->lexer->lookahead['type']; // short-circuit on first condition, usually types match if ($lookaheadType !== $token && $token !== Lexer::T_IDENTIFIER && $lookaheadType <= Lexer::T_IDENTIFIER) { - $this->syntaxError($this->_lexer->getLiteral($token)); + $this->syntaxError($this->lexer->getLiteral($token)); } - $this->_lexer->moveNext(); + $this->lexer->moveNext(); } /** @@ -283,15 +309,15 @@ class Parser public function free($deep = false, $position = 0) { // WARNING! Use this method with care. It resets the scanner! - $this->_lexer->resetPosition($position); + $this->lexer->resetPosition($position); // Deep = true cleans peek and also any previously defined errors if ($deep) { - $this->_lexer->resetPeek(); + $this->lexer->resetPeek(); } - $this->_lexer->token = null; - $this->_lexer->lookahead = null; + $this->lexer->token = null; + $this->lexer->lookahead = null; } /** @@ -303,19 +329,19 @@ class Parser { $AST = $this->getAST(); - if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) { - $this->_customTreeWalkers = $customWalkers; + if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) { + $this->customTreeWalkers = $customWalkers; } - if (($customOutputWalker = $this->_query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) { - $this->_customOutputWalker = $customOutputWalker; + if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) { + $this->customOutputWalker = $customOutputWalker; } // Run any custom tree walkers over the AST - if ($this->_customTreeWalkers) { - $treeWalkerChain = new TreeWalkerChain($this->_query, $this->_parserResult, $this->_queryComponents); + if ($this->customTreeWalkers) { + $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents); - foreach ($this->_customTreeWalkers as $walker) { + foreach ($this->customTreeWalkers as $walker) { $treeWalkerChain->addTreeWalker($walker); } @@ -334,13 +360,13 @@ class Parser } } - $outputWalkerClass = $this->_customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; - $outputWalker = new $outputWalkerClass($this->_query, $this->_parserResult, $this->_queryComponents); + $outputWalkerClass = $this->customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; + $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents); // Assign an SQL executor to the parser result - $this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); + $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); - return $this->_parserResult; + return $this->parserResult; } /** @@ -355,16 +381,16 @@ class Parser */ private function fixIdentificationVariableOrder($AST) { - if (count($this->_identVariableExpressions) <= 1) { + if (count($this->identVariableExpressions) <= 1) { return; } - foreach ($this->_queryComponents as $dqlAlias => $qComp) { - if ( ! isset($this->_identVariableExpressions[$dqlAlias])) { + foreach ($this->queryComponents as $dqlAlias => $qComp) { + if ( ! isset($this->identVariableExpressions[$dqlAlias])) { continue; } - $expr = $this->_identVariableExpressions[$dqlAlias]; + $expr = $this->identVariableExpressions[$dqlAlias]; $key = array_search($expr, $AST->selectClause->selectExpressions); unset($AST->selectClause->selectExpressions[$key]); @@ -384,16 +410,16 @@ class Parser public function syntaxError($expected = '', $token = null) { if ($token === null) { - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; } $tokenPos = (isset($token['position'])) ? $token['position'] : '-1'; $message = "line 0, col {$tokenPos}: Error: "; $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected '; - $message .= ($this->_lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'"; + $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'"; - throw QueryException::syntaxError($message, QueryException::dqlError($this->_query->getDQL())); + throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL())); } /** @@ -407,14 +433,14 @@ class Parser public function semanticalError($message = '', $token = null) { if ($token === null) { - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; } // Minimum exposed chars ahead of token $distance = 12; // Find a position of a final word to display in error string - $dql = $this->_query->getDql(); + $dql = $this->query->getDql(); $length = strlen($dql); $pos = $token['position'] + $distance; $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length); @@ -426,7 +452,7 @@ class Parser // Building informative message $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message; - throw QueryException::semanticalError($message, QueryException::dqlError($this->_query->getDQL())); + throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL())); } /** @@ -435,9 +461,9 @@ class Parser * @param boolean $resetPeek Reset peek after finding the closing parenthesis * @return array */ - private function _peekBeyondClosingParenthesis($resetPeek = true) + private function peekBeyondClosingParenthesis($resetPeek = true) { - $token = $this->_lexer->peek(); + $token = $this->lexer->peek(); $numUnmatched = 1; while ($numUnmatched > 0 && $token !== null) { @@ -454,11 +480,11 @@ class Parser // Do nothing } - $token = $this->_lexer->peek(); + $token = $this->lexer->peek(); } if ($resetPeek) { - $this->_lexer->resetPeek(); + $this->lexer->resetPeek(); } return $token; @@ -469,7 +495,7 @@ class Parser * * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise. */ - private function _isMathOperator($token) + private function isMathOperator($token) { return in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY)); } @@ -479,12 +505,12 @@ class Parser * * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise. */ - private function _isFunction() + private function isFunction() { - $peek = $this->_lexer->peek(); - $nextpeek = $this->_lexer->peek(); + $peek = $this->lexer->peek(); + $nextpeek = $this->lexer->peek(); - $this->_lexer->resetPeek(); + $this->lexer->resetPeek(); // We deny the COUNT(SELECT * FROM User u) here. COUNT won't be considered a function return ($peek['type'] === Lexer::T_OPEN_PARENTHESIS && $nextpeek['type'] !== Lexer::T_SELECT); @@ -495,7 +521,7 @@ class Parser * * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise. */ - private function _isAggregateFunction($tokenType) + private function isAggregateFunction($tokenType) { return in_array($tokenType, array(Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT)); } @@ -505,9 +531,9 @@ class Parser * * @return boolean */ - private function _isNextAllAnySome() + private function isNextAllAnySome() { - return in_array($this->_lexer->lookahead['type'], array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME)); + return in_array($this->lexer->lookahead['type'], array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME)); } /** @@ -516,19 +542,19 @@ class Parser * * @return void */ - private function _processDeferredIdentificationVariables() + private function processDeferredIdentificationVariables() { - foreach ($this->_deferredIdentificationVariables as $deferredItem) { + foreach ($this->deferredIdentificationVariables as $deferredItem) { $identVariable = $deferredItem['expression']; // Check if IdentificationVariable exists in queryComponents - if ( ! isset($this->_queryComponents[$identVariable])) { + if ( ! isset($this->queryComponents[$identVariable])) { $this->semanticalError( "'$identVariable' is not defined.", $deferredItem['token'] ); } - $qComp = $this->_queryComponents[$identVariable]; + $qComp = $this->queryComponents[$identVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['metadata'])) { @@ -546,17 +572,65 @@ class Parser } } + /** + * Validates that the given NewObjectExpression. + * + * @param \Doctrine\ORM\Query\AST\SelectClause $AST + * @return void + */ + private function processDeferredNewObjectExpressions($AST) + { + foreach ($this->deferredNewObjectExpressions as $deferredItem) { + $expression = $deferredItem['expression']; + $token = $deferredItem['token']; + $className = $expression->className; + $args = $expression->args; + $fromClassName = isset($AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName) + ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName + : null; + + // If the namespace is not given then assumes the first FROM entity namespace + if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) { + $namespace = substr($fromClassName, 0 , strrpos($fromClassName, '\\')); + $fqcn = $namespace . '\\' . $className; + + if (class_exists($fqcn)) { + $expression->className = $fqcn; + $className = $fqcn; + } + } + + if ( ! class_exists($className)) { + $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token); + } + + $class = new \ReflectionClass($className); + + if ( ! $class->isInstantiable()) { + $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token); + } + + if ($class->getConstructor() === null) { + $this->semanticalError(sprintf('Class "%s" has not a valid contructor.', $className), $token); + } + + if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) { + $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token); + } + } + } + /** * Validates that the given PartialObjectExpression is semantically correct. * It must exist in query components list. * * @return void */ - private function _processDeferredPartialObjectExpressions() + private function processDeferredPartialObjectExpressions() { - foreach ($this->_deferredPartialObjectExpressions as $deferredItem) { + foreach ($this->deferredPartialObjectExpressions as $deferredItem) { $expr = $deferredItem['expression']; - $class = $this->_queryComponents[$expr->identificationVariable]['metadata']; + $class = $this->queryComponents[$expr->identificationVariable]['metadata']; foreach ($expr->partialFieldSet as $field) { if (isset($class->fieldMappings[$field])) { @@ -583,19 +657,19 @@ class Parser * * @return void */ - private function _processDeferredResultVariables() + private function processDeferredResultVariables() { - foreach ($this->_deferredResultVariables as $deferredItem) { + foreach ($this->deferredResultVariables as $deferredItem) { $resultVariable = $deferredItem['expression']; // Check if ResultVariable exists in queryComponents - if ( ! isset($this->_queryComponents[$resultVariable])) { + if ( ! isset($this->queryComponents[$resultVariable])) { $this->semanticalError( "'$resultVariable' is not defined.", $deferredItem['token'] ); } - $qComp = $this->_queryComponents[$resultVariable]; + $qComp = $this->queryComponents[$resultVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['resultVariable'])) { @@ -625,12 +699,12 @@ class Parser * @param array $deferredItem * @param mixed $AST */ - private function _processDeferredPathExpressions($AST) + private function processDeferredPathExpressions($AST) { - foreach ($this->_deferredPathExpressions as $deferredItem) { + foreach ($this->deferredPathExpressions as $deferredItem) { $pathExpression = $deferredItem['expression']; - $qComp = $this->_queryComponents[$pathExpression->identificationVariable]; + $qComp = $this->queryComponents[$pathExpression->identificationVariable]; $class = $qComp['metadata']; if (($field = $pathExpression->field) === null) { @@ -691,16 +765,16 @@ class Parser } } - private function _processRootEntityAliasSelected() + private function processRootEntityAliasSelected() { - if ( ! count($this->_identVariableExpressions)) { + if ( ! count($this->identVariableExpressions)) { return; } $foundRootEntity = false; - foreach ($this->_identVariableExpressions as $dqlAlias => $expr) { - if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) { + foreach ($this->identVariableExpressions as $dqlAlias => $expr) { + if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) { $foundRootEntity = true; } } @@ -719,9 +793,9 @@ class Parser */ public function QueryLanguage() { - $this->_lexer->moveNext(); + $this->lexer->moveNext(); - switch ($this->_lexer->lookahead['type']) { + switch ($this->lexer->lookahead['type']) { case Lexer::T_SELECT: $statement = $this->SelectStatement(); break; @@ -740,7 +814,7 @@ class Parser } // Check for end of string - if ($this->_lexer->lookahead !== null) { + if ($this->lexer->lookahead !== null) { $this->syntaxError('end of string'); } @@ -756,10 +830,10 @@ class Parser { $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); - $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; - $selectStatement->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; - $selectStatement->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; - $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; + $selectStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; + $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; + $selectStatement->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; + $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; return $selectStatement; } @@ -773,7 +847,7 @@ class Parser { $updateStatement = new AST\UpdateStatement($this->UpdateClause()); - $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; + $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $updateStatement; } @@ -787,7 +861,7 @@ class Parser { $deleteStatement = new AST\DeleteStatement($this->DeleteClause()); - $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; + $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $deleteStatement; } @@ -801,12 +875,12 @@ class Parser { $this->match(Lexer::T_IDENTIFIER); - $identVariable = $this->_lexer->token['value']; + $identVariable = $this->lexer->token['value']; - $this->_deferredIdentificationVariables[] = array( + $this->deferredIdentificationVariables[] = array( 'expression' => $identVariable, - 'nestingLevel' => $this->_nestingLevel, - 'token' => $this->_lexer->token, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->token, ); return $identVariable; @@ -821,11 +895,11 @@ class Parser { $this->match(Lexer::T_IDENTIFIER); - $aliasIdentVariable = $this->_lexer->token['value']; - $exists = isset($this->_queryComponents[$aliasIdentVariable]); + $aliasIdentVariable = $this->lexer->token['value']; + $exists = isset($this->queryComponents[$aliasIdentVariable]); if ($exists) { - $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->_lexer->token); + $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token); } return $aliasIdentVariable; @@ -840,18 +914,18 @@ class Parser { $this->match(Lexer::T_IDENTIFIER); - $schemaName = ltrim($this->_lexer->token['value'], '\\'); + $schemaName = ltrim($this->lexer->token['value'], '\\'); if (strrpos($schemaName, ':') !== false) { list($namespaceAlias, $simpleClassName) = explode(':', $schemaName); - $schemaName = $this->_em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; + $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } $exists = class_exists($schemaName, true); if ( ! $exists) { - $this->semanticalError("Class '$schemaName' is not defined.", $this->_lexer->token); + $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token); } return $schemaName; @@ -866,11 +940,11 @@ class Parser { $this->match(Lexer::T_IDENTIFIER); - $resultVariable = $this->_lexer->token['value']; - $exists = isset($this->_queryComponents[$resultVariable]); + $resultVariable = $this->lexer->token['value']; + $exists = isset($this->queryComponents[$resultVariable]); if ($exists) { - $this->semanticalError("'$resultVariable' is already defined.", $this->_lexer->token); + $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token); } return $resultVariable; @@ -885,13 +959,13 @@ class Parser { $this->match(Lexer::T_IDENTIFIER); - $resultVariable = $this->_lexer->token['value']; + $resultVariable = $this->lexer->token['value']; // Defer ResultVariable validation - $this->_deferredResultVariables[] = array( + $this->deferredResultVariables[] = array( 'expression' => $resultVariable, - 'nestingLevel' => $this->_nestingLevel, - 'token' => $this->_lexer->token, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->token, ); return $resultVariable; @@ -906,7 +980,7 @@ class Parser { $identVariable = $this->IdentificationVariable(); - if ( ! isset($this->_queryComponents[$identVariable])) { + if ( ! isset($this->queryComponents[$identVariable])) { $this->semanticalError( 'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.' ); @@ -915,10 +989,10 @@ class Parser $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); - $field = $this->_lexer->token['value']; + $field = $this->lexer->token['value']; // Validate association field - $qComp = $this->_queryComponents[$identVariable]; + $qComp = $this->queryComponents[$identVariable]; $class = $qComp['metadata']; if ( ! $class->hasAssociation($field)) { @@ -942,21 +1016,21 @@ class Parser $identVariable = $this->IdentificationVariable(); $field = null; - if ($this->_lexer->isNextToken(Lexer::T_DOT)) { + if ($this->lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); - $field = $this->_lexer->token['value']; + $field = $this->lexer->token['value']; } // Creating AST node $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field); // Defer PathExpression validation if requested to be defered - $this->_deferredPathExpressions[] = array( + $this->deferredPathExpressions[] = array( 'expression' => $pathExpr, - 'nestingLevel' => $this->_nestingLevel, - 'token' => $this->_lexer->token, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->token, ); return $pathExpr; @@ -1029,7 +1103,7 @@ class Parser $this->match(Lexer::T_SELECT); // Check for DISTINCT - if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { + if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; @@ -1039,7 +1113,7 @@ class Parser $selectExpressions = array(); $selectExpressions[] = $this->SelectExpression(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $selectExpressions[] = $this->SelectExpression(); @@ -1058,7 +1132,7 @@ class Parser $isDistinct = false; $this->match(Lexer::T_SELECT); - if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { + if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; @@ -1075,16 +1149,16 @@ class Parser public function UpdateClause() { $this->match(Lexer::T_UPDATE); - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; $abstractSchemaName = $this->AbstractSchemaName(); - if ($this->_lexer->isNextToken(Lexer::T_AS)) { + if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); - $class = $this->_em->getClassMetadata($abstractSchemaName); + $class = $this->em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = array( @@ -1092,18 +1166,18 @@ class Parser 'parent' => null, 'relation' => null, 'map' => null, - 'nestingLevel' => $this->_nestingLevel, + 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); - $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; + $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; $this->match(Lexer::T_SET); $updateItems = array(); $updateItems[] = $this->UpdateItem(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $updateItems[] = $this->UpdateItem(); @@ -1124,21 +1198,21 @@ class Parser { $this->match(Lexer::T_DELETE); - if ($this->_lexer->isNextToken(Lexer::T_FROM)) { + if ($this->lexer->isNextToken(Lexer::T_FROM)) { $this->match(Lexer::T_FROM); } - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; $deleteClause = new AST\DeleteClause($this->AbstractSchemaName()); - if ($this->_lexer->isNextToken(Lexer::T_AS)) { + if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; - $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); + $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); // Building queryComponent $queryComponent = array( @@ -1146,11 +1220,11 @@ class Parser 'parent' => null, 'relation' => null, 'map' => null, - 'nestingLevel' => $this->_nestingLevel, + 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); - $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; + $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; return $deleteClause; } @@ -1167,7 +1241,7 @@ class Parser $identificationVariableDeclarations = array(); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); @@ -1188,7 +1262,7 @@ class Parser $identificationVariables = array(); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); @@ -1233,7 +1307,7 @@ class Parser $groupByItems = array($this->GroupByItem()); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $groupByItems[] = $this->GroupByItem(); @@ -1255,7 +1329,7 @@ class Parser $orderByItems = array(); $orderByItems[] = $this->OrderByItem(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $orderByItems[] = $this->OrderByItem(); @@ -1272,17 +1346,17 @@ class Parser public function Subselect() { // Increase query nesting level - $this->_nestingLevel++; + $this->nestingLevel++; $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); - $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; - $subselect->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; - $subselect->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; - $subselect->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; + $subselect->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; + $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; + $subselect->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; + $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; // Decrease query nesting level - $this->_nestingLevel--; + $this->nestingLevel--; return $subselect; } @@ -1311,20 +1385,20 @@ class Parser public function GroupByItem() { // 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) { return $this->SingleValuedPathExpression(); } // Still need to decide between IdentificationVariable or ResultVariable - $lookaheadValue = $this->_lexer->lookahead['value']; + $lookaheadValue = $this->lexer->lookahead['value']; - if ( ! isset($this->_queryComponents[$lookaheadValue])) { + if ( ! isset($this->queryComponents[$lookaheadValue])) { $this->semanticalError('Cannot group by undefined identification or result variable.'); } - return (isset($this->_queryComponents[$lookaheadValue]['metadata'])) + return (isset($this->queryComponents[$lookaheadValue]['metadata'])) ? $this->IdentificationVariable() : $this->ResultVariable(); } @@ -1340,15 +1414,15 @@ class Parser public function OrderByItem() { - $this->_lexer->peek(); // lookahead => '.' - $this->_lexer->peek(); // lookahead => token after '.' - $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.' - $this->_lexer->resetPeek(); - $glimpse = $this->_lexer->glimpse(); + $this->lexer->peek(); // lookahead => '.' + $this->lexer->peek(); // lookahead => token after '.' + $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' + $this->lexer->resetPeek(); + $glimpse = $this->lexer->glimpse(); switch (true) { - case ($this->_isMathOperator($peek)): + case ($this->isMathOperator($peek)): $expr = $this->SimpleArithmeticExpression(); break; @@ -1356,7 +1430,7 @@ class Parser $expr = $this->SingleValuedPathExpression(); break; - case ($this->_lexer->peek() && $this->_isMathOperator($this->_peekBeyondClosingParenthesis())): + case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())): $expr = $this->ScalarExpression(); break; @@ -1370,12 +1444,12 @@ class Parser $item = new AST\OrderByItem($expr); switch (true) { - case ($this->_lexer->isNextToken(Lexer::T_DESC)): + case ($this->lexer->isNextToken(Lexer::T_DESC)): $this->match(Lexer::T_DESC); $type = 'DESC'; break; - case ($this->_lexer->isNextToken(Lexer::T_ASC)): + case ($this->lexer->isNextToken(Lexer::T_ASC)): $this->match(Lexer::T_ASC); break; @@ -1401,16 +1475,16 @@ class Parser */ public function NewValue() { - if ($this->_lexer->isNextToken(Lexer::T_NULL)) { + if ($this->lexer->isNextToken(Lexer::T_NULL)) { $this->match(Lexer::T_NULL); return null; } - if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { + if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); - return new AST\InputParameter($this->_lexer->token['value']); + return new AST\InputParameter($this->lexer->token['value']); } return $this->SimpleArithmeticExpression(); @@ -1424,13 +1498,13 @@ class Parser public function IdentificationVariableDeclaration() { $rangeVariableDeclaration = $this->RangeVariableDeclaration(); - $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; + $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $joins = array(); while ( - $this->_lexer->isNextToken(Lexer::T_LEFT) || - $this->_lexer->isNextToken(Lexer::T_INNER) || - $this->_lexer->isNextToken(Lexer::T_JOIN) + $this->lexer->isNextToken(Lexer::T_LEFT) || + $this->lexer->isNextToken(Lexer::T_INNER) || + $this->lexer->isNextToken(Lexer::T_JOIN) ) { $joins[] = $this->Join(); } @@ -1448,7 +1522,7 @@ class Parser */ public function SubselectIdentificationVariableDeclaration() { - $this->_lexer->glimpse(); + $this->lexer->glimpse(); /* NOT YET IMPLEMENTED! @@ -1478,20 +1552,20 @@ class Parser $joinType = AST\Join::JOIN_TYPE_INNER; switch (true) { - case ($this->_lexer->isNextToken(Lexer::T_LEFT)): + case ($this->lexer->isNextToken(Lexer::T_LEFT)): $this->match(Lexer::T_LEFT); $joinType = AST\Join::JOIN_TYPE_LEFT; // Possible LEFT OUTER join - if ($this->_lexer->isNextToken(Lexer::T_OUTER)) { + if ($this->lexer->isNextToken(Lexer::T_OUTER)) { $this->match(Lexer::T_OUTER); $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; } break; - case ($this->_lexer->isNextToken(Lexer::T_INNER)): + case ($this->lexer->isNextToken(Lexer::T_INNER)): $this->match(Lexer::T_INNER); break; @@ -1501,7 +1575,7 @@ class Parser $this->match(Lexer::T_JOIN); - $next = $this->_lexer->glimpse(); + $next = $this->lexer->glimpse(); $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration(); @@ -1510,7 +1584,7 @@ class Parser $join = new AST\Join($joinType, $joinDeclaration); // Check for ad-hoc Join conditions - if ($this->_lexer->isNextToken(Lexer::T_WITH) || $joinDeclaration instanceof AST\RangeVariableDeclaration) { + if ($this->lexer->isNextToken(Lexer::T_WITH) || $joinDeclaration instanceof AST\RangeVariableDeclaration) { $this->match(Lexer::T_WITH); $join->conditionalExpression = $this->ConditionalExpression(); @@ -1528,13 +1602,13 @@ class Parser { $abstractSchemaName = $this->AbstractSchemaName(); - if ($this->_lexer->isNextToken(Lexer::T_AS)) { + if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; $aliasIdentificationVariable = $this->AliasIdentificationVariable(); - $classMetadata = $this->_em->getClassMetadata($abstractSchemaName); + $classMetadata = $this->em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = array( @@ -1542,11 +1616,11 @@ class Parser 'parent' => null, 'relation' => null, 'map' => null, - 'nestingLevel' => $this->_nestingLevel, + 'nestingLevel' => $this->nestingLevel, 'token' => $token ); - $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; + $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); } @@ -1560,18 +1634,18 @@ class Parser { $joinAssociationPathExpression = $this->JoinAssociationPathExpression(); - if ($this->_lexer->isNextToken(Lexer::T_AS)) { + if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); - $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; + $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $identificationVariable = $joinAssociationPathExpression->identificationVariable; $field = $joinAssociationPathExpression->associationField; - $class = $this->_queryComponents[$identificationVariable]['metadata']; - $targetClass = $this->_em->getClassMetadata($class->associationMappings[$field]['targetEntity']); + $class = $this->queryComponents[$identificationVariable]['metadata']; + $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']); // Building queryComponent $joinQueryComponent = array( @@ -1579,11 +1653,11 @@ class Parser 'parent' => $joinAssociationPathExpression->identificationVariable, 'relation' => $class->getAssociationMapping($field), 'map' => null, - 'nestingLevel' => $this->_nestingLevel, - 'token' => $this->_lexer->lookahead + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->lookahead ); - $this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; + $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy); } @@ -1606,13 +1680,13 @@ class Parser $this->match(Lexer::T_OPEN_CURLY_BRACE); $this->match(Lexer::T_IDENTIFIER); - $partialFieldSet[] = $this->_lexer->token['value']; + $partialFieldSet[] = $this->lexer->token['value']; - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $this->match(Lexer::T_IDENTIFIER); - $partialFieldSet[] = $this->_lexer->token['value']; + $partialFieldSet[] = $this->lexer->token['value']; } $this->match(Lexer::T_CLOSE_CURLY_BRACE); @@ -1620,15 +1694,71 @@ class Parser $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet); // Defer PartialObjectExpression validation - $this->_deferredPartialObjectExpressions[] = array( + $this->deferredPartialObjectExpressions[] = array( 'expression' => $partialObjectExpression, - 'nestingLevel' => $this->_nestingLevel, - 'token' => $this->_lexer->token, + 'nestingLevel' => $this->nestingLevel, + 'token' => $this->lexer->token, ); return $partialObjectExpression; } + /** + * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")" + * + * @return \Doctrine\ORM\Query\AST\NewObjectExpression + */ + public function NewObjectExpression() + { + $this->match(Lexer::T_NEW); + $this->match(Lexer::T_IDENTIFIER); + + $token = $this->lexer->token; + $className = $token['value']; + + if (strrpos($className, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $className); + + $className = $this->em->getConfiguration() + ->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; + } + + $this->match(Lexer::T_OPEN_PARENTHESIS); + + $args[] = $this->NewObjectArg(); + + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { + $this->match(Lexer::T_COMMA); + + $args[] = $this->NewObjectArg(); + } + + $this->match(Lexer::T_CLOSE_PARENTHESIS); + + $expression = new AST\NewObjectExpression($className, $args); + + // Defer NewObjectExpression validation + $this->deferredNewObjectExpressions[] = array( + 'token' => $token, + 'expression' => $expression, + 'nestingLevel' => $this->nestingLevel, + ); + + return $expression; + } + + /** + * NewObjectArg ::= ScalarExpression + * + * @TODO - Maybe you should support other expressions and nested "new" operator + * + * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression + */ + public function NewObjectArg() + { + return $this->ScalarExpression(); + } + /** * IndexBy ::= "INDEX" "BY" StateFieldPathExpression * @@ -1641,7 +1771,7 @@ class Parser $pathExpr = $this->StateFieldPathExpression(); // Add the INDEX BY info to the query component - $this->_queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field; + $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field; return new AST\IndexBy($pathExpr); } @@ -1655,16 +1785,16 @@ class Parser */ public function ScalarExpression() { - $lookahead = $this->_lexer->lookahead['type']; + $lookahead = $this->lexer->lookahead['type']; switch ($lookahead) { case Lexer::T_IDENTIFIER: - $this->_lexer->peek(); // lookahead => '.' - $this->_lexer->peek(); // lookahead => token after '.' - $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.' - $this->_lexer->resetPeek(); + $this->lexer->peek(); // lookahead => '.' + $this->lexer->peek(); // lookahead => token after '.' + $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' + $this->lexer->resetPeek(); - if ($this->_isMathOperator($peek)) { + if ($this->isMathOperator($peek)) { return $this->SimpleArithmeticExpression(); } @@ -1681,7 +1811,7 @@ class Parser case Lexer::T_FALSE: $this->match($lookahead); - return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']); + return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); @@ -1694,19 +1824,19 @@ class Parser return $this->CaseExpression(); default: - if ( ! ($this->_isFunction() || $this->_isAggregateFunction($lookahead))) { + if ( ! ($this->isFunction() || $this->isAggregateFunction($lookahead))) { $this->syntaxError(); } // We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator) - $this->_lexer->peek(); // "(" - $peek = $this->_peekBeyondClosingParenthesis(); + $this->lexer->peek(); // "(" + $peek = $this->peekBeyondClosingParenthesis(); - if ($this->_isMathOperator($peek)) { + if ($this->isMathOperator($peek)) { return $this->SimpleArithmeticExpression(); } - if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { + if ($this->isAggregateFunction($this->lexer->lookahead['type'])) { return $this->AggregateExpression(); } @@ -1728,7 +1858,7 @@ class Parser */ public function CaseExpression() { - $lookahead = $this->_lexer->lookahead['type']; + $lookahead = $this->lexer->lookahead['type']; switch ($lookahead) { case Lexer::T_NULLIF: @@ -1738,8 +1868,8 @@ class Parser return $this->CoalesceExpression(); case Lexer::T_CASE: - $this->_lexer->resetPeek(); - $peek = $this->_lexer->peek(); + $this->lexer->resetPeek(); + $peek = $this->lexer->peek(); if ($peek['type'] === Lexer::T_WHEN) { return $this->GeneralCaseExpression(); @@ -1769,7 +1899,7 @@ class Parser $scalarExpressions = array(); $scalarExpressions[] = $this->ScalarExpression(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $scalarExpressions[] = $this->ScalarExpression(); @@ -1813,7 +1943,7 @@ class Parser do { $whenClauses[] = $this->WhenClause(); - } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); + } while ($this->lexer->isNextToken(Lexer::T_WHEN)); $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); @@ -1836,7 +1966,7 @@ class Parser do { $simpleWhenClauses[] = $this->SimpleWhenClause(); - } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); + } while ($this->lexer->isNextToken(Lexer::T_WHEN)); $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); @@ -1885,8 +2015,8 @@ class Parser { $expression = null; $identVariable = null; - $peek = $this->_lexer->glimpse(); - $lookaheadType = $this->_lexer->lookahead['type']; + $peek = $this->lexer->glimpse(); + $lookaheadType = $this->lexer->lookahead['type']; switch (true) { // ScalarExpression (u.name) @@ -1907,16 +2037,16 @@ class Parser break; // DQL Function (SUM(u.value) or SUM(u.value) + 1) - case ($this->_isFunction()): - $this->_lexer->peek(); // "(" + case ($this->isFunction()): + $this->lexer->peek(); // "(" switch (true) { - case ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())): + case ($this->isMathOperator($this->peekBeyondClosingParenthesis())): // SUM(u.id) + COUNT(u.id) $expression = $this->ScalarExpression(); break; - case ($this->_isAggregateFunction($lookaheadType)): + case ($this->isAggregateFunction($lookaheadType)): // COUNT(u.id) $expression = $this->AggregateExpression(); break; @@ -1953,22 +2083,27 @@ class Parser $expression = $this->SimpleArithmeticExpression(); break; + // NewObjectExpression (New ClassName(id, name)) + case ($lookaheadType === Lexer::T_NEW): + $expression = $this->NewObjectExpression(); + break; + default: $this->syntaxError( 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', - $this->_lexer->lookahead + $this->lexer->lookahead ); } // [["AS"] ["HIDDEN"] AliasResultVariable] - if ($this->_lexer->isNextToken(Lexer::T_AS)) { + if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $hiddenAliasResultVariable = false; - if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) { + if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) { $this->match(Lexer::T_HIDDEN); $hiddenAliasResultVariable = true; @@ -1976,14 +2111,14 @@ class Parser $aliasResultVariable = null; - if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { - $token = $this->_lexer->lookahead; + if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { + $token = $this->lexer->lookahead; $aliasResultVariable = $this->AliasResultVariable(); // Include AliasResultVariable in query components. - $this->_queryComponents[$aliasResultVariable] = array( + $this->queryComponents[$aliasResultVariable] = array( 'resultVariable' => $expression, - 'nestingLevel' => $this->_nestingLevel, + 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); } @@ -1993,7 +2128,7 @@ class Parser $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); if ($identVariable) { - $this->_identVariableExpressions[$identVariable] = $expr; + $this->identVariableExpressions[$identVariable] = $expr; } return $expr; @@ -2009,9 +2144,9 @@ class Parser */ public function SimpleSelectExpression() { - $peek = $this->_lexer->glimpse(); + $peek = $this->lexer->glimpse(); - switch ($this->_lexer->lookahead['type']) { + switch ($this->lexer->lookahead['type']) { case Lexer::T_IDENTIFIER: switch (true) { case ($peek['type'] === Lexer::T_DOT): @@ -2024,13 +2159,13 @@ class Parser return new AST\SimpleSelectExpression($expression); - case ($this->_isFunction()): + case ($this->isFunction()): // SUM(u.id) + COUNT(u.id) - if ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())) { + if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) { return new AST\SimpleSelectExpression($this->ScalarExpression()); } // COUNT(u.id) - if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { + if ($this->isAggregateFunction($this->lexer->lookahead['type'])) { return new AST\SimpleSelectExpression($this->AggregateExpression()); } // IDENTITY(u) @@ -2060,24 +2195,24 @@ class Parser // Do nothing } - $this->_lexer->peek(); + $this->lexer->peek(); $expression = $this->ScalarExpression(); $expr = new AST\SimpleSelectExpression($expression); - if ($this->_lexer->isNextToken(Lexer::T_AS)) { + if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } - if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { - $token = $this->_lexer->lookahead; + if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { + $token = $this->lexer->lookahead; $resultVariable = $this->AliasResultVariable(); $expr->fieldIdentificationVariable = $resultVariable; // Include AliasResultVariable in query components. - $this->_queryComponents[$resultVariable] = array( + $this->queryComponents[$resultVariable] = array( 'resultvariable' => $expr, - 'nestingLevel' => $this->_nestingLevel, + 'nestingLevel' => $this->nestingLevel, 'token' => $token, ); } @@ -2095,7 +2230,7 @@ class Parser $conditionalTerms = array(); $conditionalTerms[] = $this->ConditionalTerm(); - while ($this->_lexer->isNextToken(Lexer::T_OR)) { + while ($this->lexer->isNextToken(Lexer::T_OR)) { $this->match(Lexer::T_OR); $conditionalTerms[] = $this->ConditionalTerm(); @@ -2120,7 +2255,7 @@ class Parser $conditionalFactors = array(); $conditionalFactors[] = $this->ConditionalFactor(); - while ($this->_lexer->isNextToken(Lexer::T_AND)) { + while ($this->lexer->isNextToken(Lexer::T_AND)) { $this->match(Lexer::T_AND); $conditionalFactors[] = $this->ConditionalFactor(); @@ -2144,7 +2279,7 @@ class Parser { $not = false; - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; @@ -2173,18 +2308,18 @@ class Parser { $condPrimary = new AST\ConditionalPrimary; - if ( ! $this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { + if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; } // Peek beyond the matching closing paranthesis ')' - $peek = $this->_peekBeyondClosingParenthesis(); + $peek = $this->peekBeyondClosingParenthesis(); if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) || in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS)) || - $this->_isMathOperator($peek)) { + $this->isMathOperator($peek)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; @@ -2206,54 +2341,54 @@ class Parser */ public function SimpleConditionalExpression() { - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { - $token = $this->_lexer->glimpse(); + if ($this->lexer->isNextToken(Lexer::T_NOT)) { + $token = $this->lexer->glimpse(); } if ($token['type'] === Lexer::T_EXISTS) { return $this->ExistsExpression(); } - $peek = $this->_lexer->glimpse(); + $peek = $this->lexer->glimpse(); if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER) { if ($peek['value'] == '(') { // Peek beyond the matching closing paranthesis ')' - $this->_lexer->peek(); - $token = $this->_peekBeyondClosingParenthesis(false); + $this->lexer->peek(); + $token = $this->peekBeyondClosingParenthesis(false); if ($token['type'] === Lexer::T_NOT) { - $token = $this->_lexer->peek(); + $token = $this->lexer->peek(); } - $this->_lexer->resetPeek(); + $this->lexer->resetPeek(); } else { // Peek beyond the PathExpression (or InputParameter) - $peek = $this->_lexer->peek(); + $peek = $this->lexer->peek(); while ($peek['value'] === '.') { - $this->_lexer->peek(); - $peek = $this->_lexer->peek(); + $this->lexer->peek(); + $peek = $this->lexer->peek(); } // Also peek beyond a NOT if there is one if ($peek['type'] === Lexer::T_NOT) { - $peek = $this->_lexer->peek(); + $peek = $this->lexer->peek(); } $token = $peek; // We need to go even further in case of IS (differenciate between NULL and EMPTY) - $lookahead = $this->_lexer->peek(); + $lookahead = $this->lexer->peek(); // Also peek beyond a NOT if there is one if ($lookahead['type'] === Lexer::T_NOT) { - $lookahead = $this->_lexer->peek(); + $lookahead = $this->lexer->peek(); } - $this->_lexer->resetPeek(); + $this->lexer->resetPeek(); } } @@ -2290,7 +2425,7 @@ class Parser ); $this->match(Lexer::T_IS); - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $emptyColletionCompExpr->not = true; } @@ -2313,7 +2448,7 @@ class Parser $not = false; $entityExpr = $this->EntityExpression(); - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; @@ -2321,7 +2456,7 @@ class Parser $this->match(Lexer::T_MEMBER); - if ($this->_lexer->isNextToken(Lexer::T_OF)) { + if ($this->lexer->isNextToken(Lexer::T_OF)) { $this->match(Lexer::T_OF); } @@ -2340,24 +2475,24 @@ class Parser */ public function Literal() { - switch ($this->_lexer->lookahead['type']) { + switch ($this->lexer->lookahead['type']) { case Lexer::T_STRING: $this->match(Lexer::T_STRING); - return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']); + return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); case Lexer::T_INTEGER: case Lexer::T_FLOAT: $this->match( - $this->_lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT + $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT ); - return new AST\Literal(AST\Literal::NUMERIC, $this->_lexer->token['value']); + return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']); case Lexer::T_TRUE: case Lexer::T_FALSE: $this->match( - $this->_lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE + $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE ); - return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']); + return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']); default: $this->syntaxError('Literal'); @@ -2371,7 +2506,7 @@ class Parser */ public function InParameter() { - if ($this->_lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) { + if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) { return $this->InputParameter(); } @@ -2387,7 +2522,7 @@ class Parser { $this->match(Lexer::T_INPUT_PARAMETER); - return new AST\InputParameter($this->_lexer->token['value']); + return new AST\InputParameter($this->lexer->token['value']); } /** @@ -2399,8 +2534,8 @@ class Parser { $expr = new AST\ArithmeticExpression; - if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { - $peek = $this->_lexer->glimpse(); + if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { + $peek = $this->lexer->glimpse(); if ($peek['type'] === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); @@ -2426,10 +2561,10 @@ class Parser $terms = array(); $terms[] = $this->ArithmeticTerm(); - while (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) { + while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) { $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); - $terms[] = $this->_lexer->token['value']; + $terms[] = $this->lexer->token['value']; $terms[] = $this->ArithmeticTerm(); } @@ -2452,10 +2587,10 @@ class Parser $factors = array(); $factors[] = $this->ArithmeticFactor(); - while (($isMult = $this->_lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->_lexer->isNextToken(Lexer::T_DIVIDE)) { + while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) { $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE); - $factors[] = $this->_lexer->token['value']; + $factors[] = $this->lexer->token['value']; $factors[] = $this->ArithmeticFactor(); } @@ -2477,7 +2612,7 @@ class Parser { $sign = null; - if (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) { + if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) { $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); $sign = $isPlus; } @@ -2501,7 +2636,7 @@ class Parser */ public function ArithmeticPrimary() { - if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { + if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr = $this->SimpleArithmeticExpression(); @@ -2510,14 +2645,14 @@ class Parser return $expr; } - switch ($this->_lexer->lookahead['type']) { + switch ($this->lexer->lookahead['type']) { case Lexer::T_COALESCE: case Lexer::T_NULLIF: case Lexer::T_CASE: return $this->CaseExpression(); case Lexer::T_IDENTIFIER: - $peek = $this->_lexer->glimpse(); + $peek = $this->lexer->glimpse(); if ($peek['value'] == '(') { return $this->FunctionDeclaration(); @@ -2527,7 +2662,7 @@ class Parser return $this->SingleValuedPathExpression(); } - if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) { + if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) { return $this->ResultVariable(); } @@ -2537,10 +2672,10 @@ class Parser return $this->InputParameter(); default: - $peek = $this->_lexer->glimpse(); + $peek = $this->lexer->glimpse(); if ($peek['value'] == '(') { - if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { + if ($this->isAggregateFunction($this->lexer->lookahead['type'])) { return $this->AggregateExpression(); } @@ -2559,8 +2694,8 @@ class Parser */ public function StringExpression() { - if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { - $peek = $this->_lexer->glimpse(); + if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { + $peek = $this->lexer->glimpse(); if ($peek['type'] === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); @@ -2579,11 +2714,11 @@ class Parser */ public function StringPrimary() { - $lookaheadType = $this->_lexer->lookahead['type']; + $lookaheadType = $this->lexer->lookahead['type']; switch ($lookaheadType) { case Lexer::T_IDENTIFIER: - $peek = $this->_lexer->glimpse(); + $peek = $this->lexer->glimpse(); if ($peek['value'] == '.') { return $this->StateFieldPathExpression(); @@ -2600,7 +2735,7 @@ class Parser case Lexer::T_STRING: $this->match(Lexer::T_STRING); - return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']); + return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); @@ -2611,7 +2746,7 @@ class Parser return $this->CaseExpression(); default: - if ($this->_isAggregateFunction($lookaheadType)) { + if ($this->isAggregateFunction($lookaheadType)) { return $this->AggregateExpression(); } } @@ -2629,9 +2764,9 @@ class Parser */ public function EntityExpression() { - $glimpse = $this->_lexer->glimpse(); + $glimpse = $this->lexer->glimpse(); - if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') { + if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') { return $this->SingleValuedAssociationPathExpression(); } @@ -2645,7 +2780,7 @@ class Parser */ public function SimpleEntityExpression() { - if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { + if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { return $this->InputParameter(); } @@ -2661,7 +2796,7 @@ class Parser */ public function AggregateExpression() { - $lookaheadType = $this->_lexer->lookahead['type']; + $lookaheadType = $this->lexer->lookahead['type']; $isDistinct = false; if ( ! in_array($lookaheadType, array(Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM))) { @@ -2669,10 +2804,10 @@ class Parser } $this->match($lookaheadType); - $functionName = $this->_lexer->token['value']; + $functionName = $this->lexer->token['value']; $this->match(Lexer::T_OPEN_PARENTHESIS); - if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { + if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } @@ -2693,8 +2828,8 @@ class Parser */ public function QuantifiedExpression() { - $lookaheadType = $this->_lexer->lookahead['type']; - $value = $this->_lexer->lookahead['value']; + $lookaheadType = $this->lexer->lookahead['type']; + $value = $this->lexer->lookahead['value']; if ( ! in_array($lookaheadType, array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME))) { $this->syntaxError('ALL, ANY or SOME'); @@ -2721,7 +2856,7 @@ class Parser $not = false; $arithExpr1 = $this->ArithmeticExpression(); - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } @@ -2744,11 +2879,11 @@ class Parser */ public function ComparisonExpression() { - $this->_lexer->glimpse(); + $this->lexer->glimpse(); $leftExpr = $this->ArithmeticExpression(); $operator = $this->ComparisonOperator(); - $rightExpr = ($this->_isNextAllAnySome()) + $rightExpr = ($this->isNextAllAnySome()) ? $this->QuantifiedExpression() : $this->ArithmeticExpression(); @@ -2764,7 +2899,7 @@ class Parser { $inExpression = new AST\InExpression($this->ArithmeticExpression()); - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $inExpression->not = true; } @@ -2772,13 +2907,13 @@ class Parser $this->match(Lexer::T_IN); $this->match(Lexer::T_OPEN_PARENTHESIS); - if ($this->_lexer->isNextToken(Lexer::T_SELECT)) { + if ($this->lexer->isNextToken(Lexer::T_SELECT)) { $inExpression->subselect = $this->Subselect(); } else { $literals = array(); $literals[] = $this->InParameter(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $literals[] = $this->InParameter(); } @@ -2800,7 +2935,7 @@ class Parser { $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable()); - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $instanceOfExpression->not = true; } @@ -2810,12 +2945,12 @@ class Parser $exprValues = array(); - if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { + if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); $exprValues[] = $this->InstanceOfParameter(); - while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $exprValues[] = $this->InstanceOfParameter(); @@ -2842,10 +2977,10 @@ class Parser */ public function InstanceOfParameter() { - if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { + if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); - return new AST\InputParameter($this->_lexer->token['value']); + return new AST\InputParameter($this->lexer->token['value']); } return $this->AliasIdentificationVariable(); @@ -2861,27 +2996,27 @@ class Parser $stringExpr = $this->StringExpression(); $not = false; - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_LIKE); - if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { + if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); - $stringPattern = new AST\InputParameter($this->_lexer->token['value']); + $stringPattern = new AST\InputParameter($this->lexer->token['value']); } else { $stringPattern = $this->StringPrimary(); } $escapeChar = null; - if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) { + if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) { $this->match(Lexer::T_ESCAPE); $this->match(Lexer::T_STRING); - $escapeChar = new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']); + $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); } $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar); @@ -2897,9 +3032,9 @@ class Parser */ public function NullComparisonExpression() { - if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { + if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); - $expr = new AST\InputParameter($this->_lexer->token['value']); + $expr = new AST\InputParameter($this->lexer->token['value']); } else { $expr = $this->SingleValuedPathExpression(); } @@ -2907,7 +3042,7 @@ class Parser $nullCompExpr = new AST\NullComparisonExpression($expr); $this->match(Lexer::T_IS); - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $nullCompExpr->not = true; } @@ -2926,7 +3061,7 @@ class Parser { $not = false; - if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } @@ -2949,7 +3084,7 @@ class Parser */ public function ComparisonOperator() { - switch ($this->_lexer->lookahead['value']) { + switch ($this->lexer->lookahead['value']) { case '=': $this->match(Lexer::T_EQUALS); @@ -2959,10 +3094,10 @@ class Parser $this->match(Lexer::T_LOWER_THAN); $operator = '<'; - if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) { + if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; - } else if ($this->_lexer->isNextToken(Lexer::T_GREATER_THAN)) { + } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) { $this->match(Lexer::T_GREATER_THAN); $operator .= '>'; } @@ -2973,7 +3108,7 @@ class Parser $this->match(Lexer::T_GREATER_THAN); $operator = '>'; - if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) { + if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; } @@ -2996,7 +3131,7 @@ class Parser */ public function FunctionDeclaration() { - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; $funcName = strtolower($token['value']); // Check for built-in functions first! @@ -3020,11 +3155,11 @@ class Parser */ private function CustomFunctionDeclaration() { - $token = $this->_lexer->lookahead; + $token = $this->lexer->lookahead; $funcName = strtolower($token['value']); // Check for custom functions afterwards - $config = $this->_em->getConfiguration(); + $config = $this->em->getConfiguration(); switch (true) { case ($config->getCustomStringFunction($funcName) !== null): @@ -3052,7 +3187,7 @@ class Parser */ public function FunctionsReturningNumerics() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); + $funcNameLower = strtolower($this->lexer->lookahead['value']); $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); @@ -3064,8 +3199,8 @@ class Parser public function CustomFunctionsReturningNumerics() { // getCustomNumericFunction is case-insensitive - $funcName = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName); + $funcName = strtolower($this->lexer->lookahead['value']); + $funcClass = $this->em->getConfiguration()->getCustomNumericFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); @@ -3078,7 +3213,7 @@ class Parser */ public function FunctionsReturningDatetime() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); + $funcNameLower = strtolower($this->lexer->lookahead['value']); $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); @@ -3090,8 +3225,8 @@ class Parser public function CustomFunctionsReturningDatetime() { // getCustomDatetimeFunction is case-insensitive - $funcName = $this->_lexer->lookahead['value']; - $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName); + $funcName = $this->lexer->lookahead['value']; + $funcClass = $this->em->getConfiguration()->getCustomDatetimeFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); @@ -3109,7 +3244,7 @@ class Parser */ public function FunctionsReturningStrings() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); + $funcNameLower = strtolower($this->lexer->lookahead['value']); $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); @@ -3121,8 +3256,8 @@ class Parser public function CustomFunctionsReturningStrings() { // getCustomStringFunction is case-insensitive - $funcName = $this->_lexer->lookahead['value']; - $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName); + $funcName = $this->lexer->lookahead['value']; + $funcClass = $this->em->getConfiguration()->getCustomStringFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index b8cbd3225..35505bf5c 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -118,6 +118,11 @@ class ResultSetMapping */ public $isIdentifierColumn = array(); + /** + * @var array Maps column names in the result set to field names for each new object expression. + */ + public $newObjectMappings = array(); + /** * Adds an entity result to this ResultSetMapping. * diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 13c2b5df0..75de800ae 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -34,6 +34,7 @@ use Doctrine\DBAL\LockMode, * @author Roman Borschel * @author Benjamin Eberlei * @author Alexander + * @author Fabio B. Silva * @since 2.0 * @todo Rename: SQLWalker */ @@ -77,6 +78,13 @@ class SqlWalker implements TreeWalker */ private $sqlParamIndex = 0; + /** + * Counters for generating indexes. + * + * @var integer + */ + private $newObjectCounter = 0; + /** * @var ParserResult */ @@ -1221,6 +1229,10 @@ class SqlWalker implements TreeWalker } break; + case ($expr instanceof AST\NewObjectExpression): + $sql .= $this->walkNewObject($expr); + break; + default: // IdentificationVariable or PartialObjectExpression if ($expr instanceof AST\PartialObjectExpression) { @@ -1387,6 +1399,68 @@ class SqlWalker implements TreeWalker . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression); } + /** + * @param AST\NewObjectExpression + * @return string The SQL. + */ + public function walkNewObject($newObjectExpression) + { + $sqlSelectExpressions = array(); + $objIndex = $this->newObjectCounter++; + + foreach ($newObjectExpression->args as $argIndex => $e) { + $resultAlias = $this->scalarResultCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); + + switch (true) { + case ($e instanceof AST\NewObjectExpression): + $sqlSelectExpressions[] = $e->dispatch($this); + break; + + default: + $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias; + break; + } + + switch (true) { + case ($e instanceof AST\PathExpression): + $fieldName = $e->field; + $dqlAlias = $e->identificationVariable; + $qComp = $this->queryComponents[$dqlAlias]; + $class = $qComp['metadata']; + $fieldType = $class->getTypeOfField($fieldName); + break; + + case ($e instanceof AST\Literal): + switch ($e->type) { + case AST\Literal::BOOLEAN: + $fieldType = 'boolean'; + break; + + case AST\Literal::NUMERIC: + $fieldType = is_float($e->value) ? 'float' : 'integer'; + break; + } + break; + + default: + $fieldType = 'string'; + break; + } + + $this->scalarResultAliasMap[$resultAlias] = $columnAlias; + $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType); + + $this->rsm->newObjectMappings[$columnAlias] = array( + 'className' => $newObjectExpression->className, + 'objIndex' => $objIndex, + 'argIndex' => $argIndex + ); + } + + return implode(', ', $sqlSelectExpressions); + } + /** * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. * diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddressDTO.php b/tests/Doctrine/Tests/Models/CMS/CmsAddressDTO.php new file mode 100644 index 000000000..2a4f220f8 --- /dev/null +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddressDTO.php @@ -0,0 +1,17 @@ +country = $country; + $this->city = $city; + $this->zip = $zip; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUserDTO.php b/tests/Doctrine/Tests/Models/CMS/CmsUserDTO.php new file mode 100644 index 000000000..0a07d26c1 --- /dev/null +++ b/tests/Doctrine/Tests/Models/CMS/CmsUserDTO.php @@ -0,0 +1,19 @@ +name = $name; + $this->email = $email; + $this->address = $address; + $this->phonenumbers = $phonenumbers; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php b/tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php new file mode 100644 index 000000000..4ef9d29e2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php @@ -0,0 +1,590 @@ +useModelSet('cms'); + parent::setUp(); + + $this->loadFixtures(); + } + + private function loadFixtures() + { + $u1 = new CmsUser; + $u2 = new CmsUser; + $u3 = new CmsUser; + + $u1->setEmail(new CmsEmail()); + $u1->setAddress(new CmsAddress()); + $u1->addPhonenumber(new CmsPhonenumber()); + + $u2->setEmail(new CmsEmail()); + $u2->setAddress(new CmsAddress()); + $u2->addPhonenumber(new CmsPhonenumber()); + $u2->addPhonenumber(new CmsPhonenumber()); + + $u3->setEmail(new CmsEmail()); + $u3->setAddress(new CmsAddress()); + $u3->addPhonenumber(new CmsPhonenumber()); + $u3->addPhonenumber(new CmsPhonenumber()); + $u3->addPhonenumber(new CmsPhonenumber()); + + $u1->name = 'Test 1'; + $u1->username = '1test'; + $u1->status = 'developer'; + $u1->email->email = 'email@test1.com'; + $u1->address->zip = '111111111'; + $u1->address->city = 'Some City 1'; + $u1->address->country = 'Some Country 2'; + $u1->phonenumbers[0]->phonenumber = "(11) 1111-1111"; + + $u2->name = 'Test 2'; + $u2->username = '2test'; + $u2->status = 'developer'; + $u2->email->email = 'email@test2.com'; + $u2->address->zip = '222222222'; + $u2->address->city = 'Some City 2'; + $u2->address->country = 'Some Country 2'; + $u2->phonenumbers[0]->phonenumber = "(22) 1111-1111"; + $u2->phonenumbers[1]->phonenumber = "(22) 2222-2222"; + + $u3->name = 'Test 3'; + $u3->username = '3test'; + $u3->status = 'developer'; + $u3->email->email = 'email@test3.com'; + $u3->address->zip = '33333333'; + $u3->address->city = 'Some City 3'; + $u3->address->country = 'Some Country 3'; + $u3->phonenumbers[0]->phonenumber = "(33) 1111-1111"; + $u3->phonenumbers[1]->phonenumber = "(33) 2222-2222"; + $u3->phonenumbers[2]->phonenumber = "(33) 3333-3333"; + + $this->_em->persist($u1); + $this->_em->persist($u2); + $this->_em->persist($u3); + + $this->_em->flush(); + $this->_em->clear(); + + $this->fixtures = array($u1, $u2, $u3); + } + + public function testShouldSupportsBasicUsage() + { + $dql = " + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + e.email, + a.city + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + + $this->assertEquals($this->fixtures[0]->name, $result[0]->name); + $this->assertEquals($this->fixtures[1]->name, $result[1]->name); + $this->assertEquals($this->fixtures[2]->name, $result[2]->name); + + $this->assertEquals($this->fixtures[0]->email->email, $result[0]->email); + $this->assertEquals($this->fixtures[1]->email->email, $result[1]->email); + $this->assertEquals($this->fixtures[2]->email->email, $result[2]->email); + + $this->assertEquals($this->fixtures[0]->address->city, $result[0]->address); + $this->assertEquals($this->fixtures[1]->address->city, $result[1]->address); + $this->assertEquals($this->fixtures[2]->address->city, $result[2]->address); + } + + public function testShouldAssumeFromEntityNamespaceWhenNotGiven() + { + $dql = " + SELECT + new CmsUserDTO(u.name, e.email, a.city) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + } + + public function testShouldSupportFromEntityNamespaceAlias() + { + $dql = " + SELECT + new CmsUserDTO(u.name, e.email, a.city) + FROM + cms:CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name"; + + + $this->_em->getConfiguration() + ->addEntityNamespace('cms', 'Doctrine\Tests\Models\CMS'); + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + } + + public function testShouldSupportValueObjectNamespaceAlias() + { + $dql = " + SELECT + new cms:CmsUserDTO(u.name, e.email, a.city) + FROM + cms:CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name"; + + + $this->_em->getConfiguration() + ->addEntityNamespace('cms', 'Doctrine\Tests\Models\CMS'); + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + } + + public function testShouldSupportLiteralExpression() + { + $dql = " + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + 'fabio.bat.silva@gmail.com', + FALSE, + 123 + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + JOIN + u.phonenumbers p + GROUP BY + u, e, a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + + + $this->assertEquals($this->fixtures[0]->name, $result[0]->name); + $this->assertEquals($this->fixtures[1]->name, $result[1]->name); + $this->assertEquals($this->fixtures[2]->name, $result[2]->name); + + $this->assertEquals('fabio.bat.silva@gmail.com', $result[0]->email); + $this->assertEquals('fabio.bat.silva@gmail.com', $result[1]->email); + $this->assertEquals('fabio.bat.silva@gmail.com', $result[2]->email); + + $this->assertEquals(false, $result[0]->address); + $this->assertEquals(false, $result[1]->address); + $this->assertEquals(false, $result[2]->address); + + $this->assertEquals(123, $result[0]->phonenumbers); + $this->assertEquals(123, $result[1]->phonenumbers); + $this->assertEquals(123, $result[2]->phonenumbers); + } + + public function testShouldSupportCaseExpression() + { + $dql = " + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + CASE WHEN (e.email = 'email@test1.com') THEN 'TEST1' ELSE 'OTHER_TEST' END + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + JOIN + u.phonenumbers p + GROUP BY + u, e, a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + + + $this->assertEquals($this->fixtures[0]->name, $result[0]->name); + $this->assertEquals($this->fixtures[1]->name, $result[1]->name); + $this->assertEquals($this->fixtures[2]->name, $result[2]->name); + + $this->assertEquals('TEST1', $result[0]->email); + $this->assertEquals('OTHER_TEST', $result[1]->email); + $this->assertEquals('OTHER_TEST', $result[2]->email); + } + + public function testShouldSupportSimpleArithmeticExpression() + { + $dql = " + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + e.email, + a.city, + a.id + u.id + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + JOIN + u.phonenumbers p + GROUP BY + u, e, a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + + $this->assertEquals($this->fixtures[0]->name, $result[0]->name); + $this->assertEquals($this->fixtures[1]->name, $result[1]->name); + $this->assertEquals($this->fixtures[2]->name, $result[2]->name); + + $this->assertEquals($this->fixtures[0]->email->email, $result[0]->email); + $this->assertEquals($this->fixtures[1]->email->email, $result[1]->email); + $this->assertEquals($this->fixtures[2]->email->email, $result[2]->email); + + $this->assertEquals($this->fixtures[0]->address->city, $result[0]->address); + $this->assertEquals($this->fixtures[1]->address->city, $result[1]->address); + $this->assertEquals($this->fixtures[2]->address->city, $result[2]->address); + + $this->assertEquals( + ($this->fixtures[0]->address->id + $this->fixtures[0]->id), + $result[0]->phonenumbers + ); + + $this->assertEquals( + ($this->fixtures[1]->address->id + $this->fixtures[1]->id), + $result[1]->phonenumbers + ); + + $this->assertEquals( + ($this->fixtures[2]->address->id + $this->fixtures[2]->id), + $result[2]->phonenumbers + ); + } + + public function testShouldSupportAggregateFunctions() + { + $dql = " + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + e.email, + a.city, + COUNT(p) + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + JOIN + u.phonenumbers p + GROUP BY + u, e, a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + + $this->assertEquals($this->fixtures[0]->name, $result[0]->name); + $this->assertEquals($this->fixtures[1]->name, $result[1]->name); + $this->assertEquals($this->fixtures[2]->name, $result[2]->name); + + $this->assertEquals($this->fixtures[0]->email->email, $result[0]->email); + $this->assertEquals($this->fixtures[1]->email->email, $result[1]->email); + $this->assertEquals($this->fixtures[2]->email->email, $result[2]->email); + + $this->assertEquals($this->fixtures[0]->address->city, $result[0]->address); + $this->assertEquals($this->fixtures[1]->address->city, $result[1]->address); + $this->assertEquals($this->fixtures[2]->address->city, $result[2]->address); + + $this->assertEquals( + (count($this->fixtures[0]->phonenumbers)), + $result[0]->phonenumbers + ); + + $this->assertEquals( + (count($this->fixtures[1]->phonenumbers)), + $result[1]->phonenumbers + ); + + $this->assertEquals( + (count($this->fixtures[2]->phonenumbers)), + $result[2]->phonenumbers + ); + } + + public function testShouldSupportArithmeticExpression() + { + $dql = " + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + e.email, + a.city, + COUNT(p) + u.id + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + JOIN + u.phonenumbers p + GROUP BY + u, e, a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2]); + + $this->assertEquals($this->fixtures[0]->name, $result[0]->name); + $this->assertEquals($this->fixtures[1]->name, $result[1]->name); + $this->assertEquals($this->fixtures[2]->name, $result[2]->name); + + $this->assertEquals($this->fixtures[0]->email->email, $result[0]->email); + $this->assertEquals($this->fixtures[1]->email->email, $result[1]->email); + $this->assertEquals($this->fixtures[2]->email->email, $result[2]->email); + + $this->assertEquals($this->fixtures[0]->address->city, $result[0]->address); + $this->assertEquals($this->fixtures[1]->address->city, $result[1]->address); + $this->assertEquals($this->fixtures[2]->address->city, $result[2]->address); + + $this->assertEquals( + (count($this->fixtures[0]->phonenumbers) + $this->fixtures[0]->id), + $result[0]->phonenumbers + ); + + $this->assertEquals( + (count($this->fixtures[1]->phonenumbers) + $this->fixtures[1]->id), + $result[1]->phonenumbers + ); + + $this->assertEquals( + (count($this->fixtures[2]->phonenumbers) + $this->fixtures[2]->id), + $result[2]->phonenumbers + ); + } + + public function testShouldSupportMultipleNewOperators() + { + $dql = " + SELECT + new CmsUserDTO( + u.name, + e.email + ), + new CmsAddressDTO( + a.country, + a.city + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name"; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertCount(3, $result); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[0][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[1][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUserDTO', $result[2][0]); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddressDTO', $result[0][1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddressDTO', $result[1][1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddressDTO', $result[2][1]); + + $this->assertEquals($this->fixtures[0]->name, $result[0][0]->name); + $this->assertEquals($this->fixtures[1]->name, $result[1][0]->name); + $this->assertEquals($this->fixtures[2]->name, $result[2][0]->name); + + $this->assertEquals($this->fixtures[0]->email->email, $result[0][0]->email); + $this->assertEquals($this->fixtures[1]->email->email, $result[1][0]->email); + $this->assertEquals($this->fixtures[2]->email->email, $result[2][0]->email); + + + $this->assertEquals($this->fixtures[0]->address->city, $result[0][1]->city); + $this->assertEquals($this->fixtures[1]->address->city, $result[1][1]->city); + $this->assertEquals($this->fixtures[2]->address->city, $result[2][1]->city); + + $this->assertEquals($this->fixtures[0]->address->country, $result[0][1]->country); + $this->assertEquals($this->fixtures[1]->address->country, $result[1][1]->country); + $this->assertEquals($this->fixtures[2]->address->country, $result[2][1]->country); + } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + * @expectedExceptionMessage [Semantical Error] line 0, col 11 near '\InvalidClass(u.name)': Error: Class "\InvalidClass" is not defined. + */ + public function testInvalidClassException() + { + $dql = "SELECT new \InvalidClass(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u"; + $this->_em->createQuery($dql)->getResult(); + } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + * @expectedExceptionMessage [Semantical Error] line 0, col 11 near '\stdClass(u.name)': Error: Class "\stdClass" has not a valid contructor. + */ + public function testInvalidClassConstructorException() + { + $dql = "SELECT new \stdClass(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u"; + $this->_em->createQuery($dql)->getResult(); + } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + * @expectedExceptionMessage [Semantical Error] line 0, col 11 near 'Doctrine\Tests\ORM\Functional\ClassWithTooMuchArgs(u.name)': Error: Number of arguments does not match with "Doctrine\Tests\ORM\Functional\ClassWithTooMuchArgs" constructor declaration. + */ + public function testInvalidClassWithoutConstructorException() + { + $dql = "SELECT new Doctrine\Tests\ORM\Functional\ClassWithTooMuchArgs(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u"; + $this->_em->createQuery($dql)->getResult(); + } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + * @expectedExceptionMessage [Semantical Error] line 0, col 11 near 'Doctrine\Tests\ORM\Functional\ClassWithPrivateConstructor(u.name)': Error: Class "Doctrine\Tests\ORM\Functional\ClassWithPrivateConstructor" can not be instantiated. + */ + public function testClassCantBeInstantiatedException() + { + $dql = "SELECT new Doctrine\Tests\ORM\Functional\ClassWithPrivateConstructor(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u"; + $this->_em->createQuery($dql)->getResult(); + } +} + +class ClassWithTooMuchArgs +{ + public function __construct($foo, $bar) + { + $this->foo = $foo; + $this->bor = $bar; + } +} + +class ClassWithPrivateConstructor +{ + private function __construct($foo) + { + $this->foo = $foo; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index fa5b06d82..351615f42 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1555,6 +1555,38 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase ); } + /** + * @group DDC-1574 + */ + public function testSupportsNewOperator() + { + $this->assertSqlGeneration( + "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(u.name, e.email, a.city) FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.email e JOIN u.address a", + "SELECT c0_.name AS sclr0, c1_.email AS sclr1, c2_.city AS sclr2 FROM cms_users c0_ INNER JOIN cms_emails c1_ ON c0_.email_id = c1_.id INNER JOIN cms_addresses c2_ ON c0_.id = c2_.user_id" + ); + + $this->assertSqlGeneration( + "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(u.name, e.email, a.id + u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.email e JOIN u.address a", + "SELECT c0_.name AS sclr0, c1_.email AS sclr1, c2_.id + c0_.id AS sclr2 FROM cms_users c0_ INNER JOIN cms_emails c1_ ON c0_.email_id = c1_.id INNER JOIN cms_addresses c2_ ON c0_.id = c2_.user_id" + ); + + $this->assertSqlGeneration( + "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(u.name, e.email, a.city, COUNT(p)) FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.email e JOIN u.address a JOIN u.phonenumbers p", + "SELECT c0_.name AS sclr0, c1_.email AS sclr1, c2_.city AS sclr2, COUNT(c3_.phonenumber) AS sclr3 FROM cms_users c0_ INNER JOIN cms_emails c1_ ON c0_.email_id = c1_.id INNER JOIN cms_addresses c2_ ON c0_.id = c2_.user_id INNER JOIN cms_phonenumbers c3_ ON c0_.id = c3_.user_id" + ); + + $this->assertSqlGeneration( + "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(u.name, e.email, a.city, COUNT(p) + u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.email e JOIN u.address a JOIN u.phonenumbers p", + "SELECT c0_.name AS sclr0, c1_.email AS sclr1, c2_.city AS sclr2, COUNT(c3_.phonenumber) + c0_.id AS sclr3 FROM cms_users c0_ INNER JOIN cms_emails c1_ ON c0_.email_id = c1_.id INNER JOIN cms_addresses c2_ ON c0_.id = c2_.user_id INNER JOIN cms_phonenumbers c3_ ON c0_.id = c3_.user_id" + ); + + $this->assertSqlGeneration( + "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(a.id, a.country, a.city), new Doctrine\Tests\Models\CMS\CmsAddressDTO(u.name, e.email) FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.email e JOIN u.address a ORDER BY u.name", + "SELECT c0_.id AS sclr0, c0_.country AS sclr1, c0_.city AS sclr2, c1_.name AS sclr3, c2_.email AS sclr4 FROM cms_users c1_ INNER JOIN cms_emails c2_ ON c1_.email_id = c2_.id INNER JOIN cms_addresses c0_ ON c1_.id = c0_.user_id ORDER BY c1_.name ASC" + ); + + } + public function testCustomTypeValueSql() { if (DBALType::hasType('negative_to_positive')) {