Skip to content

Commit

Permalink
Merge 3.3.x
Browse files Browse the repository at this point in the history
  • Loading branch information
beberlei committed Oct 12, 2024
2 parents 516b593 + 19d9244 commit c19afa1
Show file tree
Hide file tree
Showing 42 changed files with 1,279 additions and 217 deletions.
10 changes: 8 additions & 2 deletions .doctrine-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,23 @@
"slug": "3.0",
"maintained": false
},
{
"name": "2.21",
"branchName": "2.21.x",
"slug": "2.21",
"upcoming": true
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"upcoming": true
"maintained": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"maintained": true
"maintained": false
},
{
"name": "2.18",
Expand Down
21 changes: 18 additions & 3 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,15 @@ Use `toIterable()` instead.

# Upgrade to 2.20

## Add `Doctrine\ORM\Query\OutputWalker` interface, deprecate `Doctrine\ORM\Query\SqlWalker::getExecutor()`

Output walkers should implement the new `\Doctrine\ORM\Query\OutputWalker` interface and create
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances instead of `Doctrine\ORM\Query\Exec\AbstractSqlExecutor`s.
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
`SqlFinalizer` can be kept in the query cache and used regardless of the actual `firstResult`/`maxResult` values.
Any operation dependent on `firstResult`/`maxResult` should take place within the `SqlFinalizer::createExecutor()`
method. Details can be found at https://github.com/doctrine/orm/pull/11188.

## Explictly forbid property hooks

Property hooks are not supported yet by Doctrine ORM. Until support is added,
Expand All @@ -741,10 +750,16 @@ change in behavior.

Progress on this is tracked at https://github.com/doctrine/orm/issues/11624 .

## PARTIAL DQL syntax is undeprecated for non-object hydration
## PARTIAL DQL syntax is undeprecated

Use of the PARTIAL keyword is not deprecated anymore in DQL, because we will be
able to support PARTIAL objects with PHP 8.4 Lazy Objects and
Symfony/VarExporter in a better way. When we decided to remove this feature
these two abstractions did not exist yet.

Use of the PARTIAL keyword is not deprecated anymore in DQL when used with a hydrator
that is not creating entities, such as the ArrayHydrator.
WARNING: If you want to upgrade to 3.x and still use PARTIAL keyword in DQL
with array or object hydrators, then you have to directly migrate to ORM 3.3.x or higher.
PARTIAL keyword in DQL is not available in 3.0, 3.1 and 3.2 of ORM.

## Deprecate `\Doctrine\ORM\Query\Parser::setCustomOutputTreeWalker()`

Expand Down
1 change: 0 additions & 1 deletion docs/en/_theme
Submodule _theme deleted from 6f1bc8
65 changes: 61 additions & 4 deletions docs/en/reference/dql-doctrine-query-language.rst
Original file line number Diff line number Diff line change
Expand Up @@ -600,23 +600,80 @@ You can also nest several DTO :
// Bind values to the object properties.
}
}
class AddressDTO
{
public function __construct(string $street, string $city, string $zip)
{
// Bind values to the object properties.
}
}
.. code-block:: php
<?php
$query = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, NEW AddressDTO(a.street, a.city, a.zip)) FROM Customer c JOIN c.email e JOIN c.address a');
$users = $query->getResult(); // array of CustomerDTO
Note that you can only pass scalar expressions or other Data Transfer Objects to the constructor.

If you use your data transfer objects for multiple queries, and you would rather not have to
specify arguments that precede the ones you are really interested in, you can use named arguments.

Consider the following DTO, which uses optional arguments:

.. code-block:: php
<?php
class CustomerDTO
{
public function __construct(
public string|null $name = null,
public string|null $email = null,
public string|null $city = null,
public mixed|null $value = null,
public AddressDTO|null $address = null,
) {
}
}
You can specify arbitrary arguments in an arbitrary order by using the named argument syntax, and the ORM will try to match argument names with the selected column names.
The syntax relies on the NAMED keyword, like so:

.. code-block:: php
<?php
$query = $em->createQuery('SELECT NEW NAMED CustomerDTO(a.city, c.name) FROM Customer c JOIN c.address a');
$users = $query->getResult(); // array of CustomerDTO
// CustomerDTO => {name : 'SMITH', email: null, city: 'London', value: null}
ORM will also give precedence to column aliases over column names :

.. code-block:: php
<?php
$query = $em->createQuery('SELECT NEW NAMED CustomerDTO(c.name, CONCAT(a.city, ' ' , a.zip) AS value) FROM Customer c JOIN c.address a');
$users = $query->getResult(); // array of CustomerDTO
// CustomerDTO => {name : 'DOE', email: null, city: null, value: 'New York 10011'}
To define a custom name for a DTO constructor argument, you can either alias the column with the ``AS`` keyword.

The ``NAMED`` keyword must precede all DTO you want to instantiate :

.. code-block:: php
<?php
$query = $em->createQuery('SELECT NEW NAMED CustomerDTO(c.name, NEW NAMED AddressDTO(a.street, a.city, a.zip) AS address) FROM Customer c JOIN c.address a');
$users = $query->getResult(); // array of CustomerDTO
// CustomerDTO => {name : 'DOE', email: null, city: null, value: 'New York 10011'}
If two arguments have the same name, a ``DuplicateFieldException`` is thrown.
If a field cannot be matched with a property name, a ``NoMatchingPropertyException`` is thrown. This typically happens when using functions without aliasing them.

Using INDEX BY
~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1645,7 +1702,7 @@ Select Expressions
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= ScalarExpression | "(" Subselect ")" | NewObjectExpression
NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]
Conditional Expressions
~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
17 changes: 13 additions & 4 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,14 @@
</ParamNameMismatch>
</file>
<file src="src/Internal/Hydration/AbstractHydrator.php">
<ReferenceConstraintViolation>
<code><![CDATA[return $rowData;]]></code>
<code><![CDATA[return $rowData;]]></code>
</ReferenceConstraintViolation>
<PossiblyUndefinedArrayOffset>
<code><![CDATA[$newObject['args']]]></code>
<code><![CDATA[$newObject['args']]]></code>
</PossiblyUndefinedArrayOffset>
<ReferenceConstraintViolation>
<code><![CDATA[return $rowData;]]></code>
<code><![CDATA[return $rowData;]]></code>
</ReferenceConstraintViolation>
</file>
<file src="src/Internal/Hydration/ArrayHydrator.php">
<PossiblyInvalidArgument>
Expand Down Expand Up @@ -923,6 +923,9 @@
<ArgumentTypeCoercion>
<code><![CDATA[$stringPattern]]></code>
</ArgumentTypeCoercion>
<DeprecatedMethod>
<code><![CDATA[setSqlExecutor]]></code>
</DeprecatedMethod>
<InvalidNullableReturnType>
<code><![CDATA[AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement]]></code>
</InvalidNullableReturnType>
Expand Down Expand Up @@ -1113,6 +1116,12 @@
</RedundantConditionGivenDocblockType>
</file>
<file src="src/Tools/Pagination/LimitSubqueryOutputWalker.php">
<InvalidReturnStatement>
<code><![CDATA[$abstractSqlExecutor->getSqlStatements()]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[string]]></code>
</InvalidReturnType>
<PossiblyFalseArgument>
<code><![CDATA[strrpos($orderByItemString, ' ')]]></code>
</PossiblyFalseArgument>
Expand Down
17 changes: 11 additions & 6 deletions src/EntityManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Repository\RepositoryFactory;
use Throwable;

use function array_keys;
use function is_array;
Expand Down Expand Up @@ -178,18 +177,24 @@ public function wrapInTransaction(callable $func): mixed
{
$this->conn->beginTransaction();

$successful = false;

try {
$return = $func($this);

$this->flush();
$this->conn->commit();

return $return;
} catch (Throwable $e) {
$this->close();
$this->conn->rollBack();
$successful = true;

throw $e;
return $return;
} finally {
if (! $successful) {
$this->close();
if ($this->conn->isTransactionActive()) {
$this->conn->rollBack();
}
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/Exception/DuplicateFieldException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Exception;

use LogicException;

use function sprintf;

class DuplicateFieldException extends LogicException implements ORMException
{
public static function create(string $argName, string $columnName): self
{
return new self(sprintf('Name "%s" for "%s" already in use.', $argName, $columnName));
}
}
17 changes: 17 additions & 0 deletions src/Exception/NoMatchingPropertyException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Exception;

use LogicException;

use function sprintf;

class NoMatchingPropertyException extends LogicException implements ORMException
{
public static function create(string $property): self
{
return new self(sprintf('Column name "%s" does not match any property name. Consider aliasing it to the name of an existing property.', $property));
}
}
26 changes: 8 additions & 18 deletions src/Internal/Hydration/AbstractHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ abstract protected function hydrateAllData(): mixed;
*/
protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents): array
{
$rowData = ['data' => []];
$rowData = ['data' => [], 'newObjects' => []];

foreach ($data as $key => $value) {
$cacheKeyInfo = $this->hydrateColumnInfo($key);
Expand All @@ -282,10 +282,6 @@ protected function gatherRowData(array $data, array &$id, array &$nonemptyCompon
$value = $this->buildEnum($value, $cacheKeyInfo['enumType']);
}

if (! isset($rowData['newObjects'])) {
$rowData['newObjects'] = [];
}

$rowData['newObjects'][$objIndex]['class'] = $cacheKeyInfo['class'];
$rowData['newObjects'][$objIndex]['args'][$argIndex] = $value;
break;
Expand Down Expand Up @@ -341,28 +337,22 @@ protected function gatherRowData(array $data, array &$id, array &$nonemptyCompon
}

foreach ($this->resultSetMapping()->nestedNewObjectArguments as $objIndex => ['ownerIndex' => $ownerIndex, 'argIndex' => $argIndex]) {
if (! isset($rowData['newObjects'][$objIndex])) {
if (! isset($rowData['newObjects'][$ownerIndex . ':' . $argIndex])) {
continue;
}

$newObject = $rowData['newObjects'][$objIndex];
unset($rowData['newObjects'][$objIndex]);
$newObject = $rowData['newObjects'][$ownerIndex . ':' . $argIndex];
unset($rowData['newObjects'][$ownerIndex . ':' . $argIndex]);

$class = $newObject['class'];
$args = $newObject['args'];
$obj = $class->newInstanceArgs($args);
$obj = $newObject['class']->newInstanceArgs($newObject['args']);

$rowData['newObjects'][$ownerIndex]['args'][$argIndex] = $obj;
}

if (isset($rowData['newObjects'])) {
foreach ($rowData['newObjects'] as $objIndex => $newObject) {
$class = $newObject['class'];
$args = $newObject['args'];
$obj = $class->newInstanceArgs($args);
foreach ($rowData['newObjects'] as $objIndex => $newObject) {
$obj = $newObject['class']->newInstanceArgs($newObject['args']);

$rowData['newObjects'][$objIndex]['obj'] = $obj;
}
$rowData['newObjects'][$objIndex]['obj'] = $obj;
}

return $rowData;
Expand Down
Loading

0 comments on commit c19afa1

Please sign in to comment.