Compare commits

...

37 Commits

Author SHA1 Message Date
Grégoire Paris de7eee5ed7 Merge pull request #10385 from nicolas-grekas/uninitialized-prop-reproducer
Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors
2023-01-16 19:36:59 +01:00
Grégoire Paris 4051937fda Merge pull request #10412 from ThomasLandauer/patch-8
Adding link to Attributes reference
2023-01-16 19:35:20 +01:00
Thomas Landauer b2e42dc92d Adding link to Attributes reference
In fact, I moved it upwards, and updated it to new target :-)
2023-01-16 17:19:29 +01:00
Grégoire Paris f219b87870 Merge pull request #10393 from mpdude/traits-warning
Place a warning about the uses of traits in the documentation
2023-01-16 08:56:11 +01:00
Grégoire Paris 87fefbac34 Merge pull request #10396 from mpdude/document-inherited-declared-meaning
Document the meanings of 'inherited' and 'declared' in field mapping information
2023-01-15 15:40:38 +01:00
Matthias Pigulla 853e80ca98 Reword text 2023-01-14 22:56:23 +00:00
Grégoire Paris d68baef880 Merge pull request #10404 from greg0ire/stable-phpbench
Stop allowing phpbench's master branch
2023-01-14 20:44:08 +01:00
Grégoire Paris fdccfbd120 Stop allowing phpbench's master branch
A stable version has been published, it allows doctrine/annotations 2
2023-01-14 16:41:18 +01:00
Matthias Pigulla 180afa8c8f Write down what "transient" means (#10394)
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-14 10:33:58 +01:00
Grégoire Paris dbbf5c7279 Merge pull request #10390 from greg0ire/wrong-phpdoc-exception
Use more accurate phpdoc for OptimisticLockException
2023-01-14 10:29:13 +01:00
Grégoire Paris 5d9b8f0ea8 Merge pull request #10399 from mpdude/fix-toothbrush-example
Fix DDL example for Mapped Superclasses
2023-01-14 10:28:24 +01:00
Matthias Pigulla 174947155d Fix DDL example for Mapped Superclasses
This was not updated to reflect the changes made when the example was improved in b3ee7141eb.
2023-01-14 08:14:04 +00:00
Grégoire Paris 2138cc9383 Sync variable name with class name (#10395) 2023-01-14 00:16:41 +01:00
Matthias Pigulla 39a434914d Be more vague about the Entity Generator 2023-01-13 23:04:40 +00:00
Matthias Pigulla 227f60c832 Update lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-13 23:51:39 +01:00
Matthias Pigulla f72f6b199b Document the meanings of 'inherited' and 'declared' in field mapping information 2023-01-13 22:35:36 +00:00
Matthias Pigulla 92e63ca4f9 Place a warning about the uses of traits in the documentation 2023-01-13 13:33:53 +00:00
Grégoire Paris 3c98973ab3 Use more accurate phpdoc for OptimisticLockException
While working on migrating this part of the codebase to PHP 8, I found
phpdoc that is wrong.
2023-01-12 18:01:17 +01:00
Kevin Bond 3b8692fa4a add reproducer 2023-01-09 16:09:03 +01:00
Nicolas Grekas c9efc1cdee Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors 2023-01-09 15:58:20 +01:00
Alexander M. Turek c5e4e41e05 PHPStan 1.9.8, Psalm 5.4.0 (#10382) 2023-01-09 11:16:44 +07:00
fauVictor 9431b2ea45 fix typo for missing a comma (#10377) 2023-01-06 16:31:05 +01:00
Thomas Landauer 036ea713a5 Docs: Removing type: 'integer' from mappings (#10368)
Yet another micro-PR ;-) - as requested at https://github.com/doctrine/orm/pull/10364#issuecomment-1370155521

I also changed `$currentPrice` from `float` to `int`, since IMO it's better to store prices as `int` (=cents).

Question: Is there a reason why most typehints are `int|null`, instead of `?int`? Should I change them?
2023-01-04 00:16:22 +01:00
Thomas Landauer 0852847659 Docs: Moving *attributes* mapping to first position (#10364) 2023-01-03 22:42:12 +01:00
Thomas Landauer 1e2625a82f Docs: Deleting duplicate mapping example (#10363)
I'm guessing this was forgotten to delete some time ago...
2023-01-03 20:43:26 +01:00
Alexander M. Turek 3010fd1680 PHPStan 1.9.5 (#10359) 2023-01-02 23:12:40 +01:00
Alexander M. Turek 85ac2769a9 Shorter deprecation message (#10357) 2022-12-31 17:43:12 +01:00
Axel Venet 28e98b3475 Add Fully-Qualified class name in UnrecognizedField exception to ease debugging (#10342) 2022-12-31 15:20:29 +01:00
Alexander M. Turek 99a37d864e Include parameter types in hydration cache key generation (#10355) 2022-12-31 00:44:23 +01:00
Alexander M. Turek 27df173971 Fix Psalm errors with Collection 2.1.2 (#10343) 2022-12-28 17:19:21 +01:00
Antonio Norman 0aa45dd607 Added warning about query cache in relation to parameters (#10276)
* Added warning about query cache in relation to parameters

* Updated warning about query cache in relation to parameters

* Update docs/en/reference/filters.rst

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>

* Update docs/en/reference/filters.rst

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2022-12-24 23:01:28 +01:00
Grégoire Paris f8bf84d1aa Merge pull request #10088 from HypeMC/enums-in-simpleobjecthydrator 2022-12-20 16:50:24 +01:00
michnovka c825e34f8d Improve and fix TypedFieldMapper docs (#10327) 2022-12-20 13:09:59 +01:00
Grégoire Paris ff6bad486b Require dev version of phpbench (#10328)
It is important to have the same version of all dependencies in dev and
in the CI, otherwise it makes it hard to have the right static analysis
baseline for every environment.
2022-12-20 09:18:23 +01:00
Grégoire Paris 30a2680bfd Merge pull request #10325 from greg0ire/update-branch-metadata
Update branch metadata
2022-12-19 23:48:49 +01:00
Grégoire Paris a460a4d054 Update branch metadata 2022-12-19 23:35:07 +01:00
HypeMC 9d5ab4ce76 Ensure consistent original data with enums
Previously different hydrators would store the original data for enum
fields in different ways, the SimpleObjectHydrator would keep them as
strings while other hydrators would convert then to native php enums.

This would make the data in the internal UnitOfWork::$originalEntityData
array inconsistent which could've caused problems in the long run.

Now, all hydrators convert enum fields to native php enums ensuring the
original data is always consistent regardless of the hydrator used.
2022-12-07 04:54:22 +01:00
32 changed files with 406 additions and 269 deletions
+12 -6
View File
@@ -12,21 +12,27 @@
"upcoming": true
},
{
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"name": "2.15",
"branchName": "2.15.x",
"slug": "2.15",
"upcoming": true
},
{
"name": "2.13",
"branchName": "2.13.x",
"slug": "2.13",
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"name": "2.13",
"branchName": "2.13.x",
"slug": "2.13",
"maintained": false
},
{
"name": "2.12",
"branchName": "2.12.x",
@@ -78,9 +78,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -156,9 +153,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -220,9 +214,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -300,9 +291,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
-6
View File
@@ -57,9 +57,6 @@ jobs:
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -97,9 +94,6 @@ jobs:
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^3.1 --no-update"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
+4
View File
@@ -1,5 +1,9 @@
# Upgrade to 2.14
## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method.
Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead.
## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`
The following public constants have been deprecated:
+2 -2
View File
@@ -42,14 +42,14 @@
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^11.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.9.4",
"phpstan/phpstan": "~1.4.10 || 1.9.8",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.1",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.30.0 || 5.3.0"
"vimeo/psalm": "4.30.0 || 5.4.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"
+19
View File
@@ -94,6 +94,25 @@ classes, and non-entity classes may extend entity classes.
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
Mapped Superclasses
~~~~~~~~~~~~~~~~~~~
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity.
Mapped superclasses are explained in greater detail in the chapter
on :doc:`inheritance mapping <reference/inheritance-mapping>`.
Transient Classes
~~~~~~~~~~~~~~~~~
The term "transient class" appears in some places in the mapping
drivers as well as the code dealing with metadata handling.
A transient class is a class that is neither an entity nor a mapped
superclass. From the ORM's point of view, these classes can be
completely ignored, and no class metadata is loaded for them at all.
Entity states
~~~~~~~~~~~~~
+5 -4
View File
@@ -28,10 +28,11 @@ table alias of the SQL table of the entity.
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
Parameters for the query should be set on the filter object by
``SQLFilter#setParameter()``. Only parameters set via this function can be used
in filters. The ``SQLFilter#getParameter()`` function takes care of the
proper quoting of parameters.
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
2. The filter must be deterministic. Don't change the values base on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
.. code-block:: php
+1 -4
View File
@@ -45,8 +45,7 @@ in scenarios where data is loaded for read-only purposes.
Read-Only Entities
------------------
You can mark entities as read only (See metadata mapping
references for details).
You can mark entities as read only. For details, see :ref:`attrref_entity`
This means that the entity marked as read only is never considered for updates.
During flush on the EntityManager these entities are skipped even if properties
@@ -55,8 +54,6 @@ changed.
Read-Only allows to persist new entities of a kind and remove existing ones,
they are just not considered for updates.
See :ref:`annref_entity`
You can also explicitly mark individual entities read only directly on the
UnitOfWork via a call to ``markReadOnly()``:
+7 -1
View File
@@ -25,6 +25,12 @@ appear in the middle of an otherwise mapped inheritance hierarchy
For further support of inheritance, the single or
joined table inheritance features have to be used.
.. note::
You may be tempted to use traits to mix mapped fields or relationships
into your entity classes to circumvent some of the limitations of
mapped superclasses. Before doing that, please read the section on traits
in the :doc:`Limitations and Known Issues <reference/limitations-and-known-issues>` chapter.
Example:
@@ -77,7 +83,7 @@ like this (this is for SQLite):
.. code-block:: sql
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
CREATE TABLE Employee (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, toothbrush_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
As you can see from this DDL snippet, there is only a single table
for the entity subclass. All the mappings from the mapped
@@ -130,10 +130,51 @@ included in the core of Doctrine ORM. However there are already two
extensions out there that offer support for Nested Set with
ORM:
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
Using Traits in Entity Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The use of traits in entity or mapped superclasses, at least when they
include mapping configuration or mapped fields, is currently not
endorsed by the Doctrine project. The reasons for this are as follows.
Traits were added in PHP 5.4 more than 10 years ago, but at the same time
more than two years after the initial Doctrine 2 release and the time where
core components were designed.
In fact, this documentation mentions traits only in the context of
:doc:`overriding field association mappings in subclasses <tutorials/override-field-association-mappings-in-subclasses>`.
Coverage of traits in test cases is practically nonexistent.
Thus, you should at least be aware that when using traits in your entity and
mapped superclasses, you will be in uncharted terrain.
.. warning::
There be dragons.
From a more technical point of view, traits basically work at the language level
as if the code contained in them had been copied into the class where the trait
is used, and even private fields are accessible by the using class. In addition to
that, some precedence and conflict resolution rules apply.
When it comes to loading mapping configuration, the annotation and attribute drivers
rely on PHP reflection to inspect class properties including their docblocks.
As long as the results are consistent with what a solution _without_ traits would
have produced, this is probably fine.
However, to mention known limitations, it is currently not possible to use "class"
level `annotations <https://github.com/doctrine/orm/pull/1517>` or
`attributes <https://github.com/doctrine/orm/issues/8868>` on traits, and attempts to
improve parser support for traits as `here <https://github.com/doctrine/annotations/pull/102>`
or `there <https://github.com/doctrine/annotations/pull/63>` have been abandoned
due to complexity.
XML mapping configuration probably needs to completely re-configure or otherwise
copy-and-paste configuration for fields used from traits.
Known Issues
------------
+7 -4
View File
@@ -3,13 +3,12 @@ Implementing a TypedFieldMapper
.. versionadded:: 2.14
You can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
You can specify custom typed field mapping between PHP type and DBAL type using ``Doctrine\ORM\Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
.. code-block:: php
<?php
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
@@ -24,6 +23,7 @@ PHP type => DBAL type mappings into its constructor to override the default beha
<?php
use App\CustomIds\CustomIdObject;
use App\DBAL\Type\CustomIdObjectType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
CustomIdObject::class => CustomIdObjectType::class,
@@ -84,6 +84,7 @@ It is perfectly valid to override even the "automatic" mapping rules mentioned a
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
'int' => CustomIntType::class,
@@ -126,10 +127,12 @@ the instances were supplied to the ``ChainTypedFieldMapper`` constructor.
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(
new ChainTypedFieldMapper(
DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new CustomTypedFieldMapper()
)
);
@@ -173,4 +176,4 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMap
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
that behaves like ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
that behaves like the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
+62 -83
View File
@@ -32,9 +32,9 @@ and year of production as primary keys:
class Car
{
public function __construct(
#[Id, Column(type: 'string')]
#[Id, Column]
private string $name,
#[Id, Column(type: 'integer')]
#[Id, Column]
private int $year,
) {
}
@@ -82,27 +82,6 @@ and year of production as primary keys:
}
}
.. code-block:: annotation
<?php
/**
* @Entity
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private int|null $id = null;
}
/**
* @Entity
*/
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private User|null $user = null;
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
@@ -152,7 +131,7 @@ And for querying you can use arrays to both DQL and EntityRepositories:
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
$audi = $em->createQuery($dql)
->setParameter(1, array("name" => "Audi A8", "year" => 2010))
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
@@ -190,7 +169,52 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Article
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
#[Column]
private string $title;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column]
private string $attribute;
#[Column]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: annotation
<?php
namespace Application\Model;
@@ -241,51 +265,6 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
}
}
.. code-block:: attribute
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Article
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $title;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column(type: 'string')]
private string $attribute;
#[Column(type: 'string')]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
@@ -338,7 +317,7 @@ One good example for this is a user-address relationship:
#[Entity]
class User
{
#[Id, Column(type: 'integer'), GeneratedValue]
#[Id, Column, GeneratedValue]
private int|null $id = null;
}
@@ -386,18 +365,18 @@ of products purchased and maybe even the current price.
#[Entity]
class Order
{
#[Id, Column(type: 'integer'), GeneratedValue]
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @var ArrayCollection<int, OrderItem> */
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
private Collection $items;
#[Column(type: 'boolean')]
#[Column]
private bool $paid = false;
#[Column(type: 'boolean')]
#[Column]
private bool $shipped = false;
#[Column(type: 'datetime')]
#[Column]
private DateTime $created;
public function __construct(
@@ -412,16 +391,16 @@ of products purchased and maybe even the current price.
#[Entity]
class Product
{
#[Id, Column(type: 'integer'), GeneratedValue]
#[Id, Column, GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
#[Column]
private string $name;
#[Column(type: 'decimal')]
private float $currentPrice;
#[Column]
private int $currentPrice;
public function getCurrentPrice(): float
public function getCurrentPrice(): int
{
return $this->currentPrice;
}
@@ -436,11 +415,11 @@ of products purchased and maybe even the current price.
#[Id, ManyToOne(targetEntity: Product::class)]
private Product|null $product = null;
#[Column(type: 'integer')]
#[Column]
private int $amount = 1;
#[Column(type: 'decimal')]
private float $offeredPrice;
#[Column]
private int $offeredPrice;
public function __construct(Order $order, Product $product, int $amount = 1)
{
+3 -1
View File
@@ -1321,9 +1321,11 @@ abstract class AbstractQuery
protected function getHydrationCacheId()
{
$parameters = [];
$types = [];
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
$types[$parameter->getName()] = $parameter->getType();
}
$sql = $this->getSQL();
@@ -1335,7 +1337,7 @@ abstract class AbstractQuery
ksort($hints);
assert($queryCacheProfile !== null);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints);
}
/**
+1 -1
View File
@@ -73,7 +73,7 @@ final class Events
* has been applied to it.
*
* Note that the postLoad event occurs for an entity before any associations have been
* initialized. Therefore it is not safe to access associations in a postLoad callback
* initialized. Therefore, it is not safe to access associations in a postLoad callback
* or event handler.
*
* This is an entity lifecycle event.
@@ -698,7 +698,7 @@ abstract class AbstractHydrator
*
* @return BackedEnum|array<BackedEnum>
*/
private function buildEnum($value, string $enumType)
final protected function buildEnum($value, string $enumType)
{
if (is_array($value)) {
return array_map(static function ($value) use ($enumType): BackedEnum {
@@ -6,9 +6,11 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\ORM\Internal\SQLResultCasing;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Query;
use Exception;
use RuntimeException;
use ValueError;
use function array_keys;
use function array_search;
@@ -140,6 +142,21 @@ class SimpleObjectHydrator extends AbstractHydrator
$value = $type->convertToPHPValue($value, $this->_platform);
}
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
$originalValue = $value;
try {
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
} catch (ValueError $e) {
throw MappingException::invalidEnumValue(
$entityName,
$cacheKeyInfo['fieldName'],
(string) $originalValue,
$cacheKeyInfo['enumType'],
$e
);
}
}
$fieldName = $cacheKeyInfo['fieldName'];
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
@@ -148,6 +165,10 @@ class SimpleObjectHydrator extends AbstractHydrator
}
}
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
}
$uow = $this->_em->getUnitOfWork();
$entity = $uow->createEntity($entityName, $data, $this->_hints);
@@ -392,7 +392,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$mapping['sourceEntity'] = $subClass->name;
}
//$subclassMapping = $mapping;
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
@@ -406,6 +406,22 @@ class ClassMetadataInfo implements ClassMetadata
/**
* READ-ONLY: The names of all embedded classes based on properties.
*
* The value (definition) array may contain, among others, the following values:
*
* - <b>'inherited'</b> (string, optional)
* This is set when this embedded-class field is inherited by this class from another (inheritance) parent
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
* mapping information for this field. (If there are transient classes in the
* class hierarchy, these are ignored, so the class property may in fact come
* from a class further up in the PHP class hierarchy.)
* Fields initially declared in mapped superclasses are
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
*
* - <b>'declared'</b> (string, optional)
* This is set when the embedded-class field does not appear for the first time in this class, but is originally
* declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
* of the topmost non-transient class that contains mapping information for this field.
*
* @psalm-var array<string, mixed[]>
*/
public $embeddedClasses = [];
@@ -523,6 +539,20 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>'unique'</b> (string, optional, schema-only)
* Whether a unique constraint should be generated for the column.
*
* - <b>'inherited'</b> (string, optional)
* This is set when the field is inherited by this class from another (inheritance) parent
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
* mapping information for this field. (If there are transient classes in the
* class hierarchy, these are ignored, so the class property may in fact come
* from a class further up in the PHP class hierarchy.)
* Fields initially declared in mapped superclasses are
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
*
* - <b>'declared'</b> (string, optional)
* This is set when the field does not appear for the first time in this class, but is originally
* declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
* of the topmost non-transient class that contains mapping information for this field.
*
* @var mixed[]
* @psalm-var array<string, FieldMapping>
*/
@@ -625,6 +655,11 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>fieldName</b> (string)
* The name of the field in the entity the association is mapped to.
*
* - <b>sourceEntity</b> (string)
* The class name of the source entity. In the case of to-many associations initially
* present in mapped superclasses, the nearest <em>entity</em> subclasses will be
* considered the respective source entities.
*
* - <b>targetEntity</b> (string)
* The class name of the target entity. If it is fully-qualified it is used as is.
* If it is a simple, unqualified class name the namespace is assumed to be the same
@@ -661,6 +696,20 @@ class ClassMetadataInfo implements ClassMetadata
* This field HAS to be either the primary key or a unique column. Otherwise the collection
* does not contain all the entities that are actually related.
*
* - <b>'inherited'</b> (string, optional)
* This is set when the association is inherited by this class from another (inheritance) parent
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
* this association. (If there are transient classes in the
* class hierarchy, these are ignored, so the class property may in fact come
* from a class further up in the PHP class hierarchy.)
* To-many associations initially declared in mapped superclasses are
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
*
* - <b>'declared'</b> (string, optional)
* This is set when the association does not appear in the current class for the first time, but
* is initially declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
* of the topmost non-transient class that contains association information for this relationship.
*
* A join table definition has the following structure:
* <pre>
* array(
@@ -696,8 +696,8 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
/**
* Parses the given method.
*
* @return callable[]
* @psalm-return list<callable-array>
* @return list<array{string, string}>
* @psalm-return list<array{string, (Events::*)}>
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
@@ -615,7 +615,8 @@ class AttributeDriver extends CompatibilityAnnotationDriver
/**
* Parses the given method.
*
* @return callable[]
* @return list<array{string, string}>
* @psalm-return list<array{string, (Events::*)}>
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
+8 -8
View File
@@ -13,12 +13,12 @@ use Doctrine\ORM\Exception\ORMException;
*/
class OptimisticLockException extends ORMException
{
/** @var object|null */
/** @var object|string|null */
private $entity;
/**
* @param string $msg
* @param object|null $entity
* @param string $msg
* @param object|string|null $entity
*/
public function __construct($msg, $entity)
{
@@ -30,7 +30,7 @@ class OptimisticLockException extends ORMException
/**
* Gets the entity that caused the exception.
*
* @return object|null
* @return object|string|null
*/
public function getEntity()
{
@@ -38,7 +38,7 @@ class OptimisticLockException extends ORMException
}
/**
* @param object $entity
* @param object|class-string $entity
*
* @return OptimisticLockException
*/
@@ -48,9 +48,9 @@ class OptimisticLockException extends ORMException
}
/**
* @param object $entity
* @param int|DateTimeInterface $expectedLockVersion
* @param int|DateTimeInterface $actualLockVersion
* @param object $entity
* @param int|string|DateTimeInterface $expectedLockVersion
* @param int|string|DateTimeInterface $actualLockVersion
*
* @return OptimisticLockException
*/
@@ -487,7 +487,7 @@ class BasicEntityPersister implements EntityPersister
$targetType = PersisterHelper::getTypeOfField($targetMapping->identifier[0], $targetMapping, $this->em);
if ($targetType === []) {
throw UnrecognizedField::byName($targetMapping->identifier[0]);
throw UnrecognizedField::byFullyQualifiedName($this->class->name, $targetMapping->identifier[0]);
}
$types[] = reset($targetType);
@@ -1199,7 +1199,7 @@ class BasicEntityPersister implements EntityPersister
continue;
}
throw UnrecognizedField::byName($fieldName);
throw UnrecognizedField::byFullyQualifiedName($this->class->name, $fieldName);
}
return ' ORDER BY ' . implode(', ', $orderByList);
@@ -1506,6 +1506,9 @@ class BasicEntityPersister implements EntityPersister
$columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']);
$this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field);
if (! empty($fieldMapping['enumType'])) {
$this->currentPersisterContext->rsm->addEnumResult($columnAlias, $fieldMapping['enumType']);
}
if (isset($fieldMapping['requireSQLConversion'])) {
$type = Type::getType($fieldMapping['type']);
@@ -1754,7 +1757,7 @@ class BasicEntityPersister implements EntityPersister
return [$field];
}
throw UnrecognizedField::byName($field);
throw UnrecognizedField::byFullyQualifiedName($this->class->name, $field);
}
/**
@@ -10,8 +10,15 @@ use function sprintf;
final class UnrecognizedField extends PersisterException
{
/** @deprecated Use {@see byFullyQualifiedName()} instead. */
public static function byName(string $field): self
{
return new self(sprintf('Unrecognized field: %s', $field));
}
/** @param class-string $className */
public static function byFullyQualifiedName(string $className, string $field): self
{
return new self(sprintf('Unrecognized field: %s::$%s', $className, $field));
}
}
+15 -7
View File
@@ -18,6 +18,7 @@ use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Utility\IdentifierFlattener;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Proxy;
use ReflectionProperty;
use Symfony\Component\VarExporter\ProxyHelper;
use Symfony\Component\VarExporter\VarExporter;
@@ -313,17 +314,24 @@ EOPHP;
{
$skippedProperties = ['__isCloning' => true];
$identifiers = array_flip($class->getIdentifierFieldNames());
$filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE;
$reflector = $class->getReflectionClass();
foreach ($class->getReflectionClass()->getProperties() as $property) {
$name = $property->getName();
while ($reflector) {
foreach ($reflector->getProperties($filter) as $property) {
$name = $property->getName();
if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) {
continue;
if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) {
continue;
}
$prefix = $property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : ($property->isProtected() ? "\0*\0" : '');
$skippedProperties[$prefix . $name] = true;
}
$prefix = $property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : ($property->isProtected() ? "\0*\0" : '');
$skippedProperties[$prefix . $name] = true;
$filter = ReflectionProperty::IS_PRIVATE;
$reflector = $reflector->getParentClass();
}
uksort($skippedProperties, 'strnatcmp');
+5 -85
View File
@@ -270,11 +270,6 @@ parameters:
count: 1
path: lib/Doctrine/ORM/Persisters/Entity/CachedPersisterContext.php
-
message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Proxy::\\$__isCloning\\.$#"
count: 1
path: lib/Doctrine/ORM/Proxy/ProxyFactory.php
-
message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\:\\:\\$isEmbeddedClass\\.$#"
count: 1
@@ -285,6 +280,11 @@ parameters:
count: 1
path: lib/Doctrine/ORM/Proxy/ProxyFactory.php
-
message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Proxy\\:\\:\\$__isCloning\\.$#"
count: 1
path: lib/Doctrine/ORM/Proxy/ProxyFactory.php
-
message: "#^Call to an undefined method Doctrine\\\\Common\\\\Proxy\\\\Proxy\\:\\:__wakeup\\(\\)\\.$#"
count: 1
@@ -305,81 +305,11 @@ parameters:
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Parameter \\#2 \\$days of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateAddDaysExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Parameter \\#2 \\$hours of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateAddHourExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Parameter \\#2 \\$minutes of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateAddMinutesExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Parameter \\#2 \\$months of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateAddMonthExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Parameter \\#2 \\$seconds of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateAddSecondsExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Parameter \\#2 \\$weeks of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateAddWeeksExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Parameter \\#2 \\$years of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateAddYearsExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
-
message: "#^Access to an undefined property Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node\\:\\:\\$value\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#2 \\$days of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateSubDaysExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#2 \\$hours of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateSubHourExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#2 \\$minutes of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateSubMinutesExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#2 \\$months of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateSubMonthExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#2 \\$seconds of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateSubSecondsExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#2 \\$weeks of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateSubWeeksExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#2 \\$years of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getDateSubYearsExpression\\(\\) expects int\\|numeric\\-string, string given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#1 \\$simpleArithmeticExpr of method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkSimpleArithmeticExpression\\(\\) expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\SimpleArithmeticExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
count: 1
@@ -510,11 +440,6 @@ parameters:
count: 1
path: lib/Doctrine/ORM/Query/SqlWalker.php
-
message: "#^Parameter \\#1 \\$entity of static method Doctrine\\\\ORM\\\\OptimisticLockException\\:\\:lockFailed\\(\\) expects object, class\\-string\\<object\\> given\\.$#"
count: 1
path: lib/Doctrine/ORM/Query/SqlWalker.php
-
message: "#^Result of && is always false\\.$#"
count: 1
@@ -705,11 +630,6 @@ parameters:
count: 1
path: lib/Doctrine/ORM/UnitOfWork.php
-
message: "#^Parameter \\#3 \\$collection of class Doctrine\\\\ORM\\\\PersistentCollection constructor expects Doctrine\\\\Common\\\\Collections\\\\Collection\\<\\(int\\|string\\), mixed\\>&Doctrine\\\\Common\\\\Collections\\\\Selectable\\<\\(int\\|string\\), mixed\\>, Doctrine\\\\Common\\\\Collections\\\\Collection given\\.$#"
count: 1
path: lib/Doctrine/ORM/UnitOfWork.php
-
message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\:\\:\\$name\\.$#"
count: 1
-5
View File
@@ -41,8 +41,3 @@ parameters:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# Cache 1 compatibility
-
message: '~^Parameter #2 \$cache of class Doctrine\\Common\\Annotations\\CachedReader constructor expects Doctrine\\Common\\Cache\\Cache, Doctrine\\Common\\Cache\\ArrayCache given\.~'
path: lib/Doctrine/ORM/Configuration.php
+5 -28
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.3.0@b6faa3e96b8eb50ec71384c53799b8a107236bb6">
<files psalm-version="5.4.0@62db5d4f6a7ae0a20f7cc5a4952d730272fc0863">
<file src="lib/Doctrine/ORM/AbstractQuery.php">
<DeprecatedClass occurrences="1">
<code>IterableResult</code>
@@ -19,17 +19,11 @@
<InvalidNullableReturnType occurrences="1">
<code>\Doctrine\Common\Cache\Cache</code>
</InvalidNullableReturnType>
<LessSpecificReturnStatement occurrences="1">
<code>$queryCacheProfile-&gt;generateCacheKeys($sql, $parameters, $hints)</code>
</LessSpecificReturnStatement>
<MissingClosureParamType occurrences="3">
<code>$alias</code>
<code>$data</code>
<code>$data</code>
</MissingClosureParamType>
<MoreSpecificReturnType occurrences="1">
<code>array{string, string}</code>
</MoreSpecificReturnType>
<NullableReturnStatement occurrences="2">
<code>$this-&gt;_em-&gt;getConfiguration()-&gt;getResultCacheImpl()</code>
<code>$this-&gt;_queryCacheProfile-&gt;getResultCacheDriver()</code>
@@ -244,10 +238,6 @@
<ArgumentTypeCoercion occurrences="1">
<code>$className</code>
</ArgumentTypeCoercion>
<DeprecatedClass occurrences="2">
<code>new CachedReader($reader, new ArrayCache())</code>
<code>new SimpleAnnotationReader()</code>
</DeprecatedClass>
<DeprecatedMethod occurrences="2">
<code>getMetadataCacheImpl</code>
<code>getQueryCacheImpl</code>
@@ -809,12 +799,6 @@
</file>
<file src="lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php">
<InvalidArgument occurrences="2"/>
<InvalidArrayAccess occurrences="4">
<code>$value[0]</code>
<code>$value[0]</code>
<code>$value[1]</code>
<code>$value[1]</code>
</InvalidArrayAccess>
<LessSpecificReturnStatement occurrences="1">
<code>$mapping</code>
</LessSpecificReturnStatement>
@@ -1110,9 +1094,10 @@
</PossiblyInvalidArgument>
</file>
<file src="lib/Doctrine/ORM/PersistentCollection.php">
<ImplementedReturnTypeMismatch occurrences="2">
<ImplementedReturnTypeMismatch occurrences="3">
<code>Collection&lt;TKey, T&gt;</code>
<code>object|null</code>
<code>object|null</code>
</ImplementedReturnTypeMismatch>
<InvalidReturnStatement occurrences="2">
<code>$this-&gt;em-&gt;find($this-&gt;typeClass-&gt;name, $key)</code>
@@ -2231,12 +2216,11 @@
<ImplicitToStringCast occurrences="1">
<code>$expr</code>
</ImplicitToStringCast>
<InvalidArgument occurrences="5">
<InvalidArgument occurrences="4">
<code>$assoc</code>
<code>$condExpr</code>
<code>$condTerm</code>
<code>$factor</code>
<code>$selectedClass['class']-&gt;name</code>
</InvalidArgument>
<InvalidNullableReturnType occurrences="1">
<code>string</code>
@@ -2626,17 +2610,13 @@
<PossiblyNullArgument occurrences="1">
<code>$variableType</code>
</PossiblyNullArgument>
<PossiblyNullReference occurrences="1">
<code>getTraits</code>
</PossiblyNullReference>
<PropertyNotSetInConstructor occurrences="1">
<code>$classToExtend</code>
</PropertyNotSetInConstructor>
<RedundantCastGivenDocblockType occurrences="1">
<code>(bool) $embeddablesImmutable</code>
</RedundantCastGivenDocblockType>
<RedundantConditionGivenDocblockType occurrences="2">
<code>$reflClass !== false</code>
<RedundantConditionGivenDocblockType occurrences="1">
<code>isset($metadata-&gt;lifecycleCallbacks)</code>
</RedundantConditionGivenDocblockType>
</file>
@@ -2899,9 +2879,6 @@
<code>$collectionToUpdate</code>
<code>$em-&gt;getMetadataFactory()</code>
</InvalidArgument>
<InvalidArrayOffset occurrences="1">
<code>$commitOrder[$i]</code>
</InvalidArrayOffset>
<InvalidNullableReturnType occurrences="1">
<code>object</code>
</InvalidNullableReturnType>
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH10336;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="gh10336_entities")
*/
class GH10336Entity
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
public ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity="GH10336Relation")
* @ORM\JoinColumn(name="relation_id", referencedColumnName="id", nullable=true)
*/
public ?GH10336Relation $relation = null;
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH10336;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="gh10336_relations")
*/
class GH10336Relation
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
public ?int $id = null;
/**
* @ORM\Column(type="string")
*/
public string $value;
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\Models\GH10336\GH10336Entity;
use Doctrine\Tests\Models\GH10336\GH10336Relation;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @requires PHP 7.4
*/
final class GH10336Test extends OrmFunctionalTestCase
{
public function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
GH10336Entity::class,
GH10336Relation::class
);
}
public function testCanAccessRelationPropertyAfterClear(): void
{
$relation = new GH10336Relation();
$relation->value = 'foo';
$entity = new GH10336Entity();
$entity->relation = $relation;
$this->_em->persist($entity);
$this->_em->persist($relation);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->find(GH10336Entity::class, 1);
$this->_em->clear();
$this->assertSame('foo', $entity->relation->value);
}
}
@@ -18,12 +18,12 @@ final class GH6682Test extends OrmFunctionalTestCase
'initialValue' => '',
];
$classMetadataInfo = new ClassMetadata('test_entity');
$classMetadataInfo->setSequenceGeneratorDefinition($parsedDefinition);
$classMetadata = new ClassMetadata('test_entity');
$classMetadata->setSequenceGeneratorDefinition($parsedDefinition);
self::assertSame(
['sequenceName' => 'test_sequence', 'allocationSize' => '1', 'initialValue' => '1'],
$classMetadataInfo->sequenceGeneratorDefinition
$classMetadata->sequenceGeneratorDefinition
);
}
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Persisters\Exception;
use Doctrine\ORM\Persisters\Exception\UnrecognizedField;
use Doctrine\Tests\Models\Taxi\Car;
use PHPUnit\Framework\TestCase;
class UnrecognizedFieldTest extends TestCase
{
public function testByFullyQualifiedName(): void
{
static::expectException(UnrecognizedField::class);
static::expectExceptionMessage('Unrecognized field: Doctrine\Tests\Models\Taxi\Car::$color');
throw UnrecognizedField::byFullyQualifiedName(Car::class, 'color');
}
}