From cd44547573142ef7ca7ffe2fdc7cef9292bdfcaf Mon Sep 17 00:00:00 2001 From: chapterjason Date: Sat, 23 Oct 2021 11:32:44 +0200 Subject: [PATCH 01/19] Remove old use statements (#9146) --- .../Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php index c8576294062..657d53221d6 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php @@ -5,9 +5,7 @@ namespace Doctrine\ORM\Cache\Persister\Entity; use Doctrine\Common\Util\ClassUtils; -use Doctrine\ORM\Cache\CacheException; use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity; -use Doctrine\ORM\Utility\StaticClassNameConverter; /** * Specific read-only region entity persister From f2729b0610a3a6acca9e51df5cb6058da51b9c8b Mon Sep 17 00:00:00 2001 From: wickedOne Date: Sat, 23 Oct 2021 11:43:40 +0200 Subject: [PATCH 02/19] Return 0 when there's no metadata to process (#9147) --- .../ORM/Tools/Console/Command/ConvertMappingCommand.php | 2 +- phpstan-baseline.neon | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php index fc22d2c7664..1067647e97f 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php @@ -151,7 +151,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if (empty($metadata)) { $ui->success('No Metadata Classes to process.'); - return; + return 0; } foreach ($metadata as $class) { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 81ce4dac0fd..a2ed6b92432 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2021,11 +2021,6 @@ parameters: count: 1 path: lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php - - - message: "#^Method Doctrine\\\\ORM\\\\Tools\\\\Console\\\\Command\\\\ConvertMappingCommand\\:\\:execute\\(\\) should return int but empty return statement found\\.$#" - count: 1 - path: lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php - - message: "#^Parameter \\#1 \\$metadata of method Doctrine\\\\ORM\\\\Tools\\\\Export\\\\Driver\\\\AbstractExporter\\:\\:setMetadata\\(\\) expects array\\, array\\ given\\.$#" count: 1 From 3622381f8c5082368a0229bd0f49a11bc2a478da Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Mon, 25 Oct 2021 22:34:36 +0200 Subject: [PATCH 03/19] Overview table for events: Jump links (#9131) * Overview table for events: Jump links * Update events.rst --- docs/en/reference/events.rst | 82 ++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 6c31bca17cc..e0981a3e39c 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -134,38 +134,38 @@ see :ref:`lifecycle-callbacks` Events Overview --------------- -+-----------------------------+-----------------------+-----------+ -| Event | Dispatched by | Lifecycle | -| | | Callback | -+=============================+=======================+===========+ -| ``preRemove`` | ``$em->remove()`` | Yes | -+-----------------------------+-----------------------+-----------+ -| ``postRemove`` | ``$em->flush()`` | Yes | -+-----------------------------+-----------------------+-----------+ -| ``prePersist`` | ``$em->persist()`` | Yes | -| | on *initial* persist | | -+-----------------------------+-----------------------+-----------+ -| ``postPersist`` | ``$em->flush()`` | Yes | -+-----------------------------+-----------------------+-----------+ -| ``preUpdate`` | ``$em->flush()`` | Yes | -+-----------------------------+-----------------------+-----------+ -| ``postUpdate`` | ``$em->flush()`` | Yes | -+-----------------------------+-----------------------+-----------+ -| ``postLoad`` | Loading from database | Yes | -+-----------------------------+-----------------------+-----------+ -| ``loadClassMetadata`` | Loading of mapping | No | -| | metadata | | -+-----------------------------+-----------------------+-----------+ -| ``onClassMetadataNotFound`` | ``MappingException`` | No | -+-----------------------------+-----------------------+-----------+ -| ``preFlush`` | ``$em->flush()`` | Yes | -+-----------------------------+-----------------------+-----------+ -| ``onFlush`` | ``$em->flush()`` | No | -+-----------------------------+-----------------------+-----------+ -| ``postFlush`` | ``$em->flush()`` | No | -+-----------------------------+-----------------------+-----------+ -| ``onClear`` | ``$em->clear()`` | No | -+-----------------------------+-----------------------+-----------+ ++-----------------------------------------------------------------+-----------------------+-----------+ +| Event | Dispatched by | Lifecycle | +| | | Callback | ++=================================================================+=======================+===========+ +| :ref:`reference-events-pre-remove` | ``$em->remove()`` | Yes | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-post-update-remove-persist` | ``$em->flush()`` | Yes | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-pre-persist` | ``$em->persist()`` | Yes | +| | on *initial* persist | | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-post-update-remove-persist` | ``$em->flush()`` | Yes | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-pre-update` | ``$em->flush()`` | Yes | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-post-update-remove-persist` | ``$em->flush()`` | Yes | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-post-load` | Loading from database | Yes | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-load-class-metadata` | Loading of mapping | No | +| | metadata | | ++-----------------------------------------------------------------+-----------------------+-----------+ +| ``onClassMetadataNotFound`` | ``MappingException`` | No | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-pre-flush` | ``$em->flush()`` | Yes | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-on-flush` | ``$em->flush()`` | No | ++-----------------------------------------------------------------+-----------------------+-----------+ +| :ref:`reference-events-post-flush` | ``$em->flush()`` | No | ++-----------------------------------------------------------------+-----------------------+-----------+ +| ``onClear`` | ``$em->clear()`` | No | ++-----------------------------------------------------------------+-----------------------+-----------+ Naming convention ~~~~~~~~~~~~~~~~~ @@ -563,6 +563,8 @@ the restrictions apply as well, with the additional restriction that (prior to version 2.4) you do not have access to the ``EntityManager`` or ``UnitOfWork`` APIs inside these events. +.. _reference-events-pre-persist: + prePersist ~~~~~~~~~~ @@ -588,6 +590,8 @@ The following restrictions apply to ``prePersist``: - Doctrine will not recognize changes made to relations in a prePersist event. This includes modifications to collections such as additions, removals or replacement. + +.. _reference-events-pre-remove: preRemove ~~~~~~~~~ @@ -600,6 +604,8 @@ There are no restrictions to what methods can be called inside the ``preRemove`` event, except when the remove method itself was called during a flush operation. +.. _reference-events-pre-flush: + preFlush ~~~~~~~~ @@ -622,6 +628,8 @@ result in infinite loop. } } +.. _reference-events-on-flush: + onFlush ~~~~~~~ @@ -685,6 +693,8 @@ The following restrictions apply to the onFlush event: affected entity. This can be done by calling ``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``. +.. _reference-events-post-flush: + postFlush ~~~~~~~~~ @@ -705,6 +715,8 @@ postFlush } } +.. _reference-events-pre-update: + preUpdate ~~~~~~~~~ @@ -789,6 +801,8 @@ Restrictions for this event: API are strongly discouraged and don't work as expected outside the flush operation. +.. _reference-events-post-update-remove-persist: + postUpdate, postRemove, postPersist ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -798,6 +812,8 @@ database, but you can use these events to alter non-persistable items, like non-mapped fields, logging or even associated classes that are not directly mapped by Doctrine. +.. _reference-events-post-load: + postLoad ~~~~~~~~ @@ -1010,6 +1026,8 @@ Implementing your own resolver : $configurations->setEntityListenerResolver(new MyEntityListenerResolver); EntityManager::create(.., $configurations, ..); +.. _reference-events-load-class-metadata: + Load ClassMetadata Event ------------------------ From 3271d8f6e299192685624c131024001cb3107a91 Mon Sep 17 00:00:00 2001 From: Paul Waring Date: Tue, 26 Oct 2021 11:59:31 +0100 Subject: [PATCH 04/19] Fix markup for variable names (#9150) Three references to `$isDevMode` were marked up with a single backtick, however two backticks are required in order for the variable name to be highlighted correctly (c.f. `ArrayCache`). --- docs/en/reference/configuration.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/reference/configuration.rst b/docs/en/reference/configuration.rst index a7478a78627..36cd5978818 100644 --- a/docs/en/reference/configuration.rst +++ b/docs/en/reference/configuration.rst @@ -85,9 +85,9 @@ Or if you prefer YAML: Inside the ``Setup`` methods several assumptions are made: -- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request. -- If `$isDevMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument. -- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line. +- If ``$isDevMode`` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request. +- If ``$isDevMode`` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument. +- If ``$isDevMode`` is false, set then proxy classes have to be explicitly created through the command line. - If third argument `$proxyDir` is not set, use the systems temporary directory. If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration ` section. From 705d88eaba52c61100752fecb8c354ede56666e4 Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Wed, 13 Oct 2021 14:58:34 -0300 Subject: [PATCH 05/19] Add XSD "orm:columntoken" type in order to support reserved words in column names --- doctrine-mapping.xsd | 12 +++-- .../ORM/Mapping/AbstractMappingDriverTest.php | 46 +++++++++++++++++++ ....ORM.Mapping.ReservedWordInTableColumn.php | 18 ++++++++ ....Mapping.ReservedWordInTableColumn.dcm.xml | 18 ++++++++ ....Mapping.ReservedWordInTableColumn.dcm.yml | 10 ++++ 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.yml diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index bc28309a26e..30f30ce217d 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -295,7 +295,7 @@ - + @@ -402,7 +402,7 @@ - + @@ -497,6 +497,12 @@ + + + + + + @@ -612,7 +618,7 @@ - + diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index e47ea8b325a..0e79aad7c30 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -1119,6 +1119,13 @@ public function testDiscriminatorColumnDefaultName(): void $class = $this->createClassMetadata(SingleTableEntityIncompleteDiscriminatorColumnMapping::class); self::assertEquals('dtype', $class->discriminatorColumn['name']); } + + public function testReservedWordInTableColumn(): void + { + $metadata = $this->createClassMetadata(ReservedWordInTableColumn::class); + + self::assertSame('count', $metadata->getFieldMapping('count')['columnName']); + } } /** @@ -1774,3 +1781,42 @@ class SingleTableEntityIncompleteDiscriminatorColumnMappingSub1 extends SingleTa class SingleTableEntityIncompleteDiscriminatorColumnMappingSub2 extends SingleTableEntityIncompleteDiscriminatorColumnMapping { } + +/** @Entity */ +#[ORM\Entity] +class ReservedWordInTableColumn +{ + /** + * @var int + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="NONE") + */ + #[ORM\Id, ORM\Column(type: 'integer'), ORM\GeneratedValue(strategy: 'NONE')] + public $id; + + /** + * @var string|null + * @Column(name="`count`", type="integer") + */ + #[ORM\Column(name: '`count`', type: 'integer')] + public $count; + + public static function loadMetadata(ClassMetadataInfo $metadata): void + { + $metadata->mapField( + [ + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer', + ] + ); + $metadata->mapField( + [ + 'fieldName' => 'count', + 'type' => 'integer', + 'columnName' => '`count`', + ] + ); + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.php new file mode 100644 index 00000000000..03f5a86eb61 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.php @@ -0,0 +1,18 @@ +mapField( + [ + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer', + ] +); +$metadata->mapField( + [ + 'fieldName' => 'count', + 'type' => 'integer', + 'columnName' => '`count`', + ] +); diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.xml new file mode 100644 index 00000000000..16eb27692b3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.yml new file mode 100644 index 00000000000..a315310e4d1 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.yml @@ -0,0 +1,10 @@ +Doctrine\Tests\ORM\Mapping\ReservedWordInTableColumn: + type: entity + id: + id: + generator: + strategy: NONE + fields: + count: + type: integer + column: '`count`' From 35e680cd3ff0b8f3dc4147c90d75f27ed5dc7d8c Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Thu, 28 Oct 2021 21:29:47 +0200 Subject: [PATCH 06/19] Fixing links in overview table (#9151) I got them wrong in https://github.com/doctrine/orm/pull/9131 ;-) --- docs/en/reference/events.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index e0981a3e39c..eeb276d91d7 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -138,31 +138,31 @@ Events Overview | Event | Dispatched by | Lifecycle | | | | Callback | +=================================================================+=======================+===========+ -| :ref:`reference-events-pre-remove` | ``$em->remove()`` | Yes | +| :ref:`preRemove` | ``$em->remove()`` | Yes | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-post-update-remove-persist` | ``$em->flush()`` | Yes | +| :ref:`postRemove` | ``$em->flush()`` | Yes | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-pre-persist` | ``$em->persist()`` | Yes | +| :ref:`prePersist` | ``$em->persist()`` | Yes | | | on *initial* persist | | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-post-update-remove-persist` | ``$em->flush()`` | Yes | +| :ref:`postPersist` | ``$em->flush()`` | Yes | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-pre-update` | ``$em->flush()`` | Yes | +| :ref:`preUpdate` | ``$em->flush()`` | Yes | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-post-update-remove-persist` | ``$em->flush()`` | Yes | +| :ref:`postUpdate` | ``$em->flush()`` | Yes | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-post-load` | Loading from database | Yes | +| :ref:`postLoad` | Loading from database | Yes | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-load-class-metadata` | Loading of mapping | No | +| :ref:`loadClassMetadata` | Loading of mapping | No | | | metadata | | +-----------------------------------------------------------------+-----------------------+-----------+ | ``onClassMetadataNotFound`` | ``MappingException`` | No | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-pre-flush` | ``$em->flush()`` | Yes | +| :ref:`preFlush` | ``$em->flush()`` | Yes | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-on-flush` | ``$em->flush()`` | No | +| :ref:`onFlush` | ``$em->flush()`` | No | +-----------------------------------------------------------------+-----------------------+-----------+ -| :ref:`reference-events-post-flush` | ``$em->flush()`` | No | +| :ref:`postFlush` | ``$em->flush()`` | No | +-----------------------------------------------------------------+-----------------------+-----------+ | ``onClear`` | ``$em->clear()`` | No | +-----------------------------------------------------------------+-----------------------+-----------+ From 641330baa60bc136a96ddacedd05d6e5d07fb07d Mon Sep 17 00:00:00 2001 From: Chase Noel Date: Thu, 28 Oct 2021 17:42:05 -0400 Subject: [PATCH 07/19] Add doctrine/dbal to project composer.json (#9152) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As discussed in https://github.com/doctrine/orm/issues/9078 when entities utilize data mappings which are provided by the dbal lib it is expected behavior that users will explicitly define their dependency on the package. Co-authored-by: Grégoire Paris --- docs/en/tutorials/getting-started.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/en/tutorials/getting-started.rst b/docs/en/tutorials/getting-started.rst index 1cb108668ca..3aa79d57ef6 100644 --- a/docs/en/tutorials/getting-started.rst +++ b/docs/en/tutorials/getting-started.rst @@ -81,7 +81,8 @@ that directory with the following contents: { "require": { - "doctrine/orm": "^2.6.2", + "doctrine/orm": "^2.10.2", + "doctrine/dbal": "^3.1.1", "symfony/yaml": "2.*", "symfony/cache": "^5.3" }, @@ -112,6 +113,14 @@ Add the following directories: .. note:: The YAML driver is deprecated and will be removed in version 3.0. It is strongly recommended to switch to one of the other mappings. +.. note:: + It is strongly recommended that you require ``doctrine/dbal`` in your + ``composer.json`` as well, because using the ORM means mapping objects + and their fields to database tables and their columns, and that + requires mentioning so-called types that are defined in ``doctrine/dbal`` + in your application. Having an explicit requirement means you control + when the upgrade to the next major version happens, so that you can + do the necessary changes in your application beforehand. Obtaining the EntityManager --------------------------- From 97411f5567ec89bdbf763f6d1184c080ce5e2416 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Fri, 29 Oct 2021 01:12:02 +0200 Subject: [PATCH 08/19] Merging Lifecycle Callbacks code samples for PHP + XML + YAML IMO, the text I deleted just repeated things that are obvious in the example anyway. --- docs/en/reference/events.rst | 146 +++++++++++++++-------------------- 1 file changed, 64 insertions(+), 82 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index eeb276d91d7..06d15bd7c74 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -299,107 +299,89 @@ specific to a particular entity class's lifecycle. Note that Licecycle Callbacks are not supported for Embeddables. -.. code-block:: php - - createdAt = date('Y-m-d H:i:s'); - } - - /** @PrePersist */ - public function doOtherStuffOnPrePersist() - { - $this->value = 'changed from prePersist callback!'; - } +.. configuration-block:: - /** @PostPersist */ - public function doStuffOnPostPersist() - { - $this->value = 'changed from postPersist callback!'; - } + .. code-block:: php - /** @PostLoad */ - public function doStuffOnPostLoad() - { - $this->value = 'changed from postLoad callback!'; - } + value = 'changed from preUpdate callback!'; - } - } + // ... -Note that the methods set as lifecycle callbacks need to be public and, -when using these annotations, you have to apply the -``@HasLifecycleCallbacks`` marker annotation on the entity class. + /** + * @Column(type="string", length=255) + */ + public $value; -If you want to register lifecycle callbacks from YAML or XML you -can do it with the following. + /** @Column(name="created_at", type="string", length=255) */ + private $createdAt; -.. code-block:: yaml + /** @PrePersist */ + public function doStuffOnPrePersist() + { + $this->createdAt = date('Y-m-d H:i:s'); + } - User: - type: entity - fields: - # ... - name: - type: string(50) - lifecycleCallbacks: - prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ] - postPersist: [ doStuffOnPostPersist ] + /** @PrePersist */ + public function doOtherStuffOnPrePersist() + { + $this->value = 'changed from prePersist callback!'; + } -In YAML the ``key`` of the lifecycleCallbacks entry is the event that you -are triggering on and the value is the method (or methods) to call. The allowed -event types are the ones listed in the previous Lifecycle Events section. + /** @PostPersist */ + public function doStuffOnPostPersist() + { + $this->value = 'changed from postPersist callback!'; + } -XML would look something like this: + /** @PostLoad */ + public function doStuffOnPostLoad() + { + $this->value = 'changed from postLoad callback!'; + } -.. code-block:: xml + /** @PreUpdate */ + public function doStuffOnPreUpdate() + { + $this->value = 'changed from preUpdate callback!'; + } + } + .. code-block:: xml - + - + - + - - - - + + + + - + - + + .. code-block:: yaml -In XML the ``type`` of the lifecycle-callback entry is the event that you -are triggering on and the ``method`` is the method to call. The allowed event -types are the ones listed in the previous Lifecycle Events section. + User: + type: entity + fields: + # ... + name: + type: string(50) + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ] + postPersist: [ doStuffOnPostPersist ] -When using YAML or XML you need to remember to create public methods to match the -callback names you defined. E.g. in these examples ``doStuffOnPrePersist()``, -``doOtherStuffOnPrePersist()`` and ``doStuffOnPostPersist()`` methods need to be -defined on your ``User`` model. +Note that the methods set as lifecycle callbacks need to be public and, +when using these annotations, you have to apply the +``@HasLifecycleCallbacks`` marker annotation on the entity class. .. code-block:: php From dbaf99f3d9754eded09248efcb630ae58a331e8c Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Fri, 29 Oct 2021 01:17:32 +0200 Subject: [PATCH 09/19] Update events.rst --- docs/en/reference/events.rst | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 06d15bd7c74..e06eca51375 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -383,32 +383,6 @@ Note that the methods set as lifecycle callbacks need to be public and, when using these annotations, you have to apply the ``@HasLifecycleCallbacks`` marker annotation on the entity class. -.. code-block:: php - - Date: Fri, 29 Oct 2021 14:21:37 +0200 Subject: [PATCH 10/19] Removing paragraph on `const`s (#9158) IMO, this is better shown by example, so I added it there. --- docs/en/reference/events.rst | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index eeb276d91d7..844d33ac4ef 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -254,15 +254,6 @@ events during the life-time of their registered entities. cascade remove relations. In this case, you should load yourself the proxy in the associated pre event. -You can access the Event constants from the ``Events`` class in the -ORM package. - -.. code-block:: php - - addEventListener(array(Events::preUpdate), new MyEventListener()); + $eventManager->addEventListener([Events::preUpdate], new MyEventListener()); $eventManager->addEventSubscriber(new MyEventSubscriber()); $entityManager = EntityManager::create($dbOpts, $config, $eventManager); @@ -543,7 +536,9 @@ EntityManager was created: .. code-block:: php getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener()); + use Doctrine\ORM\Events; + + $entityManager->getEventManager()->addEventListener([Events::preUpdate], new MyEventListener()); $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); .. _reference-events-implementing-listeners: From 5e6608b48e6cebff08399646cfcc521971d13c9c Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Sat, 30 Oct 2021 13:48:03 +0200 Subject: [PATCH 11/19] Update events.rst --- docs/en/reference/events.rst | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index e06eca51375..667aa3d386d 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -305,7 +305,10 @@ specific to a particular entity class's lifecycle. value = 'changed from prePersist callback!'; } - /** @PostPersist */ - public function doStuffOnPostPersist() - { - $this->value = 'changed from postPersist callback!'; - } - /** @PostLoad */ public function doStuffOnPostLoad() { $this->value = 'changed from postLoad callback!'; } - - /** @PreUpdate */ - public function doStuffOnPreUpdate() - { - $this->value = 'changed from preUpdate callback!'; - } } .. code-block:: xml @@ -361,7 +349,8 @@ specific to a particular entity class's lifecycle. - + + @@ -372,16 +361,12 @@ specific to a particular entity class's lifecycle. User: type: entity fields: - # ... - name: - type: string(50) + # ... + value: + type: string(255) lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ] - postPersist: [ doStuffOnPostPersist ] - -Note that the methods set as lifecycle callbacks need to be public and, -when using these annotations, you have to apply the -``@HasLifecycleCallbacks`` marker annotation on the entity class. + postLoad: [ doStuffOnPostLoad ] Lifecycle Callbacks Event Argument ---------------------------------- From 16cbc169980a7c0879cb5711e9145870acbd544f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 30 Oct 2021 19:10:25 +0200 Subject: [PATCH 12/19] Document BC break (#9143) Closes #9141 --- UPGRADE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/UPGRADE.md b/UPGRADE.md index b6bcc446449..f929d10f547 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,12 @@ # Upgrade to 2.10 +## BC Break: `UnitOfWork` now relies on SPL object IDs, not hashes + +When calling the following methods, you are now supposed to use the result of +`spl_object_id()`, and not `spl_object_hash()`: +- `UnitOfWork::clearEntityChangeSet()` +- `UnitOfWork::setOriginalEntityProperty()` + ## BC Break: Removed `TABLE` id generator strategy The implementation was unfinished for 14 years. From 6f194eeabf8b16b898d54de70023a89a8d3cda13 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Mon, 1 Nov 2021 13:56:12 +0100 Subject: [PATCH 13/19] Remove reverted bc break (#9166) --- UPGRADE.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index f929d10f547..b8122db08d2 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -16,10 +16,6 @@ It is now deprecated to rely on: - `Doctrine\ORM\Mapping\ClassMetadata::$tableGeneratorDefinition`; - or `Doctrine\ORM\Mapping\ClassMetadata::isIdGeneratorTable()`. -## BC Break: Removed possibility to extend the doctrine mapping xml schema with anything - -If you want to extend it now you have to provide your own validation schema. - ## New method `Doctrine\ORM\EntityManagerInterface#wrapInTransaction($func)` Works the same as `Doctrine\ORM\EntityManagerInterface#transactional()` but returns any value returned from `$func` closure rather than just _non-empty value returned from the closure or true_. From e1c2d2e65d93d03db2a6fb9c66635baeebcef720 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 1 Nov 2021 13:42:48 +0100 Subject: [PATCH 14/19] PHPStan 1.0.1 Signed-off-by: Alexander M. Turek --- composer.json | 2 +- lib/Doctrine/ORM/AbstractQuery.php | 3 +- lib/Doctrine/ORM/EntityManagerInterface.php | 2 - .../ORM/Mapping/ClassMetadataInfo.php | 12 +- lib/Doctrine/ORM/Mapping/Column.php | 10 +- lib/Doctrine/ORM/Query/Parser.php | 2 +- phpstan-baseline.neon | 228 ++++++++++++++++-- phpstan.neon | 2 +- psalm-baseline.xml | 91 ++++--- .../Doctrine/StaticAnalysis/get-metadata.php | 4 +- 10 files changed, 270 insertions(+), 86 deletions(-) diff --git a/composer.json b/composer.json index 2f633e15cd1..a1c8427619d 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "doctrine/annotations": "^1.13", "doctrine/coding-standard": "^9.0", "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "0.12.99", + "phpstan/phpstan": "1.0.1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", "squizlabs/php_codesniffer": "3.6.1", "symfony/cache": "^4.4 || ^5.2", diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index f23144810e0..bab96a448c5 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -1253,7 +1253,8 @@ private function getTimestampKey(): ?TimestampCacheKey * Will return the configured id if it exists otherwise a hash will be * automatically generated for you. * - * @return array ($key, $hash) + * @return string[] ($key, $hash) + * @psalm-return array{string, string} ($key, $hash) */ protected function getHydrationCacheId() { diff --git a/lib/Doctrine/ORM/EntityManagerInterface.php b/lib/Doctrine/ORM/EntityManagerInterface.php index ebb841d057b..3218d9dd119 100644 --- a/lib/Doctrine/ORM/EntityManagerInterface.php +++ b/lib/Doctrine/ORM/EntityManagerInterface.php @@ -330,11 +330,9 @@ public function hasFilters(); * {@inheritDoc} * * @psalm-param string|class-string $className - * @phpstan-param string $className * * @return Mapping\ClassMetadata * @psalm-return Mapping\ClassMetadata - * @phpstan-return Mapping\ClassMetadata * * @psalm-template T of object */ diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index d736886563c..841d4821783 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -442,6 +442,7 @@ class ClassMetadataInfo implements ClassMetadata * originalField?: string, * quoted?: bool, * requireSQLConversion?: bool, + * declared?: class-string, * declaredField?: string, * options: array * }> @@ -509,7 +510,14 @@ class ClassMetadataInfo implements ClassMetadata * uniqueConstraints => array * * @var mixed[] - * @psalm-var array{name: string, schema: string, indexes: array, uniqueConstraints: array} + * @psalm-var array{ + * name: string, + * schema: string, + * indexes: array, + * uniqueConstraints: array, + * options: array, + * quoted?: bool + * } */ public $table; @@ -665,7 +673,7 @@ class ClassMetadataInfo implements ClassMetadata /** * The ReflectionClass instance of the mapped class. * - * @var ReflectionClass + * @var ReflectionClass|null */ public $reflClass; diff --git a/lib/Doctrine/ORM/Mapping/Column.php b/lib/Doctrine/ORM/Mapping/Column.php index 622b507a0e2..9602c8e6651 100644 --- a/lib/Doctrine/ORM/Mapping/Column.php +++ b/lib/Doctrine/ORM/Mapping/Column.php @@ -15,26 +15,26 @@ #[Attribute(Attribute::TARGET_PROPERTY)] final class Column implements Annotation { - /** @var string */ + /** @var string|null */ public $name; /** @var mixed */ public $type; - /** @var int */ + /** @var int|null */ public $length; /** * The precision for a decimal (exact numeric) column (Applies only for decimal column). * - * @var int + * @var int|null */ public $precision = 0; /** * The scale for a decimal (exact numeric) column (Applies only for decimal column). * - * @var int + * @var int|null */ public $scale = 0; @@ -47,7 +47,7 @@ final class Column implements Annotation /** @var array */ public $options = []; - /** @var string */ + /** @var string|null */ public $columnDefinition; /** diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 67ccb9d830e..294cda48c1d 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -214,7 +214,7 @@ class Parser */ private $customOutputWalker; - /** @psalm-var list */ + /** @psalm-var array */ private $identVariableExpressions = []; /** diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a2ed6b92432..ecde6563d6c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -100,6 +100,11 @@ parameters: count: 1 path: lib/Doctrine/ORM/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php + - + message: "#^PHPDoc type Doctrine\\\\Common\\\\Cache\\\\Cache\\|Doctrine\\\\Common\\\\Cache\\\\MultiGetCache of property Doctrine\\\\ORM\\\\Cache\\\\Region\\\\DefaultMultiGetRegion\\:\\:\\$cache is not covariant with PHPDoc type Doctrine\\\\Common\\\\Cache\\\\Cache of overridden property Doctrine\\\\ORM\\\\Cache\\\\Region\\\\DefaultRegion\\:\\:\\$cache\\.$#" + count: 1 + path: lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php + - message: "#^Method Doctrine\\\\ORM\\\\Cache\\\\Region\\\\DefaultRegion\\:\\:getCache\\(\\) should return Doctrine\\\\Common\\\\Cache\\\\CacheProvider but returns Doctrine\\\\Common\\\\Cache\\\\Cache\\.$#" count: 1 @@ -180,6 +185,11 @@ parameters: count: 2 path: lib/Doctrine/ORM/EntityManager.php + - + message: "#^Template type T of method Doctrine\\\\ORM\\\\EntityManagerInterface\\:\\:getClassMetadata\\(\\) is not referenced in a parameter\\.$#" + count: 1 + path: lib/Doctrine/ORM/EntityManagerInterface.php + - message: "#^Method Doctrine\\\\ORM\\\\EntityRepository\\:\\:findOneBy\\(\\) should return T\\|null but returns object\\|null\\.$#" count: 1 @@ -235,6 +245,16 @@ parameters: count: 1 path: lib/Doctrine/ORM/LazyCriteriaCollection.php + - + message: "#^Offset 'indexes' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php + + - + message: "#^Offset 'uniqueConstraints' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php + - message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\:\\:\\$cache\\.$#" count: 2 @@ -417,7 +437,12 @@ parameters: - message: "#^Negated boolean expression is always false\\.$#" - count: 2 + count: 1 + path: lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php + + - + message: "#^Offset 'indexes'\\|'uniqueConstraints' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 path: lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php - @@ -526,37 +551,32 @@ parameters: path: lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php - - message: "#^Array \\(array\\('name' \\=\\> string, 'schema' \\=\\> string, 'indexes' \\=\\> array, 'uniqueConstraints' \\=\\> array\\)\\) does not accept key 'options'\\.$#" - count: 1 - path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php - - - - message: "#^Array \\(array\\('name' \\=\\> string, 'schema' \\=\\> string, 'indexes' \\=\\> array, 'uniqueConstraints' \\=\\> array\\)\\) does not accept key 'quoted'\\.$#" - count: 2 + message: "#^Call to an undefined method ReflectionProperty\\:\\:getType\\(\\)\\.$#" + count: 3 path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php - - message: "#^Array \\(array\\\\) does not accept true\\.$#" + message: "#^Call to an undefined method ReflectionProperty\\:\\:hasType\\(\\)\\.$#" count: 1 path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php - - message: "#^Call to an undefined method ReflectionProperty\\:\\:getType\\(\\)\\.$#" - count: 3 + message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\NamingStrategy\\:\\:joinColumnName\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 2 path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php - - message: "#^Call to an undefined method ReflectionProperty\\:\\:hasType\\(\\)\\.$#" + message: "#^Negated boolean expression is always false\\.$#" count: 1 path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php - - message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\NamingStrategy\\:\\:joinColumnName\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 2 + message: "#^Offset 'schema' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php - - message: "#^Negated boolean expression is always false\\.$#" + message: "#^Offset 'unique' on array\\{type\\: string, fieldName\\: string, columnName\\?\\: string, inherited\\?\\: class\\-string, nullable\\?\\: bool, originalClass\\?\\: class\\-string, originalField\\?\\: string, scale\\?\\: int, \\.\\.\\.\\} in isset\\(\\) does not exist\\.$#" count: 1 path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -765,6 +785,11 @@ parameters: count: 2 path: lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php + - + message: "#^PHPDoc type array\\ of property Doctrine\\\\ORM\\\\Mapping\\\\Driver\\\\AttributeDriver\\:\\:\\$entityAnnotationClasses is not covariant with PHPDoc type array\\ of overridden property Doctrine\\\\Persistence\\\\Mapping\\\\Driver\\\\AnnotationDriver\\:\\:\\$entityAnnotationClasses\\.$#" + count: 1 + path: lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php + - message: "#^Parameter \\#1 \\$metadata of static method Doctrine\\\\ORM\\\\Mapping\\\\Builder\\\\EntityListenerBuilder\\:\\:bindEntityListener\\(\\) expects Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata, Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo&Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\ given\\.$#" count: 1 @@ -960,6 +985,11 @@ parameters: count: 1 path: lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php + - + message: "#^Offset 'version' on \\*NEVER\\* in isset\\(\\) always exists and is always null\\.$#" + count: 1 + path: lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php + - message: "#^Parameter \\#1 \\$metadata of static method Doctrine\\\\ORM\\\\Mapping\\\\Builder\\\\EntityListenerBuilder\\:\\:bindEntityListener\\(\\) expects Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata, Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\ given\\.$#" count: 1 @@ -1115,6 +1145,11 @@ parameters: count: 1 path: lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php + - + message: "#^Offset 'usage' on array\\{usage\\: string, region\\?\\: string\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php + - message: "#^Parameter \\#1 \\$metadata of static method Doctrine\\\\ORM\\\\Mapping\\\\Builder\\\\EntityListenerBuilder\\:\\:bindEntityListener\\(\\) expects Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata, Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\ given\\.$#" count: 1 @@ -1380,15 +1415,30 @@ parameters: count: 1 path: lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php + - + message: "#^PHPDoc type array\\ of property Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Andx\\:\\:\\$allowedClasses is not covariant with PHPDoc type array\\ of overridden property Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Base\\:\\:\\$allowedClasses\\.$#" + count: 1 + path: lib/Doctrine/ORM/Query/Expr/Andx.php + + - + message: "#^PHPDoc type array\\ of property Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Orx\\:\\:\\$allowedClasses is not covariant with PHPDoc type array\\ of overridden property Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Base\\:\\:\\$allowedClasses\\.$#" + count: 1 + path: lib/Doctrine/ORM/Query/Expr/Orx.php + + - + message: "#^PHPDoc type array\\ of property Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Select\\:\\:\\$allowedClasses is not covariant with PHPDoc type array\\ of overridden property Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Base\\:\\:\\$allowedClasses\\.$#" + count: 1 + path: lib/Doctrine/ORM/Query/Expr/Select.php + - message: "#^Property Doctrine\\\\ORM\\\\Query\\\\FilterCollection\\:\\:\\$em \\(Doctrine\\\\ORM\\\\EntityManager\\) does not accept Doctrine\\\\ORM\\\\EntityManagerInterface\\.$#" count: 1 path: lib/Doctrine/ORM/Query/FilterCollection.php - - message: "#^Array \\(array\\\\) does not accept key non\\-empty\\-string\\.$#" + message: "#^Property Doctrine\\\\ORM\\\\Query\\\\FilterCollection\\:\\:\\$filterHash is never written, only read\\.$#" count: 1 - path: lib/Doctrine/ORM/Query/Parser.php + path: lib/Doctrine/ORM/Query/FilterCollection.php - message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" @@ -1422,14 +1472,19 @@ parameters: path: lib/Doctrine/ORM/Query/Parser.php - - message: "#^Result of && is always false\\.$#" - count: 1 + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 3 path: lib/Doctrine/ORM/Query/Parser.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 4 - path: lib/Doctrine/ORM/Query/Parser.php + message: "#^Offset 'columns' on array\\{name\\: string, entities\\: array, columns\\: array\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php + + - + message: "#^Offset 'entities' on array\\{name\\: string, entities\\: array, columns\\: array\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php - message: "#^Parameter \\#2 \\$class of static method Doctrine\\\\ORM\\\\Utility\\\\PersisterHelper\\:\\:getTypeOfColumn\\(\\) expects Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata, Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo given\\.$#" @@ -1476,6 +1531,16 @@ parameters: count: 1 path: lib/Doctrine/ORM/Query/SqlWalker.php + - + message: "#^Offset 'resultVariable' on array\\{metadata\\: Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata, parent\\: string, relation\\: array, map\\: mixed, nestingLevel\\: int, token\\: array\\} in isset\\(\\) does not exist\\.$#" + count: 3 + path: lib/Doctrine/ORM/Query/SqlWalker.php + + - + message: "#^Offset string on array\\ in isset\\(\\) does not exist\\.$#" + count: 1 + path: lib/Doctrine/ORM/Query/SqlWalker.php + - message: "#^Parameter \\#1 \\$entity of static method Doctrine\\\\ORM\\\\OptimisticLockException\\:\\:lockFailed\\(\\) expects object, class\\-string\\ given\\.$#" count: 1 @@ -1982,7 +2047,7 @@ parameters: path: lib/Doctrine/ORM/QueryBuilder.php - - message: "#^Parameter \\#2 \\$dqlPart of method Doctrine\\\\ORM\\\\QueryBuilder\\:\\:add\\(\\) expects array\\<'join'\\|int, array\\\\|string\\>\\|object\\|string, array\\&nonEmpty given\\.$#" + message: "#^Parameter \\#2 \\$dqlPart of method Doctrine\\\\ORM\\\\QueryBuilder\\:\\:add\\(\\) expects array\\<'join'\\|int, array\\\\|string\\>\\|object\\|string, non\\-empty\\-array\\ given\\.$#" count: 2 path: lib/Doctrine/ORM/QueryBuilder.php @@ -2057,7 +2122,47 @@ parameters: path: lib/Doctrine/ORM/Tools/EntityGenerator.php - - message: "#^Result of && is always false\\.$#" + message: "#^Offset 'allocationSize' on array\\{sequenceName\\: string, allocationSize\\: string, initialValue\\: string, quoted\\?\\: mixed\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Offset 'indexes' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Offset 'initialValue' on array\\{sequenceName\\: string, allocationSize\\: string, initialValue\\: string, quoted\\?\\: mixed\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Offset 'name' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Offset 'options' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Offset 'schema' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Offset 'sequenceName' on array\\{sequenceName\\: string, allocationSize\\: string, initialValue\\: string, quoted\\?\\: mixed\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Offset 'uniqueConstraints' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/EntityGenerator.php + + - + message: "#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\\\:\\:\\$lifecycleCallbacks \\(array\\\\>\\) in isset\\(\\) is not nullable\\.$#" count: 1 path: lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -2091,6 +2196,41 @@ parameters: count: 1 path: lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php + - + message: "#^Offset 'indexes' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + + - + message: "#^Offset 'name' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + + - + message: "#^Offset 'options' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + + - + message: "#^Offset 'options' on array\\{type\\: string, fieldName\\: string, columnName\\?\\: string, length\\?\\: int, id\\?\\: bool, nullable\\?\\: bool, columnDefinition\\?\\: string, precision\\?\\: int, \\.\\.\\.\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + + - + message: "#^Offset 'schema' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + + - + message: "#^Offset 'uniqueConstraints' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + + - + message: "#^Offset 'version' on array\\{type\\: string, fieldName\\: string, options\\: array, columnName\\?\\: string, length\\?\\: int, id\\?\\: bool, nullable\\?\\: bool, columnDefinition\\?\\: string, \\.\\.\\.\\} in isset\\(\\) does not exist\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + - message: "#^Parameter \\#1 \\$policy of method Doctrine\\\\ORM\\\\Tools\\\\Export\\\\Driver\\\\AbstractExporter\\:\\:_getChangeTrackingPolicyString\\(\\) expects 1\\|2\\|3, int given\\.$#" count: 1 @@ -2101,6 +2241,11 @@ parameters: count: 2 path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + - + message: "#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\\\:\\:\\$lifecycleCallbacks \\(array\\\\>\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php + - message: "#^Right side of && is always true\\.$#" count: 2 @@ -2111,6 +2256,16 @@ parameters: count: 1 path: lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php + - + message: "#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\\\:\\:\\$lifecycleCallbacks \\(array\\\\>\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php + + - + message: "#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\\\:\\:\\$table \\(array\\\\) on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php + - message: "#^Return type \\(void\\) of method Doctrine\\\\ORM\\\\Tools\\\\Pagination\\\\CountWalker\\:\\:walkSelectStatement\\(\\) should be compatible with return type \\(string\\) of method Doctrine\\\\ORM\\\\Query\\\\TreeWalker\\:\\:walkSelectStatement\\(\\)$#" count: 1 @@ -2121,6 +2276,11 @@ parameters: count: 1 path: lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php + - + message: "#^Offset 'parent' on array\\{metadata\\: Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata, parent\\: string, relation\\: array, map\\: mixed, nestingLevel\\: int, token\\: array\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php + - message: "#^Return type \\(void\\) of method Doctrine\\\\ORM\\\\Tools\\\\Pagination\\\\LimitSubqueryWalker\\:\\:walkSelectStatement\\(\\) should be compatible with return type \\(string\\) of method Doctrine\\\\ORM\\\\Query\\\\TreeWalker\\:\\:walkSelectStatement\\(\\)$#" count: 1 @@ -2156,6 +2316,26 @@ parameters: count: 1 path: lib/Doctrine/ORM/Tools/SchemaTool.php + - + message: "#^Offset 'columnDefinition' on array\\{type\\: string, fieldName\\: string, columnName\\?\\: string, inherited\\?\\: class\\-string, nullable\\?\\: bool, originalClass\\?\\: class\\-string, originalField\\?\\: string, scale\\?\\: int, \\.\\.\\.\\} in isset\\(\\) does not exist\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/SchemaTool.php + + - + message: "#^Offset 'indexes' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/SchemaTool.php + + - + message: "#^Offset 'options' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/SchemaTool.php + + - + message: "#^Offset 'uniqueConstraints' on array\\{name\\: string, schema\\: string, indexes\\: array, uniqueConstraints\\: array, options\\: array\\, quoted\\?\\: bool\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: lib/Doctrine/ORM/Tools/SchemaTool.php + - message: "#^Binary operation \"&\" between string and 3 results in an error\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index 2ccd6581380..7a0b60a06b1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -21,7 +21,7 @@ parameters: message: '/^Call to an undefined method Doctrine\\DBAL\\Platforms\\AbstractPlatform::getSQLResultCasing\(\)\.$/' path: lib/Doctrine/ORM/Internal/SQLResultCasing.php - - message: '/^Parameter \$stmt of method .* has invalid typehint type Doctrine\\DBAL\\Driver\\ResultStatement\.$/' + message: '/^Parameter \$stmt of method .* has invalid type Doctrine\\DBAL\\Driver\\ResultStatement\.$/' path: lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php - message: '/^Class Doctrine\\DBAL\\Driver\\ResultStatement not found\.$/' diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 378203ee6f9..3f29dd590cf 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -777,8 +777,7 @@ - - ClassMetadata + ClassMetadata ClassMetadata ClassMetadata @@ -825,8 +824,7 @@ addNamedNativeQuery addNamedQuery - - $class->reflClass + $definition @@ -869,6 +867,9 @@ $parent->table $parent->versionField + + $subClass->table[$indexType][$indexName] + $parentClass->table[$indexType] @@ -936,9 +937,10 @@ protected function _validateAndCompleteOneToOneMapping(array $mapping) public $inheritanceType = self::INHERITANCE_TYPE_NONE; - + ReflectionProperty ReflectionProperty + getReflectionClass $definition @@ -967,7 +969,8 @@ array{usage: int, region: string|null} - + + $this->reflClass $this->reflFields[$name] $this->reflFields[$this->identifier[0]] @@ -987,12 +990,17 @@ $queryMapping['resultClass'] $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']) - - $reflService->getClass($this->name) - $reflService->getClass($this->name) + null - + + $embeddable->reflClass->name + $this->reflClass->name + + + getProperty + getProperty + getProperty getValue getValue getValue @@ -1013,12 +1021,11 @@ $this->fieldMappings[$idProperty]['columnName'] $this->fieldMappings[$idProperty]['columnName'] - + $discriminatorColumn $idGenerator $isVersioned $namespace - $reflClass $sequenceGeneratorDefinition $table $tableGeneratorDefinition @@ -1041,21 +1048,11 @@ $mapping !== false $mapping !== false - joinColumnName joinColumnName - - - $columnDefinition - $length - $name - $precision - $scale - - $name @@ -1107,6 +1104,10 @@ $class + + $mapping + + $metadata->inheritanceType $metadata->isEmbeddedClass @@ -1121,10 +1122,6 @@ $primaryTable['indexes'] $primaryTable['uniqueConstraints'] - - isset($column->columnDefinition) - isset($column->name) - addEntityListener addLifecycleCallback @@ -1173,6 +1170,10 @@ $value[1] $value[1] + + $mapping + + $entityAnnotationClasses @@ -1189,10 +1190,8 @@ assert($method instanceof ReflectionMethod) assert($property instanceof ReflectionProperty) - + assert($cacheAttribute instanceof Mapping\Cache) - isset($column->columnDefinition) - isset($column->name) @@ -2629,16 +2628,9 @@ call_user_func($functionClass, $functionName) call_user_func($functionClass, $functionName) - - $this->identVariableExpressions[$dqlAlias] - $this->queryComponents[$dqlAlias] - SelectStatement|UpdateStatement|DeleteStatement - - $this->identVariableExpressions - $primary $this->CollectionMemberExpression() @@ -2843,6 +2835,14 @@ $renameMode $renameMode + + $classMetadata->reflClass->name + + + getShortName + getShortName + getShortName + $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName'] @@ -3337,9 +3337,6 @@ new ClassMetadataExporter() new EntityGenerator() - - int - configure @@ -3479,10 +3476,8 @@ ClassMetadataInfo::GENERATOR_TYPE_UUID - - $metadata->reflClass !== null + class_exists($metadata->name) - new ReflectionClass($metadata->name) public function setFieldVisibility($visibility) @@ -3508,12 +3503,11 @@ $classToExtend - + + (array) $metadata->table['options'] (bool) $embeddablesImmutable - - $metadata->reflClass - $metadata->reflClass !== null + isset($metadata->lifecycleCallbacks) @@ -3752,6 +3746,11 @@ $indexName + + + isAbstract + + new ClassLoader('Doctrine', $directory) diff --git a/tests/Doctrine/StaticAnalysis/get-metadata.php b/tests/Doctrine/StaticAnalysis/get-metadata.php index 0b9f29bc60a..c9b11036155 100644 --- a/tests/Doctrine/StaticAnalysis/get-metadata.php +++ b/tests/Doctrine/StaticAnalysis/get-metadata.php @@ -17,16 +17,14 @@ abstract class GetMetadata { /** * @param string|object $class - * @phpstan-param class-string|object $class + * @psalm-param class-string|object $class */ abstract public function getEntityManager($class): EntityManagerInterface; /** * @psalm-param class-string $class - * @phpstan-param class-string $class * * @psalm-return ClassMetadata - * @phpstan-return ClassMetadata * * @psalm-template TObject of object */ From 9e37c788ef87cea017b11a2ee1465c53c87e1a8d Mon Sep 17 00:00:00 2001 From: Rafael Armenio Date: Fri, 5 Nov 2021 09:25:18 -0300 Subject: [PATCH 15/19] Infer type from field instead of column getTypeOfColumn() relies on getTypeOfField(), and does not suffer from mismatching issues caused by quoting, because you cannot quote a field. Since a field can be composite, that method returns an array, hence why we need to select the first element. --- .../Collection/ManyToManyPersister.php | 2 +- .../ORM/Functional/Ticket/GH9109Test.php | 217 ++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/GH9109Test.php diff --git a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php index 0e06e889df7..b008e63ebaa 100644 --- a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php @@ -249,7 +249,7 @@ public function loadCriteria(PersistentCollection $collection, Criteria $criteri $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform); $whereClauses[] = sprintf('te.%s %s ?', $field, $operator); $params[] = $value; - $paramTypes[] = PersisterHelper::getTypeOfColumn($field, $targetClass, $this->em); + $paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0]; } $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9109Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9109Test.php new file mode 100644 index 00000000000..97d5da842b2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9109Test.php @@ -0,0 +1,217 @@ +_schemaTool->createSchema( + [ + $this->_em->getClassMetadata(GH9109User::class), + $this->_em->getClassMetadata(GH9109Product::class), + ] + ); + } + + protected function tearDown(): void + { + $this->_schemaTool->dropSchema( + [ + $this->_em->getClassMetadata(GH9109User::class), + $this->_em->getClassMetadata(GH9109Product::class), + ] + ); + + parent::tearDown(); + } + + public function testIssue(): void + { + $userFirstName = 'GH9109Test'; + $userLastName = 'UserGH9109'; + $productTitle = 'Test product'; + + $userRepository = $this->_em->getRepository(GH9109User::class); + + $user = new GH9109User(); + $user->setFirstName($userFirstName); + $user->setLastName($userLastName); + + $product = new GH9109Product(); + $product->setTitle($productTitle); + + $this->_em->persist($user); + $this->_em->persist($product); + $this->_em->flush(); + + $product->addBuyer($user); + + $this->_em->persist($product); + $this->_em->flush(); + + $this->_em->clear(); + + $persistedProduct = $this->_em->find(GH9109Product::class, $product->getId()); + + // assert Product was persisted + self::assertInstanceOf(GH9109Product::class, $persistedProduct); + self::assertEquals($productTitle, $persistedProduct->getTitle()); + + // assert Product has a Buyer + $count = $persistedProduct->getBuyers()->count(); + self::assertEquals(1, $count); + + // assert NOT QUOTED will WORK with findOneBy + $user = $userRepository->findOneBy(['lastName' => $userLastName]); + self::assertInstanceOf(GH9109User::class, $user); + self::assertEquals($userLastName, $user->getLastName()); + + // assert NOT QUOTED will WORK with Criteria + $criteria = Criteria::create(); + $criteria->where($criteria->expr()->eq('lastName', $userLastName)); + $user = $persistedProduct->getBuyers()->matching($criteria)->first(); + self::assertInstanceOf(GH9109User::class, $user); + self::assertEquals($userLastName, $user->getLastName()); + + // assert QUOTED will WORK with findOneBy + $user = $userRepository->findOneBy(['firstName' => $userFirstName]); + self::assertInstanceOf(GH9109User::class, $user); + self::assertEquals($userFirstName, $user->getFirstName()); + + // assert QUOTED will WORK with Criteria + $criteria = Criteria::create(); + $criteria->where($criteria->expr()->eq('firstName', $userFirstName)); + $user = $persistedProduct->getBuyers()->matching($criteria)->first(); + self::assertInstanceOf(GH9109User::class, $user); + self::assertEquals($userFirstName, $user->getFirstName()); + } +} + +/** + * @Entity + */ +class GH9109Product +{ + /** + * @var int $id + * @Column(name="`id`", type="integer") + * @Id + * @GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @var string $title + * @Column(name="`title`", type="string", length=255) + */ + private $title; + + /** + * @var Collection|GH9109User[] + * @psalm-var Collection + * @ManyToMany(targetEntity="GH9109User") + */ + private $buyers; + + public function __construct() + { + $this->buyers = new ArrayCollection(); + } + + public function getId(): int + { + return $this->id; + } + + public function setTitle(string $title): void + { + $this->title = $title; + } + + public function getTitle(): string + { + return $this->title; + } + + /** + * @psalm-return Collection + */ + public function getBuyers(): Collection + { + return $this->buyers; + } + + public function addBuyer(GH9109User $buyer): void + { + $this->buyers[] = $buyer; + } +} + +/** + * @Entity + */ +class GH9109User +{ + /** + * @var int + * @Column(name="`id`", type="integer") + * @Id + * @GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @var string + * @Column(name="`first_name`", type="string") + */ + private $firstName; + + /** + * @var string + * @Column(name="last_name", type="string") + */ + private $lastName; + + public function getId(): int + { + return $this->id; + } + + public function getFirstName(): string + { + return $this->firstName; + } + + public function setFirstName(string $firstName): void + { + $this->firstName = $firstName; + } + + public function getLastName(): string + { + return $this->lastName; + } + + public function setLastName(string $lastName): void + { + $this->lastName = $lastName; + } +} From a6b7569d7af8eb94d91ac4253aaf854bc38003c2 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Sat, 6 Nov 2021 20:49:54 +0100 Subject: [PATCH 16/19] Fixing more links (#9154) * Fixing more links The first two I missed in https://github.com/doctrine/orm/pull/9151 The third is probably older. Shouldn't the chapter name be displayed as link text by default?? Are you sure that everything is set up correctly with the parser? * Update architecture.rst * Update getting-started.rst * Update events.rst --- docs/en/reference/architecture.rst | 2 ++ docs/en/reference/events.rst | 9 +++++---- docs/en/tutorials/getting-started.rst | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/en/reference/architecture.rst b/docs/en/reference/architecture.rst index 6558c3ee8e7..6c005e43298 100644 --- a/docs/en/reference/architecture.rst +++ b/docs/en/reference/architecture.rst @@ -184,6 +184,8 @@ in well defined units of work. Work with your objects and modify them as usual and when you're done call ``EntityManager#flush()`` to make your changes persistent. +.. _unit-of-work: + The Unit of Work ~~~~~~~~~~~~~~~~ diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 7faf18d11e0..9f4e2efba31 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -127,9 +127,10 @@ Registering Events There are two ways to register an event: * *All events* can be registered by calling ``$eventManager->addEventListener()`` -or ``eventManager->addEventSubscriber()``, see :ref:`listening-and-subscribing-to-lifecycle-events` +or ``eventManager->addEventSubscriber()``, see +:ref:`Listening and subscribing to Lifecycle Events` * *Lifecycle Callbacks* can also be registered in the entity mapping (annotation, attribute, etc.), -see :ref:`lifecycle-callbacks` +see :ref:`Lifecycle Callbacks` Events Overview --------------- @@ -288,7 +289,7 @@ specific to a particular entity class's lifecycle. .. note:: - Note that Licecycle Callbacks are not supported for Embeddables. + Lifecycle Callbacks are not supported for :doc:`Embeddables `. .. configuration-block:: @@ -394,7 +395,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:`reference-events-implementing-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 diff --git a/docs/en/tutorials/getting-started.rst b/docs/en/tutorials/getting-started.rst index 3aa79d57ef6..846264331e7 100644 --- a/docs/en/tutorials/getting-started.rst +++ b/docs/en/tutorials/getting-started.rst @@ -651,7 +651,7 @@ Let's continue by creating a script to display the name of a product based on it echo sprintf("-%s\n", $product->getName()); Next we'll update a product's name, given its id. This simple example will -help demonstrate Doctrine's implementation of the UnitOfWork pattern. Doctrine +help demonstrate Doctrine's implementation of the :ref:`UnitOfWork pattern `. Doctrine keeps track of all the entities that were retrieved from the Entity Manager, and can detect when any of those entities' properties have been modified. As a result, rather than needing to call ``persist($entity)`` for each individual @@ -1343,7 +1343,7 @@ call this script as follows: php create_bug.php 1 1 1 See how simple it is to relate a Bug, Reporter, Engineer and Products? -Also recall that thanks to the UnitOfWork pattern, Doctrine will detect +Also recall that thanks to the :ref:`UnitOfWork pattern `, Doctrine will detect these relations and update all of the modified entities in the database automatically when ``flush()`` is called. From 1b15af44b6734d6001ce163e7fecf07e266b980a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Mon, 8 Nov 2021 21:21:41 +0100 Subject: [PATCH 17/19] Remove similar assertions for other platforms Testing with several platforms should not increase code coverage here, since the DBAL is responsible for providing the concat expression for each platform. Moreover, whenever that concat expression changes for one of the tested platforms, this test will break. In doctrine/dbal 3.2, that is the case for SQLServer2012Platform, which means this test no longer passes. --- .../ORM/Query/SelectSqlGenerationTest.php | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index baabb66c7a8..5efd836d74a 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -2116,26 +2116,6 @@ public function testSupportsMoreThanTwoParametersInConcatFunction(): void 'SELECT CONCAT(c0_.id, c0_.name, c0_.status) AS sclr_0 FROM cms_users c0_ WHERE c0_.id = ?' ); - $connMock->setDatabasePlatform(new PostgreSQL94Platform()); - $this->assertSqlGeneration( - "SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE CONCAT(u.name, u.status, 's') = ?1", - "SELECT c0_.id AS id_0 FROM cms_users c0_ WHERE c0_.name || c0_.status || 's' = ?" - ); - $this->assertSqlGeneration( - 'SELECT CONCAT(u.id, u.name, u.status) FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1', - 'SELECT c0_.id || c0_.name || c0_.status AS sclr_0 FROM cms_users c0_ WHERE c0_.id = ?' - ); - - $connMock->setDatabasePlatform(new SQLServer2012Platform()); - $this->assertSqlGeneration( - "SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE CONCAT(u.name, u.status, 's') = ?1", - "SELECT c0_.id AS id_0 FROM cms_users c0_ WHERE (c0_.name + c0_.status + 's') = ?" - ); - $this->assertSqlGeneration( - 'SELECT CONCAT(u.id, u.name, u.status) FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1', - 'SELECT (c0_.id + c0_.name + c0_.status) AS sclr_0 FROM cms_users c0_ WHERE c0_.id = ?' - ); - $connMock->setDatabasePlatform($orgPlatform); } From 176fbedc69a4c76b776baa73df16a18d2211d481 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Wed, 10 Nov 2021 22:43:09 +0100 Subject: [PATCH 18/19] Fine-tuning codeblock (#9176) * Deleting "Not needed for XML and YAML mapping" - this was stupid of me, since *all* annotations are obviously not needed in XML&YAML ;-) * Shortening the @Column annotation, for consistency with the following event handlers * Removing some blank lines from XML, for consistency with YAML * Adding PHP Attributes --- docs/en/reference/events.rst | 46 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 9f4e2efba31..f3e5fe75c38 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -293,21 +293,52 @@ specific to a particular entity class's lifecycle. .. configuration-block:: - .. code-block:: php + .. code-block:: attribute + + createdAt = date('Y-m-d H:i:s'); + } + + #[PrePersist] + public function doOtherStuffOnPrePersist() + { + $this->value = 'changed from prePersist callback!'; + } + + #[PostLoad] + public function doStuffOnPostLoad() + { + $this->value = 'changed from postLoad callback!'; + } + } + .. code-block:: annotation - - + - - .. code-block:: yaml From 9a74ae6280714aa4fe30960bce9c715535939fd3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 11 Nov 2021 23:01:34 +0100 Subject: [PATCH 19/19] Fix discriminatorColumn phpdoc (#9168) --- .../Hydration/SimpleObjectHydrator.php | 3 ++- .../ORM/Mapping/ClassMetadataInfo.php | 15 +++++++++++++- .../AbstractEntityInheritancePersister.php | 2 +- .../Entity/JoinedSubclassPersister.php | 14 ++++++------- .../Entity/SingleTablePersister.php | 20 ++++++++++--------- .../ORM/Query/ResultSetMappingBuilder.php | 2 +- lib/Doctrine/ORM/Query/SqlWalker.php | 6 +++--- lib/Doctrine/ORM/Tools/EntityGenerator.php | 6 +++++- 8 files changed, 44 insertions(+), 24 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 13319dc5b9c..454330290eb 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -79,7 +79,8 @@ protected function hydrateRowData(array $row, array &$result) // We need to find the correct entity class name if we have inheritance in resultset if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { - $discrColumnName = $this->getSQLResultCasing($this->_platform, $this->class->discriminatorColumn['name']); + $discrColumn = $this->class->getDiscriminatorColumn(); + $discrColumnName = $this->getSQLResultCasing($this->_platform, $discrColumn['name']); // Find mapped discriminator column from the result set. $metaMappingDiscrColumnName = array_search($discrColumnName, $this->resultSetMapping()->metaMappings, true); diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 841d4821783..c898198f675 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -20,6 +20,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\ReflectionService; use InvalidArgumentException; +use LogicException; use ReflectionClass; use ReflectionNamedType; use ReflectionProperty; @@ -496,7 +497,7 @@ class ClassMetadataInfo implements ClassMetadata * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE * inheritance mappings. * - * @psalm-var array + * @psalm-var array|null */ public $discriminatorColumn; @@ -3098,6 +3099,18 @@ public function setDiscriminatorColumn($columnDef) } } + /** + * @return array + */ + final public function getDiscriminatorColumn(): array + { + if ($this->discriminatorColumn === null) { + throw new LogicException('The discriminator column was not set.'); + } + + return $this->discriminatorColumn; + } + /** * Sets the discriminator values used by this class. * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. diff --git a/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php index 61751a8706d..c5f5e04f10e 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php @@ -24,7 +24,7 @@ protected function prepareInsertData($entity) $data = parent::prepareInsertData($entity); // Populate the discriminator column - $discColumn = $this->class->discriminatorColumn; + $discColumn = $this->class->getDiscriminatorColumn(); $this->columnTypes[$discColumn['name']] = $discColumn['type']; $data[$this->getDiscriminatorColumnTableName()][$discColumn['name']] = $this->class->discriminatorValue; diff --git a/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php index 151aad7bc92..5c59168eb75 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php @@ -13,7 +13,6 @@ use function array_combine; use function implode; -use function is_array; /** * The joined subclass persister maps a single entity instance to several tables in the @@ -412,14 +411,15 @@ protected function getSelectColumnsSQL() } $columnList = []; - $discrColumn = $this->class->discriminatorColumn['name']; - $discrColumnType = $this->class->discriminatorColumn['type']; + $discrColumn = $this->class->getDiscriminatorColumn(); + $discrColumnName = $discrColumn['name']; + $discrColumnType = $discrColumn['type']; $baseTableAlias = $this->getSQLTableAlias($this->class->name); - $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumn); + $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); - $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumn, false, $discrColumnType); + $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); // Add regular columns foreach ($this->class->fieldMappings as $fieldName => $mapping) { @@ -457,7 +457,7 @@ protected function getSelectColumnsSQL() ? $baseTableAlias : $this->getSQLTableAlias($this->class->rootEntityName); - $columnList[] = $tableAlias . '.' . $discrColumn; + $columnList[] = $tableAlias . '.' . $discrColumnName; // sub tables foreach ($this->class->subClasses as $subClassName) { @@ -540,7 +540,7 @@ protected function getInsertColumnList() // Add discriminator column if it is the topmost class. if ($this->class->name === $this->class->rootEntityName) { - $columns[] = $this->class->discriminatorColumn['name']; + $columns[] = $this->class->getDiscriminatorColumn()['name']; } return $columns; diff --git a/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php index 2eca457c4f4..b9f95c58123 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php @@ -44,16 +44,17 @@ protected function getSelectColumnsSQL() $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); $tableAlias = $this->getSQLTableAlias($rootClass->name); - // Append discriminator column - $discrColumn = $this->class->discriminatorColumn['name']; - $discrColumnType = $this->class->discriminatorColumn['type']; + // Append discriminator column + $discrColumn = $this->class->getDiscriminatorColumn(); + $discrColumnName = $discrColumn['name']; + $discrColumnType = $discrColumn['type']; - $columnList[] = $tableAlias . '.' . $discrColumn; + $columnList[] = $tableAlias . '.' . $discrColumnName; - $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumn); + $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); - $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumn, false, $discrColumnType); + $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); // Append subclass columns foreach ($this->class->subClasses as $subClassName) { @@ -100,7 +101,7 @@ protected function getInsertColumnList() $columns = parent::getInsertColumnList(); // Add discriminator column to the INSERT SQL - $columns[] = $this->class->discriminatorColumn['name']; + $columns[] = $this->class->getDiscriminatorColumn()['name']; return $columns; } @@ -158,11 +159,12 @@ protected function getSelectConditionDiscriminatorValueSQL() $values[] = $this->conn->quote($discrValues[$subclassName]); } + $discColumnName = $this->class->getDiscriminatorColumn()['name']; + $values = implode(', ', $values); - $discColumn = $this->class->discriminatorColumn['name']; $tableAlias = $this->getSQLTableAlias($this->class->name); - return $tableAlias . '.' . $discColumn . ' IN (' . $values . ')'; + return $tableAlias . '.' . $discColumnName . ' IN (' . $values . ')'; } /** diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 4815e706c4a..65618a9ef7d 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -369,7 +369,7 @@ public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classM { if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) { $discriminatorColumn = $entityMapping['discriminatorColumn']; - $discriminatorType = $classMetadata->discriminatorColumn['type']; + $discriminatorType = $classMetadata->getDiscriminatorColumn()['type']; $this->setDiscriminatorColumn($alias, $discriminatorColumn); $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn, false, $discriminatorType); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 26099f79e8c..2a1139181cb 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -465,7 +465,7 @@ private function generateDiscriminatorColumnConditionSQL(array $dqlAliases): str ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : ''; - $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; + $sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()['name'] . ' IN (' . implode(', ', $values) . ')'; } $sql = implode(' AND ', $sqlParts); @@ -739,7 +739,7 @@ public function walkSelectClause($selectClause) // Add discriminator columns to SQL $rootClass = $this->em->getClassMetadata($class->rootEntityName); $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); - $discrColumn = $rootClass->discriminatorColumn; + $discrColumn = $rootClass->getDiscriminatorColumn(); $columnAlias = $this->getSQLColumnAlias($discrColumn['name']); $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias; @@ -2091,7 +2091,7 @@ public function walkInstanceOfExpression($instanceOfExpr) $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.'; } - $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN '); + $sql .= $class->getDiscriminatorColumn()['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN '); $sql .= $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr); return $sql; diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 06d6b0243c7..92c17708ade 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -1140,7 +1140,11 @@ protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $meta return ''; } - $discrColumn = $metadata->discriminatorColumn; + $discrColumn = $metadata->discriminatorColumn; + if ($discrColumn === null) { + return ''; + } + $columnDefinition = 'name="' . $discrColumn['name'] . '", type="' . $discrColumn['type'] . '", length=' . $discrColumn['length'];