diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index f6994c05b..a205eb71f 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -358,6 +358,7 @@ class Parser default: $treeWalkerChain->walkSelectStatement($AST); } + $this->queryComponents = $treeWalkerChain->getQueryComponents(); } $outputWalkerClass = $this->customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; 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..72d087824 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,7 @@ class TreeWalkerChain implements TreeWalker { foreach ($this->_walkers as $walker) { $walker->walkSelectStatement($AST); + $this->_queryComponents = $walker->getQueryComponents(); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php new file mode 100644 index 000000000..cde0aadb8 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php @@ -0,0 +1,112 @@ +. + */ + +namespace Doctrine\Tests\ORM\Functional; + +use Doctrine\ORM\Query; + +require_once __DIR__ . '/../../TestInit.php'; + +/** + * Test case for custom AST walking and adding new joins. + * + * @author Lukasz Cybula + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @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\Functional\CustomTreeWalker2')) + ->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 CustomTreeWalker2 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 + ) + ); + } + } + } +}