Skip to content

Commit dd4e8fe

Browse files
authored
Merge pull request #12198 from stof/arbitrary_join_on
Update DQL arbitrary joins to use the ON keyword instead of WITH
2 parents 7cc2104 + 587caf8 commit dd4e8fe

File tree

14 files changed

+77
-41
lines changed

14 files changed

+77
-41
lines changed

UPGRADE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ Using `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
6767
is deprecated in favor of using an associative array of join parts with the
6868
root alias as key.
6969

70+
## Deprecate using the `WITH` keyword for arbitrary DQL joins
71+
72+
Using the `WITH` keyword to specify the condition for an arbitrary DQL join is
73+
deprecated in favor of using the `ON` keyword (similar to the SQL syntax for
74+
joins).
75+
The `WITH` keyword is now meant to be used only for filtering conditions in
76+
association joins.
77+
7078
# Upgrade to 3.5
7179

7280
See the General notes to upgrading to 3.x versions above.

docs/en/reference/dql-doctrine-query-language.rst

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ where you can generate an arbitrary join with the following syntax:
490490
.. code-block:: php
491491
492492
<?php
493-
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b WITH u.email = b.email');
493+
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b ON u.email = b.email');
494494
495495
With an arbitrary join the result differs from the joins using a mapped property.
496496
The result of an arbitrary join is an one dimensional array with a mix of the entity from the ``SELECT``
@@ -513,13 +513,15 @@ it loads all the related ``Banlist`` objects corresponding to this ``User``. Thi
513513
when the DQL is switched to an arbitrary join.
514514

515515
.. note::
516-
The differences between WHERE, WITH and HAVING clauses may be
516+
The differences between WHERE, WITH, ON and HAVING clauses may be
517517
confusing.
518518

519519
- WHERE is applied to the results of an entire query
520-
- WITH is applied to a join as an additional condition. For
521-
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
522-
the WITH is required, even if it is 1 = 1
520+
- ON is applied to arbitrary joins as the join condition. For
521+
arbitrary joins (SELECT f, b FROM Foo f, Bar b ON f.id = b.id)
522+
the ON is required, even if it is 1 = 1. WITH is also
523+
supported as alternative keyword for that case for BC reasons.
524+
- WITH is applied to an association join as an additional condition.
523525
- HAVING is applied to the results of a query after
524526
aggregation (GROUP BY)
525527

@@ -1699,9 +1701,14 @@ From, Join and Index by
16991701
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
17001702
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
17011703
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1702-
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
1704+
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
17031705
IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
17041706
1707+
.. note::
1708+
Using the ``WITH`` keyword for the ``ConditionalExpression`` of a
1709+
``RangeVariableDeclaration`` is deprecated and will be removed in
1710+
ORM 4.0. Use the ``ON`` keyword instead.
1711+
17051712
Select Expressions
17061713
~~~~~~~~~~~~~~~~~~
17071714

src/Query/Parser.php

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,8 +1609,7 @@ public function SubselectIdentificationVariableDeclaration(): AST\Identification
16091609

16101610
/**
16111611
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1612-
* (JoinAssociationDeclaration | RangeVariableDeclaration)
1613-
* ["WITH" ConditionalExpression]
1612+
* (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
16141613
*/
16151614
public function Join(): AST\Join
16161615
{
@@ -1644,22 +1643,32 @@ public function Join(): AST\Join
16441643

16451644
$next = $this->lexer->glimpse();
16461645
assert($next !== null);
1647-
$joinDeclaration = $next->type === TokenType::T_DOT ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1648-
$adhocConditions = $this->lexer->isNextToken(TokenType::T_WITH);
1649-
$join = new AST\Join($joinType, $joinDeclaration);
1646+
$conditionalExpression = null;
16501647

1651-
// Describe non-root join declaration
1652-
if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1653-
$joinDeclaration->isRoot = false;
1654-
}
1648+
if ($next->type === TokenType::T_DOT) {
1649+
$joinDeclaration = $this->JoinAssociationDeclaration();
16551650

1656-
// Check for ad-hoc Join conditions
1657-
if ($adhocConditions) {
1658-
$this->match(TokenType::T_WITH);
1651+
if ($this->lexer->isNextToken(TokenType::T_WITH)) {
1652+
$this->match(TokenType::T_WITH);
1653+
$conditionalExpression = $this->ConditionalExpression();
1654+
}
1655+
} else {
1656+
$joinDeclaration = $this->RangeVariableDeclaration();
1657+
$joinDeclaration->isRoot = false;
16591658

1660-
$join->conditionalExpression = $this->ConditionalExpression();
1659+
if ($this->lexer->isNextToken(TokenType::T_ON)) {
1660+
$this->match(TokenType::T_ON);
1661+
$conditionalExpression = $this->ConditionalExpression();
1662+
} elseif ($this->lexer->isNextToken(TokenType::T_WITH)) {
1663+
$this->match(TokenType::T_WITH);
1664+
$conditionalExpression = $this->ConditionalExpression();
1665+
Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/issues/12192', 'Using WITH for the join condition of arbitrary joins is deprecated. Use ON instead.');
1666+
}
16611667
}
16621668

1669+
$join = new AST\Join($joinType, $joinDeclaration);
1670+
$join->conditionalExpression = $conditionalExpression;
1671+
16631672
return $join;
16641673
}
16651674

src/Query/TokenType.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,5 @@ enum TokenType: int
9090
case T_WHERE = 255;
9191
case T_WITH = 256;
9292
case T_NAMED = 257;
93+
case T_ON = 258;
9394
}

tests/Tests/ORM/Functional/QueryTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ public function testToIterableWithMultipleSelectElements(): void
390390
$this->_em->flush();
391391
$this->_em->clear();
392392

393-
$query = $this->_em->createQuery('select a, u from ' . CmsArticle::class . ' a JOIN ' . CmsUser::class . ' u WITH a.user = u');
393+
$query = $this->_em->createQuery('select a, u from ' . CmsArticle::class . ' a JOIN ' . CmsUser::class . ' u ON a.user = u');
394394

395395
$result = iterator_to_array($query->toIterable());
396396

@@ -1061,7 +1061,7 @@ public function testMultipleJoinComponentsUsingInnerJoin(): void
10611061
$query = $this->_em->createQuery('
10621062
SELECT u, p
10631063
FROM Doctrine\Tests\Models\CMS\CmsUser u
1064-
INNER JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user
1064+
INNER JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p ON u = p.user
10651065
');
10661066
$users = $query->execute();
10671067

@@ -1094,7 +1094,7 @@ public function testMultipleJoinComponentsUsingLeftJoin(): void
10941094
$query = $this->_em->createQuery('
10951095
SELECT u, p
10961096
FROM Doctrine\Tests\Models\CMS\CmsUser u
1097-
LEFT JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user
1097+
LEFT JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p ON u = p.user
10981098
');
10991099
$users = $query->execute();
11001100

tests/Tests/ORM/Functional/Ticket/DDC3042Test.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function testSQLGenerationDoesNotProvokeAliasCollisions(): void
3131
$this
3232
->_em
3333
->createQuery(
34-
'SELECT f, b FROM ' . __NAMESPACE__ . '\DDC3042Foo f JOIN ' . __NAMESPACE__ . '\DDC3042Bar b WITH 1 = 1',
34+
'SELECT f, b FROM ' . __NAMESPACE__ . '\DDC3042Foo f JOIN ' . __NAMESPACE__ . '\DDC3042Bar b ON 1 = 1',
3535
)
3636
->getSQL(),
3737
'field_11',

tests/Tests/ORM/Functional/Ticket/GH6362Test.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected function setUp(): void
3939
* SELECT a as base, b, c, d
4040
* FROM Start a
4141
* LEFT JOIN a.bases b
42-
* LEFT JOIN Child c WITH b.id = c.id
42+
* LEFT JOIN Child c ON b.id = c.id
4343
* LEFT JOIN c.joins d
4444
*/
4545
#[Group('GH-6362')]

tests/Tests/ORM/Functional/Ticket/GH6464Test.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function testIssue(): void
3939
$query = $this->_em->createQueryBuilder()
4040
->select('p')
4141
->from(GH6464Post::class, 'p')
42-
->innerJoin(GH6464Author::class, 'a', 'WITH', 'p.authorId = a.id')
42+
->innerJoin(GH6464Author::class, 'a', 'ON', 'p.authorId = a.id')
4343
->getQuery();
4444

4545
self::assertDoesNotMatchRegularExpression(

tests/Tests/ORM/Functional/Ticket/GH7496WithToIterableTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ protected function setUp(): void
4040
public function testNonUniqueObjectHydrationDuringIteration(): void
4141
{
4242
$q = $this->_em->createQuery(
43-
'SELECT b FROM ' . GH7496EntityAinB::class . ' aib JOIN ' . GH7496EntityB::class . ' b WITH aib.eB = b',
43+
'SELECT b FROM ' . GH7496EntityAinB::class . ' aib JOIN ' . GH7496EntityB::class . ' b ON aib.eB = b',
4444
);
4545

4646
$bs = IterableTester::iterableToArray(

tests/Tests/ORM/Query/LanguageRecognitionTest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Doctrine\Tests\ORM\Query;
66

7+
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
78
use Doctrine\ORM\AbstractQuery;
89
use Doctrine\ORM\EntityManagerInterface;
910
use Doctrine\ORM\Mapping\Column;
@@ -20,9 +21,12 @@
2021
use Doctrine\Tests\OrmTestCase;
2122
use PHPUnit\Framework\Attributes\DataProvider;
2223
use PHPUnit\Framework\Attributes\Group;
24+
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
2325

2426
class LanguageRecognitionTest extends OrmTestCase
2527
{
28+
use VerifyDeprecations;
29+
2630
private EntityManagerInterface $entityManager;
2731
private int $hydrationMode = AbstractQuery::HYDRATE_OBJECT;
2832

@@ -262,8 +266,15 @@ public function testMixingOfJoins(): void
262266
$this->assertValidDQL('SELECT u.name, a.topic, p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles a LEFT JOIN u.phonenumbers p');
263267
}
264268

269+
public function testJoinClassPathUsingON(): void
270+
{
271+
$this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsArticle a ON a.user = u.id');
272+
}
273+
274+
#[IgnoreDeprecations]
265275
public function testJoinClassPathUsingWITH(): void
266276
{
277+
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issues/12192');
267278
$this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsArticle a WITH a.user = u.id');
268279
}
269280

@@ -638,7 +649,7 @@ public function testHavingSupportIsNullExpression(): void
638649
#[Group('DDC-3085')]
639650
public function testHavingSupportResultVariableInNullComparisonExpression(): void
640651
{
641-
$this->assertValidDQL('SELECT u AS user, SUM(a.id) AS score FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN Doctrine\Tests\Models\CMS\CmsAddress a WITH a.user = u GROUP BY u HAVING score IS NOT NULL AND score >= 5');
652+
$this->assertValidDQL('SELECT u AS user, SUM(a.id) AS score FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN Doctrine\Tests\Models\CMS\CmsAddress a ON a.user = u GROUP BY u HAVING score IS NOT NULL AND score >= 5');
642653
}
643654

644655
#[Group('DDC-1858')]

0 commit comments

Comments
 (0)