mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5fb1a4a8f | ||
|
|
8cb7a5e212 | ||
|
|
da9b9de590 | ||
|
|
9ec697db7d | ||
|
|
a50a611bee | ||
|
|
abb30093ed | ||
|
|
df559d8ac0 | ||
|
|
a6de4b9663 | ||
|
|
6db296cd5c | ||
|
|
7c2adde6f2 | ||
|
|
69543ba1bf | ||
|
|
a33885575f | ||
|
|
e28dee0742 | ||
|
|
a28e2d8277 | ||
|
|
4759a1bf75 | ||
|
|
e0ad7ac506 | ||
|
|
9485d4d835 | ||
|
|
9a6e1b3505 | ||
|
|
c286742814 | ||
|
|
052887765b | ||
|
|
5464e21022 | ||
|
|
c26c55926f | ||
|
|
5369e4f425 | ||
|
|
29bc6cc955 | ||
|
|
31ff969628 | ||
|
|
01f139d76c | ||
|
|
660197ea71 | ||
|
|
ab06e07b53 | ||
|
|
022b945ed5 | ||
|
|
7203d05539 | ||
|
|
0bd5fbf215 | ||
|
|
6a713dd39e | ||
|
|
5f169d9325 | ||
|
|
cf4680d0e6 | ||
|
|
cc5775c3f4 | ||
|
|
c5cf6a046b | ||
|
|
77df5db3b9 | ||
|
|
ee8269ea55 | ||
|
|
d038f23570 | ||
|
|
3843d7e0cc | ||
|
|
d50ba2e248 | ||
|
|
8debb92d78 | ||
|
|
8ff7938e31 | ||
|
|
d6c0031d44 | ||
|
|
c78f933e57 | ||
|
|
80eb85beaa | ||
|
|
ed56f42cd5 | ||
|
|
8b28543939 | ||
|
|
eec3c42494 | ||
|
|
bc394877bc | ||
|
|
1753d03500 | ||
|
|
7ce6d8d427 | ||
|
|
a48d95c71d | ||
|
|
aee1d33042 | ||
|
|
8da741ad75 | ||
|
|
ba7387fd8c | ||
|
|
a83e4f7978 | ||
|
|
4f335ab565 | ||
|
|
f88b0032ad | ||
|
|
69c7791ba2 | ||
|
|
1090dbe9be | ||
|
|
c46b604ed7 | ||
|
|
8d9ebeded8 | ||
|
|
55f9178e84 | ||
|
|
60955755e0 | ||
|
|
a8e979819a | ||
|
|
4e8e3ef30b | ||
|
|
de7eee5ed7 | ||
|
|
4051937fda | ||
|
|
b2e42dc92d | ||
|
|
f219b87870 | ||
|
|
87fefbac34 | ||
|
|
853e80ca98 | ||
|
|
d68baef880 | ||
|
|
fdccfbd120 | ||
|
|
180afa8c8f | ||
|
|
dbbf5c7279 | ||
|
|
5d9b8f0ea8 | ||
|
|
174947155d | ||
|
|
2138cc9383 | ||
|
|
39a434914d | ||
|
|
227f60c832 | ||
|
|
f72f6b199b | ||
|
|
92e63ca4f9 | ||
|
|
3c98973ab3 | ||
|
|
3b8692fa4a | ||
|
|
c9efc1cdee | ||
|
|
c5e4e41e05 | ||
|
|
9431b2ea45 | ||
|
|
036ea713a5 | ||
|
|
0852847659 | ||
|
|
1e2625a82f | ||
|
|
3010fd1680 | ||
|
|
85ac2769a9 | ||
|
|
28e98b3475 | ||
|
|
99a37d864e | ||
|
|
27df173971 | ||
|
|
0aa45dd607 | ||
|
|
f8bf84d1aa | ||
|
|
c825e34f8d | ||
|
|
ff6bad486b | ||
|
|
30a2680bfd | ||
|
|
a460a4d054 | ||
|
|
9d5ab4ce76 |
@@ -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",
|
||||
|
||||
18
.github/workflows/continuous-integration.yml
vendored
18
.github/workflows/continuous-integration.yml
vendored
@@ -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:
|
||||
@@ -119,13 +116,18 @@ jobs:
|
||||
- "3@dev"
|
||||
postgres-version:
|
||||
- "15"
|
||||
extension:
|
||||
- pdo_pgsql
|
||||
- pgsql
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
postgres-version: "14"
|
||||
extension: pdo_pgsql
|
||||
- php-version: "8.2"
|
||||
dbal-version: "default"
|
||||
postgres-version: "9.6"
|
||||
extension: pdo_pgsql
|
||||
|
||||
services:
|
||||
postgres:
|
||||
@@ -149,6 +151,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
extensions: "pgsql pdo_pgsql"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
@@ -156,9 +159,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 +220,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 +297,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
.github/workflows/static-analysis.yml
vendored
6
.github/workflows/static-analysis.yml
vendored
@@ -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:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,5 +15,6 @@ vendor/
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
composer.lock
|
||||
.phpunit.cache
|
||||
.phpunit.result.cache
|
||||
/*.phpunit.xml
|
||||
|
||||
@@ -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:
|
||||
|
||||
40
ci/github/phpunit/pgsql.xml
Normal file
40
ci/github/phpunit/pgsql.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTodoAnnotatedTests="true"
|
||||
failOnRisky="true"
|
||||
convertDeprecationsToExceptions="true"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<var name="db_driver" value="pgsql"/>
|
||||
<var name="db_host" value="localhost" />
|
||||
<var name="db_user" value="postgres" />
|
||||
<var name="db_password" value="postgres" />
|
||||
<var name="db_dbname" value="doctrine_tests" />
|
||||
|
||||
<!-- necessary change for some CLI/console output test assertions -->
|
||||
<env name="COLUMNS" value="120"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Doctrine DBAL Test Suite">
|
||||
<directory>../../../tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">../../../lib/Doctrine</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -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",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.10.6",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.7.1",
|
||||
"squizlabs/php_codesniffer": "3.7.2",
|
||||
"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.9.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 3.0"
|
||||
|
||||
@@ -21,7 +21,7 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
#[Column(type='datetime')]
|
||||
#[Column(type: 'datetime')]
|
||||
private DateTime $updated;
|
||||
|
||||
public function setUpdated(): void
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
@@ -1386,6 +1386,14 @@ Is essentially the same as following:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
@@ -1396,14 +1404,6 @@ Is essentially the same as following:
|
||||
*/
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
|
||||
@@ -534,8 +534,6 @@ the above example with ``allocationSize=100`` Doctrine ORM would only
|
||||
need to access the sequence once to generate the identifiers for
|
||||
100 new entities.
|
||||
|
||||
*The default allocationSize for a @SequenceGenerator is currently 10.*
|
||||
|
||||
.. caution::
|
||||
|
||||
The allocationSize is detected by SchemaTool and
|
||||
|
||||
@@ -19,11 +19,13 @@ especially what the strategies presented here provide help with.
|
||||
.. note::
|
||||
|
||||
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
|
||||
To avoid that you should disable it in the DBAL configuration:
|
||||
To avoid that you should remove the corresponding middleware.
|
||||
To remove all middlewares, you can use this line:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
$em->getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2
|
||||
|
||||
Bulk Inserts
|
||||
------------
|
||||
|
||||
@@ -38,7 +38,7 @@ upon insert:
|
||||
|
||||
private string $algorithm = "sha1";
|
||||
/** @var self::STATUS_* */
|
||||
private int $status = self:STATUS_DISABLED;
|
||||
private int $status = self::STATUS_DISABLED;
|
||||
}
|
||||
|
||||
.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()``:
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
Inheritance Mapping
|
||||
===================
|
||||
|
||||
This chapter explains the available options for mapping class
|
||||
hierarchies.
|
||||
|
||||
Mapped Superclasses
|
||||
-------------------
|
||||
|
||||
@@ -14,6 +17,10 @@ Mapped superclasses, just as regular, non-mapped classes, can
|
||||
appear in the middle of an otherwise mapped inheritance hierarchy
|
||||
(through Single Table Inheritance or Class Table Inheritance).
|
||||
|
||||
No database table will be created for a mapped superclass itself,
|
||||
only for entity classes inheriting from it. Also, a mapped superclass
|
||||
need not have an ``#[Id]`` property.
|
||||
|
||||
.. note::
|
||||
|
||||
A mapped superclass cannot be an entity, it is not query-able and
|
||||
@@ -25,6 +32,25 @@ 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.
|
||||
|
||||
.. warning::
|
||||
|
||||
At least when using attributes or annotations to specify your mapping,
|
||||
it _seems_ as if you could inherit from a base class that is neither
|
||||
an entity nor a mapped superclass, but has properties with mapping configuration
|
||||
on them that would also be used in the inheriting class.
|
||||
|
||||
This, however, is due to how the corresponding mapping
|
||||
drivers work and what the PHP reflection API reports for inherited fields.
|
||||
|
||||
Such a configuration is explicitly not supported. To give just one example,
|
||||
it will break for ``private`` properties.
|
||||
|
||||
.. 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,21 +103,76 @@ 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
|
||||
superclass were inherited to the subclass as if they had been
|
||||
defined on that class directly.
|
||||
|
||||
Entity Inheritance
|
||||
------------------
|
||||
|
||||
As soon as one entity class inherits from another entity class, either
|
||||
directly, with a mapped superclass or other unmapped (also called
|
||||
"transient") classes in between, these entities form an inheritance
|
||||
hierarchy. The topmost entity class in this hierarchy is called the
|
||||
root entity, and the hierarchy includes all entities that are
|
||||
descendants of this root entity.
|
||||
|
||||
On the root entity class, ``#[InheritanceType]``,
|
||||
``#[DiscriminatorColumn]`` and ``#[DiscriminatorMap]`` must be specified.
|
||||
|
||||
``#[InheritanceType]`` specifies one of the two available inheritance
|
||||
mapping strategies that are explained in the following sections.
|
||||
|
||||
``#[DiscriminatorColumn]`` designates the so-called discriminator column.
|
||||
This is an extra column in the table that keeps information about which
|
||||
type from the hierarchy applies for a particular database row.
|
||||
|
||||
``#[DiscriminatorMap]`` declares the possible values for the discriminator
|
||||
column and maps them to class names in the hierarchy. This discriminator map
|
||||
has to declare all non-abstract entity classes that exist in that particular
|
||||
inheritance hierarchy. That includes the root as well as any intermediate
|
||||
entity classes, given they are not abstract.
|
||||
|
||||
The names of the classes in the discriminator map do not need to be fully
|
||||
qualified if the classes are contained in the same namespace as the entity
|
||||
class on which the discriminator map is applied.
|
||||
|
||||
If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map contains the
|
||||
lowercase short name of each class as key.
|
||||
|
||||
.. note::
|
||||
|
||||
Automatically generating the discriminator map is very expensive
|
||||
computation-wise. The mapping driver has to provide all classes
|
||||
for which mapping configuration exists, and those have to be
|
||||
loaded and checked against the current inheritance hierarchy
|
||||
to see if they are part of it. The resulting map, however, can be kept
|
||||
in the metadata cache.
|
||||
|
||||
Performance impact on to-one associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There is a general performance consideration when using entity inheritance:
|
||||
If the target-entity of a many-to-one or one-to-one association is part of
|
||||
an inheritance hierarchy, it is preferable for performance reasons that it
|
||||
be a leaf entity (ie. have no subclasses).
|
||||
|
||||
Otherwise, the ORM is currently unable to tell beforehand which entity class
|
||||
will have to be used, and so no appropriate proxy instance can be created.
|
||||
That means the referred-to entities will *always* be loaded eagerly, which
|
||||
might even propagate to relationships of these entities (in the case of
|
||||
self-referencing associations).
|
||||
|
||||
Single Table Inheritance
|
||||
------------------------
|
||||
|
||||
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where all classes of a hierarchy
|
||||
are mapped to a single database table. In order to distinguish
|
||||
which row represents which type in the hierarchy a so-called
|
||||
discriminator column is used.
|
||||
is an inheritance mapping strategy where all classes of a hierarchy are
|
||||
mapped to a single database table.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -156,27 +237,9 @@ Example:
|
||||
MyProject\Model\Employee:
|
||||
type: entity
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
- The ``#[InheritanceType]`` and ``#[DiscriminatorColumn]`` must be
|
||||
specified on the topmost class that is part of the mapped entity
|
||||
hierarchy.
|
||||
- The ``#[DiscriminatorMap]`` specifies which values of the
|
||||
discriminator column identify a row as being of a certain type. In
|
||||
the case above a value of "person" identifies a row as being of
|
||||
type ``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- All entity classes that is part of the mapped entity hierarchy
|
||||
(including the topmost class) should be specified in the
|
||||
``#[DiscriminatorMap]``. In the case above Person class included.
|
||||
- The names of the classes in the discriminator map do not need to
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
In this example, the ``#[DiscriminatorMap]`` specifies that in the
|
||||
discriminator column, a value of "person" identifies a row as being of type
|
||||
``Person`` and employee" identifies a row as being of type ``Employee``.
|
||||
|
||||
Design-time considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -192,17 +255,10 @@ Performance impact
|
||||
|
||||
This strategy is very efficient for querying across all types in
|
||||
the hierarchy or for specific types. No table joins are required,
|
||||
only a WHERE clause listing the type identifiers. In particular,
|
||||
only a ``WHERE`` clause listing the type identifiers. In particular,
|
||||
relationships involving types that employ this mapping strategy are
|
||||
very performing.
|
||||
|
||||
There is a general performance consideration with Single Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is an STI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -210,7 +266,7 @@ For Single-Table-Inheritance to work in scenarios where you are
|
||||
using either a legacy database schema or a self-written database
|
||||
schema you have to make sure that all columns that are not in the
|
||||
root entity but in any of the different sub-entities has to allow
|
||||
null values. Columns that have NOT NULL constraints have to be on
|
||||
null values. Columns that have ``NOT NULL`` constraints have to be on
|
||||
the root entity of the single-table inheritance hierarchy.
|
||||
|
||||
Class Table Inheritance
|
||||
@@ -220,10 +276,11 @@ Class Table Inheritance
|
||||
is an inheritance mapping strategy where each class in a hierarchy
|
||||
is mapped to several tables: its own table and the tables of all
|
||||
parent classes. The table of a child class is linked to the table
|
||||
of a parent class through a foreign key constraint. Doctrine ORM
|
||||
implements this strategy through the use of a discriminator column
|
||||
in the topmost table of the hierarchy because this is the easiest
|
||||
way to achieve polymorphic queries with Class Table Inheritance.
|
||||
of a parent class through a foreign key constraint.
|
||||
|
||||
The discriminator column is placed in the topmost table of the hierarchy,
|
||||
because this is the easiest way to achieve polymorphic queries with Class
|
||||
Table Inheritance.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -247,24 +304,9 @@ Example:
|
||||
// ...
|
||||
}
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
- The ``#[InheritanceType]``, ``#[DiscriminatorColumn]`` and
|
||||
``#[DiscriminatorMap]`` must be specified on the topmost class that is
|
||||
part of the mapped entity hierarchy.
|
||||
- The ``#[DiscriminatorMap]`` specifies which values of the
|
||||
discriminator column identify a row as being of which type. In the
|
||||
case above a value of "person" identifies a row as being of type
|
||||
``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- The names of the classes in the discriminator map do not need to
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
As before, the ``#[DiscriminatorMap]`` specifies that in the
|
||||
discriminator column, a value of "person" identifies a row as being of type
|
||||
``Person`` and "employee" identifies a row as being of type ``Employee``.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -295,20 +337,13 @@ perform just about any query which can have a negative impact on
|
||||
performance, especially with large tables and/or large hierarchies.
|
||||
When partial objects are allowed, either globally or on the
|
||||
specific query, then querying for any type will not cause the
|
||||
tables of subtypes to be OUTER JOINed which can increase
|
||||
tables of subtypes to be ``OUTER JOIN``ed which can increase
|
||||
performance but the resulting partial objects will not fully load
|
||||
themselves on access of any subtype fields, so accessing fields of
|
||||
subtypes after such a query is not safe.
|
||||
|
||||
There is a general performance consideration with Class Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is a CTI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
There is also another important performance consideration that it is *NOT POSSIBLE*
|
||||
to query for the base entity without any LEFT JOINs to the sub-types.
|
||||
There is also another important performance consideration that it is *not possible*
|
||||
to query for the base entity without any ``LEFT JOIN``s to the sub-types.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -326,14 +361,16 @@ column and cascading on delete.
|
||||
Overrides
|
||||
---------
|
||||
|
||||
Used to override a mapping for an entity field or relationship. Can only be
|
||||
applied to an entity that extends a mapped superclass or uses a trait to
|
||||
override a relationship or field mapping defined by the mapped superclass or
|
||||
trait.
|
||||
Overrides can only be applied to entities that extend a mapped superclass or
|
||||
use traits. They are used to override a mapping for an entity field or
|
||||
relationship defined in that mapped superclass or trait.
|
||||
|
||||
It is not possible to override attributes or associations in entity to entity
|
||||
inheritance scenarios, because this can cause unforseen edge case behavior and
|
||||
increases complexity in ORM internal classes.
|
||||
It is not supported to use overrides in entity inheritance scenarios.
|
||||
|
||||
.. note::
|
||||
|
||||
When using traits, make sure not to miss the warnings given in the
|
||||
:doc:`Limitations and Known Issues<reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
|
||||
Association Override
|
||||
@@ -538,10 +575,11 @@ Example:
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "association override" specifies the overrides base on the property name.
|
||||
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
|
||||
- The association type *CANNOT* be changed.
|
||||
- The override could redefine the joinTables or joinColumns depending on the association type.
|
||||
- The "association override" specifies the overrides based on the property
|
||||
name.
|
||||
- This feature is available for all kind of associations (OneToOne, OneToMany, ManyToOne, ManyToMany).
|
||||
- The association type *cannot* be changed.
|
||||
- The override could redefine the ``joinTables`` or ``joinColumns`` depending on the association type.
|
||||
- The override could redefine ``inversedBy`` to reference more than one extended entity.
|
||||
- The override could redefine fetch to modify the fetch strategy of the extended entity.
|
||||
|
||||
@@ -714,8 +752,8 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "attribute override" specifies the overrides base on the property name.
|
||||
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
|
||||
- The "attribute override" specifies the overrides based on the property name.
|
||||
- The column type *cannot* be changed. If the column type is not equal, you get a ``MappingException``.
|
||||
- The override can redefine all the attributes except the type.
|
||||
|
||||
Query the Type
|
||||
|
||||
@@ -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
|
||||
------------
|
||||
|
||||
|
||||
@@ -114,8 +114,8 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
commit and rolls back the transaction when an
|
||||
exception occurs.
|
||||
commit and in case of an exception the ``EntityManager`` gets closed
|
||||
in addition to the transaction rollback.
|
||||
|
||||
.. _transactions-and-concurrency_exception-handling:
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -166,7 +166,7 @@ step:
|
||||
$connection = DriverManager::getConnection([
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => __DIR__ . '/db.sqlite',
|
||||
], $config)
|
||||
], $config);
|
||||
|
||||
// obtaining the entity manager
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -246,7 +246,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*
|
||||
* @param string $query
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param string[] $orderBy
|
||||
* @param string[]|null $orderBy
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
*
|
||||
|
||||
@@ -227,7 +227,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
$alias
|
||||
);
|
||||
} else {
|
||||
NotSupported::createForPersistence3(sprintf(
|
||||
throw NotSupported::createForPersistence3(sprintf(
|
||||
'Using short namespace alias "%s" by calling %s',
|
||||
$alias,
|
||||
__METHOD__
|
||||
|
||||
@@ -814,7 +814,7 @@ class EntityManager implements EntityManagerInterface
|
||||
$entityName
|
||||
);
|
||||
} else {
|
||||
NotSupported::createForPersistence3(sprintf(
|
||||
throw NotSupported::createForPersistence3(sprintf(
|
||||
'Using short namespace alias "%s" when calling %s',
|
||||
$entityName,
|
||||
__METHOD__
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$class->setVersioned($parent->isVersioned);
|
||||
$class->setVersionField($parent->versionField);
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
$class->addSubClasses($parent->subClasses);
|
||||
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
|
||||
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
|
||||
|
||||
@@ -219,11 +220,16 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$this->addDefaultDiscriminatorMap($class);
|
||||
}
|
||||
|
||||
// During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402.
|
||||
// So, we must not discover the missing subclasses before that.
|
||||
|
||||
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
|
||||
$eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
|
||||
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
|
||||
}
|
||||
|
||||
$this->findAbstractEntityClassesNotListedInDiscriminatorMap($class);
|
||||
|
||||
if ($class->changeTrackingPolicy === ClassMetadata::CHANGETRACKING_NOTIFY) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
@@ -338,6 +344,57 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$class->setDiscriminatorMap($map);
|
||||
}
|
||||
|
||||
private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void
|
||||
{
|
||||
// Only root classes in inheritance hierarchies need contain a discriminator map,
|
||||
// so skip for other classes.
|
||||
if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$processedClasses = [$rootEntityClass->name => true];
|
||||
foreach ($rootEntityClass->subClasses as $knownSubClass) {
|
||||
$processedClasses[$knownSubClass] = true;
|
||||
}
|
||||
|
||||
foreach ($rootEntityClass->discriminatorMap as $declaredClassName) {
|
||||
// This fetches non-transient parent classes only
|
||||
$parentClasses = $this->getParentClasses($declaredClassName);
|
||||
|
||||
foreach ($parentClasses as $parentClass) {
|
||||
if (isset($processedClasses[$parentClass])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$processedClasses[$parentClass] = true;
|
||||
|
||||
// All non-abstract entity classes must be listed in the discriminator map, and
|
||||
// this will be validated/enforced at runtime (possibly at a later time, when the
|
||||
// subclass is loaded, but anyways). Also, subclasses is about entity classes only.
|
||||
// That means we can ignore non-abstract classes here. The (expensive) driver
|
||||
// check for mapped superclasses need only be run for abstract candidate classes.
|
||||
if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter)
|
||||
$rootEntityClass->addSubClass($parentClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param class-string $className */
|
||||
private function peekIfIsMappedSuperclass(string $className): bool
|
||||
{
|
||||
$reflService = $this->getReflectionService();
|
||||
$class = $this->newClassMetadataInstance($className);
|
||||
$this->initializeReflection($class, $reflService);
|
||||
|
||||
$this->driver->loadMetadataForClass($className, $class);
|
||||
|
||||
return $class->isMappedSuperclass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lower-case short name of a class.
|
||||
*
|
||||
@@ -384,15 +441,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->associationMappings as $field => $mapping) {
|
||||
if ($parentClass->isMappedSuperclass) {
|
||||
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
|
||||
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
|
||||
}
|
||||
|
||||
$mapping['sourceEntity'] = $subClass->name;
|
||||
}
|
||||
|
||||
//$subclassMapping = $mapping;
|
||||
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
|
||||
$mapping['inherited'] = $parentClass->name;
|
||||
}
|
||||
@@ -401,6 +449,20 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$mapping['declared'] = $parentClass->name;
|
||||
}
|
||||
|
||||
// When the class inheriting the relation ($subClass) is the first entity class since the
|
||||
// relation has been defined in a mapped superclass (or in a chain
|
||||
// of mapped superclasses) above, then declare this current entity class as the source of
|
||||
// the relationship.
|
||||
// According to the definitions given in https://github.com/doctrine/orm/pull/10396/,
|
||||
// this is the case <=> ! isset($mapping['inherited']).
|
||||
if (! isset($mapping['inherited'])) {
|
||||
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
|
||||
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
|
||||
}
|
||||
|
||||
$mapping['sourceEntity'] = $subClass->name;
|
||||
}
|
||||
|
||||
$subClass->addInheritedAssociationMapping($mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ use const PHP_VERSION_ID;
|
||||
* columnDefinition?: string,
|
||||
* precision?: int,
|
||||
* scale?: int,
|
||||
* unique?: string,
|
||||
* unique?: bool,
|
||||
* inherited?: class-string,
|
||||
* originalClass?: class-string,
|
||||
* originalField?: string,
|
||||
@@ -397,7 +397,27 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
public $parentClasses = [];
|
||||
|
||||
/**
|
||||
* READ-ONLY: The names of all subclasses (descendants).
|
||||
* READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all
|
||||
* <em>entity</em> subclasses of this class. These may also be abstract classes.
|
||||
*
|
||||
* This list is used, for example, to enumerate all necessary tables in JTI when querying for root
|
||||
* or subclass entities, or to gather all fields comprised in an entity inheritance tree.
|
||||
*
|
||||
* For classes that do not use STI/JTI, this list is empty.
|
||||
*
|
||||
* Implementation note:
|
||||
*
|
||||
* In PHP, there is no general way to discover all subclasses of a given class at runtime. For that
|
||||
* reason, the list of classes given in the discriminator map at the root entity is considered
|
||||
* authoritative. The discriminator map must contain all <em>concrete</em> classes that can
|
||||
* appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract
|
||||
* entity classes, users are not required to list such classes with a discriminator value.
|
||||
*
|
||||
* The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the
|
||||
* root entity has been loaded.
|
||||
*
|
||||
* For subclasses of such root entities, the list can be reused/passed downwards, it only needs to
|
||||
* be filtered accordingly (only keep remaining subclasses)
|
||||
*
|
||||
* @psalm-var list<class-string>
|
||||
*/
|
||||
@@ -406,6 +426,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 = [];
|
||||
@@ -520,9 +556,23 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* - <b>scale</b> (integer, optional, schema-only)
|
||||
* The scale of a decimal column. Only valid if the column type is decimal.
|
||||
*
|
||||
* - <b>'unique'</b> (string, optional, schema-only)
|
||||
* - <b>'unique'</b> (boolean, 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 +675,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 +716,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(
|
||||
@@ -1284,7 +1353,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* @param string $fieldName
|
||||
* @param array $cache
|
||||
* @psalm-param array{usage?: int, region?: string|null} $cache
|
||||
* @psalm-param array{usage?: int|null, region?: string|null} $cache
|
||||
*
|
||||
* @return int[]|string[]
|
||||
* @psalm-return array{usage: int, region: string|null}
|
||||
@@ -1724,25 +1793,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @psalm-param array<string, mixed> $mapping The mapping.
|
||||
*
|
||||
* @return mixed[] The updated mapping.
|
||||
* @psalm-return array{
|
||||
* mappedBy: mixed|null,
|
||||
* inversedBy: mixed|null,
|
||||
* isOwningSide: bool,
|
||||
* sourceEntity: class-string,
|
||||
* targetEntity: string,
|
||||
* fieldName: mixed,
|
||||
* fetch: mixed,
|
||||
* cascade: array<array-key,string>,
|
||||
* isCascadeRemove: bool,
|
||||
* isCascadePersist: bool,
|
||||
* isCascadeRefresh: bool,
|
||||
* isCascadeMerge: bool,
|
||||
* isCascadeDetach: bool,
|
||||
* type: int,
|
||||
* originalField: string,
|
||||
* originalClass: class-string,
|
||||
* ?orphanRemoval: bool
|
||||
* }
|
||||
* @psalm-return AssociationMapping
|
||||
*
|
||||
* @throws MappingException If something is wrong with the mapping.
|
||||
*/
|
||||
@@ -1872,7 +1923,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
|
||||
*
|
||||
* @return mixed[] The validated & completed mapping.
|
||||
* @psalm-return array{isOwningSide: mixed, orphanRemoval: bool, isCascadeRemove: bool}
|
||||
* @psalm-return array{
|
||||
* mappedBy: mixed|null,
|
||||
* inversedBy: mixed|null,
|
||||
@@ -2027,7 +2077,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* Validates & completes a many-to-many association mapping.
|
||||
*
|
||||
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
|
||||
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
|
||||
*
|
||||
* @return mixed[] The validated & completed mapping.
|
||||
* @psalm-return array{
|
||||
@@ -3275,6 +3324,21 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
|
||||
}
|
||||
|
||||
$this->addSubClass($className);
|
||||
}
|
||||
|
||||
/** @param array<class-string> $classes */
|
||||
public function addSubClasses(array $classes): void
|
||||
{
|
||||
foreach ($classes as $className) {
|
||||
$this->addSubClass($className);
|
||||
}
|
||||
}
|
||||
|
||||
public function addSubClass(string $className): void
|
||||
{
|
||||
// By ignoring classes that are not subclasses of the current class, we simplify inheriting
|
||||
// the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata.
|
||||
if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) {
|
||||
$this->subClasses[] = $className;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -16,10 +16,10 @@ class SimplifiedXmlDriver extends XmlDriver
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
|
||||
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = false)
|
||||
{
|
||||
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
|
||||
|
||||
parent::__construct($locator, $fileExtension);
|
||||
parent::__construct($locator, $fileExtension, $isXsdValidationEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,11 +903,10 @@ class YamlDriver extends FileDriver
|
||||
* Parse / Normalize the cache configuration
|
||||
*
|
||||
* @param mixed[] $cacheMapping
|
||||
* @psalm-param array{usage: mixed, region: (string|null)} $cacheMapping
|
||||
* @psalm-param array{usage: string, region?: string} $cacheMapping
|
||||
* @psalm-param array{usage: string|null, region?: mixed} $cacheMapping
|
||||
*
|
||||
* @return mixed[]
|
||||
* @psalm-return array{usage: int, region: string|null}
|
||||
* @psalm-return array{usage: int|null, region: string|null}
|
||||
*/
|
||||
private function cacheToArray(array $cacheMapping): array
|
||||
{
|
||||
|
||||
@@ -46,9 +46,9 @@ final class Table implements MappingAttribute
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @param array<Index> $indexes
|
||||
* @param array<UniqueConstraint> $uniqueConstraints
|
||||
* @param array<string,mixed> $options
|
||||
* @param array<Index>|null $indexes
|
||||
* @param array<UniqueConstraint>|null $uniqueConstraints
|
||||
* @param array<string,mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
|
||||
@@ -40,9 +40,9 @@ final class UniqueConstraint implements MappingAttribute
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @param array<string> $columns
|
||||
* @param array<string> $fields
|
||||
* @param array<string,mixed> $options
|
||||
* @param array<string>|null $columns
|
||||
* @param array<string>|null $fields
|
||||
* @param array<string,mixed>|null $options
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -258,6 +258,8 @@ class SqlWalker implements TreeWalker
|
||||
* @psalm-param QueryComponent $queryComponent
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function setQueryComponent($dqlAlias, array $queryComponent)
|
||||
{
|
||||
@@ -276,6 +278,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\DeleteStatement|AST\UpdateStatement|AST\SelectStatement $AST
|
||||
*
|
||||
* @return Exec\AbstractSqlExecutor
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function getExecutor($AST)
|
||||
{
|
||||
@@ -628,6 +632,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param string $identVariable
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkEntityIdentificationVariable($identVariable)
|
||||
{
|
||||
@@ -649,6 +655,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkIdentificationVariable($identificationVariable, $fieldName = null)
|
||||
{
|
||||
@@ -670,6 +678,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\PathExpression $pathExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkPathExpression($pathExpr)
|
||||
{
|
||||
@@ -731,6 +741,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\SelectClause $selectClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSelectClause($selectClause)
|
||||
{
|
||||
@@ -854,6 +866,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\FromClause $fromClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkFromClause($fromClause)
|
||||
{
|
||||
@@ -873,6 +887,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\IdentificationVariableDeclaration $identificationVariableDecl
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkIdentificationVariableDeclaration($identificationVariableDecl)
|
||||
{
|
||||
@@ -895,6 +911,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\IndexBy $indexBy
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkIndexBy($indexBy)
|
||||
{
|
||||
@@ -948,6 +966,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\RangeVariableDeclaration $rangeVariableDeclaration
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkRangeVariableDeclaration($rangeVariableDeclaration)
|
||||
{
|
||||
@@ -998,6 +1018,8 @@ class SqlWalker implements TreeWalker
|
||||
* @return string
|
||||
*
|
||||
* @throws QueryException
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
|
||||
{
|
||||
@@ -1162,6 +1184,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\Functions\FunctionNode $function
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkFunction($function)
|
||||
{
|
||||
@@ -1174,6 +1198,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\OrderByClause $orderByClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkOrderByClause($orderByClause)
|
||||
{
|
||||
@@ -1193,6 +1219,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\OrderByItem $orderByItem
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkOrderByItem($orderByItem)
|
||||
{
|
||||
@@ -1217,6 +1245,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\HavingClause $havingClause
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkHavingClause($havingClause)
|
||||
{
|
||||
@@ -1229,6 +1259,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\Join $join
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkJoin($join)
|
||||
{
|
||||
@@ -1291,6 +1323,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\CoalesceExpression $coalesceExpression
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkCoalesceExpression($coalesceExpression)
|
||||
{
|
||||
@@ -1311,6 +1345,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\NullIfExpression $nullIfExpression
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkNullIfExpression($nullIfExpression)
|
||||
{
|
||||
@@ -1329,6 +1365,8 @@ class SqlWalker implements TreeWalker
|
||||
* Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
|
||||
{
|
||||
@@ -1350,6 +1388,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\SimpleCaseExpression $simpleCaseExpression
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSimpleCaseExpression($simpleCaseExpression)
|
||||
{
|
||||
@@ -1371,6 +1411,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\SelectExpression $selectExpression
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSelectExpression($selectExpression)
|
||||
{
|
||||
@@ -1575,6 +1617,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\QuantifiedExpression $qExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkQuantifiedExpression($qExpr)
|
||||
{
|
||||
@@ -1587,6 +1631,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\Subselect $subselect
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSubselect($subselect)
|
||||
{
|
||||
@@ -1616,6 +1662,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\SubselectFromClause $subselectFromClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSubselectFromClause($subselectFromClause)
|
||||
{
|
||||
@@ -1635,6 +1683,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\SimpleSelectClause $simpleSelectClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSimpleSelectClause($simpleSelectClause)
|
||||
{
|
||||
@@ -1733,6 +1783,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\SimpleSelectExpression $simpleSelectExpression
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSimpleSelectExpression($simpleSelectExpression)
|
||||
{
|
||||
@@ -1788,6 +1840,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\AggregateExpression $aggExpression
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkAggregateExpression($aggExpression)
|
||||
{
|
||||
@@ -1801,6 +1855,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\GroupByClause $groupByClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkGroupByClause($groupByClause)
|
||||
{
|
||||
@@ -1819,6 +1875,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\PathExpression|string $groupByItem
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkGroupByItem($groupByItem)
|
||||
{
|
||||
@@ -1868,6 +1926,8 @@ class SqlWalker implements TreeWalker
|
||||
* Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkDeleteClause(AST\DeleteClause $deleteClause)
|
||||
{
|
||||
@@ -1887,6 +1947,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\UpdateClause $updateClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkUpdateClause($updateClause)
|
||||
{
|
||||
@@ -1906,6 +1968,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\UpdateItem $updateItem
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkUpdateItem($updateItem)
|
||||
{
|
||||
@@ -1941,6 +2005,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\WhereClause $whereClause
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkWhereClause($whereClause)
|
||||
{
|
||||
@@ -1985,6 +2051,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\ConditionalExpression $condExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkConditionalExpression($condExpr)
|
||||
{
|
||||
@@ -2003,6 +2071,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\ConditionalTerm $condTerm
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkConditionalTerm($condTerm)
|
||||
{
|
||||
@@ -2021,6 +2091,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\ConditionalFactor $factor
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkConditionalFactor($factor)
|
||||
{
|
||||
@@ -2037,6 +2109,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\ConditionalPrimary $primary
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkConditionalPrimary($primary)
|
||||
{
|
||||
@@ -2057,6 +2131,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\ExistsExpression $existsExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkExistsExpression($existsExpr)
|
||||
{
|
||||
@@ -2073,6 +2149,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\CollectionMemberExpression $collMemberExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkCollectionMemberExpression($collMemberExpr)
|
||||
{
|
||||
@@ -2174,6 +2252,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\EmptyCollectionComparisonExpression $emptyCollCompExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
|
||||
{
|
||||
@@ -2189,6 +2269,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\NullComparisonExpression $nullCompExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkNullComparisonExpression($nullCompExpr)
|
||||
{
|
||||
@@ -2275,6 +2357,8 @@ class SqlWalker implements TreeWalker
|
||||
* @return string
|
||||
*
|
||||
* @throws QueryException
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkInstanceOfExpression($instanceOfExpr)
|
||||
{
|
||||
@@ -2301,6 +2385,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param mixed $inParam
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkInParameter($inParam)
|
||||
{
|
||||
@@ -2315,6 +2401,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\Literal $literal
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkLiteral($literal)
|
||||
{
|
||||
@@ -2339,6 +2427,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\BetweenExpression $betweenExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkBetweenExpression($betweenExpr)
|
||||
{
|
||||
@@ -2360,6 +2450,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\LikeExpression $likeExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkLikeExpression($likeExpr)
|
||||
{
|
||||
@@ -2399,6 +2491,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\PathExpression $stateFieldPathExpression
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkStateFieldPathExpression($stateFieldPathExpression)
|
||||
{
|
||||
@@ -2411,6 +2505,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\ComparisonExpression $compExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkComparisonExpression($compExpr)
|
||||
{
|
||||
@@ -2437,6 +2533,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\InputParameter $inputParam
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkInputParameter($inputParam)
|
||||
{
|
||||
@@ -2460,6 +2558,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\ArithmeticExpression $arithmeticExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkArithmeticExpression($arithmeticExpr)
|
||||
{
|
||||
@@ -2474,6 +2574,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\SimpleArithmeticExpression $simpleArithmeticExpr
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
|
||||
{
|
||||
@@ -2490,6 +2592,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param mixed $term
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkArithmeticTerm($term)
|
||||
{
|
||||
@@ -2514,6 +2618,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param mixed $factor
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkArithmeticFactor($factor)
|
||||
{
|
||||
@@ -2540,6 +2646,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param mixed $primary
|
||||
*
|
||||
* @return string The SQL.
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkArithmeticPrimary($primary)
|
||||
{
|
||||
@@ -2560,6 +2668,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param mixed $stringPrimary
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkStringPrimary($stringPrimary)
|
||||
{
|
||||
@@ -2574,6 +2684,8 @@ class SqlWalker implements TreeWalker
|
||||
* @param string $resultVariable
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @not-deprecated
|
||||
*/
|
||||
public function walkResultVariable($resultVariable)
|
||||
{
|
||||
|
||||
@@ -20,9 +20,7 @@ use function sprintf;
|
||||
*/
|
||||
class CollectionRegionCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:clear-cache:region:collection')
|
||||
|
||||
@@ -20,9 +20,7 @@ use function sprintf;
|
||||
*/
|
||||
class EntityRegionCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:clear-cache:region:entity')
|
||||
|
||||
@@ -18,9 +18,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
*/
|
||||
class MetadataCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:clear-cache:metadata')
|
||||
|
||||
@@ -27,9 +27,7 @@ use function sprintf;
|
||||
*/
|
||||
class QueryCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:clear-cache:query')
|
||||
|
||||
@@ -20,9 +20,7 @@ use function sprintf;
|
||||
*/
|
||||
class QueryRegionCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:clear-cache:region:query')
|
||||
|
||||
@@ -29,9 +29,7 @@ use function sprintf;
|
||||
*/
|
||||
class ResultCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:clear-cache:result')
|
||||
|
||||
@@ -72,9 +72,7 @@ class ConvertDoctrine1SchemaCommand extends Command
|
||||
$this->metadataExporter = $metadataExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:convert-d1-schema')
|
||||
|
||||
@@ -37,9 +37,7 @@ use function strtolower;
|
||||
*/
|
||||
class ConvertMappingCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:convert-mapping')
|
||||
|
||||
@@ -19,9 +19,7 @@ use Throwable;
|
||||
*/
|
||||
class EnsureProductionSettingsCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:ensure-production-settings')
|
||||
|
||||
@@ -28,9 +28,7 @@ use function sprintf;
|
||||
*/
|
||||
class GenerateEntitiesCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:generate-entities')
|
||||
|
||||
@@ -26,9 +26,7 @@ use function sprintf;
|
||||
*/
|
||||
class GenerateProxiesCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:generate-proxies')
|
||||
|
||||
@@ -27,9 +27,7 @@ use function sprintf;
|
||||
*/
|
||||
class GenerateRepositoriesCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:generate-repositories')
|
||||
|
||||
@@ -20,9 +20,7 @@ use function sprintf;
|
||||
*/
|
||||
class InfoCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:info')
|
||||
|
||||
@@ -27,9 +27,7 @@ use function strtoupper;
|
||||
*/
|
||||
class RunDqlCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:run-dql')
|
||||
|
||||
@@ -19,9 +19,7 @@ use function sprintf;
|
||||
*/
|
||||
class CreateCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:schema-tool:create')
|
||||
|
||||
@@ -20,9 +20,7 @@ use function sprintf;
|
||||
*/
|
||||
class DropCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:schema-tool:drop')
|
||||
|
||||
@@ -24,9 +24,7 @@ class UpdateCommand extends AbstractCommand
|
||||
/** @var string */
|
||||
protected $name = 'orm:schema-tool:update';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName($this->name)
|
||||
@@ -79,7 +77,10 @@ EOT
|
||||
$saveMode = ! $input->getOption('complete');
|
||||
|
||||
if ($saveMode) {
|
||||
$notificationUi->warning('Not passing the "--complete" option to "orm:schema-tool:update" is deprecated and will not be supported when using doctrine/dbal 4');
|
||||
$notificationUi->warning(sprintf(
|
||||
'Not passing the "--complete" option to "%s" is deprecated and will not be supported when using doctrine/dbal 4',
|
||||
$this->getName() ?? $this->name
|
||||
));
|
||||
}
|
||||
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
|
||||
|
||||
@@ -20,9 +20,7 @@ use function sprintf;
|
||||
*/
|
||||
class ValidateSchemaCommand extends AbstractEntityManagerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('orm:validate-schema')
|
||||
|
||||
@@ -21,7 +21,8 @@ use Traversable;
|
||||
use function array_key_exists;
|
||||
use function array_map;
|
||||
use function array_sum;
|
||||
use function count;
|
||||
use function assert;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* The paginator can handle various complex scenarios with DQL.
|
||||
@@ -158,11 +159,12 @@ class Paginator implements Countable, IteratorAggregate
|
||||
$ids = array_map('current', $foundIdRows);
|
||||
|
||||
$this->appendTreeWalker($whereInQuery, WhereInWalker::class);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_HAS_IDS, true);
|
||||
$whereInQuery->setFirstResult(0)->setMaxResults(null);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
|
||||
$whereInQuery->setCacheable($this->query->isCacheable());
|
||||
$whereInQuery->expireQueryCache();
|
||||
|
||||
$databaseIds = $this->convertWhereInIdentifiersToDatabaseValues($ids);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $databaseIds);
|
||||
|
||||
$result = $whereInQuery->getResult($this->query->getHydrationMode());
|
||||
} else {
|
||||
@@ -265,4 +267,23 @@ class Paginator implements Countable, IteratorAggregate
|
||||
|
||||
$query->setParameters($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $identifiers
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function convertWhereInIdentifiersToDatabaseValues(array $identifiers): array
|
||||
{
|
||||
$query = $this->cloneQuery($this->query);
|
||||
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class);
|
||||
|
||||
$connection = $this->query->getEntityManager()->getConnection();
|
||||
$type = $query->getSQL();
|
||||
assert(is_string($type));
|
||||
|
||||
return array_map(static function ($id) use ($connection, $type) {
|
||||
return $connection->convertToDatabaseValue($id, $type);
|
||||
}, $identifiers);
|
||||
}
|
||||
}
|
||||
|
||||
48
lib/Doctrine/ORM/Tools/Pagination/RootTypeWalker.php
Normal file
48
lib/Doctrine/ORM/Tools/Pagination/RootTypeWalker.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Pagination;
|
||||
|
||||
use Doctrine\ORM\Query\AST;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use RuntimeException;
|
||||
|
||||
use function count;
|
||||
use function reset;
|
||||
|
||||
/**
|
||||
* Infers the DBAL type of the #Id (identifier) column of the given query's root entity, and
|
||||
* returns it in place of a real SQL statement.
|
||||
*
|
||||
* Obtaining this type is a necessary intermediate step for \Doctrine\ORM\Tools\Pagination\Paginator.
|
||||
* We can best do this from a tree walker because it gives us access to the AST.
|
||||
*
|
||||
* Returning the type instead of a "real" SQL statement is a slight hack. However, it has the
|
||||
* benefit that the DQL -> root entity id type resolution can be cached in the query cache.
|
||||
*/
|
||||
final class RootTypeWalker extends SqlWalker
|
||||
{
|
||||
public function walkSelectStatement(AST\SelectStatement $AST): string
|
||||
{
|
||||
// Get the root entity and alias from the AST fromClause
|
||||
$from = $AST->fromClause->identificationVariableDeclarations;
|
||||
|
||||
if (count($from) > 1) {
|
||||
throw new RuntimeException('Can only process queries that select only one FROM component');
|
||||
}
|
||||
|
||||
$fromRoot = reset($from);
|
||||
$rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
|
||||
$rootClass = $this->getMetadataForDqlAlias($rootAlias);
|
||||
$identifierFieldName = $rootClass->getSingleIdentifierFieldName();
|
||||
|
||||
return PersisterHelper::getTypeOfField(
|
||||
$identifierFieldName,
|
||||
$rootClass,
|
||||
$this->getQuery()
|
||||
->getEntityManager()
|
||||
)[0];
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,9 @@ use Doctrine\ORM\Query\AST\SelectStatement;
|
||||
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
|
||||
use Doctrine\ORM\Query\AST\WhereClause;
|
||||
use Doctrine\ORM\Query\TreeWalkerAdapter;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_map;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function reset;
|
||||
|
||||
/**
|
||||
@@ -32,15 +28,15 @@ use function reset;
|
||||
* The parameter namespace (dpid) is defined by
|
||||
* the PAGINATOR_ID_ALIAS
|
||||
*
|
||||
* The total number of parameters is retrieved from
|
||||
* the HINT_PAGINATOR_ID_COUNT query hint.
|
||||
* The HINT_PAGINATOR_HAS_IDS query hint indicates whether there are
|
||||
* any ids in the parameter at all.
|
||||
*/
|
||||
class WhereInWalker extends TreeWalkerAdapter
|
||||
{
|
||||
/**
|
||||
* ID Count hint name.
|
||||
*/
|
||||
public const HINT_PAGINATOR_ID_COUNT = 'doctrine.id.count';
|
||||
public const HINT_PAGINATOR_HAS_IDS = 'doctrine.paginator_has_ids';
|
||||
|
||||
/**
|
||||
* Primary key alias for query.
|
||||
@@ -69,9 +65,9 @@ class WhereInWalker extends TreeWalkerAdapter
|
||||
$pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $rootAlias, $identifierFieldName);
|
||||
$pathExpression->type = $pathType;
|
||||
|
||||
$count = $this->_getQuery()->getHint(self::HINT_PAGINATOR_ID_COUNT);
|
||||
$hasIds = $this->_getQuery()->getHint(self::HINT_PAGINATOR_HAS_IDS);
|
||||
|
||||
if ($count > 0) {
|
||||
if ($hasIds) {
|
||||
$arithmeticExpression = new ArithmeticExpression();
|
||||
$arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression(
|
||||
[$pathExpression]
|
||||
@@ -80,15 +76,6 @@ class WhereInWalker extends TreeWalkerAdapter
|
||||
$arithmeticExpression,
|
||||
[new InputParameter(':' . self::PAGINATOR_ID_ALIAS)]
|
||||
);
|
||||
|
||||
$this->convertWhereInIdentifiersToDatabaseValue(
|
||||
PersisterHelper::getTypeOfField(
|
||||
$identifierFieldName,
|
||||
$rootClass,
|
||||
$this->_getQuery()
|
||||
->getEntityManager()
|
||||
)[0]
|
||||
);
|
||||
} else {
|
||||
$expression = new NullComparisonExpression($pathExpression);
|
||||
}
|
||||
@@ -130,24 +117,4 @@ class WhereInWalker extends TreeWalkerAdapter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function convertWhereInIdentifiersToDatabaseValue(string $type): void
|
||||
{
|
||||
$query = $this->_getQuery();
|
||||
$identifiersParameter = $query->getParameter(self::PAGINATOR_ID_ALIAS);
|
||||
|
||||
assert($identifiersParameter !== null);
|
||||
|
||||
$identifiers = $identifiersParameter->getValue();
|
||||
|
||||
assert(is_array($identifiers));
|
||||
|
||||
$connection = $this->_getQuery()
|
||||
->getEntityManager()
|
||||
->getConnection();
|
||||
|
||||
$query->setParameter(self::PAGINATOR_ID_ALIAS, array_map(static function ($id) use ($connection, $type) {
|
||||
return $connection->convertToDatabaseValue($id, $type);
|
||||
}, $identifiers));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class SchemaTool
|
||||
|
||||
foreach ($createSchemaSql as $sql) {
|
||||
try {
|
||||
$conn->executeQuery($sql);
|
||||
$conn->executeStatement($sql);
|
||||
} catch (Throwable $e) {
|
||||
throw ToolsException::schemaToolFailure($sql, $e);
|
||||
}
|
||||
@@ -831,7 +831,7 @@ class SchemaTool
|
||||
|
||||
foreach ($dropSchemaSql as $sql) {
|
||||
try {
|
||||
$conn->executeQuery($sql);
|
||||
$conn->executeStatement($sql);
|
||||
} catch (Throwable $e) {
|
||||
// ignored
|
||||
}
|
||||
@@ -849,7 +849,7 @@ class SchemaTool
|
||||
$conn = $this->em->getConnection();
|
||||
|
||||
foreach ($dropSchemaSql as $sql) {
|
||||
$conn->executeQuery($sql);
|
||||
$conn->executeStatement($sql);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,7 +939,7 @@ class SchemaTool
|
||||
$conn = $this->em->getConnection();
|
||||
|
||||
foreach ($updateSchemaSql as $sql) {
|
||||
$conn->executeQuery($sql);
|
||||
$conn->executeStatement($sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,13 @@ class SchemaValidator
|
||||
}
|
||||
}
|
||||
|
||||
if (! $class->isInheritanceTypeNone() && ! $class->isRootEntity() && ! $class->isMappedSuperclass && array_search($class->name, $class->discriminatorMap, true) === false) {
|
||||
if (
|
||||
! $class->isInheritanceTypeNone()
|
||||
&& ! $class->isRootEntity()
|
||||
&& ($class->reflClass !== null && ! $class->reflClass->isAbstract())
|
||||
&& ! $class->isMappedSuperclass
|
||||
&& array_search($class->name, $class->discriminatorMap, true) === false
|
||||
) {
|
||||
$ce[] = "Entity class '" . $class->name . "' is part of inheritance hierarchy, but is " .
|
||||
"not mapped in the root entity '" . $class->rootEntityName . "' discriminator map. " .
|
||||
'All subclasses must be listed in the discriminator map.';
|
||||
|
||||
@@ -170,6 +170,11 @@ parameters:
|
||||
count: 2
|
||||
path: lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\:\\:_validateAndCompleteManyToManyMapping\\(\\) should return array\\{mappedBy\\: mixed, inversedBy\\: mixed, isOwningSide\\: bool, sourceEntity\\: class\\-string, targetEntity\\: string, fieldName\\: mixed, fetch\\: mixed, cascade\\: array\\<string\\>, \\.\\.\\.\\} but returns array\\{cache\\?\\: array, cascade\\: array\\<string\\>, declared\\?\\: class\\-string, fetch\\: mixed, fieldName\\: string, id\\?\\: bool, inherited\\?\\: class\\-string, indexBy\\?\\: string, \\.\\.\\.\\}\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
|
||||
|
||||
-
|
||||
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\:\\:fullyQualifiedClassName\\(\\) should return class\\-string\\|null but returns string\\|null\\.$#"
|
||||
count: 1
|
||||
@@ -230,11 +235,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Mapping/Driver/XmlDriver.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: "#^Call to function is_int\\(\\) with string will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
@@ -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
|
||||
@@ -490,11 +420,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node\\:\\:\\$pathExpression\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Query/SqlWalker.php
|
||||
|
||||
-
|
||||
message: "#^Call to function is_string\\(\\) with Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
@@ -510,11 +435,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
|
||||
@@ -685,11 +605,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php
|
||||
|
||||
-
|
||||
message: "#^Result of \\|\\| is always true\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php
|
||||
|
||||
-
|
||||
message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#"
|
||||
count: 1
|
||||
@@ -705,11 +620,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
|
||||
|
||||
@@ -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
|
||||
|
||||
2611
psalm-baseline.xml
2611
psalm-baseline.xml
File diff suppressed because it is too large
Load Diff
17
psalm.xml
17
psalm.xml
@@ -3,6 +3,8 @@
|
||||
errorLevel="2"
|
||||
phpVersion="8.2"
|
||||
resolveFromConfigFile="true"
|
||||
findUnusedBaselineEntry="true"
|
||||
findUnusedCode="false"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
@@ -40,8 +42,6 @@
|
||||
<referencedClass name="Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand"/>
|
||||
<referencedClass name="Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper"/>
|
||||
<referencedClass name="Doctrine\ORM\Tools\Console\EntityManagerProvider\HelperSetManagerProvider"/>
|
||||
<!-- https://github.com/vimeo/psalm/issues/8617 -->
|
||||
<referencedClass name="Doctrine\ORM\Mapping\Annotation"/>
|
||||
</errorLevel>
|
||||
</DeprecatedClass>
|
||||
<DeprecatedConstant>
|
||||
@@ -128,12 +128,6 @@
|
||||
<file name="lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php"/>
|
||||
</errorLevel>
|
||||
</InvalidParamDefault>
|
||||
<InvalidReturnType>
|
||||
<errorLevel type="suppress">
|
||||
<!-- https://github.com/vimeo/psalm/issues/8819 -->
|
||||
<file name="lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php"/>
|
||||
</errorLevel>
|
||||
</InvalidReturnType>
|
||||
<MethodSignatureMismatch>
|
||||
<errorLevel type="suppress">
|
||||
<!-- See https://github.com/vimeo/psalm/issues/7357 -->
|
||||
@@ -237,11 +231,10 @@
|
||||
<referencedMethod name="Doctrine\DBAL\Platforms\AbstractPlatform::getGuidExpression"/>
|
||||
</errorLevel>
|
||||
</UndefinedMethod>
|
||||
<ArgumentTypeCoercion>
|
||||
<MissingClosureReturnType>
|
||||
<errorLevel type="suppress">
|
||||
<!-- See https://github.com/JetBrains/phpstorm-stubs/pull/1383 -->
|
||||
<file name="lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php"/>
|
||||
<file name="lib/Doctrine/ORM/Tools/Pagination/Paginator.php"/>
|
||||
</errorLevel>
|
||||
</ArgumentTypeCoercion>
|
||||
</MissingClosureReturnType>
|
||||
</issueHandlers>
|
||||
</psalm>
|
||||
|
||||
@@ -134,7 +134,7 @@ class DDC964User
|
||||
'fieldName' => 'address',
|
||||
'targetEntity' => 'DDC964Address',
|
||||
'cascade' => ['persist','merge'],
|
||||
'joinColumn' => ['name' => 'address_id', 'referencedColumnMame' => 'id'],
|
||||
'joinColumns' => [['name' => 'address_id', 'referencedColumnMame' => 'id']],
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
27
tests/Doctrine/Tests/Models/GH10336/GH10336Entity.php
Normal file
27
tests/Doctrine/Tests/Models/GH10336/GH10336Entity.php
Normal file
@@ -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;
|
||||
}
|
||||
26
tests/Doctrine/Tests/Models/GH10336/GH10336Relation.php
Normal file
26
tests/Doctrine/Tests/Models/GH10336/GH10336Relation.php
Normal file
@@ -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;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ final class AbstractQueryTest extends TestCase
|
||||
}
|
||||
|
||||
/** @return array<string, array{string}> */
|
||||
public function provideSettersWithDeprecatedDefault(): array
|
||||
public static function provideSettersWithDeprecatedDefault(): array
|
||||
{
|
||||
return [
|
||||
'setHydrationCacheProfile' => ['setHydrationCacheProfile'],
|
||||
|
||||
@@ -8,6 +8,7 @@ use Doctrine\Common\Annotations\SimpleAnnotationReader;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Cache\Exception\MetadataCacheNotConfigured;
|
||||
@@ -17,6 +18,7 @@ use Doctrine\ORM\Cache\Exception\QueryCacheUsesNonPersistentCache;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Exception\InvalidEntityRepository;
|
||||
use Doctrine\ORM\Exception\NotSupported;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating;
|
||||
use Doctrine\ORM\Mapping as AnnotationNamespace;
|
||||
@@ -128,7 +130,11 @@ class ConfigurationTest extends DoctrineTestCase
|
||||
|
||||
public function testSetGetEntityNamespace(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issues/8818');
|
||||
if (class_exists(PersistentObject::class)) {
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issues/8818');
|
||||
} else {
|
||||
$this->expectException(NotSupported::class);
|
||||
}
|
||||
|
||||
$this->configuration->addEntityNamespace('TestNamespace', __NAMESPACE__);
|
||||
self::assertSame(__NAMESPACE__, $this->configuration->getEntityNamespace('TestNamespace'));
|
||||
|
||||
@@ -46,7 +46,7 @@ class EntityManagerDecoratorTest extends TestCase
|
||||
}
|
||||
|
||||
/** @psalm-return Generator<string, mixed[]> */
|
||||
public function getMethodParameters(): Generator
|
||||
public static function getMethodParameters(): Generator
|
||||
{
|
||||
$class = new ReflectionClass(EntityManagerInterface::class);
|
||||
|
||||
@@ -55,12 +55,12 @@ class EntityManagerDecoratorTest extends TestCase
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $method->getName() => $this->getParameters($method);
|
||||
yield $method->getName() => self::getParameters($method);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
private function getParameters(ReflectionMethod $method): array
|
||||
private static function getParameters(ReflectionMethod $method): array
|
||||
{
|
||||
/** Special case EntityManager::createNativeQuery() */
|
||||
if ($method->getName() === 'createNativeQuery') {
|
||||
|
||||
@@ -198,7 +198,7 @@ class EntityManagerTest extends OrmTestCase
|
||||
}
|
||||
|
||||
/** @return Generator<array{mixed}> */
|
||||
public function dataToBeReturnedByWrapInTransaction(): Generator
|
||||
public static function dataToBeReturnedByWrapInTransaction(): Generator
|
||||
{
|
||||
yield [[]];
|
||||
yield [[1]];
|
||||
|
||||
@@ -384,7 +384,7 @@ EXCEPTION
|
||||
}
|
||||
|
||||
/** @return array<string, array{class-string}> */
|
||||
public function provideCardClasses(): array
|
||||
public static function provideCardClasses(): array
|
||||
{
|
||||
return [
|
||||
Card::class => [Card::class],
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddressDTO;
|
||||
@@ -13,6 +14,7 @@ use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\CMS\CmsUserDTO;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function class_exists;
|
||||
use function count;
|
||||
|
||||
/** @group DDC-1574 */
|
||||
@@ -31,7 +33,7 @@ class NewOperatorTest extends OrmFunctionalTestCase
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{int}> */
|
||||
public function provideDataForHydrationMode(): array
|
||||
public static function provideDataForHydrationMode(): array
|
||||
{
|
||||
return [
|
||||
[Query::HYDRATE_ARRAY],
|
||||
@@ -208,6 +210,10 @@ class NewOperatorTest extends OrmFunctionalTestCase
|
||||
|
||||
public function testShouldSupportFromEntityNamespaceAlias(): void
|
||||
{
|
||||
if (! class_exists(PersistentObject::class)) {
|
||||
self::markTestSkipped('This test requires doctrine/persistence 2');
|
||||
}
|
||||
|
||||
$dql = '
|
||||
SELECT
|
||||
new CmsUserDTO(u.name, e.email, a.city)
|
||||
@@ -235,6 +241,10 @@ class NewOperatorTest extends OrmFunctionalTestCase
|
||||
|
||||
public function testShouldSupportValueObjectNamespaceAlias(): void
|
||||
{
|
||||
if (! class_exists(PersistentObject::class)) {
|
||||
self::markTestSkipped('This test requires doctrine/persistence 2');
|
||||
}
|
||||
|
||||
$dql = '
|
||||
SELECT
|
||||
new cms:CmsUserDTO(u.name, e.email, a.city)
|
||||
|
||||
@@ -77,8 +77,8 @@ class OneToOneBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
public function testLazyLoadsObjectsOnTheOwningSide(): void
|
||||
{
|
||||
$this->createFixture();
|
||||
$metadata = $this->_em->getClassMetadata(ECommerceCart::class);
|
||||
$metadata->associationMappings['customer']['fetchMode'] = ClassMetadata::FETCH_LAZY;
|
||||
$metadata = $this->_em->getClassMetadata(ECommerceCart::class);
|
||||
$metadata->associationMappings['customer']['fetch'] = ClassMetadata::FETCH_LAZY;
|
||||
|
||||
$query = $this->_em->createQuery('select c from Doctrine\Tests\Models\ECommerce\ECommerceCart c');
|
||||
$result = $query->getResult();
|
||||
|
||||
@@ -667,6 +667,28 @@ SQL
|
||||
self::assertCount(9, $paginator->getIterator());
|
||||
}
|
||||
|
||||
public function testDifferentResultLengthsDoNotRequireExtraQueryCacheEntries(): void
|
||||
{
|
||||
$dql = 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id >= :id';
|
||||
$query = $this->_em->createQuery($dql);
|
||||
$query->setMaxResults(10);
|
||||
|
||||
$query->setParameter('id', 1);
|
||||
$paginator = new Paginator($query);
|
||||
$initialResult = iterator_to_array($paginator->getIterator()); // exercise the Paginator
|
||||
self::assertCount(9, $initialResult);
|
||||
|
||||
$initialQueryCount = count(self::$queryCache->getValues());
|
||||
|
||||
$query->setParameter('id', $initialResult[1]->id); // skip the first result element
|
||||
$paginator = new Paginator($query);
|
||||
self::assertCount(8, $paginator->getIterator()); // exercise the Paginator again, with a smaller result set
|
||||
|
||||
$newCount = count(self::$queryCache->getValues());
|
||||
|
||||
self::assertSame($initialQueryCount, $newCount);
|
||||
}
|
||||
|
||||
public function populate(): void
|
||||
{
|
||||
$groups = [];
|
||||
@@ -736,8 +758,17 @@ SQL
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{bool, bool}> */
|
||||
public function useOutputWalkers(): array
|
||||
/** @psalm-return list<array{bool}> */
|
||||
public static function useOutputWalkers(): array
|
||||
{
|
||||
return [
|
||||
[true],
|
||||
[false],
|
||||
];
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{bool}> */
|
||||
public static function fetchJoinCollection(): array
|
||||
{
|
||||
return [
|
||||
[true],
|
||||
@@ -746,16 +777,7 @@ SQL
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{bool, bool}> */
|
||||
public function fetchJoinCollection(): array
|
||||
{
|
||||
return [
|
||||
[true],
|
||||
[false],
|
||||
];
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{bool, bool}> */
|
||||
public function useOutputWalkersAndFetchJoinCollection(): array
|
||||
public static function useOutputWalkersAndFetchJoinCollection(): array
|
||||
{
|
||||
return [
|
||||
[true, false],
|
||||
|
||||
@@ -364,7 +364,7 @@ class QueryDqlFunctionTest extends OrmFunctionalTestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function dateAddSubProvider(): array
|
||||
public static function dateAddSubProvider(): array
|
||||
{
|
||||
$secondsInDay = 86400;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\DBAL\Logging\Middleware as LoggingMiddleware;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
@@ -557,6 +558,10 @@ class QueryTest extends OrmFunctionalTestCase
|
||||
|
||||
public function testSupportsQueriesWithEntityNamespaces(): void
|
||||
{
|
||||
if (! class_exists(PersistentObject::class)) {
|
||||
self::markTestSkipped('This test requires doctrine/persistence 2');
|
||||
}
|
||||
|
||||
$this->_em->getConfiguration()->addEntityNamespace('CMS', 'Doctrine\Tests\Models\CMS');
|
||||
|
||||
try {
|
||||
|
||||
@@ -77,7 +77,7 @@ class DDC2825Test extends OrmFunctionalTestCase
|
||||
*
|
||||
* @return string[][]
|
||||
*/
|
||||
public function getTestedClasses(): array
|
||||
public static function getTestedClasses(): array
|
||||
{
|
||||
return [
|
||||
[ExplicitSchemaAndTable::class, 'explicit_schema', 'explicit_table'],
|
||||
|
||||
96
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC6558Test.php
Normal file
96
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC6558Test.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
/**
|
||||
* @group DDC-6558
|
||||
*/
|
||||
class DDC6558Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_schemaTool->createSchema([
|
||||
$this->_em->getClassMetadata(DDC6558Person::class),
|
||||
$this->_em->getClassMetadata(DDC6558Employee::class),
|
||||
$this->_em->getClassMetadata(DDC6558Staff::class),
|
||||
$this->_em->getClassMetadata(DDC6558Developer::class),
|
||||
$this->_em->getClassMetadata(DDC6558Manager::class),
|
||||
]);
|
||||
}
|
||||
|
||||
public function testEmployeeIsPopulated(): void
|
||||
{
|
||||
$developer = new DDC6558Developer();
|
||||
$developer->phoneNumber = 1231231231;
|
||||
$developer->emailAddress = 'email@address.com';
|
||||
|
||||
$this->_em->persist($developer);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$persistedDeveloper = $this->_em->find(DDC6558Person::class, $developer->id);
|
||||
|
||||
self::assertNotNull($persistedDeveloper->phoneNumber);
|
||||
self::assertNotNull($persistedDeveloper->emailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity()
|
||||
* @ORM\InheritanceType("JOINED")
|
||||
* @ORM\DiscriminatorColumn(name="discr", type="string")
|
||||
* @ORM\DiscriminatorMap({"manager" = "DDC6558Manager", "staff" = "DDC6558Staff", "developer" = "DDC6558Developer"})
|
||||
*/
|
||||
abstract class DDC6558Person
|
||||
{
|
||||
/**
|
||||
* @ORM\Id()
|
||||
* @ORM\GeneratedValue()
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue()
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/** @ORM\Entity() */
|
||||
class DDC6558Manager extends DDC6558Person
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity()
|
||||
*/
|
||||
abstract class DDC6558Employee extends DDC6558Person
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $phoneNumber;
|
||||
}
|
||||
|
||||
/** @ORM\Entity() */
|
||||
class DDC6558Staff extends DDC6558Employee
|
||||
{
|
||||
}
|
||||
|
||||
/** @ORM\Entity() */
|
||||
class DDC6558Developer extends DDC6558Employee
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $emailAddress;
|
||||
}
|
||||
44
tests/Doctrine/Tests/ORM/Functional/Ticket/GH10336Test.php
Normal file
44
tests/Doctrine/Tests/ORM/Functional/Ticket/GH10336Test.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
173
tests/Doctrine/Tests/ORM/Functional/Ticket/GH10387Test.php
Normal file
173
tests/Doctrine/Tests/ORM/Functional/Ticket/GH10387Test.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Generator;
|
||||
|
||||
use function array_map;
|
||||
|
||||
/**
|
||||
* @group GH-10387
|
||||
*/
|
||||
class GH10387Test extends OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider classHierachies
|
||||
*/
|
||||
public function testSchemaToolCreatesColumnForFieldInTheMiddleClass(array $classes): void
|
||||
{
|
||||
$em = $this->getTestEntityManager();
|
||||
$schemaTool = new SchemaTool($em);
|
||||
$metadata = array_map(static function (string $class) use ($em) {
|
||||
return $em->getClassMetadata($class);
|
||||
}, $classes);
|
||||
$schema = $schemaTool->getSchemaFromMetadata([$metadata[0]]);
|
||||
|
||||
self::assertNotNull($schema->getTable('root')->getColumn('middle_class_field'));
|
||||
self::assertNotNull($schema->getTable('root')->getColumn('leaf_class_field'));
|
||||
}
|
||||
|
||||
public static function classHierachies(): Generator
|
||||
{
|
||||
yield 'hierarchy with Entity classes only' => [[GH10387EntitiesOnlyRoot::class, GH10387EntitiesOnlyMiddle::class, GH10387EntitiesOnlyLeaf::class]];
|
||||
yield 'MappedSuperclass in the middle of the hierarchy' => [[GH10387MappedSuperclassRoot::class, GH10387MappedSuperclassMiddle::class, GH10387MappedSuperclassLeaf::class]];
|
||||
yield 'abstract entity the the root and in the middle of the hierarchy' => [[GH10387AbstractEntitiesRoot::class, GH10387AbstractEntitiesMiddle::class, GH10387AbstractEntitiesLeaf::class]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="root")
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorMap({ "A": "GH10387EntitiesOnlyRoot", "B": "GH10387EntitiesOnlyMiddle", "C": "GH10387EntitiesOnlyLeaf"})
|
||||
*/
|
||||
class GH10387EntitiesOnlyRoot
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH10387EntitiesOnlyMiddle extends GH10387EntitiesOnlyRoot
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="middle_class_field")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $parentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH10387EntitiesOnlyLeaf extends GH10387EntitiesOnlyMiddle
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="leaf_class_field")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $childValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="root")
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorMap({ "A": "GH10387MappedSuperclassRoot", "B": "GH10387MappedSuperclassLeaf"})
|
||||
* ^- This DiscriminatorMap contains the Entity classes only, not the Mapped Superclass
|
||||
*/
|
||||
class GH10387MappedSuperclassRoot
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass
|
||||
*/
|
||||
class GH10387MappedSuperclassMiddle extends GH10387MappedSuperclassRoot
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="middle_class_field")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $parentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH10387MappedSuperclassLeaf extends GH10387MappedSuperclassMiddle
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="leaf_class_field")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $childValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="root")
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorMap({ "A": "GH10387AbstractEntitiesLeaf"})
|
||||
* ^- This DiscriminatorMap contains the single non-abstract Entity class only
|
||||
*/
|
||||
abstract class GH10387AbstractEntitiesRoot
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
abstract class GH10387AbstractEntitiesMiddle extends GH10387AbstractEntitiesRoot
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="middle_class_field")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $parentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH10387AbstractEntitiesLeaf extends GH10387AbstractEntitiesMiddle
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="leaf_class_field")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $childValue;
|
||||
}
|
||||
255
tests/Doctrine/Tests/ORM/Functional/Ticket/GH5998Test.php
Normal file
255
tests/Doctrine/Tests/ORM/Functional/Ticket/GH5998Test.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
/**
|
||||
* @group GH-5998
|
||||
*/
|
||||
class GH5998Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_schemaTool->createSchema([
|
||||
$this->_em->getClassMetadata(GH5998JTI::class),
|
||||
$this->_em->getClassMetadata(GH5998JTIChild::class),
|
||||
$this->_em->getClassMetadata(GH5998STI::class),
|
||||
$this->_em->getClassMetadata(GH5998Basic::class),
|
||||
$this->_em->getClassMetadata(GH5998Related::class),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that MappedSuperclasses work within an inheritance hierarchy.
|
||||
*/
|
||||
public function testIssue(): void
|
||||
{
|
||||
// Test JTI
|
||||
$this->classTests(GH5998JTIChild::class);
|
||||
// Test STI
|
||||
$this->classTests(GH5998STIChild::class);
|
||||
// Test Basic
|
||||
$this->classTests(GH5998Basic::class);
|
||||
}
|
||||
|
||||
private function classTests($className): void
|
||||
{
|
||||
// Test insert
|
||||
$child = new $className('Sam', 0, 1);
|
||||
$child->rel = new GH5998Related();
|
||||
$this->_em->persist($child);
|
||||
$this->_em->persist($child->rel);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
// Test find by rel
|
||||
$child = $this->_em->getRepository($className)->findOneBy(['rel' => $child->rel]);
|
||||
self::assertNotNull($child);
|
||||
$this->_em->clear();
|
||||
|
||||
// Test query by id with fetch join
|
||||
$child = $this->_em->createQuery('SELECT t, r FROM ' . $className . ' t JOIN t.rel r WHERE t.id = 1')->getOneOrNullResult();
|
||||
self::assertNotNull($child);
|
||||
|
||||
// Test lock and update
|
||||
$this->_em->transactional(static function ($em) use ($child): void {
|
||||
$em->lock($child, LockMode::NONE);
|
||||
$child->firstName = 'Bob';
|
||||
$child->status = 0;
|
||||
});
|
||||
$this->_em->clear();
|
||||
$child = $this->_em->getRepository($className)->find(1);
|
||||
self::assertEquals($child->firstName, 'Bob');
|
||||
self::assertEquals($child->status, 0);
|
||||
|
||||
// Test delete
|
||||
$this->_em->remove($child);
|
||||
$this->_em->flush();
|
||||
$child = $this->_em->getRepository($className)->find(1);
|
||||
self::assertNull($child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass
|
||||
*/
|
||||
class GH5998Common
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=GH5998Related::class)
|
||||
* @ORM\JoinColumn(name="related_id", referencedColumnName="id")
|
||||
*
|
||||
* @var GH5998Related
|
||||
*/
|
||||
public $rel;
|
||||
/**
|
||||
* @ORM\Version
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $version;
|
||||
|
||||
/** @var mixed */
|
||||
public $other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\InheritanceType("JOINED")
|
||||
* @ORM\DiscriminatorMap({"child" = GH5998JTIChild::class})
|
||||
*/
|
||||
abstract class GH5998JTI extends GH5998Common
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $firstName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass
|
||||
*/
|
||||
class GH5998JTICommon extends GH5998JTI
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH5998JTIChild extends GH5998JTICommon
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $type;
|
||||
|
||||
public function __construct(string $firstName, int $type, int $status)
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
$this->type = $type;
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorMap({"child" = GH5998STIChild::class})
|
||||
*/
|
||||
abstract class GH5998STI extends GH5998Common
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $firstName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass
|
||||
*/
|
||||
class GH5998STICommon extends GH5998STI
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH5998STIChild extends GH5998STICommon
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $type;
|
||||
|
||||
public function __construct(string $firstName, int $type, int $status)
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
$this->type = $type;
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH5998Basic extends GH5998Common
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $firstName;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $status;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $type;
|
||||
|
||||
public function __construct(string $firstName, int $type, int $status)
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
$this->type = $type;
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity()
|
||||
*/
|
||||
class GH5998Related
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
|
||||
use function array_map;
|
||||
use function is_string;
|
||||
@@ -69,48 +73,85 @@ class GH7820Test extends OrmFunctionalTestCase
|
||||
|
||||
public function testWillFindSongsInPaginator(): void
|
||||
{
|
||||
$query = $this->_em->getRepository(GH7820Line::class)
|
||||
->createQueryBuilder('l')
|
||||
->orderBy('l.lineNumber', Criteria::ASC);
|
||||
$lines = $this->fetchSongLinesWithPaginator();
|
||||
|
||||
self::assertSame(
|
||||
self::SONG,
|
||||
array_map(static function (GH7820Line $line): string {
|
||||
return $line->toString();
|
||||
}, iterator_to_array(new Paginator($query)))
|
||||
);
|
||||
self::assertSame(self::SONG, $lines);
|
||||
}
|
||||
|
||||
/** @group GH7837 */
|
||||
public function testWillFindSongsInPaginatorEvenWithCachedQueryParsing(): void
|
||||
{
|
||||
// Enable the query cache
|
||||
$this->_em->getConfiguration()
|
||||
->getQueryCache()
|
||||
->clear();
|
||||
|
||||
// Fetch song lines with the paginator, also priming the query cache
|
||||
$lines = $this->fetchSongLinesWithPaginator();
|
||||
self::assertSame(self::SONG, $lines, 'Expected to return expected data before query cache is populated with DQL -> SQL translation. Were SQL parameters translated?');
|
||||
|
||||
// Fetch song lines again
|
||||
$lines = $this->fetchSongLinesWithPaginator();
|
||||
self::assertSame(self::SONG, $lines, 'Expected to return expected data even when DQL -> SQL translation is present in cache. Were SQL parameters translated again?');
|
||||
}
|
||||
|
||||
public function testPaginatorDoesNotForceCacheToUpdateEntries(): void
|
||||
{
|
||||
$this->_em->getConfiguration()->setQueryCache(new class extends ArrayAdapter {
|
||||
public function save(CacheItemInterface $item): bool
|
||||
{
|
||||
Assert::assertFalse($this->hasItem($item->getKey()), 'The cache should not have to overwrite the entry');
|
||||
|
||||
return parent::save($item);
|
||||
}
|
||||
});
|
||||
|
||||
// "Prime" the cache (in fact, that should not even happen)
|
||||
$this->fetchSongLinesWithPaginator();
|
||||
|
||||
// Make sure we can query again without overwriting the cache
|
||||
$this->fetchSongLinesWithPaginator();
|
||||
}
|
||||
|
||||
public function testPaginatorQueriesWillBeCached(): void
|
||||
{
|
||||
$cache = new class extends ArrayAdapter {
|
||||
/** @var bool */
|
||||
private $failOnCacheMiss = false;
|
||||
|
||||
public function failOnCacheMiss(): void
|
||||
{
|
||||
$this->failOnCacheMiss = true;
|
||||
}
|
||||
|
||||
public function getItem($key): CacheItem
|
||||
{
|
||||
$item = parent::getItem($key);
|
||||
Assert::assertTrue(! $this->failOnCacheMiss || $item->isHit(), 'cache was missed');
|
||||
|
||||
return $item;
|
||||
}
|
||||
};
|
||||
$this->_em->getConfiguration()->setQueryCache($cache);
|
||||
|
||||
// Prime the cache
|
||||
$this->fetchSongLinesWithPaginator();
|
||||
|
||||
$cache->failOnCacheMiss();
|
||||
|
||||
$this->fetchSongLinesWithPaginator();
|
||||
}
|
||||
|
||||
private function fetchSongLinesWithPaginator(): array
|
||||
{
|
||||
$query = $this->_em->getRepository(GH7820Line::class)
|
||||
->createQueryBuilder('l')
|
||||
->orderBy('l.lineNumber', Criteria::ASC);
|
||||
->orderBy('l.lineNumber', Criteria::ASC)
|
||||
->setMaxResults(100);
|
||||
|
||||
self::assertSame(
|
||||
self::SONG,
|
||||
array_map(static function (GH7820Line $line): string {
|
||||
return $line->toString();
|
||||
}, iterator_to_array(new Paginator($query))),
|
||||
'Expected to return expected data before query cache is populated with DQL -> SQL translation. Were SQL parameters translated?'
|
||||
);
|
||||
|
||||
$query = $this->_em->getRepository(GH7820Line::class)
|
||||
->createQueryBuilder('l')
|
||||
->orderBy('l.lineNumber', Criteria::ASC);
|
||||
|
||||
self::assertSame(
|
||||
self::SONG,
|
||||
array_map(static function (GH7820Line $line): string {
|
||||
return $line->toString();
|
||||
}, iterator_to_array(new Paginator($query))),
|
||||
'Expected to return expected data even when DQL -> SQL translation is present in cache. Were SQL parameters translated again?'
|
||||
);
|
||||
return array_map(static function (GH7820Line $line): string {
|
||||
return $line->toString();
|
||||
}, iterator_to_array(new Paginator($query)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ final class GH7875Test extends OrmFunctionalTestCase
|
||||
}
|
||||
|
||||
/** @return array<array<string|callable|null>> */
|
||||
public function provideUpdateSchemaSqlWithSchemaAssetFilter(): array
|
||||
public static function provideUpdateSchemaSqlWithSchemaAssetFilter(): array
|
||||
{
|
||||
return [
|
||||
['/^(?!my_enti)/', null],
|
||||
|
||||
102
tests/Doctrine/Tests/ORM/Functional/Ticket/GH8127Test.php
Normal file
102
tests/Doctrine/Tests/ORM/Functional/Ticket/GH8127Test.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH8127Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(
|
||||
GH8127Root::class,
|
||||
GH8127Middle::class,
|
||||
GH8127Leaf::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider queryClasses
|
||||
*/
|
||||
public function testLoadFieldsFromAllClassesInHierarchy(string $queryClass): void
|
||||
{
|
||||
$entity = new GH8127Leaf();
|
||||
$entity->root = 'root';
|
||||
$entity->middle = 'middle';
|
||||
$entity->leaf = 'leaf';
|
||||
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$loadedEntity = $this->_em->find($queryClass, $entity->id);
|
||||
|
||||
self::assertSame('root', $loadedEntity->root);
|
||||
self::assertSame('middle', $loadedEntity->middle);
|
||||
self::assertSame('leaf', $loadedEntity->leaf);
|
||||
}
|
||||
|
||||
public static function queryClasses(): array
|
||||
{
|
||||
return [
|
||||
'query via root entity' => [GH8127Root::class],
|
||||
'query via leaf entity' => [GH8127Leaf::class],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="root")
|
||||
* @ORM\InheritanceType("JOINED")
|
||||
* @ORM\DiscriminatorMap({ "leaf": "GH8127Leaf" })
|
||||
*/
|
||||
abstract class GH8127Root
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @ORM\Column(type="integer")
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
abstract class GH8127Middle extends GH8127Root
|
||||
{
|
||||
/**
|
||||
* @ORM\Column
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $middle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH8127Leaf extends GH8127Middle
|
||||
{
|
||||
/**
|
||||
* @ORM\Column
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $leaf;
|
||||
}
|
||||
121
tests/Doctrine/Tests/ORM/Functional/Ticket/GH8415Test.php
Normal file
121
tests/Doctrine/Tests/ORM/Functional/Ticket/GH8415Test.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH8415Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema(
|
||||
[
|
||||
GH8415BaseClass::class,
|
||||
GH8415MiddleMappedSuperclass::class,
|
||||
GH8415LeafClass::class,
|
||||
GH8415AssociationTarget::class,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function testAssociationIsBasedOnBaseClass(): void
|
||||
{
|
||||
$target = new GH8415AssociationTarget();
|
||||
$leaf = new GH8415LeafClass();
|
||||
$leaf->baseField = 'base';
|
||||
$leaf->middleField = 'middle';
|
||||
$leaf->leafField = 'leaf';
|
||||
$leaf->target = $target;
|
||||
|
||||
$this->_em->persist($target);
|
||||
$this->_em->persist($leaf);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$query = $this->_em->createQuery('SELECT leaf FROM Doctrine\Tests\ORM\Functional\Ticket\GH8415LeafClass leaf JOIN leaf.target t');
|
||||
$result = $query->getOneOrNullResult();
|
||||
|
||||
$this->assertInstanceOf(GH8415LeafClass::class, $result);
|
||||
$this->assertSame('base', $result->baseField);
|
||||
$this->assertSame('middle', $result->middleField);
|
||||
$this->assertSame('leaf', $result->leafField);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH8415AssociationTarget
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\InheritanceType("JOINED")
|
||||
* @ORM\DiscriminatorColumn(name="discriminator", type="string")
|
||||
* @ORM\DiscriminatorMap({"1" = "Doctrine\Tests\ORM\Functional\Ticket\GH8415BaseClass", "2" = "Doctrine\Tests\ORM\Functional\Ticket\GH8415LeafClass"})
|
||||
*/
|
||||
class GH8415BaseClass
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="GH8415AssociationTarget")
|
||||
*
|
||||
* @var GH8415AssociationTarget
|
||||
*/
|
||||
public $target;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $baseField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass
|
||||
*/
|
||||
class GH8415MiddleMappedSuperclass extends GH8415BaseClass
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $middleField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH8415LeafClass extends GH8415MiddleMappedSuperclass
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $leafField;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
class GH8415ToManyAssociationTest extends OrmTestCase
|
||||
{
|
||||
public function testToManyAssociationOnBaseClassAllowedWhenThereAreMappedSuperclassesAsChildren(): void
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
$em = $this->getTestEntityManager();
|
||||
$em->getClassMetadata(GH8415ToManyLeafClass::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH8415ToManyAssociationTarget
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="GH8415ToManyBaseClass", inversedBy="targets")
|
||||
*
|
||||
* @var GH8415ToManyBaseClass
|
||||
*/
|
||||
public $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorColumn(name="discriminator", type="string")
|
||||
* @ORM\DiscriminatorMap({"1" = "Doctrine\Tests\ORM\Functional\Ticket\GH8415ToManyBaseClass", "2" = "Doctrine\Tests\ORM\Functional\Ticket\GH8415ToManyLeafClass"})
|
||||
*/
|
||||
class GH8415ToManyBaseClass
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="GH8415ToManyAssociationTarget", mappedBy="base")
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
public $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass
|
||||
*/
|
||||
class GH8415ToManyMappedSuperclass extends GH8415ToManyBaseClass
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH8415ToManyLeafClass extends GH8415ToManyMappedSuperclass
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $leafField;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class GH9230Test extends OrmFunctionalTestCase
|
||||
/**
|
||||
* This does not work before the fix in PR#9663, but is does work after the fix is applied
|
||||
*/
|
||||
public function failingValuesBeforeFix(): array
|
||||
public static function failingValuesBeforeFix(): array
|
||||
{
|
||||
return [
|
||||
'string=""' => ['name', '', 'test name'],
|
||||
@@ -66,7 +66,7 @@ class GH9230Test extends OrmFunctionalTestCase
|
||||
/**
|
||||
* This already works before the fix in PR#9663 is applied because none of these are falsy values in php
|
||||
*/
|
||||
public function succeedingValuesBeforeFix(): array
|
||||
public static function succeedingValuesBeforeFix(): array
|
||||
{
|
||||
return [
|
||||
'string="test"' => ['name', 'test', 'test2'],
|
||||
|
||||
77
tests/Doctrine/Tests/ORM/Functional/Ticket/GH9516Test.php
Executable file
77
tests/Doctrine/Tests/ORM/Functional/Ticket/GH9516Test.php
Executable file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\MappedSuperclass;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH9516Test extends OrmFunctionalTestCase
|
||||
{
|
||||
public function testEntityCanHaveInverseOneToManyAssociationWithChildMappedSuperclass(): void
|
||||
{
|
||||
$sportsCarMetadata = $this->_em->getClassMetadata(GH9516SportsCar::class);
|
||||
$this->assertTrue($sportsCarMetadata->hasAssociation('passengers'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class GH9516Passenger
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @var int $id
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="GH9516Vehicle", inversedBy="passengers")
|
||||
* @var GH9516Vehicle $vehicle
|
||||
*/
|
||||
private $vehicle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\DiscriminatorColumn(name="type", type="string")
|
||||
* @ORM\DiscriminatorMap({ "sports" = "\Doctrine\Tests\ORM\Functional\Ticket\GH9516SportsCar" })
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
*
|
||||
* @Entity
|
||||
*/
|
||||
abstract class GH9516Vehicle
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @var int $id
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="GH9516Passenger", mappedBy="vehicle")
|
||||
* @var GH9516Passenger[] $passengers
|
||||
*/
|
||||
private $passengers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @MappedSuperclass
|
||||
*/
|
||||
abstract class GH9516Car extends GH9516Vehicle
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class GH9516SportsCar extends GH9516Car
|
||||
{
|
||||
}
|
||||
@@ -345,7 +345,7 @@ class ValueObjectsTest extends OrmFunctionalTestCase
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{string, string}> */
|
||||
public function getInfiniteEmbeddableNestingData(): array
|
||||
public static function getInfiniteEmbeddableNestingData(): array
|
||||
{
|
||||
return [
|
||||
['DDCInfiniteNestingEmbeddable', 'DDCInfiniteNestingEmbeddable'],
|
||||
|
||||
@@ -17,7 +17,7 @@ use Doctrine\Tests\Models\Forum\ForumCategory;
|
||||
class ArrayHydratorTest extends HydrationTestCase
|
||||
{
|
||||
/** @psalm-return list<array{mixed}> */
|
||||
public function provideDataForUserEntityResult(): array
|
||||
public static function provideDataForUserEntityResult(): array
|
||||
{
|
||||
return [
|
||||
[0],
|
||||
|
||||
@@ -29,13 +29,14 @@ use Doctrine\Tests\Models\Hydration\SimpleEntity;
|
||||
use Doctrine\Tests\PHPUnitCompatibility\MockBuilderCompatibilityTools;
|
||||
|
||||
use function count;
|
||||
use function property_exists;
|
||||
|
||||
class ObjectHydratorTest extends HydrationTestCase
|
||||
{
|
||||
use MockBuilderCompatibilityTools;
|
||||
|
||||
/** @psalm-return list<array{mixed}> */
|
||||
public function provideDataForUserEntityResult(): array
|
||||
public static function provideDataForUserEntityResult(): array
|
||||
{
|
||||
return [
|
||||
[0],
|
||||
@@ -44,7 +45,7 @@ class ObjectHydratorTest extends HydrationTestCase
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{mixed, mixed}> */
|
||||
public function provideDataForMultipleRootEntityResult(): array
|
||||
public static function provideDataForMultipleRootEntityResult(): array
|
||||
{
|
||||
return [
|
||||
[0, 0],
|
||||
@@ -55,7 +56,7 @@ class ObjectHydratorTest extends HydrationTestCase
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{mixed}> */
|
||||
public function provideDataForProductEntityResult(): array
|
||||
public static function provideDataForProductEntityResult(): array
|
||||
{
|
||||
return [
|
||||
[0],
|
||||
@@ -927,10 +928,10 @@ class ObjectHydratorTest extends HydrationTestCase
|
||||
self::assertEquals(1, $result[0]->getId());
|
||||
self::assertEquals(2, $result[1]->getId());
|
||||
|
||||
self::assertObjectHasAttribute('boards', $result[0]);
|
||||
self::assertTrue(property_exists($result[0], 'boards'));
|
||||
self::assertEquals(3, count($result[0]->boards));
|
||||
|
||||
self::assertObjectHasAttribute('boards', $result[1]);
|
||||
self::assertTrue(property_exists($result[1], 'boards'));
|
||||
self::assertEquals(1, count($result[1]->boards));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class AssignedGeneratorTest extends OrmTestCase
|
||||
$this->assignedGen->generateId($this->entityManager, $entity);
|
||||
}
|
||||
|
||||
public function entitiesWithoutId(): array
|
||||
public static function entitiesWithoutId(): array
|
||||
{
|
||||
return [
|
||||
'single' => [new AssignedSingleIdEntity()],
|
||||
|
||||
@@ -154,7 +154,7 @@ class HydrationCompleteHandlerTest extends TestCase
|
||||
}
|
||||
|
||||
/** @psalm-return list<array{int}> */
|
||||
public function invocationFlagProvider(): array
|
||||
public static function invocationFlagProvider(): array
|
||||
{
|
||||
return [
|
||||
[ListenersInvoker::INVOKE_LISTENERS],
|
||||
|
||||
@@ -276,7 +276,7 @@ class AnnotationDriverTest extends MappingDriverTestCase
|
||||
self::assertSame($expectedLength, $metadata->discriminatorColumn['length']);
|
||||
}
|
||||
|
||||
public function provideDiscriminatorColumnTestcases(): Generator
|
||||
public static function provideDiscriminatorColumnTestcases(): Generator
|
||||
{
|
||||
yield [DiscriminatorColumnWithNullLength::class, 255];
|
||||
yield [DiscriminatorColumnWithNoLength::class, 255];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user