diff --git a/UPGRADE.md b/UPGRADE.md index ef30c4f04..387337083 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -33,6 +33,12 @@ Also, related functions were affected: Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker, you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin. +## New methods in TreeWalker interface *BC break* + +Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations +including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the +above you must implement these new methods. + ## Metadata Drivers Metadata drivers have been rewritten to reuse code from Doctrine\Common. Anyone who is using the diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index f6994c05b..d057ba754 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -358,6 +358,8 @@ class Parser default: $treeWalkerChain->walkSelectStatement($AST); } + + $this->queryComponents = $treeWalkerChain->getQueryComponents(); } $outputWalkerClass = $this->customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; @@ -1750,7 +1752,7 @@ class Parser * NewObjectArg ::= ScalarExpression * * @TODO - Maybe you should support other expressions and nested "new" operator - * + * * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression */ public function NewObjectArg() @@ -2093,7 +2095,7 @@ class Parser case ($lookaheadType === Lexer::T_NEW): $expression = $this->NewObjectExpression(); break; - + default: $this->syntaxError( 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 26135bc75..fc15370c8 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -140,7 +140,7 @@ class SqlWalker implements TreeWalker /** * The DQL alias of the root class of the currently traversed query. - * + * * @var array */ private $rootAliases = array(); @@ -223,13 +223,23 @@ class SqlWalker implements TreeWalker return $this->queryComponents[$dqlAlias]; } + /** + * Return internal queryComponents array + * + * @return array + */ + public function getQueryComponents() + { + return $this->queryComponents; + } + /** * Set or override a query component for a given dql alias. * * @param string $dqlAlias The DQL alias. * @param array $queryComponent */ - protected function setQueryComponent($dqlAlias, array $queryComponent) + public function setQueryComponent($dqlAlias, array $queryComponent) { $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); @@ -1986,7 +1996,7 @@ class SqlWalker implements TreeWalker $dqlAlias = $instanceOfExpr->identificationVariable; $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata']; - + if ($class->discriminatorColumn) { $discrClass = $this->em->getClassMetadata($class->rootEntityName); } diff --git a/lib/Doctrine/ORM/Query/TreeWalker.php b/lib/Doctrine/ORM/Query/TreeWalker.php index c88ca1328..ceb117c0a 100644 --- a/lib/Doctrine/ORM/Query/TreeWalker.php +++ b/lib/Doctrine/ORM/Query/TreeWalker.php @@ -36,6 +36,21 @@ interface TreeWalker */ public function __construct($query, $parserResult, array $queryComponents); + /** + * Return internal queryComponents array + * + * @return array + */ + public function getQueryComponents(); + + /** + * Set or override a query component for a given dql alias. + * + * @param string $dqlAlias The DQL alias. + * @param array $queryComponent + */ + public function setQueryComponent($dqlAlias, array $queryComponent); + /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * diff --git a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php index 4446a85e0..32009a3c4 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php @@ -42,6 +42,33 @@ abstract class TreeWalkerAdapter implements TreeWalker $this->_queryComponents = $queryComponents; } + /** + * Return internal queryComponents array + * + * @return array: + */ + public function getQueryComponents() + { + return $this->_queryComponents; + } + + /** + * Set or override a query component for a given dql alias. + * + * @param string $dqlAlias The DQL alias. + * @param array $queryComponent + */ + public function setQueryComponent($dqlAlias, array $queryComponent) + { + $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); + + if (array_diff($requiredKeys, array_keys($queryComponent))) { + throw QueryException::invalidQueryComponent($dqlAlias); + } + + $this->_queryComponents[$dqlAlias] = $queryComponent; + } + /** * @return array */ diff --git a/lib/Doctrine/ORM/Query/TreeWalkerChain.php b/lib/Doctrine/ORM/Query/TreeWalkerChain.php index 13bbcdeef..f20413c40 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerChain.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerChain.php @@ -38,6 +38,33 @@ class TreeWalkerChain implements TreeWalker /** The query components of the original query (the "symbol table") that was produced by the Parser. */ private $_queryComponents; + /** + * Return internal queryComponents array + * + * @return array + */ + public function getQueryComponents() + { + return $this->_queryComponents; + } + + /** + * Set or override a query component for a given dql alias. + * + * @param string $dqlAlias The DQL alias. + * @param array $queryComponent + */ + public function setQueryComponent($dqlAlias, array $queryComponent) + { + $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); + + if (array_diff($requiredKeys, array_keys($queryComponent))) { + throw QueryException::invalidQueryComponent($dqlAlias); + } + + $this->_queryComponents[$dqlAlias] = $queryComponent; + } + /** * @inheritdoc */ @@ -67,6 +94,8 @@ class TreeWalkerChain implements TreeWalker { foreach ($this->_walkers as $walker) { $walker->walkSelectStatement($AST); + + $this->_queryComponents = $walker->getQueryComponents(); } } diff --git a/tests/Doctrine/Tests/ORM/Query/CustomTreeWalkersJoinTest.php b/tests/Doctrine/Tests/ORM/Query/CustomTreeWalkersJoinTest.php new file mode 100644 index 000000000..18d34acd7 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Query/CustomTreeWalkersJoinTest.php @@ -0,0 +1,110 @@ +. + */ + +namespace Doctrine\Tests\ORM\Query; + +use Doctrine\ORM\Query; + +/** + * Test case for custom AST walking and adding new joins. + * + * @author Lukasz Cybula + * @license MIT + * @link http://www.doctrine-project.org + */ +class CustomTreeWalkersJoinTest extends \Doctrine\Tests\OrmTestCase +{ + private $em; + + protected function setUp() + { + $this->em = $this->_getTestEntityManager(); + } + + public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) + { + try { + $query = $this->em->createQuery($dqlToBeTested); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\Tests\ORM\Query\CustomTreeWalkerJoin')) + ->useQueryCache(false); + + $this->assertEquals($sqlToBeConfirmed, $query->getSql()); + $query->free(); + } catch (\Exception $e) { + $this->fail($e->getMessage() . ' at "' . $e->getFile() . '" on line ' . $e->getLine()); + + } + } + + public function testAddsJoin() + { + $this->assertSqlGeneration( + 'select u from Doctrine\Tests\Models\CMS\CmsUser u', + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c1_.id AS id4, c1_.country AS country5, c1_.zip AS zip6, c1_.city AS city7, c0_.email_id AS email_id8, c1_.user_id AS user_id9 FROM cms_users c0_ LEFT JOIN cms_addresses c1_ ON c0_.id = c1_.user_id" + ); + } + + public function testDoesNotAddJoin() + { + $this->assertSqlGeneration( + 'select a from Doctrine\Tests\Models\CMS\CmsAddress a', + "SELECT c0_.id AS id0, c0_.country AS country1, c0_.zip AS zip2, c0_.city AS city3, c0_.user_id AS user_id4 FROM cms_addresses c0_" + ); + } +} + +class CustomTreeWalkerJoin extends Query\TreeWalkerAdapter +{ + public function walkSelectStatement(Query\AST\SelectStatement $selectStatement) + { + foreach ($selectStatement->fromClause->identificationVariableDeclarations as $identificationVariableDeclaration) { + if ($identificationVariableDeclaration->rangeVariableDeclaration->abstractSchemaName == 'Doctrine\Tests\Models\CMS\CmsUser') { + $identificationVariableDeclaration->joins[] = new Query\AST\Join( + Query\AST\Join::JOIN_TYPE_LEFT, + new Query\AST\JoinAssociationDeclaration( + new Query\AST\JoinAssociationPathExpression( + $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable, + 'address' + ), + $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a', + null + ) + ); + $selectStatement->selectClause->selectExpressions[] = + new Query\AST\SelectExpression( + $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a', + null, + false + ); + $meta1 = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $meta = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); + $this->setQueryComponent($identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a', + array( + 'metadata' => $meta, + 'parent' => $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable, + 'relation' => $meta1->getAssociationMapping('address'), + 'map' => null, + 'nestingLevel' => 0, + 'token' => null + ) + ); + } + } + } +}