From 9acc70d5b8be55a6ac33e5c7313a124fa90dbe21 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 9 Feb 2024 15:23:22 +0100 Subject: [PATCH 01/16] fix: support array-type arg in QB variadic calls (#11242) --- src/Query/Expr/Base.php | 6 ++++++ tests/Tests/ORM/QueryBuilderTest.php | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Query/Expr/Base.php b/src/Query/Expr/Base.php index a98ea3b8cb3..e0f257277df 100644 --- a/src/Query/Expr/Base.php +++ b/src/Query/Expr/Base.php @@ -7,10 +7,12 @@ use InvalidArgumentException; use Stringable; +use function array_key_exists; use function count; use function get_debug_type; use function implode; use function in_array; +use function is_array; use function is_string; use function sprintf; @@ -33,6 +35,10 @@ abstract class Base implements Stringable public function __construct(mixed $args = []) { + if (is_array($args) && array_key_exists(0, $args) && is_array($args[0])) { + $args = $args[0]; + } + $this->addMultiple($args); } diff --git a/tests/Tests/ORM/QueryBuilderTest.php b/tests/Tests/ORM/QueryBuilderTest.php index 3c3f05ba4db..e7025395bbc 100644 --- a/tests/Tests/ORM/QueryBuilderTest.php +++ b/tests/Tests/ORM/QueryBuilderTest.php @@ -80,6 +80,15 @@ public function testSimpleSelect(): void $this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u'); } + public function testSimpleSelectArray(): void + { + $qb = $this->entityManager->createQueryBuilder() + ->from(CmsUser::class, 'u') + ->select(['u.id', 'u.username']); + + $this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u'); + } + public function testSimpleDelete(): void { $qb = $this->entityManager->createQueryBuilder() From 1d218bae30d67662498eb0e30cce2c5e329a2c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Mon, 12 Feb 2024 23:46:09 +0100 Subject: [PATCH 02/16] Make docs valid according to guides 0.3.3 (#11252) --- .github/workflows/documentation.yml | 6 +- docs/en/reference/basic-mapping.rst | 2 +- docs/en/reference/events.rst | 80 +++++++++++------------ docs/en/reference/faq.rst | 2 +- docs/en/reference/inheritance-mapping.rst | 2 +- docs/en/sidebar.rst | 2 + 6 files changed, 46 insertions(+), 48 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 58c53f5ae38..f0bbf5b9e2a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -40,9 +40,5 @@ jobs: with: dependency-versions: "highest" - - name: "Add dummy title to the sidebar" - run: | - printf '%s\n%s\n\n%s\n' "Dummy title" "===========" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst - - name: "Run guides-cli" - run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'Unknown directive' | ( ! grep WARNING )" + run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )" diff --git a/docs/en/reference/basic-mapping.rst b/docs/en/reference/basic-mapping.rst index 66b552a1d87..a61e5d8d0e4 100644 --- a/docs/en/reference/basic-mapping.rst +++ b/docs/en/reference/basic-mapping.rst @@ -462,7 +462,7 @@ Here is the list of possible generation strategies: a new entity is passed to ``EntityManager#persist``. NONE is the same as leaving off the ``#[GeneratedValue]`` entirely. - ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute. - It will allow you to pass a :ref:`class of your own to generate the identifiers.` + It will allow you to pass a :ref:`class of your own to generate the identifiers. ` Sequence Generator ^^^^^^^^^^^^^^^^^^ diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 408014cf417..dbde6d19df2 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -131,47 +131,47 @@ There are two ways to set up an event handler: * For *all events* you can create a Lifecycle Event Listener or Subscriber class and register it by calling ``$eventManager->addEventListener()`` or ``eventManager->addEventSubscriber()``, see -:ref:`Listening and subscribing to Lifecycle Events` +:ref:`Listening and subscribing to Lifecycle Events ` * For *some events* (see table below), you can create a *Lifecycle Callback* method in the -entity, see :ref:`Lifecycle Callbacks`. +entity, see :ref:`Lifecycle Callbacks `. .. _reference-events-lifecycle-events: Events Overview --------------- -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| Event | Dispatched by | Lifecycle | Passed | -| | | Callback | Argument | -+=================================================================+=======================+===========+=====================================+ -| :ref:`preRemove` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`postRemove` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`prePersist` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ | -| | on *initial* persist | | | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`postPersist` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`preUpdate` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`postUpdate` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`postLoad` | Loading from database | Yes | `PostLoadEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`loadClassMetadata` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ | -| | metadata | | | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`preFlush` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`onFlush` | ``$em->flush()`` | No | `OnFlushEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`postFlush` | ``$em->flush()`` | No | `PostFlushEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ -| :ref:`onClear` | ``$em->clear()`` | No | `OnClearEventArgs`_ | -+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| Event | Dispatched by | Lifecycle | Passed | +| | | Callback | Argument | ++==================================================================+=======================+===========+=====================================+ +| :ref:`preRemove ` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`postRemove ` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`prePersist ` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ | +| | on *initial* persist | | | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`postPersist ` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`preUpdate ` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`postUpdate ` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`postLoad ` | Loading from database | Yes | `PostLoadEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`loadClassMetadata ` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ | +| | metadata | | | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`preFlush ` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`onFlush ` | ``$em->flush()`` | No | `OnFlushEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`postFlush ` | ``$em->flush()`` | No | `PostFlushEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ +| :ref:`onClear ` | ``$em->clear()`` | No | `OnClearEventArgs`_ | ++------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+ .. warning:: @@ -358,7 +358,7 @@ behaviors across different entity classes. Note that they require much more detailed knowledge about the inner workings of the ``EntityManager`` and ``UnitOfWork`` classes. Please -read the :ref:`Implementing Event Listeners` section +read the :ref:`Implementing Event Listeners ` section carefully if you are trying to write your own listener. For event subscribers, there are no surprises. They declare the @@ -471,11 +471,11 @@ prePersist There are two ways for the ``prePersist`` event to be triggered: - One is when you call ``EntityManager::persist()``. The - event is also called for all :ref:`cascaded associations`. + event is also called for all :ref:`cascaded associations `. - The other is inside the ``flush()`` method when changes to associations are computed and - this association is marked as :ref:`cascade: persist`. Any new entity found + this association is marked as :ref:`cascade: persist `. Any new entity found during this operation is also persisted and ``prePersist`` called - on it. This is called :ref:`persistence by reachability`. + on it. This is called :ref:`persistence by reachability `. In both cases you get passed a ``PrePersistEventArgs`` instance which has access to the entity and the entity manager. @@ -499,7 +499,7 @@ preRemove The ``preRemove`` event is called on every entity immediately when it is passed to the ``EntityManager::remove()`` method. It is cascaded for all -associations that are marked as :ref:`cascade: remove` +associations that are marked as :ref:`cascade: remove ` It is not called for a DQL ``DELETE`` statement. @@ -547,7 +547,7 @@ entities and their associations have been computed. This means, the - Collections scheduled for removal To make use of the ``onFlush`` event you have to be familiar with the -internal :ref:`UnitOfWork` API, which grants you access to the previously +internal :ref:`UnitOfWork ` API, which grants you access to the previously mentioned sets. See this example: .. code-block:: php diff --git a/docs/en/reference/faq.rst b/docs/en/reference/faq.rst index a81812a9c2b..85b6e35d851 100644 --- a/docs/en/reference/faq.rst +++ b/docs/en/reference/faq.rst @@ -101,7 +101,7 @@ The many-to-many association is only supporting foreign keys in the table defini To work with many-to-many tables containing extra columns you have to use the foreign keys as primary keys feature of Doctrine ORM. -See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`. +See :doc:`the tutorial on composite primary keys for more information <../tutorials/composite-primary-keys>`. How can i paginate fetch-joined collections? diff --git a/docs/en/reference/inheritance-mapping.rst b/docs/en/reference/inheritance-mapping.rst index 0e23c8f7d87..448c9dd401b 100644 --- a/docs/en/reference/inheritance-mapping.rst +++ b/docs/en/reference/inheritance-mapping.rst @@ -380,7 +380,7 @@ It is not supported to use overrides in entity inheritance scenarios. .. note:: When using traits, make sure not to miss the warnings given in the - :doc:`Limitations and Known Issues` chapter. + :doc:`Limitations and Known Issues ` chapter. Association Override diff --git a/docs/en/sidebar.rst b/docs/en/sidebar.rst index 0430bcc7504..c87651c2fbe 100644 --- a/docs/en/sidebar.rst +++ b/docs/en/sidebar.rst @@ -1,3 +1,5 @@ +:orphan: + .. toc:: .. tocheader:: Tutorials From b6f4220493579b88cafdf2331423ccbffa54effe Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 13 Feb 2024 18:28:17 +0100 Subject: [PATCH 03/16] Throw if a variadic parameter contains unexpected named arguments (#11260) --- psalm-baseline.xml | 4 -- src/Internal/NoUnknownNamedArguments.php | 55 ++++++++++++++++++++++++ src/QueryBuilder.php | 23 ++++++++++ tests/Tests/ORM/QueryBuilderTest.php | 13 ++++++ 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 src/Internal/NoUnknownNamedArguments.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 17a090f3a2c..688463b1bb2 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1042,10 +1042,6 @@ - $having - $having - $where - $where $join]]]> $join]]]> diff --git a/src/Internal/NoUnknownNamedArguments.php b/src/Internal/NoUnknownNamedArguments.php new file mode 100644 index 00000000000..7584744c162 --- /dev/null +++ b/src/Internal/NoUnknownNamedArguments.php @@ -0,0 +1,55 @@ + $parameter + */ + private static function validateVariadicParameter(array $parameter): void + { + if (array_is_list($parameter)) { + return; + } + + [, $trace] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + assert(isset($trace['class'])); + + $additionalArguments = array_values(array_filter( + array_keys($parameter), + is_string(...), + )); + + throw new BadMethodCallException(sprintf( + 'Invalid call to %s::%s(), unknown named arguments: %s', + $trace['class'], + $trace['function'], + implode(', ', $additionalArguments), + )); + } +} diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 115dea0187d..f79e2fe83f0 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -6,6 +6,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; +use Doctrine\ORM\Internal\NoUnknownNamedArguments; use Doctrine\ORM\Internal\QueryType; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Parameter; @@ -38,6 +39,8 @@ */ class QueryBuilder implements Stringable { + use NoUnknownNamedArguments; + /** * The array of DQL parts collected. * @@ -611,6 +614,8 @@ public function add(string $dqlPartName, string|object|array $dqlPart, bool $app */ public function select(mixed ...$select): static { + self::validateVariadicParameter($select); + $this->type = QueryType::Select; if ($select === []) { @@ -657,6 +662,8 @@ public function distinct(bool $flag = true): static */ public function addSelect(mixed ...$select): static { + self::validateVariadicParameter($select); + $this->type = QueryType::Select; if ($select === []) { @@ -951,6 +958,8 @@ public function set(string $key, mixed $value): static */ public function where(mixed ...$predicates): static { + self::validateVariadicParameter($predicates); + if (! (count($predicates) === 1 && $predicates[0] instanceof Expr\Composite)) { $predicates = new Expr\Andx($predicates); } @@ -976,6 +985,8 @@ public function where(mixed ...$predicates): static */ public function andWhere(mixed ...$where): static { + self::validateVariadicParameter($where); + $dql = $this->getDQLPart('where'); if ($dql instanceof Expr\Andx) { @@ -1006,6 +1017,8 @@ public function andWhere(mixed ...$where): static */ public function orWhere(mixed ...$where): static { + self::validateVariadicParameter($where); + $dql = $this->getDQLPart('where'); if ($dql instanceof Expr\Orx) { @@ -1033,6 +1046,8 @@ public function orWhere(mixed ...$where): static */ public function groupBy(string ...$groupBy): static { + self::validateVariadicParameter($groupBy); + return $this->add('groupBy', new Expr\GroupBy($groupBy)); } @@ -1051,6 +1066,8 @@ public function groupBy(string ...$groupBy): static */ public function addGroupBy(string ...$groupBy): static { + self::validateVariadicParameter($groupBy); + return $this->add('groupBy', new Expr\GroupBy($groupBy), true); } @@ -1062,6 +1079,8 @@ public function addGroupBy(string ...$groupBy): static */ public function having(mixed ...$having): static { + self::validateVariadicParameter($having); + if (! (count($having) === 1 && ($having[0] instanceof Expr\Andx || $having[0] instanceof Expr\Orx))) { $having = new Expr\Andx($having); } @@ -1077,6 +1096,8 @@ public function having(mixed ...$having): static */ public function andHaving(mixed ...$having): static { + self::validateVariadicParameter($having); + $dql = $this->getDQLPart('having'); if ($dql instanceof Expr\Andx) { @@ -1097,6 +1118,8 @@ public function andHaving(mixed ...$having): static */ public function orHaving(mixed ...$having): static { + self::validateVariadicParameter($having); + $dql = $this->getDQLPart('having'); if ($dql instanceof Expr\Orx) { diff --git a/tests/Tests/ORM/QueryBuilderTest.php b/tests/Tests/ORM/QueryBuilderTest.php index 3c3f05ba4db..96927bbbf92 100644 --- a/tests/Tests/ORM/QueryBuilderTest.php +++ b/tests/Tests/ORM/QueryBuilderTest.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM; +use BadMethodCallException; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Cache; @@ -275,6 +276,18 @@ public function testWhere(): void $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = :uid'); } + public function testWhereWithUnexpectedNamedArguments(): void + { + $qb = $this->entityManager->createQueryBuilder() + ->select('u') + ->from(CmsUser::class, 'u'); + + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage('Invalid call to Doctrine\ORM\QueryBuilder::where(), unknown named arguments: foo, bar'); + + $qb->where(foo: 'u.id = :uid', bar: 'u.name = :name'); + } + public function testComplexAndWhere(): void { $qb = $this->entityManager->createQueryBuilder() From 6290747bf9a2538316b88bc12478fd078557e5ec Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 14 Feb 2024 00:33:12 +0100 Subject: [PATCH 04/16] Validate more variadic parameters (#11261) --- src/Mapping/ChainTypedFieldMapper.php | 10 ++++++---- src/Query/Expr.php | 11 +++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Mapping/ChainTypedFieldMapper.php b/src/Mapping/ChainTypedFieldMapper.php index 85e7faa6e4c..ed1ba93706c 100644 --- a/src/Mapping/ChainTypedFieldMapper.php +++ b/src/Mapping/ChainTypedFieldMapper.php @@ -4,18 +4,20 @@ namespace Doctrine\ORM\Mapping; +use Doctrine\ORM\Internal\NoUnknownNamedArguments; use ReflectionProperty; final class ChainTypedFieldMapper implements TypedFieldMapper { - /** - * @readonly - * @var TypedFieldMapper[] $typedFieldMappers - */ + use NoUnknownNamedArguments; + + /** @var list $typedFieldMappers */ private readonly array $typedFieldMappers; public function __construct(TypedFieldMapper ...$typedFieldMappers) { + self::validateVariadicParameter($typedFieldMappers); + $this->typedFieldMappers = $typedFieldMappers; } diff --git a/src/Query/Expr.php b/src/Query/Expr.php index 0629156d837..65f30827f24 100644 --- a/src/Query/Expr.php +++ b/src/Query/Expr.php @@ -4,6 +4,7 @@ namespace Doctrine\ORM\Query; +use Doctrine\ORM\Internal\NoUnknownNamedArguments; use Traversable; use function implode; @@ -23,6 +24,8 @@ */ class Expr { + use NoUnknownNamedArguments; + /** * Creates a conjunction of the given boolean expressions. * @@ -38,6 +41,8 @@ class Expr */ public function andX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Andx { + self::validateVariadicParameter($x); + return new Expr\Andx($x); } @@ -56,6 +61,8 @@ public function andX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): */ public function orX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Orx { + self::validateVariadicParameter($x); + return new Expr\Orx($x); } @@ -225,6 +232,8 @@ public function count(mixed $x): Expr\Func */ public function countDistinct(mixed ...$x): string { + self::validateVariadicParameter($x); + return 'COUNT(DISTINCT ' . implode(', ', $x) . ')'; } @@ -470,6 +479,8 @@ public function notLike(string $x, mixed $y): Expr\Comparison */ public function concat(mixed ...$x): Expr\Func { + self::validateVariadicParameter($x); + return new Expr\Func('CONCAT', $x); } From 3918dcfb42912492a06d753e2ebc77b65a93f278 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Thu, 15 Feb 2024 22:12:57 +0100 Subject: [PATCH 05/16] [Documentation] Adding link to Postgres upgrade article (#11257) * [Documentation] Adding link to Postgres upgrade article * Update UPGRADE.md * Update UPGRADE.md --- UPGRADE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 09ce079e7b9..e6a61238a89 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -13,9 +13,9 @@ so `$targetEntity` is a first argument now. This change affects only non-named a When using the `AUTO` strategy to let Doctrine determine the identity generation mechanism for an entity, and when using `doctrine/dbal` 4, PostgreSQL now uses `IDENTITY` -instead of `SEQUENCE`. When upgrading from ORM 2.x and preference is on keeping -the `SEQUENCE` based identity generation, then configure the ORM this way: - +instead of `SEQUENCE` or `SERIAL`. +* If you want to upgrade your existing tables to identity columns, you will need to follow [migration to identity columns on PostgreSQL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/how-to/postgresql-identity-migration.html) +* If you want to keep using SQL sequences, you need to configure the ORM this way: ```php use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\ORM\Configuration; From aa3b331cae5e9881ca690fc345b3f1411eaf8bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 17 Feb 2024 11:14:47 +0100 Subject: [PATCH 06/16] Remove unused trait --- tests/Tests/ORM/ConfigurationTest.php | 3 --- tests/Tests/ORM/EntityManagerTest.php | 3 --- tests/Tests/ORM/Functional/NativeQueryTest.php | 2 -- tests/Tests/ORM/Functional/Ticket/DDC117Test.php | 3 --- tests/Tests/ORM/Mapping/BasicInheritanceMappingTest.php | 3 --- tests/Tests/ORM/Mapping/ClassMetadataFactoryTest.php | 3 --- tests/Tests/ORM/Query/QueryTest.php | 3 --- tests/Tests/ORM/UnitOfWorkTest.php | 3 --- 8 files changed, 23 deletions(-) diff --git a/tests/Tests/ORM/ConfigurationTest.php b/tests/Tests/ORM/ConfigurationTest.php index 3e61fa89add..269228e72ea 100644 --- a/tests/Tests/ORM/ConfigurationTest.php +++ b/tests/Tests/ORM/ConfigurationTest.php @@ -4,7 +4,6 @@ namespace Doctrine\Tests\ORM; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\Cache\CacheConfiguration; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityRepository; @@ -26,8 +25,6 @@ */ class ConfigurationTest extends TestCase { - use VerifyDeprecations; - private Configuration $configuration; protected function setUp(): void diff --git a/tests/Tests/ORM/EntityManagerTest.php b/tests/Tests/ORM/EntityManagerTest.php index 5abcffc67d6..067b1c6874b 100644 --- a/tests/Tests/ORM/EntityManagerTest.php +++ b/tests/Tests/ORM/EntityManagerTest.php @@ -6,7 +6,6 @@ use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Exception\EntityManagerClosed; @@ -27,8 +26,6 @@ class EntityManagerTest extends OrmTestCase { - use VerifyDeprecations; - private EntityManagerMock $entityManager; protected function setUp(): void diff --git a/tests/Tests/ORM/Functional/NativeQueryTest.php b/tests/Tests/ORM/Functional/NativeQueryTest.php index 6b8f883f418..c9f665387b7 100644 --- a/tests/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Tests/ORM/Functional/NativeQueryTest.php @@ -8,7 +8,6 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type as DBALType; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\Internal\Hydration\HydrationException; use Doctrine\ORM\Internal\SQLResultCasing; use Doctrine\ORM\PersistentCollection; @@ -33,7 +32,6 @@ class NativeQueryTest extends OrmFunctionalTestCase { use SQLResultCasing; - use VerifyDeprecations; private AbstractPlatform|null $platform = null; diff --git a/tests/Tests/ORM/Functional/Ticket/DDC117Test.php b/tests/Tests/ORM/Functional/Ticket/DDC117Test.php index 138d167d366..dba78b9afd7 100644 --- a/tests/Tests/ORM/Functional/Ticket/DDC117Test.php +++ b/tests/Tests/ORM/Functional/Ticket/DDC117Test.php @@ -4,7 +4,6 @@ namespace Doctrine\Tests\ORM\Functional\Ticket; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\UnitOfWork; use Doctrine\Tests\Models\DDC117\DDC117ApproveChanges; use Doctrine\Tests\Models\DDC117\DDC117Article; @@ -23,8 +22,6 @@ #[Group('DDC-117')] class DDC117Test extends OrmFunctionalTestCase { - use VerifyDeprecations; - private DDC117Article|null $article1; private DDC117Article|null $article2; diff --git a/tests/Tests/ORM/Mapping/BasicInheritanceMappingTest.php b/tests/Tests/ORM/Mapping/BasicInheritanceMappingTest.php index cade45f5a91..28642118e01 100644 --- a/tests/Tests/ORM/Mapping/BasicInheritanceMappingTest.php +++ b/tests/Tests/ORM/Mapping/BasicInheritanceMappingTest.php @@ -4,7 +4,6 @@ namespace Doctrine\Tests\ORM\Mapping; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Id\SequenceGenerator as IdSequenceGenerator; use Doctrine\ORM\Mapping\ClassMetadata; @@ -42,8 +41,6 @@ class BasicInheritanceMappingTest extends OrmTestCase { - use VerifyDeprecations; - private ClassMetadataFactory $cmf; protected function setUp(): void diff --git a/tests/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index 315dd203600..23054d4899e 100644 --- a/tests/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -9,7 +9,6 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs; @@ -55,8 +54,6 @@ class ClassMetadataFactoryTest extends OrmTestCase { - use VerifyDeprecations; - public function testGetMetadataForSingleClass(): void { $platform = $this->createMock(AbstractPlatform::class); diff --git a/tests/Tests/ORM/Query/QueryTest.php b/tests/Tests/ORM/Query/QueryTest.php index d24d9292ca9..0336c3da6c8 100644 --- a/tests/Tests/ORM/Query/QueryTest.php +++ b/tests/Tests/ORM/Query/QueryTest.php @@ -15,7 +15,6 @@ use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Types; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\UnitOfWork; @@ -40,8 +39,6 @@ class QueryTest extends OrmTestCase { - use VerifyDeprecations; - /** @var EntityManagerMock */ protected $entityManager; diff --git a/tests/Tests/ORM/UnitOfWorkTest.php b/tests/Tests/ORM/UnitOfWorkTest.php index 63e13c319b1..550b1cfe1c8 100644 --- a/tests/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Tests/ORM/UnitOfWorkTest.php @@ -11,7 +11,6 @@ use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\EntityNotFoundException; use Doctrine\ORM\Exception\EntityIdentityCollisionException; use Doctrine\ORM\Mapping\ClassMetadata; @@ -46,8 +45,6 @@ */ class UnitOfWorkTest extends OrmTestCase { - use VerifyDeprecations; - /** * SUT */ From 7b3db4a0375128bc10a617f90384c360a43fda8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 17 Feb 2024 14:59:24 +0100 Subject: [PATCH 07/16] Use correction sectionauthor syntax --- .../en/cookbook/implementing-arrayaccess-for-domain-objects.rst | 2 +- .../cookbook/implementing-the-notify-changetracking-policy.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst b/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst index 363d1ad8ba1..40ca4fbdfc5 100644 --- a/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst +++ b/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst @@ -1,7 +1,7 @@ Implementing ArrayAccess for Domain Objects =========================================== -.. sectionauthor:: Roman Borschel (roman@code-factory.org) +.. sectionauthor:: Roman Borschel This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example diff --git a/docs/en/cookbook/implementing-the-notify-changetracking-policy.rst b/docs/en/cookbook/implementing-the-notify-changetracking-policy.rst index 2415db1dc97..13ada8fb572 100644 --- a/docs/en/cookbook/implementing-the-notify-changetracking-policy.rst +++ b/docs/en/cookbook/implementing-the-notify-changetracking-policy.rst @@ -1,7 +1,7 @@ Implementing the Notify ChangeTracking Policy ============================================= -.. sectionauthor:: Roman Borschel (roman@code-factory.org) +.. sectionauthor:: Roman Borschel The NOTIFY change-tracking policy is the most effective change-tracking policy provided by Doctrine but it requires some From fe0647053a7e87e25b571d1375ce947431eea0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 17 Feb 2024 15:06:46 +0100 Subject: [PATCH 08/16] Mark document as orphan It is here for backward compatibilty reasons. --- docs/en/reference/installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/reference/installation.rst b/docs/en/reference/installation.rst index dab1364f777..2c0d5823009 100644 --- a/docs/en/reference/installation.rst +++ b/docs/en/reference/installation.rst @@ -1,3 +1,5 @@ +:orphan: + Installation ============ From dba9d72b2d370035ced6d634fc89814e90ddfa66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 17 Feb 2024 15:10:28 +0100 Subject: [PATCH 09/16] Add type field mapper documentation to the sidebar --- docs/en/sidebar.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/sidebar.rst b/docs/en/sidebar.rst index c87651c2fbe..f67304e8457 100644 --- a/docs/en/sidebar.rst +++ b/docs/en/sidebar.rst @@ -33,6 +33,7 @@ reference/inheritance-mapping reference/working-with-objects reference/working-with-associations + reference/typedfieldmapper reference/events reference/unitofwork reference/unitofwork-associations From 7c2907805184107e464dab2bfd0743df77ea9ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 18 Feb 2024 11:32:14 +0100 Subject: [PATCH 10/16] Treat '0' as a legitimate trim char Because of a loose comparison, it was not. --- src/Query/AST/Functions/TrimFunction.php | 2 +- tests/Tests/ORM/Query/LanguageRecognitionTest.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Query/AST/Functions/TrimFunction.php b/src/Query/AST/Functions/TrimFunction.php index 8c2b307faea..f9c6e406fea 100644 --- a/src/Query/AST/Functions/TrimFunction.php +++ b/src/Query/AST/Functions/TrimFunction.php @@ -74,7 +74,7 @@ public function parse(Parser $parser) $this->trimChar = $lexer->token->value; } - if ($this->leading || $this->trailing || $this->both || $this->trimChar) { + if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) { $parser->match(Lexer::T_FROM); } diff --git a/tests/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Tests/ORM/Query/LanguageRecognitionTest.php index 4d1972593f3..a2e0e600969 100644 --- a/tests/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Tests/ORM/Query/LanguageRecognitionTest.php @@ -172,6 +172,11 @@ public function testFunctionalExpressionsSupportedInWherePart(): void $this->assertValidDQL("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM(u.name) = 'someone'"); } + public function testTrimFalsyString(): void + { + $this->assertValidDQL("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM('0' FROM u.name) = 'someone'"); + } + public function testArithmeticExpressionsSupportedInWherePart(): void { $this->assertValidDQL('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); From cf408ad9ae817948e66fc5c6487a28bc2e5b98ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 18 Feb 2024 12:26:18 +0100 Subject: [PATCH 11/16] Remove unused baseline entries --- psalm-baseline.xml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 34b44307ff3..3123bf33d28 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1543,15 +1543,6 @@ - - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - ArithmeticPrimary()]]> ArithmeticPrimary()]]> @@ -1572,15 +1563,6 @@ - - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - intervalExpression->dispatch($sqlWalker)]]> - unit->value]]> From e4769d319146cde533429a381a6fa3636203e3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 18 Feb 2024 15:51:05 +0100 Subject: [PATCH 12/16] docs: recommend safer way to disable logging (#11269) * Remove trailing newlines * Recommend safer way to disable logging Resetting the middlewares on the configuration object will only work if the connection object hasn't been built from that configuration object yet. Instead, people should find the logger bound to the logging middleware and disable it. --- docs/en/reference/batch-processing.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/en/reference/batch-processing.rst b/docs/en/reference/batch-processing.rst index 12cac934164..ee381bbe5c4 100644 --- a/docs/en/reference/batch-processing.rst +++ b/docs/en/reference/batch-processing.rst @@ -18,14 +18,20 @@ especially what the strategies presented here provide help with. .. note:: - Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage. - To avoid that you should remove the corresponding middleware. - To remove all middlewares, you can use this line: + Having an SQL logger enabled when processing batches can have a + serious impact on performance and resource usage. + To avoid that, you should use a PSR logger implementation that can be + disabled at runtime. + For example, with Monolog, you can use ``Logger::pushHandler()`` + to push a ``NullHandler`` to the logger instance, and then pop it + when you need to enable logging again. + + With DBAL 2, you can disable the SQL logger like below: + .. code-block:: php getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3 - $em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2 + $em->getConnection()->getConfiguration()->setSQLLogger(null); Bulk Inserts ------------ @@ -188,6 +194,3 @@ problems using the following approach: Iterating results is not possible with queries that fetch-join a collection-valued association. The nature of such SQL result sets is not suitable for incremental hydration. - - - From 4bd574daee7d617a4394eaffd37c284a4925284b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Mon, 19 Feb 2024 09:26:32 +0100 Subject: [PATCH 13/16] Improve static analysis on AttachEntityListenersListener $listenerCallback is supposed to be a method name, so it is safe to require it is not a falsy string. --- src/Tools/AttachEntityListenersListener.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Tools/AttachEntityListenersListener.php b/src/Tools/AttachEntityListenersListener.php index 63e8f7b5a13..9203cfe782c 100644 --- a/src/Tools/AttachEntityListenersListener.php +++ b/src/Tools/AttachEntityListenersListener.php @@ -5,8 +5,10 @@ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; +use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; +use function assert; use function ltrim; /** @@ -14,16 +16,22 @@ */ class AttachEntityListenersListener { - /** @var mixed[][] */ + /** + * @var array> + */ private array $entityListeners = []; /** * Adds an entity listener for a specific entity. * - * @param string $entityClass The entity to attach the listener. - * @param string $listenerClass The listener class. - * @param string|null $eventName The entity lifecycle event. - * @param string|null $listenerCallback The listener callback method or NULL to use $eventName. + * @param class-string $entityClass The entity to attach the listener. + * @param class-string $listenerClass The listener class. + * @param Events::*|null $eventName The entity lifecycle event. + * @param non-falsy-string|null $listenerCallback The listener callback method or NULL to use $eventName. */ public function addEntityListener( string $entityClass, @@ -34,7 +42,7 @@ public function addEntityListener( $this->entityListeners[ltrim($entityClass, '\\')][] = [ 'event' => $eventName, 'class' => $listenerClass, - 'method' => $listenerCallback ?: $eventName, + 'method' => $listenerCallback ?? $eventName, ]; } @@ -53,6 +61,7 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event): void if ($listener['event'] === null) { EntityListenerBuilder::bindEntityListener($metadata, $listener['class']); } else { + assert($listener['method'] !== null); $metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']); } } From e0081b59beb7e82e6c918694ecbbaca38e090026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Mon, 19 Feb 2024 22:17:51 +0100 Subject: [PATCH 14/16] Account for inversedBy being a non-falsy-string or null It is supposed to hold the name of a PHP property, and those cannot be falsy strings. --- src/Internal/Hydration/ObjectHydrator.php | 4 ++-- src/Mapping/Builder/ClassMetadataBuilder.php | 6 +++--- src/Persisters/Entity/BasicEntityPersister.php | 2 +- src/Tools/SchemaValidator.php | 2 +- src/UnitOfWork.php | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Internal/Hydration/ObjectHydrator.php b/src/Internal/Hydration/ObjectHydrator.php index c83c1e4701e..d24323d8689 100644 --- a/src/Internal/Hydration/ObjectHydrator.php +++ b/src/Internal/Hydration/ObjectHydrator.php @@ -91,7 +91,7 @@ protected function prepare(): void } // handle fetch-joined owning side bi-directional one-to-one associations - if ($assoc->inversedBy) { + if ($assoc->inversedBy !== null) { $class = $this->getClassMetadata($className); $inverseAssoc = $class->associationMappings[$assoc->inversedBy]; @@ -439,7 +439,7 @@ protected function hydrateRowData(array $row, array &$result): void if ($relation->isOwningSide()) { // TODO: Just check hints['fetched'] here? // If there is an inverse mapping on the target class its bidirectional - if ($relation->inversedBy) { + if ($relation->inversedBy !== null) { $inverseAssoc = $targetClass->associationMappings[$relation->inversedBy]; if ($inverseAssoc->isToOne()) { $targetClass->reflFields[$inverseAssoc->fieldName]->setValue($element, $parentObject); diff --git a/src/Mapping/Builder/ClassMetadataBuilder.php b/src/Mapping/Builder/ClassMetadataBuilder.php index 0f208f29139..b9d3cc81f5e 100644 --- a/src/Mapping/Builder/ClassMetadataBuilder.php +++ b/src/Mapping/Builder/ClassMetadataBuilder.php @@ -288,7 +288,7 @@ public function addManyToOne( ): ClassMetadataBuilder { $builder = $this->createManyToOne($name, $targetEntity); - if ($inversedBy) { + if ($inversedBy !== null) { $builder->inversedBy($inversedBy); } @@ -348,7 +348,7 @@ public function addOwningOneToOne( ): ClassMetadataBuilder { $builder = $this->createOneToOne($name, $targetEntity); - if ($inversedBy) { + if ($inversedBy !== null) { $builder->inversedBy($inversedBy); } @@ -380,7 +380,7 @@ public function addOwningManyToMany( ): ClassMetadataBuilder { $builder = $this->createManyToMany($name, $targetEntity); - if ($inversedBy) { + if ($inversedBy !== null) { $builder->inversedBy($inversedBy); } diff --git a/src/Persisters/Entity/BasicEntityPersister.php b/src/Persisters/Entity/BasicEntityPersister.php index e6b09774408..679e8a17069 100644 --- a/src/Persisters/Entity/BasicEntityPersister.php +++ b/src/Persisters/Entity/BasicEntityPersister.php @@ -764,7 +764,7 @@ public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEnti $targetClass = $this->em->getClassMetadata($assoc->targetEntity); if ($assoc->isOwningSide()) { - $isInverseSingleValued = $assoc->inversedBy && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy); + $isInverseSingleValued = $assoc->inversedBy !== null && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy); // Mark inverse side as fetched in the hints, otherwise the UoW would // try to load it in a separate query (remember: to-one inverse sides can not be lazy). diff --git a/src/Tools/SchemaValidator.php b/src/Tools/SchemaValidator.php index 43b10ee3983..6ebe991876c 100644 --- a/src/Tools/SchemaValidator.php +++ b/src/Tools/SchemaValidator.php @@ -162,7 +162,7 @@ public function validateClass(ClassMetadata $class): array } } - if ($assoc->isOwningSide() && $assoc->inversedBy) { + if ($assoc->isOwningSide() && $assoc->inversedBy !== null) { if ($targetMetadata->hasField($assoc->inversedBy)) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' . 'field ' . $assoc->targetEntity . '#' . $assoc->inversedBy . ' which is not defined as association.'; diff --git a/src/UnitOfWork.php b/src/UnitOfWork.php index 3b020c955f2..5c16c511ce1 100644 --- a/src/UnitOfWork.php +++ b/src/UnitOfWork.php @@ -2550,7 +2550,7 @@ public function createEntity(string $className, array $data, array &$hints = []) $this->originalEntityData[$oid][$field] = $newValue; $class->reflFields[$field]->setValue($entity, $newValue); - if ($assoc->inversedBy && $assoc->isOneToOne() && $newValue !== null) { + if ($assoc->inversedBy !== null && $assoc->isOneToOne() && $newValue !== null) { $inverseAssoc = $targetClass->associationMappings[$assoc->inversedBy]; $targetClass->reflFields[$inverseAssoc->fieldName]->setValue($newValue, $entity); } From b6b4cbcb939d5ccff7c244604939d97b8d49438b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Tue, 20 Feb 2024 20:29:56 +0100 Subject: [PATCH 15/16] Remove broken assertion from DateAddFunction and DateSubFunction (#11243) * Remove wrong asserts in DATE_ADD and DATE_SUB query AST function handlers * Require DBAL 3.8.2 --- .github/workflows/static-analysis.yml | 4 +-- composer.json | 2 +- src/Query/AST/Functions/DateAddFunction.php | 13 ++------ src/Query/AST/Functions/DateSubFunction.php | 13 ++------ .../ORM/Functional/QueryDqlFunctionTest.php | 31 +++++++++++++++++++ 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 025f29ea02d..75f46a70e60 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -32,7 +32,7 @@ jobs: include: - dbal-version: default config: phpstan.neon - - dbal-version: 3.7 + - dbal-version: 3.8.2 config: phpstan-dbal3.neon steps: @@ -65,7 +65,7 @@ jobs: matrix: dbal-version: - default - - 3.7 + - 3.8.2 steps: - name: "Checkout code" diff --git a/composer.json b/composer.json index 6aefbec39e4..2a002989c21 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "composer-runtime-api": "^2", "ext-ctype": "*", "doctrine/collections": "^2.1", - "doctrine/dbal": "^3.6 || ^4", + "doctrine/dbal": "^3.8.2 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", diff --git a/src/Query/AST/Functions/DateAddFunction.php b/src/Query/AST/Functions/DateAddFunction.php index 385ebac99c9..12920dcbd0f 100644 --- a/src/Query/AST/Functions/DateAddFunction.php +++ b/src/Query/AST/Functions/DateAddFunction.php @@ -11,8 +11,6 @@ use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; -use function assert; -use function is_numeric; use function strtolower; /** @@ -63,17 +61,10 @@ public function getSql(SqlWalker $sqlWalker): string }; } - /** - * @return numeric-string - * - * @throws ASTException - */ + /** @throws ASTException */ private function dispatchIntervalExpression(SqlWalker $sqlWalker): string { - $sql = $this->intervalExpression->dispatch($sqlWalker); - assert(is_numeric($sql)); - - return $sql; + return $this->intervalExpression->dispatch($sqlWalker); } public function parse(Parser $parser): void diff --git a/src/Query/AST/Functions/DateSubFunction.php b/src/Query/AST/Functions/DateSubFunction.php index 254f1219277..5363680e2a4 100644 --- a/src/Query/AST/Functions/DateSubFunction.php +++ b/src/Query/AST/Functions/DateSubFunction.php @@ -8,8 +8,6 @@ use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\SqlWalker; -use function assert; -use function is_numeric; use function strtolower; /** @@ -56,16 +54,9 @@ public function getSql(SqlWalker $sqlWalker): string }; } - /** - * @return numeric-string - * - * @throws ASTException - */ + /** @throws ASTException */ private function dispatchIntervalExpression(SqlWalker $sqlWalker): string { - $sql = $this->intervalExpression->dispatch($sqlWalker); - assert(is_numeric($sql)); - - return $sql; + return $this->intervalExpression->dispatch($sqlWalker); } } diff --git a/tests/Tests/ORM/Functional/QueryDqlFunctionTest.php b/tests/Tests/ORM/Functional/QueryDqlFunctionTest.php index 9fdc7d64516..5a6aa5da3ab 100644 --- a/tests/Tests/ORM/Functional/QueryDqlFunctionTest.php +++ b/tests/Tests/ORM/Functional/QueryDqlFunctionTest.php @@ -7,6 +7,7 @@ use DateTimeImmutable; use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\ORM\AbstractQuery; +use Doctrine\Tests\Models\Company\CompanyEmployee; use Doctrine\Tests\Models\Company\CompanyManager; use Doctrine\Tests\OrmFunctionalTestCase; use PHPUnit\Framework\Attributes\DataProvider; @@ -487,4 +488,34 @@ protected function generateFixture(): void $this->_em->flush(); $this->_em->clear(); } + + #[Group('GH-11240')] + public function testDateAddWithColumnInterval(): void + { + $query = sprintf( + 'SELECT DATE_ADD(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m', + CompanyEmployee::class, + ); + + $result = $this->_em->createQuery($query) + ->setMaxResults(1) + ->getSingleResult(AbstractQuery::HYDRATE_ARRAY); + + self::assertArrayHasKey('add', $result); + } + + #[Group('GH-11240')] + public function testDateSubWithColumnInterval(): void + { + $query = sprintf( + 'SELECT DATE_SUB(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m', + CompanyEmployee::class, + ); + + $result = $this->_em->createQuery($query) + ->setMaxResults(1) + ->getSingleResult(AbstractQuery::HYDRATE_ARRAY); + + self::assertArrayHasKey('add', $result); + } } From 0efac091417a499e88fe2907b0699d4f5fbf6ffd Mon Sep 17 00:00:00 2001 From: Karoly Gossler Date: Wed, 21 Feb 2024 18:51:21 +0100 Subject: [PATCH 16/16] Fix Static Analysis folder reference (#11281) --- .github/workflows/static-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index c21e5cbea56..88b7196e357 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ on: - src/** - phpstan* - psalm* - - tests/Doctrine/StaticAnalysis/** + - tests/StaticAnalysis/** push: branches: - "*.x" @@ -20,7 +20,7 @@ on: - src/** - phpstan* - psalm* - - tests/Doctrine/StaticAnalysis/** + - tests/StaticAnalysis/** jobs: static-analysis-phpstan: