mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
495cd06b9a | ||
|
|
fd7a14ad22 | ||
|
|
0d3ce5d4f8 | ||
|
|
f01d107edc | ||
|
|
3cc30c4024 | ||
|
|
d2de4ec03c | ||
|
|
64ee76e94e | ||
|
|
8e20e1598e | ||
|
|
24df74d61d | ||
|
|
442f073d25 | ||
|
|
a5161e9485 | ||
|
|
ddc7d953b9 | ||
|
|
db51ed4f4c | ||
|
|
6d27797b2e | ||
|
|
89250b8ca2 | ||
|
|
f7e4b61459 | ||
|
|
6b0afdbd58 | ||
|
|
d3cf17b26d | ||
|
|
bc61d7d21e | ||
|
|
5213228a64 | ||
|
|
dca7ddf969 | ||
|
|
e781639812 | ||
|
|
7848417488 | ||
|
|
56e5856ad7 | ||
|
|
b5987ad29a | ||
|
|
385bdd33f1 | ||
|
|
8c513a6523 | ||
|
|
1413b496d7 | ||
|
|
3b3056f910 | ||
|
|
fa5c37e972 | ||
|
|
f3e36debfe | ||
|
|
ca7abd04a2 | ||
|
|
a555626150 | ||
|
|
ec7a8a7a0f | ||
|
|
e94fa8588d | ||
|
|
f26946b477 | ||
|
|
efc83bce8e | ||
|
|
450cae2caa | ||
|
|
81ddeb426c | ||
|
|
42e63bf358 | ||
|
|
4978e0e336 | ||
|
|
eee87c376d | ||
|
|
0b9060c728 | ||
|
|
514f6b8c28 | ||
|
|
c1018fe299 | ||
|
|
075824f5b5 | ||
|
|
d6f4834476 | ||
|
|
9dadffe270 | ||
|
|
b6e7e6d723 | ||
|
|
710937d6f8 | ||
|
|
5d2d6642c8 | ||
|
|
4d56711d8c | ||
|
|
a157bc3fb3 | ||
|
|
1aeab391c7 | ||
|
|
a4ecd02349 | ||
|
|
bb21865cba | ||
|
|
606da9280d | ||
|
|
21708e43c4 | ||
|
|
8c59828f6c | ||
|
|
0877ecbe56 | ||
|
|
8eb69922e6 | ||
|
|
e9b6fd89a4 | ||
|
|
01a14327d2 | ||
|
|
4887359827 | ||
|
|
2df1071e7a | ||
|
|
5afe9b80a8 | ||
|
|
7fc359c2bb | ||
|
|
853b9f75ae | ||
|
|
584c4aeed1 | ||
|
|
44d2a83e09 | ||
|
|
c9c5157fda | ||
|
|
dba90c1a91 | ||
|
|
5f079c2061 | ||
|
|
55d477dc50 | ||
|
|
4da8d3be96 | ||
|
|
4aadba65ce | ||
|
|
814d8d4d39 | ||
|
|
dc411954ad | ||
|
|
da29eb675c | ||
|
|
b17e52ba6b | ||
|
|
7ef4afc688 | ||
|
|
4e138903d0 | ||
|
|
efb50b9bdd | ||
|
|
70bcff7410 | ||
|
|
1989531d4f | ||
|
|
f778d8cf98 | ||
|
|
18b32ab9db | ||
|
|
d738ecfcfe | ||
|
|
aa3ff458c7 | ||
|
|
f76bab2b73 | ||
|
|
0e06d6b67d | ||
|
|
5114dcee0b | ||
|
|
8bc74c624a | ||
|
|
6c0a5ecbf9 | ||
|
|
5f6501f842 | ||
|
|
338deacb58 | ||
|
|
41f704cd96 | ||
|
|
5c74795893 | ||
|
|
8961bfe90c | ||
|
|
15e3a7e861 | ||
|
|
1280e005b6 | ||
|
|
79f53d5dae | ||
|
|
bf2937e63a | ||
|
|
ed212ab924 | ||
|
|
dd0e02e912 | ||
|
|
aad875eea1 | ||
|
|
84ab535e56 | ||
|
|
a72a0c3597 | ||
|
|
6217285544 | ||
|
|
330c0bc67e | ||
|
|
b5595ca041 | ||
|
|
b779b112f3 | ||
|
|
04e08640fb | ||
|
|
aa27b3a35f | ||
|
|
d76fc4ebf6 | ||
|
|
ae60cf005f | ||
|
|
ddd3066bc4 | ||
|
|
6662195936 | ||
|
|
d951aa05b9 | ||
|
|
be297b9fd3 | ||
|
|
4e137f77a5 | ||
|
|
a33aa15c79 | ||
|
|
255ce51526 | ||
|
|
38e47fdeab | ||
|
|
9ac063d879 | ||
|
|
b52a8f8b9e | ||
|
|
1b2771f964 | ||
|
|
9766b6b03e | ||
|
|
37c8953015 | ||
|
|
a199ca3002 | ||
|
|
ed34327941 | ||
|
|
b42cf99402 | ||
|
|
33a19f1bf3 | ||
|
|
b6669746b7 |
51
.github/workflows/documentation.yml
vendored
Normal file
51
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: "Documentation"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/documentation.yml
|
||||
- docs/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/documentation.yml
|
||||
- docs/**
|
||||
|
||||
jobs:
|
||||
validate-with-guides:
|
||||
name: "Validate documentation with phpDocumentor/guides"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v3"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
|
||||
- name: "Remove existing composer file"
|
||||
run: "rm composer.json"
|
||||
|
||||
- name: "Require phpdocumentor/guides-cli"
|
||||
run: "composer require --dev phpdocumentor/guides-cli dev-main@dev --no-update"
|
||||
|
||||
- name: "Configure minimum stability"
|
||||
run: "composer config minimum-stability dev"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Add dummy title to the sidebar"
|
||||
run: |
|
||||
printf '%s\n%s\n\n%s\n' "Dummy title" "===========" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
|
||||
|
||||
- name: "Run guides-cli"
|
||||
run: "vendor/bin/guides -vvv --no-progress docs/en /tmp/test 2>&1 | ( ! grep WARNING )"
|
||||
28
UPGRADE.md
28
UPGRADE.md
@@ -1,3 +1,31 @@
|
||||
# Upgrade to 2.16
|
||||
|
||||
## Deprecated `\Doctrine\ORM\Internal\CommitOrderCalculator` and related classes
|
||||
|
||||
With changes made to the commit order computation, the internal classes
|
||||
`\Doctrine\ORM\Internal\CommitOrderCalculator`, `\Doctrine\ORM\Internal\CommitOrder\Edge`,
|
||||
`\Doctrine\ORM\Internal\CommitOrder\Vertex` and `\Doctrine\ORM\Internal\CommitOrder\VertexState`
|
||||
have been deprecated and will be removed in ORM 3.0.
|
||||
|
||||
## Deprecated returning post insert IDs from `EntityPersister::executeInserts()`
|
||||
|
||||
Persisters implementing `\Doctrine\ORM\Persisters\Entity\EntityPersister` should no longer
|
||||
return an array of post insert IDs from their `::executeInserts()` method. Make the
|
||||
persister call `Doctrine\ORM\UnitOfWork::assignPostInsertId()` instead.
|
||||
|
||||
## Changing the way how reflection-based mapping drivers report fields, deprecated the "old" mode
|
||||
|
||||
In ORM 3.0, a change will be made regarding how the `AttributeDriver` reports field mappings.
|
||||
This change is necessary to be able to detect (and reject) some invalid mapping configurations.
|
||||
|
||||
To avoid surprises during 2.x upgrades, the new mode is opt-in. It can be activated on the
|
||||
`AttributeDriver` and `AnnotationDriver` by setting the `$reportFieldsWhereDeclared`
|
||||
constructor parameter to `true`. It will cause `MappingException`s to be thrown when invalid
|
||||
configurations are detected.
|
||||
|
||||
Not enabling the new mode will cause a deprecation notice to be raised. In ORM 3.0,
|
||||
only the new mode will be available.
|
||||
|
||||
# Upgrade to 2.15
|
||||
|
||||
## Deprecated configuring `JoinColumn` on the inverse side of one-to-one associations
|
||||
|
||||
@@ -42,14 +42,14 @@
|
||||
"doctrine/annotations": "^1.13 || ^2",
|
||||
"doctrine/coding-standard": "^9.0.2 || ^12.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.10.18",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.10.25",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"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.12.0"
|
||||
"vimeo/psalm": "4.30.0 || 5.13.1"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 3.0"
|
||||
|
||||
@@ -18,8 +18,8 @@ Doctrine ORM don't panic. You can get help from different sources:
|
||||
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
|
||||
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
|
||||
|
||||
If you need more structure over the different topics you can browse the :doc:`table
|
||||
of contents <toc>`.
|
||||
If you need more structure over the different topics you can browse the table
|
||||
of contents.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
@@ -34,32 +34,32 @@ Mapping Objects onto a Database
|
||||
-------------------------------
|
||||
|
||||
* **Mapping**:
|
||||
:doc:`Objects <reference/basic-mapping>` |
|
||||
:doc:`Associations <reference/association-mapping>` |
|
||||
:doc:`Objects <reference/basic-mapping>` \|
|
||||
:doc:`Associations <reference/association-mapping>` \|
|
||||
:doc:`Inheritance <reference/inheritance-mapping>`
|
||||
|
||||
* **Drivers**:
|
||||
:doc:`Docblock Annotations <reference/annotations-reference>` |
|
||||
:doc:`Attributes <reference/attributes-reference>` |
|
||||
:doc:`XML <reference/xml-mapping>` |
|
||||
:doc:`YAML <reference/yaml-mapping>` |
|
||||
:doc:`Docblock Annotations <reference/annotations-reference>` \|
|
||||
:doc:`Attributes <reference/attributes-reference>` \|
|
||||
:doc:`XML <reference/xml-mapping>` \|
|
||||
:doc:`YAML <reference/yaml-mapping>` \|
|
||||
:doc:`PHP <reference/php-mapping>`
|
||||
|
||||
Working with Objects
|
||||
--------------------
|
||||
|
||||
* **Basic Reference**:
|
||||
:doc:`Entities <reference/working-with-objects>` |
|
||||
:doc:`Associations <reference/working-with-associations>` |
|
||||
:doc:`Entities <reference/working-with-objects>` \|
|
||||
:doc:`Associations <reference/working-with-associations>` \|
|
||||
:doc:`Events <reference/events>`
|
||||
|
||||
* **Query Reference**:
|
||||
:doc:`DQL <reference/dql-doctrine-query-language>` |
|
||||
:doc:`QueryBuilder <reference/query-builder>` |
|
||||
:doc:`DQL <reference/dql-doctrine-query-language>` \|
|
||||
:doc:`QueryBuilder <reference/query-builder>` \|
|
||||
:doc:`Native SQL <reference/native-sql>`
|
||||
|
||||
* **Internals**:
|
||||
:doc:`Internals explained <reference/unitofwork>` |
|
||||
:doc:`Internals explained <reference/unitofwork>` \|
|
||||
:doc:`Associations <reference/unitofwork-associations>`
|
||||
|
||||
Advanced Topics
|
||||
@@ -102,20 +102,20 @@ Cookbook
|
||||
--------
|
||||
|
||||
* **Patterns**:
|
||||
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
|
||||
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
|
||||
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
|
||||
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
|
||||
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||
|
||||
* **DQL Extension Points**:
|
||||
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
|
||||
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` \|
|
||||
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
|
||||
|
||||
* **Implementation**:
|
||||
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
|
||||
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
|
||||
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
|
||||
:doc:`Validation <cookbook/validation-of-entities>` |
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` |
|
||||
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` \|
|
||||
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` \|
|
||||
:doc:`Working with DateTime <cookbook/working-with-datetime>` \|
|
||||
:doc:`Validation <cookbook/validation-of-entities>` \|
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` \|
|
||||
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
|
||||
|
||||
* **Hidden Gems**
|
||||
@@ -124,5 +124,3 @@ Cookbook
|
||||
* **Custom Datatypes**
|
||||
:doc:`MySQL Enums <cookbook/mysql-enums>`
|
||||
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
|
||||
|
||||
.. include:: toc.rst
|
||||
|
||||
@@ -311,10 +311,12 @@ Reference Proxies
|
||||
|
||||
The method ``EntityManager#getReference($entityName, $identifier)``
|
||||
lets you obtain a reference to an entity for which the identifier
|
||||
is known, without loading that entity from the database. This is
|
||||
useful, for example, as a performance enhancement, when you want to
|
||||
establish an association to an entity for which you have the
|
||||
identifier. You could simply do this:
|
||||
is known, without necessarily loading that entity from the database.
|
||||
This is useful, for example, as a performance enhancement, when you
|
||||
want to establish an association to an entity for which you have the
|
||||
identifier.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -324,15 +326,33 @@ identifier. You could simply do this:
|
||||
$item = $em->getReference('MyProject\Model\Item', $itemId);
|
||||
$cart->addItem($item);
|
||||
|
||||
Here, we added an Item to a Cart without loading the Item from the
|
||||
database. If you access any state that isn't yet available in the
|
||||
Item instance, the proxying mechanism would fully initialize the
|
||||
object's state transparently from the database. Here
|
||||
$item is actually an instance of the proxy class that was generated
|
||||
for the Item class but your code does not need to care. In fact it
|
||||
**should not care**. Proxy objects should be transparent to your
|
||||
Whether the object being returned from ``EntityManager#getReference()``
|
||||
is a proxy or a direct instance of the entity class may depend on different
|
||||
factors, including whether the entity has already been loaded into memory
|
||||
or entity inheritance being used. But your code does not need to care
|
||||
and in fact it **should not care**. Proxy objects should be transparent to your
|
||||
code.
|
||||
|
||||
When using the ``EntityManager#getReference()`` method, you need to be aware
|
||||
of a few peculiarities.
|
||||
|
||||
At the best case, the ORM can avoid querying the database at all. But, that
|
||||
also means that this method will not throw an exception when an invalid value
|
||||
for the ``$identifier`` parameter is passed. ``$identifier`` values are
|
||||
not checked and there is no guarantee that the requested entity instance even
|
||||
exists – the method will still return a proxy object.
|
||||
|
||||
Its only when the proxy has to be fully initialized or associations cannot
|
||||
be written to the database that invalid ``$identifier`` values may lead to
|
||||
exceptions.
|
||||
|
||||
The ``EntityManager#getReference()`` is mostly useful when you only
|
||||
need a reference to some entity to make an association, like in the example
|
||||
above. In that case, it can save you from loading data from the database
|
||||
that you don't need. But remember – as soon as you read any property values
|
||||
besides those making up the ID, a database request will be made to initialize
|
||||
all fields.
|
||||
|
||||
Association proxies
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ 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>`.
|
||||
on :doc:`inheritance mapping </reference/inheritance-mapping>`.
|
||||
|
||||
Transient Classes
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -460,7 +460,7 @@ Here is the list of possible generation strategies:
|
||||
a new entity is passed to ``EntityManager#persist``. NONE is the
|
||||
same as leaving off the ``#[GeneratedValue]`` entirely.
|
||||
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
|
||||
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
|
||||
It will allow you to pass a :ref:`class of your own to generate the identifiers.<annref_customidgenerator>`
|
||||
|
||||
Sequence Generator
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -104,7 +104,7 @@ Inside the ``ORMSetup`` methods several assumptions are made:
|
||||
In order to have ``ORMSetup`` configure the cache automatically, the library ``symfony/cache``
|
||||
has to be installed as a dependency.
|
||||
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration </reference/advanced-configuration>` section.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -1336,8 +1336,8 @@ There are situations when a query you want to execute returns a
|
||||
very large result-set that needs to be processed. All the
|
||||
previously described hydration modes completely load a result-set
|
||||
into memory which might not be feasible with large result sets. See
|
||||
the `Batch Processing <batch-processing.html>`_ section on details how
|
||||
to iterate large result sets.
|
||||
the :doc:`Batch Processing </reference/batch-processing>` section on
|
||||
details how to iterate large result sets.
|
||||
|
||||
Functions
|
||||
~~~~~~~~~
|
||||
|
||||
@@ -281,10 +281,10 @@ specific to a particular entity class's lifecycle.
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="User">
|
||||
<!-- ... -->
|
||||
<lifecycle-callbacks>
|
||||
@@ -707,8 +707,8 @@ not directly mapped by Doctrine.
|
||||
``UPDATE`` statement.
|
||||
- The ``postPersist`` event occurs for an entity after
|
||||
the entity has been made persistent. It will be invoked after the
|
||||
database insert operations. Generated primary key values are
|
||||
available in the postPersist event.
|
||||
database insert operation for that entity. A generated primary key value for
|
||||
the entity will be available in the postPersist event.
|
||||
- The ``postRemove`` event occurs for an entity after the
|
||||
entity has been deleted. It will be invoked after the database
|
||||
delete operations. It is not called for a DQL ``DELETE`` statement.
|
||||
|
||||
@@ -93,3 +93,34 @@ object.
|
||||
want to refresh or reload an object after having modified a filter or the
|
||||
FilterCollection, then you should clear the EntityManager and re-fetch your
|
||||
entities, having the new rules for filtering applied.
|
||||
|
||||
|
||||
Suspending/Restoring Filters
|
||||
----------------------------
|
||||
When a filter is disabled, the instance is fully deleted and all the filter
|
||||
parameters previously set are lost. Then, if you enable it again, a new filter
|
||||
is created without the previous filter parameters. If you want to keep a filter
|
||||
(in order to use it later) but temporary disable it, you'll need to use the
|
||||
``FilterCollection#suspend($name)`` and ``FilterCollection#restore($name)``
|
||||
methods instead.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$filter = $em->getFilters()->enable("locale");
|
||||
$filter->setParameter('locale', 'en');
|
||||
|
||||
// Temporary suspend the filter
|
||||
$filter = $em->getFilters()->suspend("locale");
|
||||
|
||||
// Do things
|
||||
|
||||
// Then restore it, the locale parameter will still be set
|
||||
$filter = $em->getFilters()->restore("locale");
|
||||
|
||||
.. warning::
|
||||
If you enable a previously disabled filter, doctrine will create a new
|
||||
one without keeping any of the previously parameter set with
|
||||
``SQLFilter#setParameter()`` or ``SQLFilter#getParameterList()``. If you
|
||||
want to restore the previously disabled filter instead, you must use the
|
||||
``FilterCollection#restore($name)`` method.
|
||||
|
||||
@@ -91,7 +91,7 @@ Apply Best Practices
|
||||
A lot of the points mentioned in the Best Practices chapter will
|
||||
also positively affect the performance of Doctrine.
|
||||
|
||||
See :doc:`Best Practices <reference/best-practices>`
|
||||
See :doc:`Best Practices </reference/best-practices>`
|
||||
|
||||
Change Tracking policies
|
||||
------------------------
|
||||
|
||||
@@ -35,7 +35,7 @@ have to be used.
|
||||
superclass, since they require the "many" side to hold the foreign
|
||||
key.
|
||||
|
||||
It is, however, possible to use the :doc:`ResolveTargetEntityListener <cookbook/resolve-target-entity-listener>`
|
||||
It is, however, possible to use the :doc:`ResolveTargetEntityListener </cookbook/resolve-target-entity-listener>`
|
||||
to replace references to a mapped superclass with an entity class at runtime.
|
||||
As long as there is only one entity subclass inheriting from the mapped
|
||||
superclass and all references to the mapped superclass are resolved to that
|
||||
@@ -45,7 +45,7 @@ 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
|
||||
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.
|
||||
|
||||
@@ -60,7 +60,7 @@ have to be used.
|
||||
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.
|
||||
in the :doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -380,7 +380,7 @@ It is not supported to use overrides in entity inheritance scenarios.
|
||||
.. note::
|
||||
|
||||
When using traits, make sure not to miss the warnings given in the
|
||||
:doc:`Limitations and Known Issues<reference/limitations-and-known-issues>` chapter.
|
||||
:doc:`Limitations and Known Issues</reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
|
||||
Association Override
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
|
||||
The installation chapter has moved to :doc:`Installation and Configuration </reference/configuration>`.
|
||||
|
||||
@@ -145,7 +145,7 @@ 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>`.
|
||||
: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
|
||||
@@ -162,7 +162,7 @@ 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
|
||||
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"
|
||||
|
||||
@@ -578,8 +578,6 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
|
||||
not (no effect on the ``where`` and ``having`` DQL query parts,
|
||||
which always override all previously defined items)
|
||||
|
||||
-
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
@@ -322,7 +322,10 @@ level cache region.
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="Country">
|
||||
<cache usage="READ_ONLY" region="my_entity_region" />
|
||||
<id name="id" type="integer" column="id">
|
||||
@@ -427,7 +430,10 @@ It caches the primary keys of association and cache each element will be cached
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="State">
|
||||
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
|
||||
@@ -37,8 +37,8 @@ will still end up with the same reference:
|
||||
public function testIdentityMapReference(): void
|
||||
{
|
||||
$objectA = $this->entityManager->getReference('EntityName', 1);
|
||||
// check for proxyinterface
|
||||
$this->assertInstanceOf('Doctrine\Persistence\Proxy', $objectA);
|
||||
// check entity is not initialized
|
||||
$this->assertTrue($this->entityManager->isUninitializedObject($objectA));
|
||||
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ setup for the latest code in trunk.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
...
|
||||
@@ -102,9 +102,9 @@ of several common elements:
|
||||
|
||||
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
@@ -769,9 +769,9 @@ entity relationship. You can define this in XML with the "association-key" attri
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
Welcome to Doctrine 2 ORM's documentation!
|
||||
==========================================
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
tutorials/getting-started
|
||||
tutorials/getting-started-database
|
||||
tutorials/getting-started-models
|
||||
tutorials/working-with-indexed-associations
|
||||
tutorials/extra-lazy-associations
|
||||
tutorials/composite-primary-keys
|
||||
tutorials/ordered-associations
|
||||
tutorials/override-field-association-mappings-in-subclasses
|
||||
tutorials/pagination.rst
|
||||
tutorials/embeddables.rst
|
||||
|
||||
Reference Guide
|
||||
---------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:numbered:
|
||||
|
||||
reference/architecture
|
||||
reference/configuration.rst
|
||||
reference/faq
|
||||
reference/basic-mapping
|
||||
reference/association-mapping
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
reference/transactions-and-concurrency
|
||||
reference/batch-processing
|
||||
reference/dql-doctrine-query-language
|
||||
reference/query-builder
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/partial-objects
|
||||
reference/annotations-reference
|
||||
reference/attributes-reference
|
||||
reference/xml-mapping
|
||||
reference/yaml-mapping
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
reference/tools
|
||||
reference/metadata-drivers
|
||||
reference/best-practices
|
||||
reference/limitations-and-known-issues
|
||||
tutorials/pagination
|
||||
reference/filters
|
||||
reference/namingstrategy
|
||||
reference/advanced-configuration
|
||||
reference/second-level-cache
|
||||
reference/security
|
||||
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
cookbook/aggregate-fields
|
||||
cookbook/custom-mapping-types
|
||||
cookbook/decorator-pattern
|
||||
cookbook/dql-custom-walkers
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
cookbook/validation-of-entities
|
||||
cookbook/working-with-datetime
|
||||
cookbook/mysql-enums
|
||||
cookbook/advanced-field-value-conversion-using-custom-mapping-types
|
||||
cookbook/entities-in-session
|
||||
|
||||
@@ -85,9 +85,9 @@ and year of production as primary keys:
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="VehicleCatalogue\Model\Car">
|
||||
@@ -267,9 +267,9 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
|
||||
@@ -85,9 +85,9 @@ switch to extra lazy as shown in these examples:
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
|
||||
|
||||
@@ -102,8 +102,7 @@ Install Doctrine using the Composer Dependency Management tool, by calling:
|
||||
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
|
||||
into the ``vendor`` directory.
|
||||
|
||||
Add the following directories:
|
||||
::
|
||||
Add the following directories::
|
||||
|
||||
doctrine2-tutorial
|
||||
|-- config
|
||||
@@ -558,10 +557,10 @@ methods, but you only need to choose one.
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- config/xml/Product.dcm.xml -->
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Product" table="products">
|
||||
<id name="id" type="integer">
|
||||
@@ -1139,10 +1138,10 @@ the ``Product`` before:
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- config/xml/Bug.dcm.xml -->
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Bug" table="bugs">
|
||||
<id name="id" type="integer">
|
||||
@@ -1294,10 +1293,10 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- config/xml/User.dcm.xml -->
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User" table="users">
|
||||
<id name="id" type="integer">
|
||||
@@ -1344,8 +1343,7 @@ means the join details have already been defined on the owning
|
||||
side. Therefore we only have to specify the property on the Bug
|
||||
class that holds the owning sides.
|
||||
|
||||
Update your database schema by running:
|
||||
::
|
||||
Update your database schema by running::
|
||||
|
||||
$ php bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
@@ -1819,9 +1817,9 @@ we have to adjust the metadata slightly.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Bug" table="bugs" repository-class="BugRepository">
|
||||
|
||||
@@ -161,9 +161,9 @@ The code and mappings for the Market entity looks like this:
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Market">
|
||||
@@ -278,9 +278,9 @@ here are the code and mappings for it:
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
|
||||
|
||||
@@ -1010,7 +1010,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
|
||||
*
|
||||
* @return mixed The scalar result.
|
||||
* @return bool|float|int|string The scalar result.
|
||||
*
|
||||
* @throws NoResultException If the query returned no result.
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
|
||||
@@ -16,7 +16,6 @@ use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
@@ -345,7 +344,7 @@ class DefaultQueryCache implements QueryCache
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
|
||||
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
|
||||
|
||||
if (! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
|
||||
if (! $this->uow->isUninitializedObject($assocValue) && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
|
||||
// Entity put fail
|
||||
if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
|
||||
return null;
|
||||
|
||||
@@ -23,6 +23,7 @@ use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function serialize;
|
||||
use function sha1;
|
||||
@@ -314,7 +315,13 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function executeInserts()
|
||||
{
|
||||
$this->queuedCache['insert'] = $this->persister->getInserts();
|
||||
// The commit order/foreign key relationships may make it necessary that multiple calls to executeInsert()
|
||||
// are performed, so collect all the new entities.
|
||||
$newInserts = $this->persister->getInserts();
|
||||
|
||||
if ($newInserts) {
|
||||
$this->queuedCache['insert'] = array_merge($this->queuedCache['insert'] ?? [], $newInserts);
|
||||
}
|
||||
|
||||
return $this->persister->executeInserts();
|
||||
}
|
||||
|
||||
@@ -62,8 +62,6 @@ use function trim;
|
||||
* It combines all configuration options from DBAL & ORM.
|
||||
*
|
||||
* Internal note: When adding a new configuration option just write a getter/setter pair.
|
||||
*
|
||||
* @psalm-import-type AutogenerateMode from ProxyFactory
|
||||
*/
|
||||
class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
@@ -95,8 +93,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @return int Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
|
||||
* @psalm-return AutogenerateMode
|
||||
* @return ProxyFactory::AUTOGENERATE_*
|
||||
*/
|
||||
public function getAutoGenerateProxyClasses()
|
||||
{
|
||||
@@ -106,9 +103,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Sets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @param bool|int $autoGenerate Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
|
||||
* @psalm-param bool|AutogenerateMode $autoGenerate
|
||||
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
|
||||
* @param bool|ProxyFactory::AUTOGENERATE_* $autoGenerate True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -164,7 +159,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @return AnnotationDriver
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
|
||||
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true, bool $reportFieldsWhereDeclared = false)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
@@ -203,15 +198,16 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
return new AnnotationDriver(
|
||||
$reader,
|
||||
(array) $paths
|
||||
(array) $paths,
|
||||
$reportFieldsWhereDeclared
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated No replacement planned.
|
||||
*
|
||||
* Adds a namespace under a certain alias.
|
||||
*
|
||||
* @deprecated No replacement planned.
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $namespace
|
||||
*
|
||||
|
||||
@@ -953,6 +953,14 @@ class EntityManager implements EntityManagerInterface
|
||||
$this->unitOfWork->initializeObject($obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isUninitializedObject($obj): bool
|
||||
{
|
||||
return $this->unitOfWork->isUninitializedObject($obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create EntityManager instances.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\CommitOrder;
|
||||
|
||||
/** @internal */
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
final class Edge
|
||||
{
|
||||
/**
|
||||
@@ -27,6 +32,13 @@ final class Edge
|
||||
|
||||
public function __construct(string $from, string $to, int $weight)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10547',
|
||||
'The %s class is deprecated and will be removed in ORM 3.0',
|
||||
self::class
|
||||
);
|
||||
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
$this->weight = $weight;
|
||||
|
||||
@@ -4,9 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\CommitOrder;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/** @internal */
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
final class Vertex
|
||||
{
|
||||
/**
|
||||
@@ -32,6 +36,13 @@ final class Vertex
|
||||
|
||||
public function __construct(string $hash, ClassMetadata $value)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10547',
|
||||
'The %s class is deprecated and will be removed in ORM 3.0',
|
||||
self::class
|
||||
);
|
||||
|
||||
$this->hash = $hash;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\CommitOrder;
|
||||
|
||||
/** @internal */
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
final class VertexState
|
||||
{
|
||||
public const NOT_VISITED = 0;
|
||||
@@ -13,5 +18,11 @@ final class VertexState
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10547',
|
||||
'The %s class is deprecated and will be removed in ORM 3.0',
|
||||
self::class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Internal\CommitOrder\Edge;
|
||||
use Doctrine\ORM\Internal\CommitOrder\Vertex;
|
||||
use Doctrine\ORM\Internal\CommitOrder\VertexState;
|
||||
@@ -17,6 +18,8 @@ use function array_reverse;
|
||||
* using a depth-first searching (DFS) to traverse the graph built in memory.
|
||||
* This algorithm have a linear running time based on nodes (V) and dependency
|
||||
* between the nodes (E), resulting in a computational complexity of O(V + E).
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class CommitOrderCalculator
|
||||
{
|
||||
@@ -45,6 +48,16 @@ class CommitOrderCalculator
|
||||
*/
|
||||
private $sortedNodeList = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10547',
|
||||
'The %s class is deprecated and will be removed in ORM 3.0',
|
||||
self::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for node (vertex) existence in graph.
|
||||
*
|
||||
|
||||
@@ -10,7 +10,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
use function array_fill_keys;
|
||||
use function array_keys;
|
||||
@@ -313,7 +312,6 @@ class ObjectHydrator extends AbstractHydrator
|
||||
* that belongs to a particular component/class. Afterwards, all these chunks
|
||||
* are processed, one after the other. For each chunk of class data only one of the
|
||||
* following code paths is executed:
|
||||
*
|
||||
* Path A: The data chunk belongs to a joined/associated object and the association
|
||||
* is collection-valued.
|
||||
* Path B: The data chunk belongs to a joined/associated object and the association
|
||||
@@ -440,7 +438,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
// PATH B: Single-valued association
|
||||
$reflFieldValue = $reflField->getValue($parentObject);
|
||||
|
||||
if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && ! $reflFieldValue->__isInitialized())) {
|
||||
if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || $this->_uow->isUninitializedObject($reflFieldValue)) {
|
||||
// we only need to take action if this value is null,
|
||||
// we refresh the entity or its an uninitialized proxy.
|
||||
if (isset($nonemptyComponents[$dqlAlias])) {
|
||||
@@ -458,9 +456,6 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
|
||||
$this->_uow->setOriginalEntityProperty(spl_object_id($element), $inverseAssoc['fieldName'], $parentObject);
|
||||
}
|
||||
} elseif ($parentClass === $targetClass && $relation['mappedBy']) {
|
||||
// Special case: bi-directional self-referencing one-one on the same class
|
||||
$targetClass->reflFields[$relationField]->setValue($element, $parentObject);
|
||||
}
|
||||
} else {
|
||||
// For sure bidirectional, as there is no inverse side in unidirectional mappings
|
||||
|
||||
165
lib/Doctrine/ORM/Internal/TopologicalSort.php
Normal file
165
lib/Doctrine/ORM/Internal/TopologicalSort.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal;
|
||||
|
||||
use Doctrine\ORM\Internal\TopologicalSort\CycleDetectedException;
|
||||
|
||||
use function array_keys;
|
||||
use function array_reverse;
|
||||
use function array_unshift;
|
||||
use function spl_object_id;
|
||||
|
||||
/**
|
||||
* TopologicalSort implements topological sorting, which is an ordering
|
||||
* algorithm for directed graphs (DG) using a depth-first searching (DFS)
|
||||
* to traverse the graph built in memory.
|
||||
* This algorithm has a linear running time based on nodes (V) and edges
|
||||
* between the nodes (E), resulting in a computational complexity of O(V + E).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TopologicalSort
|
||||
{
|
||||
private const NOT_VISITED = 1;
|
||||
private const IN_PROGRESS = 2;
|
||||
private const VISITED = 3;
|
||||
|
||||
/**
|
||||
* Array of all nodes, indexed by object ids.
|
||||
*
|
||||
* @var array<int, object>
|
||||
*/
|
||||
private $nodes = [];
|
||||
|
||||
/**
|
||||
* DFS state for the different nodes, indexed by node object id and using one of
|
||||
* this class' constants as value.
|
||||
*
|
||||
* @var array<int, self::*>
|
||||
*/
|
||||
private $states = [];
|
||||
|
||||
/**
|
||||
* Edges between the nodes. The first-level key is the object id of the outgoing
|
||||
* node; the second array maps the destination node by object id as key. The final
|
||||
* boolean value indicates whether the edge is optional or not.
|
||||
*
|
||||
* @var array<int, array<int, bool>>
|
||||
*/
|
||||
private $edges = [];
|
||||
|
||||
/**
|
||||
* Builds up the result during the DFS.
|
||||
*
|
||||
* @var list<object>
|
||||
*/
|
||||
private $sortResult = [];
|
||||
|
||||
/** @param object $node */
|
||||
public function addNode($node): void
|
||||
{
|
||||
$id = spl_object_id($node);
|
||||
$this->nodes[$id] = $node;
|
||||
$this->states[$id] = self::NOT_VISITED;
|
||||
$this->edges[$id] = [];
|
||||
}
|
||||
|
||||
/** @param object $node */
|
||||
public function hasNode($node): bool
|
||||
{
|
||||
return isset($this->nodes[spl_object_id($node)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new edge between two nodes to the graph
|
||||
*
|
||||
* @param object $from
|
||||
* @param object $to
|
||||
* @param bool $optional This indicates whether the edge may be ignored during the topological sort if it is necessary to break cycles.
|
||||
*/
|
||||
public function addEdge($from, $to, bool $optional): void
|
||||
{
|
||||
$fromId = spl_object_id($from);
|
||||
$toId = spl_object_id($to);
|
||||
|
||||
if (isset($this->edges[$fromId][$toId]) && $this->edges[$fromId][$toId] === false) {
|
||||
return; // we already know about this dependency, and it is not optional
|
||||
}
|
||||
|
||||
$this->edges[$fromId][$toId] = $optional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a topological sort of all nodes. When we have an edge A->B between two nodes
|
||||
* A and B, then A will be listed before B in the result.
|
||||
*
|
||||
* @return list<object>
|
||||
*/
|
||||
public function sort(): array
|
||||
{
|
||||
/*
|
||||
* When possible, keep objects in the result in the same order in which they were added as nodes.
|
||||
* Since nodes are unshifted into $this->>sortResult (see the visit() method), that means we
|
||||
* need to work them in array_reverse order here.
|
||||
*/
|
||||
foreach (array_reverse(array_keys($this->nodes)) as $oid) {
|
||||
if ($this->states[$oid] === self::NOT_VISITED) {
|
||||
$this->visit($oid);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sortResult;
|
||||
}
|
||||
|
||||
private function visit(int $oid): void
|
||||
{
|
||||
if ($this->states[$oid] === self::IN_PROGRESS) {
|
||||
// This node is already on the current DFS stack. We've found a cycle!
|
||||
throw new CycleDetectedException($this->nodes[$oid]);
|
||||
}
|
||||
|
||||
if ($this->states[$oid] === self::VISITED) {
|
||||
// We've reached a node that we've already seen, including all
|
||||
// other nodes that are reachable from here. We're done here, return.
|
||||
return;
|
||||
}
|
||||
|
||||
$this->states[$oid] = self::IN_PROGRESS;
|
||||
|
||||
// Continue the DFS downwards the edge list
|
||||
foreach ($this->edges[$oid] as $adjacentId => $optional) {
|
||||
try {
|
||||
$this->visit($adjacentId);
|
||||
} catch (CycleDetectedException $exception) {
|
||||
if ($exception->isCycleCollected()) {
|
||||
// There is a complete cycle downstream of the current node. We cannot
|
||||
// do anything about that anymore.
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if ($optional) {
|
||||
// The current edge is part of a cycle, but it is optional and the closest
|
||||
// such edge while backtracking. Break the cycle here by skipping the edge
|
||||
// and continuing with the next one.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have found a cycle and cannot break it at $edge. Best we can do
|
||||
// is to retreat from the current vertex, hoping that somewhere up the
|
||||
// stack this can be salvaged.
|
||||
$this->states[$oid] = self::NOT_VISITED;
|
||||
$exception->addToCycle($this->nodes[$oid]);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
// We have traversed all edges and visited all other nodes reachable from here.
|
||||
// So we're done with this vertex as well.
|
||||
|
||||
$this->states[$oid] = self::VISITED;
|
||||
array_unshift($this->sortResult, $this->nodes[$oid]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\TopologicalSort;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
use function array_unshift;
|
||||
|
||||
class CycleDetectedException extends RuntimeException
|
||||
{
|
||||
/** @var list<object> */
|
||||
private $cycle;
|
||||
|
||||
/** @var object */
|
||||
private $startNode;
|
||||
|
||||
/**
|
||||
* Do we have the complete cycle collected?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $cycleCollected = false;
|
||||
|
||||
/** @param object $startNode */
|
||||
public function __construct($startNode)
|
||||
{
|
||||
parent::__construct('A cycle has been detected, so a topological sort is not possible. The getCycle() method provides the list of nodes that form the cycle.');
|
||||
|
||||
$this->startNode = $startNode;
|
||||
$this->cycle = [$startNode];
|
||||
}
|
||||
|
||||
/** @return list<object> */
|
||||
public function getCycle(): array
|
||||
{
|
||||
return $this->cycle;
|
||||
}
|
||||
|
||||
/** @param object $node */
|
||||
public function addToCycle($node): void
|
||||
{
|
||||
array_unshift($this->cycle, $node);
|
||||
|
||||
if ($node === $this->startNode) {
|
||||
$this->cycleCollected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function isCycleCollected(): bool
|
||||
{
|
||||
return $this->cycleCollected;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
if ($parent) {
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
$class->setIdGeneratorType($parent->generatorType);
|
||||
$this->inheritIdGeneratorMapping($class, $parent);
|
||||
$this->addInheritedFields($class, $parent);
|
||||
$this->addInheritedRelations($class, $parent);
|
||||
$this->addInheritedEmbeddedClasses($class, $parent);
|
||||
@@ -141,12 +141,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
throw MappingException::reflectionFailure($class->getName(), $e);
|
||||
}
|
||||
|
||||
// If this class has a parent the id generator strategy is inherited.
|
||||
// However this is only true if the hierarchy of parents contains the root entity,
|
||||
// if it consists of mapped superclasses these don't necessarily include the id field.
|
||||
if ($parent && $rootEntityFound) {
|
||||
$this->inheritIdGeneratorMapping($class, $parent);
|
||||
} else {
|
||||
// Complete id generator mapping when the generator was declared/added in this class
|
||||
if ($class->identifier && (! $parent || ! $parent->identifier)) {
|
||||
$this->completeIdGeneratorMapping($class);
|
||||
}
|
||||
|
||||
|
||||
@@ -1176,7 +1176,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$this->namespace = $reflService->getClassNamespace($this->name);
|
||||
|
||||
if ($this->reflClass) {
|
||||
$this->name = $this->rootEntityName = $this->reflClass->getName();
|
||||
$this->name = $this->rootEntityName = $this->reflClass->name;
|
||||
}
|
||||
|
||||
$this->table['name'] = $this->namingStrategy->classToTableName($this->name);
|
||||
@@ -2764,6 +2764,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
|
||||
$this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
|
||||
$this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
|
||||
|
||||
if (isset($fieldMapping['generated'])) {
|
||||
$this->requiresFetchAfterChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3862,7 +3866,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
{
|
||||
$reflectionProperty = $reflService->getAccessibleProperty($class, $field);
|
||||
if ($reflectionProperty !== null && PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) {
|
||||
$declaringClass = $reflectionProperty->getDeclaringClass()->name;
|
||||
$declaringClass = $reflectionProperty->class;
|
||||
if ($declaringClass !== $class) {
|
||||
$reflectionProperty = $reflService->getAccessibleProperty($declaringClass, $field);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ use function is_numeric;
|
||||
class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
{
|
||||
use ColocatedMappingDriver;
|
||||
use ReflectionBasedDriver;
|
||||
|
||||
/**
|
||||
* The annotation reader.
|
||||
@@ -60,7 +61,7 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
* @param Reader $reader The AnnotationReader to use
|
||||
* @param string|string[]|null $paths One or multiple paths where mapping classes can be found.
|
||||
*/
|
||||
public function __construct($reader, $paths = null)
|
||||
public function __construct($reader, $paths = null, bool $reportFieldsWhereDeclared = false)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
@@ -70,6 +71,17 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
$this->reader = $reader;
|
||||
|
||||
$this->addPaths((array) $paths);
|
||||
|
||||
if (! $reportFieldsWhereDeclared) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10455',
|
||||
'In ORM 3.0, the AttributeDriver will report fields for the classes where they are declared. This may uncover invalid mapping configurations. To opt into the new mode also with the AnnotationDriver today, set the "reportFieldsWhereDeclared" constructor parameter to true.',
|
||||
self::class
|
||||
);
|
||||
}
|
||||
|
||||
$this->reportFieldsWhereDeclared = $reportFieldsWhereDeclared;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,20 +360,12 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
|
||||
// Evaluate annotations on properties/fields
|
||||
foreach ($class->getProperties() as $property) {
|
||||
if (
|
||||
$metadata->isMappedSuperclass && ! $property->isPrivate()
|
||||
||
|
||||
$metadata->isInheritedField($property->name)
|
||||
||
|
||||
$metadata->isInheritedAssociation($property->name)
|
||||
||
|
||||
$metadata->isInheritedEmbeddedClass($property->name)
|
||||
) {
|
||||
if ($this->isRepeatedPropertyDeclaration($property, $metadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapping = [];
|
||||
$mapping['fieldName'] = $property->getName();
|
||||
$mapping['fieldName'] = $property->name;
|
||||
|
||||
// Evaluate @Cache annotation
|
||||
$cacheAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class);
|
||||
@@ -394,7 +398,7 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
// @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
|
||||
$columnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Column::class);
|
||||
if ($columnAnnot) {
|
||||
$mapping = $this->columnToArray($property->getName(), $columnAnnot);
|
||||
$mapping = $this->columnToArray($property->name, $columnAnnot);
|
||||
|
||||
$idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
|
||||
if ($idAnnot) {
|
||||
|
||||
@@ -23,13 +23,13 @@ use function class_exists;
|
||||
use function constant;
|
||||
use function defined;
|
||||
use function get_class;
|
||||
use function sprintf;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
{
|
||||
use ColocatedMappingDriver;
|
||||
use ReflectionBasedDriver;
|
||||
|
||||
private const ENTITY_ATTRIBUTE_CLASSES = [
|
||||
Mapping\Entity::class => 1,
|
||||
@@ -53,13 +53,13 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
protected $reader;
|
||||
|
||||
/** @param array<string> $paths */
|
||||
public function __construct(array $paths)
|
||||
public function __construct(array $paths, bool $reportFieldsWhereDeclared = false)
|
||||
{
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
throw new LogicException(sprintf(
|
||||
throw new LogicException(
|
||||
'The attribute metadata driver cannot be enabled on PHP 7. Please upgrade to PHP 8 or choose a different'
|
||||
. ' metadata driver.'
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
$this->reader = new AttributeReader();
|
||||
@@ -73,6 +73,17 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
self::class
|
||||
);
|
||||
}
|
||||
|
||||
if (! $reportFieldsWhereDeclared) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10455',
|
||||
'In ORM 3.0, the AttributeDriver will report fields for the classes where they are declared. This may uncover invalid mapping configurations. To opt into the new mode today, set the "reportFieldsWhereDeclared" constructor parameter to true.',
|
||||
self::class
|
||||
);
|
||||
}
|
||||
|
||||
$this->reportFieldsWhereDeclared = $reportFieldsWhereDeclared;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,20 +309,13 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
|
||||
foreach ($reflectionClass->getProperties() as $property) {
|
||||
assert($property instanceof ReflectionProperty);
|
||||
if (
|
||||
$metadata->isMappedSuperclass && ! $property->isPrivate()
|
||||
||
|
||||
$metadata->isInheritedField($property->name)
|
||||
||
|
||||
$metadata->isInheritedAssociation($property->name)
|
||||
||
|
||||
$metadata->isInheritedEmbeddedClass($property->name)
|
||||
) {
|
||||
|
||||
if ($this->isRepeatedPropertyDeclaration($property, $metadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapping = [];
|
||||
$mapping['fieldName'] = $property->getName();
|
||||
$mapping['fieldName'] = $property->name;
|
||||
|
||||
// Evaluate #[Cache] attribute
|
||||
$cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class);
|
||||
@@ -346,7 +350,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
$embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class);
|
||||
|
||||
if ($columnAttribute !== null) {
|
||||
$mapping = $this->columnToArray($property->getName(), $columnAttribute);
|
||||
$mapping = $this->columnToArray($property->name, $columnAttribute);
|
||||
|
||||
if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
|
||||
$mapping['id'] = true;
|
||||
|
||||
54
lib/Doctrine/ORM/Mapping/Driver/ReflectionBasedDriver.php
Normal file
54
lib/Doctrine/ORM/Mapping/Driver/ReflectionBasedDriver.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use ReflectionProperty;
|
||||
|
||||
/** @internal */
|
||||
trait ReflectionBasedDriver
|
||||
{
|
||||
/** @var bool */
|
||||
private $reportFieldsWhereDeclared = false;
|
||||
|
||||
/**
|
||||
* Helps to deal with the case that reflection may report properties inherited from parent classes.
|
||||
* When we know about the fields already (inheritance has been anticipated in ClassMetadataFactory),
|
||||
* the driver must skip them.
|
||||
*
|
||||
* The declaring classes may mismatch when there are private properties: The same property name may be
|
||||
* reported multiple times, but since it is private, it is in fact multiple (different) properties in
|
||||
* different classes. In that case, report the property as an individual field. (ClassMetadataFactory will
|
||||
* probably fail in that case, though.)
|
||||
*/
|
||||
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool
|
||||
{
|
||||
if (! $this->reportFieldsWhereDeclared) {
|
||||
return $metadata->isMappedSuperclass && ! $property->isPrivate()
|
||||
|| $metadata->isInheritedField($property->name)
|
||||
|| $metadata->isInheritedAssociation($property->name)
|
||||
|| $metadata->isInheritedEmbeddedClass($property->name);
|
||||
}
|
||||
|
||||
$declaringClass = $property->class;
|
||||
|
||||
if (
|
||||
isset($metadata->fieldMappings[$property->name]['declared'])
|
||||
&& $metadata->fieldMappings[$property->name]['declared'] === $declaringClass
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
isset($metadata->associationMappings[$property->name]['declared'])
|
||||
&& $metadata->associationMappings[$property->name]['declared'] === $declaringClass
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isset($metadata->embeddedClasses[$property->name]['declared'])
|
||||
&& $metadata->embeddedClasses[$property->name]['declared'] === $declaringClass;
|
||||
}
|
||||
}
|
||||
@@ -50,27 +50,25 @@ class XmlDriver extends FileDriver
|
||||
public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = false)
|
||||
{
|
||||
if (! extension_loaded('simplexml')) {
|
||||
throw new LogicException(sprintf(
|
||||
throw new LogicException(
|
||||
'The XML metadata driver cannot be enabled because the SimpleXML PHP extension is missing.'
|
||||
. ' Please configure PHP with SimpleXML or choose a different metadata driver.'
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
if (! $isXsdValidationEnabled) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/6728',
|
||||
sprintf(
|
||||
'Using XML mapping driver with XSD validation disabled is deprecated'
|
||||
. ' and will not be supported in Doctrine ORM 3.0.'
|
||||
)
|
||||
'Using XML mapping driver with XSD validation disabled is deprecated'
|
||||
. ' and will not be supported in Doctrine ORM 3.0.'
|
||||
);
|
||||
}
|
||||
|
||||
if ($isXsdValidationEnabled && ! extension_loaded('dom')) {
|
||||
throw new LogicException(sprintf(
|
||||
throw new LogicException(
|
||||
'XSD validation cannot be enabled because the DOM extension is missing.'
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
$this->isXsdValidationEnabled = $isXsdValidationEnabled;
|
||||
@@ -378,30 +376,8 @@ class XmlDriver extends FileDriver
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapping = [
|
||||
'id' => true,
|
||||
'fieldName' => (string) $idElement['name'],
|
||||
];
|
||||
|
||||
if (isset($idElement['type'])) {
|
||||
$mapping['type'] = (string) $idElement['type'];
|
||||
}
|
||||
|
||||
if (isset($idElement['length'])) {
|
||||
$mapping['length'] = (int) $idElement['length'];
|
||||
}
|
||||
|
||||
if (isset($idElement['column'])) {
|
||||
$mapping['columnName'] = (string) $idElement['column'];
|
||||
}
|
||||
|
||||
if (isset($idElement['column-definition'])) {
|
||||
$mapping['columnDefinition'] = (string) $idElement['column-definition'];
|
||||
}
|
||||
|
||||
if (isset($idElement->options)) {
|
||||
$mapping['options'] = $this->parseOptions($idElement->options->children());
|
||||
}
|
||||
$mapping = $this->columnToArray($idElement);
|
||||
$mapping['id'] = true;
|
||||
|
||||
$metadata->mapField($mapping);
|
||||
|
||||
|
||||
@@ -48,11 +48,11 @@ class YamlDriver extends FileDriver
|
||||
);
|
||||
|
||||
if (! class_exists(Yaml::class)) {
|
||||
throw new LogicException(sprintf(
|
||||
throw new LogicException(
|
||||
'The YAML metadata driver cannot be enabled because the "symfony/yaml" library'
|
||||
. ' is not installed. Please run "composer require symfony/yaml" or choose a different'
|
||||
. ' metadata driver.'
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
parent::__construct($locator, $fileExtension);
|
||||
|
||||
@@ -73,7 +73,7 @@ final class ReflectionPropertiesGetter
|
||||
|
||||
$parentClass = $currentClass->getParentClass();
|
||||
if ($parentClass) {
|
||||
$parentClassName = $parentClass->getName();
|
||||
$parentClassName = $parentClass->name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +111,14 @@ final class ReflectionPropertiesGetter
|
||||
private function getAccessibleProperty(ReflectionProperty $property): ?ReflectionProperty
|
||||
{
|
||||
return $this->reflectionService->getAccessibleProperty(
|
||||
$property->getDeclaringClass()->getName(),
|
||||
$property->getName()
|
||||
$property->class,
|
||||
$property->name
|
||||
);
|
||||
}
|
||||
|
||||
private function getLogicalName(ReflectionProperty $property): string
|
||||
{
|
||||
$propertyName = $property->getName();
|
||||
$propertyName = $property->name;
|
||||
|
||||
if ($property->isPublic()) {
|
||||
return $propertyName;
|
||||
@@ -128,6 +128,6 @@ final class ReflectionPropertiesGetter
|
||||
return "\0*\0" . $propertyName;
|
||||
}
|
||||
|
||||
return "\0" . $property->getDeclaringClass()->getName() . "\0" . $propertyName;
|
||||
return "\0" . $property->class . "\0" . $propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class ReflectionEmbeddedProperty extends ReflectionProperty
|
||||
$this->childProperty = $childProperty;
|
||||
$this->embeddedClass = (string) $embeddedClass;
|
||||
|
||||
parent::__construct($childProperty->getDeclaringClass()->getName(), $childProperty->getName());
|
||||
parent::__construct($childProperty->class, $childProperty->name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,8 +28,8 @@ class ReflectionEnumProperty extends ReflectionProperty
|
||||
$this->enumType = $enumType;
|
||||
|
||||
parent::__construct(
|
||||
$originalReflectionProperty->getDeclaringClass()->getName(),
|
||||
$originalReflectionProperty->getName()
|
||||
$originalReflectionProperty->class,
|
||||
$originalReflectionProperty->name
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ class ReflectionEnumProperty extends ReflectionProperty
|
||||
} catch (ValueError $e) {
|
||||
throw MappingException::invalidEnumValue(
|
||||
get_class($object),
|
||||
$this->originalReflectionProperty->getName(),
|
||||
$this->originalReflectionProperty->name,
|
||||
(string) $value,
|
||||
$enumType,
|
||||
$e
|
||||
|
||||
@@ -24,7 +24,6 @@ use function apcu_enabled;
|
||||
use function class_exists;
|
||||
use function extension_loaded;
|
||||
use function md5;
|
||||
use function sprintf;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
final class ORMSetup
|
||||
@@ -63,7 +62,8 @@ final class ORMSetup
|
||||
*/
|
||||
public static function createDefaultAnnotationDriver(
|
||||
array $paths = [],
|
||||
?CacheItemPoolInterface $cache = null
|
||||
?CacheItemPoolInterface $cache = null,
|
||||
bool $reportFieldsWhereDeclared = false
|
||||
): AnnotationDriver {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
@@ -72,11 +72,11 @@ final class ORMSetup
|
||||
__METHOD__
|
||||
);
|
||||
if (! class_exists(AnnotationReader::class)) {
|
||||
throw new LogicException(sprintf(
|
||||
throw new LogicException(
|
||||
'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library'
|
||||
. ' is not installed. Please run "composer require doctrine/annotations" or choose a different'
|
||||
. ' metadata driver.'
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
$reader = new AnnotationReader();
|
||||
@@ -89,7 +89,7 @@ final class ORMSetup
|
||||
$reader = new PsrCachedReader($reader, $cache);
|
||||
}
|
||||
|
||||
return new AnnotationDriver($reader, $paths);
|
||||
return new AnnotationDriver($reader, $paths, $reportFieldsWhereDeclared);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -183,7 +183,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
*
|
||||
* @var IdentifierFlattener
|
||||
*/
|
||||
private $identifierFlattener;
|
||||
protected $identifierFlattener;
|
||||
|
||||
/** @var CachedPersisterContext */
|
||||
protected $currentPersisterContext;
|
||||
@@ -256,17 +256,17 @@ class BasicEntityPersister implements EntityPersister
|
||||
public function executeInserts()
|
||||
{
|
||||
if (! $this->queuedInserts) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
|
||||
$postInsertIds = [];
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$idGenerator = $this->class->idGenerator;
|
||||
$isPostInsertId = $idGenerator->isPostInsertGenerator();
|
||||
|
||||
$stmt = $this->conn->prepare($this->getInsertSQL());
|
||||
$tableName = $this->class->getTableName();
|
||||
|
||||
foreach ($this->queuedInserts as $entity) {
|
||||
foreach ($this->queuedInserts as $key => $entity) {
|
||||
$insertData = $this->prepareInsertData($entity);
|
||||
|
||||
if (isset($insertData[$tableName])) {
|
||||
@@ -280,12 +280,10 @@ class BasicEntityPersister implements EntityPersister
|
||||
$stmt->executeStatement();
|
||||
|
||||
if ($isPostInsertId) {
|
||||
$generatedId = $idGenerator->generateId($this->em, $entity);
|
||||
$id = [$this->class->identifier[0] => $generatedId];
|
||||
$postInsertIds[] = [
|
||||
'generatedId' => $generatedId,
|
||||
'entity' => $entity,
|
||||
];
|
||||
$generatedId = $idGenerator->generateId($this->em, $entity);
|
||||
$id = [$this->class->identifier[0] => $generatedId];
|
||||
|
||||
$uow->assignPostInsertId($entity, $generatedId);
|
||||
} else {
|
||||
$id = $this->class->getIdentifierValues($entity);
|
||||
}
|
||||
@@ -293,11 +291,16 @@ class BasicEntityPersister implements EntityPersister
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
|
||||
// Unset this queued insert, so that the prepareUpdateData() method knows right away
|
||||
// (for the next entity already) that the current entity has been written to the database
|
||||
// and no extra updates need to be scheduled to refer to it.
|
||||
//
|
||||
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
|
||||
// from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they
|
||||
// were given to our addInsert() method.
|
||||
unset($this->queuedInserts[$key]);
|
||||
}
|
||||
|
||||
$this->queuedInserts = [];
|
||||
|
||||
return $postInsertIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,7 +379,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
* @return int[]|null[]|string[]
|
||||
* @psalm-return list<int|string|null>
|
||||
*/
|
||||
private function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array
|
||||
final protected function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array
|
||||
{
|
||||
$types = [];
|
||||
|
||||
@@ -675,10 +678,30 @@ class BasicEntityPersister implements EntityPersister
|
||||
if ($newVal !== null) {
|
||||
$oid = spl_object_id($newVal);
|
||||
|
||||
if (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) {
|
||||
// The associated entity $newVal is not yet persisted, so we must
|
||||
// set $newVal = null, in order to insert a null value and schedule an
|
||||
// extra update on the UnitOfWork.
|
||||
// If the associated entity $newVal is not yet persisted and/or does not yet have
|
||||
// an ID assigned, we must set $newVal = null. This will insert a null value and
|
||||
// schedule an extra update on the UnitOfWork.
|
||||
//
|
||||
// This gives us extra time to a) possibly obtain a database-generated identifier
|
||||
// value for $newVal, and b) insert $newVal into the database before the foreign
|
||||
// key reference is being made.
|
||||
//
|
||||
// When looking at $this->queuedInserts and $uow->isScheduledForInsert, be aware
|
||||
// of the implementation details that our own executeInserts() method will remove
|
||||
// entities from the former as soon as the insert statement has been executed and
|
||||
// a post-insert ID has been assigned (if necessary), and that the UnitOfWork has
|
||||
// already removed entities from its own list at the time they were passed to our
|
||||
// addInsert() method.
|
||||
//
|
||||
// Then, there is one extra exception we can make: An entity that references back to itself
|
||||
// _and_ uses an application-provided ID (the "NONE" generator strategy) also does not
|
||||
// need the extra update, although it is still in the list of insertions itself.
|
||||
// This looks like a minor optimization at first, but is the capstone for being able to
|
||||
// use non-NULLable, self-referencing associations in applications that provide IDs (like UUIDs).
|
||||
if (
|
||||
(isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal))
|
||||
&& ! ($newVal === $entity && $this->class->isIdentifierNatural())
|
||||
) {
|
||||
$uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]);
|
||||
|
||||
$newVal = null;
|
||||
|
||||
@@ -109,17 +109,15 @@ interface EntityPersister
|
||||
public function addInsert($entity);
|
||||
|
||||
/**
|
||||
* Executes all queued entity insertions and returns any generated post-insert
|
||||
* identifiers that were created as a result of the insertions.
|
||||
* Executes all queued entity insertions.
|
||||
*
|
||||
* If no inserts are queued, invoking this method is a NOOP.
|
||||
*
|
||||
* @psalm-return list<array{
|
||||
* @psalm-return void|list<array{
|
||||
* generatedId: int,
|
||||
* entity: object
|
||||
* }> An array of any generated post-insert IDs. This will be
|
||||
* an empty array if the entity class does not use the
|
||||
* IDENTITY generation strategy.
|
||||
* }> Returning an array of generated post-insert IDs is deprecated, implementations
|
||||
* should call UnitOfWork::assignPostInsertId() and return void.
|
||||
*/
|
||||
public function executeInserts();
|
||||
|
||||
|
||||
@@ -11,8 +11,11 @@ use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use LengthException;
|
||||
|
||||
use function array_combine;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
@@ -109,10 +112,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
public function executeInserts()
|
||||
{
|
||||
if (! $this->queuedInserts) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
|
||||
$postInsertIds = [];
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$idGenerator = $this->class->idGenerator;
|
||||
$isPostInsertId = $idGenerator->isPostInsertGenerator();
|
||||
$rootClass = $this->class->name !== $this->class->rootEntityName
|
||||
@@ -157,20 +160,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$rootTableStmt->executeStatement();
|
||||
|
||||
if ($isPostInsertId) {
|
||||
$generatedId = $idGenerator->generateId($this->em, $entity);
|
||||
$id = [$this->class->identifier[0] => $generatedId];
|
||||
$postInsertIds[] = [
|
||||
'generatedId' => $generatedId,
|
||||
'entity' => $entity,
|
||||
];
|
||||
$generatedId = $idGenerator->generateId($this->em, $entity);
|
||||
$id = [$this->class->identifier[0] => $generatedId];
|
||||
|
||||
$uow->assignPostInsertId($entity, $generatedId);
|
||||
} else {
|
||||
$id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
}
|
||||
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
|
||||
// Execute inserts on subtables.
|
||||
// The order doesn't matter because all child tables link to the root table via FK.
|
||||
foreach ($subTableStmts as $tableName => $stmt) {
|
||||
@@ -191,11 +188,13 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
$stmt->executeStatement();
|
||||
}
|
||||
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedInserts = [];
|
||||
|
||||
return $postInsertIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,6 +513,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|| isset($this->class->associationMappings[$name]['inherited'])
|
||||
|| ($this->class->isVersioned && $this->class->versionField === $name)
|
||||
|| isset($this->class->embeddedClasses[$name])
|
||||
|| isset($this->class->fieldMappings[$name]['notInsertable'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -556,6 +556,60 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchVersionAndNotUpsertableValues($versionedClass, array $id)
|
||||
{
|
||||
$columnNames = [];
|
||||
foreach ($this->class->fieldMappings as $key => $column) {
|
||||
$class = null;
|
||||
if ($this->class->isVersioned && $key === $versionedClass->versionField) {
|
||||
$class = $versionedClass;
|
||||
} elseif (isset($column['generated'])) {
|
||||
$class = isset($column['inherited'])
|
||||
? $this->em->getClassMetadata($column['inherited'])
|
||||
: $this->class;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnNames[$key] = $this->getSelectColumnSQL($key, $class);
|
||||
}
|
||||
|
||||
$tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform);
|
||||
$baseTableAlias = $this->getSQLTableAlias($this->class->name);
|
||||
$joinSql = $this->getJoinSql($baseTableAlias);
|
||||
$identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform);
|
||||
foreach ($identifier as $i => $idValue) {
|
||||
$identifier[$i] = $baseTableAlias . '.' . $idValue;
|
||||
}
|
||||
|
||||
$sql = 'SELECT ' . implode(', ', $columnNames)
|
||||
. ' FROM ' . $tableName . ' ' . $baseTableAlias
|
||||
. $joinSql
|
||||
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
|
||||
|
||||
$flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id);
|
||||
$values = $this->conn->fetchNumeric(
|
||||
$sql,
|
||||
array_values($flatId),
|
||||
$this->extractIdentifierTypes($id, $versionedClass)
|
||||
);
|
||||
|
||||
if ($values === false) {
|
||||
throw new LengthException('Unexpected empty result for database query.');
|
||||
}
|
||||
|
||||
$values = array_combine(array_keys($columnNames), $values);
|
||||
|
||||
if (! $values) {
|
||||
throw new LengthException('Unexpected number of database columns.');
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function getJoinSql(string $baseTableAlias): string
|
||||
{
|
||||
$joinSql = '';
|
||||
|
||||
19
lib/Doctrine/ORM/Proxy/InternalProxy.php
Normal file
19
lib/Doctrine/ORM/Proxy/InternalProxy.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @template T of object
|
||||
* @template-extends Proxy<T>
|
||||
*
|
||||
* @method void __setInitialized(bool $initialized)
|
||||
*/
|
||||
interface InternalProxy extends Proxy
|
||||
{
|
||||
}
|
||||
@@ -10,7 +10,11 @@ use Doctrine\Common\Proxy\Proxy as BaseProxy;
|
||||
* Interface for proxy classes.
|
||||
*
|
||||
* @deprecated 2.14. Use \Doctrine\Persistence\Proxy instead
|
||||
*
|
||||
* @template T of object
|
||||
* @template-extends BaseProxy<T>
|
||||
* @template-extends InternalProxy<T>
|
||||
*/
|
||||
interface Proxy extends BaseProxy
|
||||
interface Proxy extends BaseProxy, InternalProxy
|
||||
{
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ use Doctrine\ORM\Proxy\Proxy as LegacyProxy;
|
||||
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;
|
||||
@@ -31,8 +30,6 @@ use function uksort;
|
||||
|
||||
/**
|
||||
* This factory is used to create proxy objects for entities at runtime.
|
||||
*
|
||||
* @psalm-type AutogenerateMode = ProxyFactory::AUTOGENERATE_NEVER|ProxyFactory::AUTOGENERATE_ALWAYS|ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS|ProxyFactory::AUTOGENERATE_EVAL|ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED
|
||||
*/
|
||||
class ProxyFactory extends AbstractProxyFactory
|
||||
{
|
||||
@@ -48,13 +45,12 @@ class <proxyShortClassName> extends \<className> implements \<baseProxyInterface
|
||||
{
|
||||
<useLazyGhostTrait>
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public bool $__isCloning = false;
|
||||
|
||||
public function __construct(?\Closure $initializer = null)
|
||||
public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
|
||||
{
|
||||
if ($cloner !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::createLazyGhost($initializer, <skippedProperties>, $this);
|
||||
}
|
||||
|
||||
@@ -63,17 +59,6 @@ class <proxyShortClassName> extends \<className> implements \<baseProxyInterface
|
||||
return isset($this->lazyObjectState) && $this->isLazyObjectInitialized();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->__isCloning = true;
|
||||
|
||||
try {
|
||||
$this->__doClone();
|
||||
} finally {
|
||||
$this->__isCloning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
<serializeImpl>
|
||||
@@ -98,23 +83,24 @@ EOPHP;
|
||||
*/
|
||||
private $identifierFlattener;
|
||||
|
||||
/** @var ProxyDefinition[] */
|
||||
private $definitions = [];
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
|
||||
* connected to the given <tt>EntityManager</tt>.
|
||||
*
|
||||
* @param EntityManagerInterface $em The EntityManager the new factory works for.
|
||||
* @param string $proxyDir The directory to use for the proxy classes. It must exist.
|
||||
* @param string $proxyNs The namespace to use for the proxy classes.
|
||||
* @param bool|int $autoGenerate The strategy for automatically generating proxy classes. Possible
|
||||
* values are constants of {@see ProxyFactory::AUTOGENERATE_*}.
|
||||
* @psalm-param bool|AutogenerateMode $autoGenerate
|
||||
* @param EntityManagerInterface $em The EntityManager the new factory works for.
|
||||
* @param string $proxyDir The directory to use for the proxy classes. It must exist.
|
||||
* @param string $proxyNs The namespace to use for the proxy classes.
|
||||
* @param bool|self::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, $proxyDir, $proxyNs, $autoGenerate = self::AUTOGENERATE_NEVER)
|
||||
{
|
||||
$proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs);
|
||||
|
||||
if ($em->getConfiguration()->isLazyGhostObjectEnabled()) {
|
||||
$proxyGenerator->setPlaceholder('baseProxyInterface', Proxy::class);
|
||||
$proxyGenerator->setPlaceholder('baseProxyInterface', InternalProxy::class);
|
||||
$proxyGenerator->setPlaceholder('useLazyGhostTrait', Closure::fromCallable([$this, 'generateUseLazyGhostTrait']));
|
||||
$proxyGenerator->setPlaceholder('skippedProperties', Closure::fromCallable([$this, 'generateSkippedProperties']));
|
||||
$proxyGenerator->setPlaceholder('serializeImpl', Closure::fromCallable([$this, 'generateSerializeImpl']));
|
||||
@@ -131,6 +117,26 @@ EOPHP;
|
||||
$this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getProxy($className, array $identifier)
|
||||
{
|
||||
$proxy = parent::getProxy($className, $identifier);
|
||||
|
||||
if (! $this->em->getConfiguration()->isLazyGhostObjectEnabled()) {
|
||||
return $proxy;
|
||||
}
|
||||
|
||||
$initializer = $this->definitions[$className]->initializer;
|
||||
|
||||
$proxy->__construct(static function (InternalProxy $object) use ($initializer, $proxy): void {
|
||||
$initializer($object, $proxy);
|
||||
});
|
||||
|
||||
return $proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -158,7 +164,7 @@ EOPHP;
|
||||
$cloner = $this->createCloner($classMetadata, $entityPersister);
|
||||
}
|
||||
|
||||
return new ProxyDefinition(
|
||||
return $this->definitions[$className] = new ProxyDefinition(
|
||||
ClassUtils::generateProxyClassName($className, $this->proxyNs),
|
||||
$classMetadata->getIdentifierFieldNames(),
|
||||
$classMetadata->getReflectionProperties(),
|
||||
@@ -231,15 +237,15 @@ EOPHP;
|
||||
/**
|
||||
* Creates a closure capable of initializing a proxy
|
||||
*
|
||||
* @return Closure(Proxy):void
|
||||
* @return Closure(InternalProxy, InternalProxy):void
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
*/
|
||||
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure
|
||||
{
|
||||
return function (Proxy $proxy) use ($entityPersister, $classMetadata): void {
|
||||
$identifier = $classMetadata->getIdentifierValues($proxy);
|
||||
$entity = $entityPersister->loadById($identifier, $proxy->__isCloning ? null : $proxy);
|
||||
return function (InternalProxy $proxy, InternalProxy $original) use ($entityPersister, $classMetadata): void {
|
||||
$identifier = $classMetadata->getIdentifierValues($original);
|
||||
$entity = $entityPersister->loadById($identifier, $original);
|
||||
|
||||
if ($entity === null) {
|
||||
throw EntityNotFoundException::fromClassNameAndIdentifier(
|
||||
@@ -248,7 +254,7 @@ EOPHP;
|
||||
);
|
||||
}
|
||||
|
||||
if (! $proxy->__isCloning) {
|
||||
if ($proxy === $original) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -315,7 +321,6 @@ EOPHP;
|
||||
isLazyObjectInitialized as private;
|
||||
createLazyGhost as private;
|
||||
resetLazyObject as private;
|
||||
__clone as private __doClone;
|
||||
}'), $code);
|
||||
|
||||
return $code;
|
||||
@@ -323,20 +328,20 @@ EOPHP;
|
||||
|
||||
private function generateSkippedProperties(ClassMetadata $class): string
|
||||
{
|
||||
$skippedProperties = ['__isCloning' => true];
|
||||
$skippedProperties = [];
|
||||
$identifiers = array_flip($class->getIdentifierFieldNames());
|
||||
$filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE;
|
||||
$reflector = $class->getReflectionClass();
|
||||
|
||||
while ($reflector) {
|
||||
foreach ($reflector->getProperties($filter) as $property) {
|
||||
$name = $property->getName();
|
||||
$name = $property->name;
|
||||
|
||||
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" : '');
|
||||
$prefix = $property->isPrivate() ? "\0" . $property->class . "\0" : ($property->isProtected() ? "\0*\0" : '');
|
||||
|
||||
$skippedProperties[$prefix . $name] = true;
|
||||
}
|
||||
@@ -371,7 +376,7 @@ EOPHP;
|
||||
return $code . '$data = [];
|
||||
|
||||
foreach (parent::__sleep() as $name) {
|
||||
$value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->getName() . '\0$name"] ?? $k = null;
|
||||
$value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->name . '\0$name"] ?? $k = null;
|
||||
|
||||
if (null === $k) {
|
||||
trigger_error(sprintf(\'serialize(): "%s" returned as member variable from __sleep() but does not exist\', $name), \E_USER_NOTICE);
|
||||
|
||||
@@ -53,10 +53,9 @@ abstract class AbstractSqlExecutor
|
||||
/**
|
||||
* Executes all sql statements.
|
||||
*
|
||||
* @param Connection $conn The database connection that is used to execute the queries.
|
||||
* @psalm-param list<mixed>|array<string, mixed> $params The parameters.
|
||||
* @psalm-param array<int, int|string|Type|null>|
|
||||
* array<string, int|string|Type|null> $types The parameter types.
|
||||
* @param Connection $conn The database connection that is used to execute the queries.
|
||||
* @param list<mixed>|array<string, mixed> $params The parameters.
|
||||
* @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types The parameter types.
|
||||
*
|
||||
* @return Result|int
|
||||
*/
|
||||
|
||||
@@ -51,6 +51,14 @@ class FilterCollection
|
||||
*/
|
||||
private $enabledFilters = [];
|
||||
|
||||
/**
|
||||
* Instances of suspended filters.
|
||||
*
|
||||
* @var SQLFilter[]
|
||||
* @psalm-var array<string, SQLFilter>
|
||||
*/
|
||||
private $suspendedFilters = [];
|
||||
|
||||
/**
|
||||
* The filter hash from the last time the query was parsed.
|
||||
*
|
||||
@@ -83,6 +91,17 @@ class FilterCollection
|
||||
return $this->enabledFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the suspended filters.
|
||||
*
|
||||
* @return SQLFilter[] The suspended filters.
|
||||
* @psalm-return array<string, SQLFilter>
|
||||
*/
|
||||
public function getSuspendedFilters(): array
|
||||
{
|
||||
return $this->suspendedFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables a filter from the collection.
|
||||
*
|
||||
@@ -105,6 +124,9 @@ class FilterCollection
|
||||
|
||||
$this->enabledFilters[$name] = new $filterClass($this->em);
|
||||
|
||||
// In case a suspended filter with the same name was forgotten
|
||||
unset($this->suspendedFilters[$name]);
|
||||
|
||||
// Keep the enabled filters sorted for the hash
|
||||
ksort($this->enabledFilters);
|
||||
|
||||
@@ -135,6 +157,54 @@ class FilterCollection
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend a filter.
|
||||
*
|
||||
* @param string $name Name of the filter.
|
||||
*
|
||||
* @return SQLFilter The suspended filter.
|
||||
*
|
||||
* @throws InvalidArgumentException If the filter does not exist.
|
||||
*/
|
||||
public function suspend(string $name): SQLFilter
|
||||
{
|
||||
// Get the filter to return it
|
||||
$filter = $this->getFilter($name);
|
||||
|
||||
$this->suspendedFilters[$name] = $filter;
|
||||
unset($this->enabledFilters[$name]);
|
||||
|
||||
$this->setFiltersStateDirty();
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a disabled filter from the collection.
|
||||
*
|
||||
* @param string $name Name of the filter.
|
||||
*
|
||||
* @return SQLFilter The restored filter.
|
||||
*
|
||||
* @throws InvalidArgumentException If the filter does not exist.
|
||||
*/
|
||||
public function restore(string $name): SQLFilter
|
||||
{
|
||||
if (! $this->isSuspended($name)) {
|
||||
throw new InvalidArgumentException("Filter '" . $name . "' is not suspended.");
|
||||
}
|
||||
|
||||
$this->enabledFilters[$name] = $this->suspendedFilters[$name];
|
||||
unset($this->suspendedFilters[$name]);
|
||||
|
||||
// Keep the enabled filters sorted for the hash
|
||||
ksort($this->enabledFilters);
|
||||
|
||||
$this->setFiltersStateDirty();
|
||||
|
||||
return $this->enabledFilters[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an enabled filter from the collection.
|
||||
*
|
||||
@@ -177,6 +247,18 @@ class FilterCollection
|
||||
return isset($this->enabledFilters[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a filter is suspended.
|
||||
*
|
||||
* @param string $name Name of the filter.
|
||||
*
|
||||
* @return bool True if the filter is suspended, false otherwise.
|
||||
*/
|
||||
public function isSuspended(string $name): bool
|
||||
{
|
||||
return isset($this->suspendedFilters[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the filter collection is clean.
|
||||
*
|
||||
|
||||
@@ -2566,7 +2566,7 @@ class Parser
|
||||
* EmptyCollectionComparisonExpression | CollectionMemberExpression |
|
||||
* InstanceOfExpression
|
||||
*
|
||||
* @return AST\BetweenExpression|
|
||||
* @return (AST\BetweenExpression|
|
||||
* AST\CollectionMemberExpression|
|
||||
* AST\ComparisonExpression|
|
||||
* AST\EmptyCollectionComparisonExpression|
|
||||
@@ -2574,7 +2574,7 @@ class Parser
|
||||
* AST\InExpression|
|
||||
* AST\InstanceOfExpression|
|
||||
* AST\LikeExpression|
|
||||
* AST\NullComparisonExpression
|
||||
* AST\NullComparisonExpression)
|
||||
*/
|
||||
public function SimpleConditionalExpression()
|
||||
{
|
||||
|
||||
@@ -809,7 +809,12 @@ class QueryBuilder
|
||||
*/
|
||||
public function distinct($flag = true)
|
||||
{
|
||||
$this->dqlParts['distinct'] = (bool) $flag;
|
||||
$flag = (bool) $flag;
|
||||
|
||||
if ($this->dqlParts['distinct'] !== $flag) {
|
||||
$this->dqlParts['distinct'] = $flag;
|
||||
$this->state = self::STATE_DIRTY;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,5 @@ class AttachEntityListenersListener
|
||||
$metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']);
|
||||
}
|
||||
}
|
||||
|
||||
unset($this->entityListeners[$metadata->name]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use function assert;
|
||||
use function get_debug_type;
|
||||
use function sprintf;
|
||||
|
||||
@@ -63,32 +64,46 @@ EOT
|
||||
{
|
||||
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();
|
||||
|
||||
$em = $this->getEntityManager($input);
|
||||
$cache = $em->getConfiguration()->getQueryCache();
|
||||
$cacheDriver = $em->getConfiguration()->getQueryCacheImpl();
|
||||
$em = $this->getEntityManager($input);
|
||||
$cache = $em->getConfiguration()->getQueryCache();
|
||||
|
||||
if (! $cacheDriver) {
|
||||
throw new InvalidArgumentException('No Query cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof ApcCache || $cache instanceof ApcuAdapter) {
|
||||
if ($cache instanceof ApcuAdapter) {
|
||||
throw new LogicException('Cannot clear APCu Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof XcacheCache) {
|
||||
throw new LogicException('Cannot clear XCache Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.');
|
||||
}
|
||||
$cacheDriver = null;
|
||||
if (! $cache) {
|
||||
$cacheDriver = $em->getConfiguration()->getQueryCacheImpl();
|
||||
|
||||
if (! ($cacheDriver instanceof ClearableCache)) {
|
||||
throw new LogicException(sprintf(
|
||||
'Can only clear cache when ClearableCache interface is implemented, %s does not implement.',
|
||||
get_debug_type($cacheDriver)
|
||||
));
|
||||
if (! $cacheDriver) {
|
||||
throw new InvalidArgumentException('No Query cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof ApcCache) {
|
||||
throw new LogicException('Cannot clear APCu Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof XcacheCache) {
|
||||
throw new LogicException('Cannot clear XCache Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.');
|
||||
}
|
||||
|
||||
if (! ($cacheDriver instanceof ClearableCache)) {
|
||||
throw new LogicException(sprintf(
|
||||
'Can only clear cache when ClearableCache interface is implemented, %s does not implement.',
|
||||
get_debug_type($cacheDriver)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$ui->comment('Clearing <info>all</info> Query cache entries');
|
||||
|
||||
$result = $cache ? $cache->clear() : $cacheDriver->deleteAll();
|
||||
if ($cache) {
|
||||
$result = $cache->clear();
|
||||
} else {
|
||||
assert($cacheDriver !== null);
|
||||
$result = $cacheDriver->deleteAll();
|
||||
}
|
||||
|
||||
$message = $result ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.';
|
||||
|
||||
if ($input->getOption('flush') === true && ! $cache) {
|
||||
|
||||
@@ -9,7 +9,6 @@ use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use ReflectionObject;
|
||||
|
||||
use function count;
|
||||
@@ -87,7 +86,7 @@ class DebugUnitOfWorkListener
|
||||
if ($value === null) {
|
||||
fwrite($fh, " NULL\n");
|
||||
} else {
|
||||
if ($value instanceof Proxy && ! $value->__isInitialized()) {
|
||||
if ($uow->isUninitializedObject($value)) {
|
||||
fwrite($fh, '[PROXY] ');
|
||||
}
|
||||
|
||||
|
||||
@@ -969,7 +969,7 @@ public function __construct(<params>)
|
||||
{
|
||||
$refl = new ReflectionClass($this->getClassToExtend());
|
||||
|
||||
return '\\' . $refl->getName();
|
||||
return '\\' . $refl->name;
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
|
||||
@@ -821,8 +821,8 @@ class SchemaTool
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = array_intersect_key($mappingOptions, array_flip(self::KNOWN_COLUMN_OPTIONS));
|
||||
$options['customSchemaOptions'] = array_diff_key($mappingOptions, $options);
|
||||
$options = array_intersect_key($mappingOptions, array_flip(self::KNOWN_COLUMN_OPTIONS));
|
||||
$options['platformOptions'] = array_diff_key($mappingOptions, $options);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ use Doctrine\ORM\Exception\UnexpectedAssociationValue;
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Internal\CommitOrderCalculator;
|
||||
use Doctrine\ORM\Internal\HydrationCompleteHandler;
|
||||
use Doctrine\ORM\Internal\TopologicalSort;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Mapping\Reflection\ReflectionPropertiesGetter;
|
||||
@@ -38,12 +39,12 @@ use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
|
||||
use Doctrine\ORM\Persisters\Entity\SingleTablePersister;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
|
||||
use Doctrine\Persistence\NotifyPropertyChanged;
|
||||
use Doctrine\Persistence\ObjectManagerAware;
|
||||
use Doctrine\Persistence\PropertyChangedListener;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
@@ -56,11 +57,9 @@ use function array_filter;
|
||||
use function array_key_exists;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_pop;
|
||||
use function array_sum;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function current;
|
||||
use function func_get_arg;
|
||||
use function func_num_args;
|
||||
@@ -74,6 +73,7 @@ use function method_exists;
|
||||
use function reset;
|
||||
use function spl_object_id;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* The UnitOfWork is responsible for tracking changes to objects during an
|
||||
@@ -419,9 +419,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
$this->dispatchOnFlushEvent();
|
||||
|
||||
// Now we need a commit order to maintain referential integrity
|
||||
$commitOrder = $this->getCommitOrder();
|
||||
|
||||
$conn = $this->em->getConnection();
|
||||
$conn->beginTransaction();
|
||||
|
||||
@@ -437,32 +434,37 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
if ($this->entityInsertions) {
|
||||
foreach ($commitOrder as $class) {
|
||||
$this->executeInserts($class);
|
||||
}
|
||||
// Perform entity insertions first, so that all new entities have their rows in the database
|
||||
// and can be referred to by foreign keys. The commit order only needs to take new entities
|
||||
// into account (new entities referring to other new entities), since all other types (entities
|
||||
// with updates or scheduled deletions) are currently not a problem, since they are already
|
||||
// in the database.
|
||||
$this->executeInserts();
|
||||
}
|
||||
|
||||
if ($this->entityUpdates) {
|
||||
foreach ($commitOrder as $class) {
|
||||
$this->executeUpdates($class);
|
||||
}
|
||||
// Updates do not need to follow a particular order
|
||||
$this->executeUpdates();
|
||||
}
|
||||
|
||||
// Extra updates that were requested by persisters.
|
||||
// This may include foreign keys that could not be set when an entity was inserted,
|
||||
// which may happen in the case of circular foreign key relationships.
|
||||
if ($this->extraUpdates) {
|
||||
$this->executeExtraUpdates();
|
||||
}
|
||||
|
||||
// Collection updates (deleteRows, updateRows, insertRows)
|
||||
// No particular order is necessary, since all entities themselves are already
|
||||
// in the database
|
||||
foreach ($this->collectionUpdates as $collectionToUpdate) {
|
||||
$this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
|
||||
}
|
||||
|
||||
// Entity deletions come last and need to be in reverse commit order
|
||||
// Entity deletions come last. Their order only needs to take care of other deletions
|
||||
// (first delete entities depending upon others, before deleting depended-upon entities).
|
||||
if ($this->entityDeletions) {
|
||||
for ($count = count($commitOrder), $i = $count - 1; $i >= 0 && $this->entityDeletions; --$i) {
|
||||
$this->executeDeletions($commitOrder[$i]);
|
||||
}
|
||||
$this->executeDeletions();
|
||||
}
|
||||
|
||||
// Commit failed silently
|
||||
@@ -581,7 +583,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
// Ignore uninitialized proxy objects
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
||||
if ($this->isUninitializedObject($entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -906,7 +908,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
foreach ($entitiesToProcess as $entity) {
|
||||
// Ignore uninitialized proxy objects
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
||||
if ($this->isUninitializedObject($entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -931,7 +933,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
private function computeAssociationChanges(array $assoc, $value): void
|
||||
{
|
||||
if ($value instanceof Proxy && ! $value->__isInitialized()) {
|
||||
if ($this->isUninitializedObject($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1124,6 +1126,20 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($actualData as $propName => $actualValue) {
|
||||
$orgValue = $originalData[$propName] ?? null;
|
||||
|
||||
if (isset($class->fieldMappings[$propName]['enumType'])) {
|
||||
if (is_array($orgValue)) {
|
||||
foreach ($orgValue as $id => $val) {
|
||||
if ($val instanceof BackedEnum) {
|
||||
$orgValue[$id] = $val->value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($orgValue instanceof BackedEnum) {
|
||||
$orgValue = $orgValue->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($orgValue !== $actualValue) {
|
||||
$changeSet[$propName] = [$orgValue, $actualValue];
|
||||
}
|
||||
@@ -1142,64 +1158,46 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all entity insertions for entities of the specified type.
|
||||
* Executes entity insertions
|
||||
*/
|
||||
private function executeInserts(ClassMetadata $class): void
|
||||
private function executeInserts(): void
|
||||
{
|
||||
$entities = [];
|
||||
$className = $class->name;
|
||||
$persister = $this->getEntityPersister($className);
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
|
||||
$entities = $this->computeInsertExecutionOrder();
|
||||
|
||||
$insertionsForClass = [];
|
||||
|
||||
foreach ($this->entityInsertions as $oid => $entity) {
|
||||
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$insertionsForClass[$oid] = $entity;
|
||||
foreach ($entities as $entity) {
|
||||
$oid = spl_object_id($entity);
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
$persister = $this->getEntityPersister($class->name);
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
|
||||
|
||||
$persister->addInsert($entity);
|
||||
|
||||
unset($this->entityInsertions[$oid]);
|
||||
|
||||
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
||||
$entities[] = $entity;
|
||||
}
|
||||
}
|
||||
$postInsertIds = $persister->executeInserts();
|
||||
|
||||
$postInsertIds = $persister->executeInserts();
|
||||
if (is_array($postInsertIds)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10743/',
|
||||
'Returning post insert IDs from \Doctrine\ORM\Persisters\Entity\EntityPersister::executeInserts() is deprecated and will not be supported in Doctrine ORM 3.0. Make the persister call Doctrine\ORM\UnitOfWork::assignPostInsertId() instead.'
|
||||
);
|
||||
|
||||
if ($postInsertIds) {
|
||||
// Persister returned post-insert IDs
|
||||
foreach ($postInsertIds as $postInsertId) {
|
||||
$idField = $class->getSingleIdentifierFieldName();
|
||||
$idValue = $this->convertSingleFieldIdentifierToPHPValue($class, $postInsertId['generatedId']);
|
||||
|
||||
$entity = $postInsertId['entity'];
|
||||
$oid = spl_object_id($entity);
|
||||
|
||||
$class->reflFields[$idField]->setValue($entity, $idValue);
|
||||
|
||||
$this->entityIdentifiers[$oid] = [$idField => $idValue];
|
||||
$this->entityStates[$oid] = self::STATE_MANAGED;
|
||||
$this->originalEntityData[$oid][$idField] = $idValue;
|
||||
|
||||
$this->addToIdentityMap($entity);
|
||||
}
|
||||
} else {
|
||||
foreach ($insertionsForClass as $oid => $entity) {
|
||||
if (! isset($this->entityIdentifiers[$oid])) {
|
||||
//entity was not added to identity map because some identifiers are foreign keys to new entities.
|
||||
//add it now
|
||||
$this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity);
|
||||
// Persister returned post-insert IDs
|
||||
foreach ($postInsertIds as $postInsertId) {
|
||||
$this->assignPostInsertId($postInsertId['entity'], $postInsertId['generatedId']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$this->listenersInvoker->invoke($class, Events::postPersist, $entity, new PostPersistEventArgs($entity, $this->em), $invoke);
|
||||
if (! isset($this->entityIdentifiers[$oid])) {
|
||||
//entity was not added to identity map because some identifiers are foreign keys to new entities.
|
||||
//add it now
|
||||
$this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity);
|
||||
}
|
||||
|
||||
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
||||
$this->listenersInvoker->invoke($class, Events::postPersist, $entity, new PostPersistEventArgs($entity, $this->em), $invoke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1237,19 +1235,15 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all entity updates for entities of the specified type.
|
||||
* Executes all entity updates
|
||||
*/
|
||||
private function executeUpdates(ClassMetadata $class): void
|
||||
private function executeUpdates(): void
|
||||
{
|
||||
$className = $class->name;
|
||||
$persister = $this->getEntityPersister($className);
|
||||
$preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
|
||||
$postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
|
||||
|
||||
foreach ($this->entityUpdates as $oid => $entity) {
|
||||
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
||||
continue;
|
||||
}
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
$persister = $this->getEntityPersister($class->name);
|
||||
$preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
|
||||
$postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
|
||||
|
||||
if ($preUpdateInvoke !== ListenersInvoker::INVOKE_NONE) {
|
||||
$this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
|
||||
@@ -1270,18 +1264,17 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all entity deletions for entities of the specified type.
|
||||
* Executes all entity deletions
|
||||
*/
|
||||
private function executeDeletions(ClassMetadata $class): void
|
||||
private function executeDeletions(): void
|
||||
{
|
||||
$className = $class->name;
|
||||
$persister = $this->getEntityPersister($className);
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
|
||||
$entities = $this->computeDeleteExecutionOrder();
|
||||
|
||||
foreach ($this->entityDeletions as $oid => $entity) {
|
||||
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
|
||||
continue;
|
||||
}
|
||||
foreach ($entities as $entity) {
|
||||
$oid = spl_object_id($entity);
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
$persister = $this->getEntityPersister($class->name);
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
|
||||
|
||||
$persister->delete($entity);
|
||||
|
||||
@@ -1305,73 +1298,116 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the commit order.
|
||||
*
|
||||
* @return list<ClassMetadata>
|
||||
*/
|
||||
private function getCommitOrder(): array
|
||||
/** @return list<object> */
|
||||
private function computeInsertExecutionOrder(): array
|
||||
{
|
||||
$calc = $this->getCommitOrderCalculator();
|
||||
$sort = new TopologicalSort();
|
||||
|
||||
// See if there are any new classes in the changeset, that are not in the
|
||||
// commit order graph yet (don't have a node).
|
||||
// We have to inspect changeSet to be able to correctly build dependencies.
|
||||
// It is not possible to use IdentityMap here because post inserted ids
|
||||
// are not yet available.
|
||||
$newNodes = [];
|
||||
|
||||
foreach (array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
if ($calc->hasNode($class->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$calc->addNode($class->name, $class);
|
||||
|
||||
$newNodes[] = $class;
|
||||
// First make sure we have all the nodes
|
||||
foreach ($this->entityInsertions as $entity) {
|
||||
$sort->addNode($entity);
|
||||
}
|
||||
|
||||
// Calculate dependencies for new nodes
|
||||
while ($class = array_pop($newNodes)) {
|
||||
// Now add edges
|
||||
foreach ($this->entityInsertions as $entity) {
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
// We only need to consider the owning sides of to-one associations,
|
||||
// since many-to-many associations are persisted at a later step and
|
||||
// have no insertion order problems (all entities already in the database
|
||||
// at that time).
|
||||
if (! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$targetEntity = $class->getFieldValue($entity, $assoc['fieldName']);
|
||||
|
||||
if (! $calc->hasNode($targetClass->name)) {
|
||||
$calc->addNode($targetClass->name, $targetClass);
|
||||
|
||||
$newNodes[] = $targetClass;
|
||||
}
|
||||
|
||||
$joinColumns = reset($assoc['joinColumns']);
|
||||
|
||||
$calc->addDependency($targetClass->name, $class->name, (int) empty($joinColumns['nullable']));
|
||||
|
||||
// If the target class has mapped subclasses, these share the same dependency.
|
||||
if (! $targetClass->subClasses) {
|
||||
// If there is no entity that we need to refer to, or it is already in the
|
||||
// database (i. e. does not have to be inserted), no need to consider it.
|
||||
if ($targetEntity === null || ! $sort->hasNode($targetEntity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($targetClass->subClasses as $subClassName) {
|
||||
$targetSubClass = $this->em->getClassMetadata($subClassName);
|
||||
|
||||
if (! $calc->hasNode($subClassName)) {
|
||||
$calc->addNode($targetSubClass->name, $targetSubClass);
|
||||
|
||||
$newNodes[] = $targetSubClass;
|
||||
}
|
||||
|
||||
$calc->addDependency($targetSubClass->name, $class->name, 1);
|
||||
// An entity that references back to itself _and_ uses an application-provided ID
|
||||
// (the "NONE" generator strategy) can be exempted from commit order computation.
|
||||
// See https://github.com/doctrine/orm/pull/10735/ for more details on this edge case.
|
||||
// A non-NULLable self-reference would be a cycle in the graph.
|
||||
if ($targetEntity === $entity && $class->isIdentifierNatural()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// According to https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/annotations-reference.html#annref_joincolumn,
|
||||
// the default for "nullable" is true. Unfortunately, it seems this default is not applied at the metadata driver, factory or other
|
||||
// level, but in fact we may have an undefined 'nullable' key here, so we must assume that default here as well.
|
||||
//
|
||||
// Same in \Doctrine\ORM\Tools\EntityGenerator::isAssociationIsNullable or \Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getJoinSQLForJoinColumns,
|
||||
// to give two examples.
|
||||
assert(isset($assoc['joinColumns']));
|
||||
$joinColumns = reset($assoc['joinColumns']);
|
||||
$isNullable = ! isset($joinColumns['nullable']) || $joinColumns['nullable'];
|
||||
|
||||
// Add dependency. The dependency direction implies that "$targetEntity has to go before $entity",
|
||||
// so we can work through the topo sort result from left to right (with all edges pointing right).
|
||||
$sort->addEdge($targetEntity, $entity, $isNullable);
|
||||
}
|
||||
}
|
||||
|
||||
return $calc->sort();
|
||||
return $sort->sort();
|
||||
}
|
||||
|
||||
/** @return list<object> */
|
||||
private function computeDeleteExecutionOrder(): array
|
||||
{
|
||||
$sort = new TopologicalSort();
|
||||
|
||||
// First make sure we have all the nodes
|
||||
foreach ($this->entityDeletions as $entity) {
|
||||
$sort->addNode($entity);
|
||||
}
|
||||
|
||||
// Now add edges
|
||||
foreach ($this->entityDeletions as $entity) {
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
// We only need to consider the owning sides of to-one associations,
|
||||
// since many-to-many associations can always be (and have already been)
|
||||
// deleted in a preceding step.
|
||||
if (! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// For associations that implement a database-level cascade/set null operation,
|
||||
// we do not have to follow a particular order: If the referred-to entity is
|
||||
// deleted first, the DBMS will either delete the current $entity right away
|
||||
// (CASCADE) or temporarily set the foreign key to NULL (SET NULL).
|
||||
// Either way, we can skip it in the computation.
|
||||
assert(isset($assoc['joinColumns']));
|
||||
$joinColumns = reset($assoc['joinColumns']);
|
||||
if (isset($joinColumns['onDelete'])) {
|
||||
$onDeleteOption = strtolower($joinColumns['onDelete']);
|
||||
if ($onDeleteOption === 'cascade' || $onDeleteOption === 'set null') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$targetEntity = $class->getFieldValue($entity, $assoc['fieldName']);
|
||||
|
||||
// If the association does not refer to another entity or that entity
|
||||
// is not to be deleted, there is no ordering problem and we can
|
||||
// skip this particular association.
|
||||
if ($targetEntity === null || ! $sort->hasNode($targetEntity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add dependency. The dependency direction implies that "$entity has to be removed before $targetEntity",
|
||||
// so we can work through the topo sort result from left to right (with all edges pointing right).
|
||||
$sort->addEdge($entity, $targetEntity, false);
|
||||
}
|
||||
}
|
||||
|
||||
return $sort->sort();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1597,6 +1633,30 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$className = $classMetadata->rootEntityName;
|
||||
|
||||
if (isset($this->identityMap[$className][$idHash])) {
|
||||
if ($this->identityMap[$className][$idHash] !== $entity) {
|
||||
throw new RuntimeException(sprintf(
|
||||
<<<'EXCEPTION'
|
||||
While adding an entity of class %s with an ID hash of "%s" to the identity map,
|
||||
another object of class %s was already present for the same ID. This exception
|
||||
is a safeguard against an internal inconsistency - IDs should uniquely map to
|
||||
entity object instances. This problem may occur if:
|
||||
|
||||
- you use application-provided IDs and reuse ID values;
|
||||
- database-provided IDs are reassigned after truncating the database without
|
||||
clearing the EntityManager;
|
||||
- you might have been using EntityManager#getReference() to create a reference
|
||||
for a nonexistent ID that was subsequently (by the RDBMS) assigned to another
|
||||
entity.
|
||||
|
||||
Otherwise, it might be an ORM-internal inconsistency, please report it.
|
||||
EXCEPTION
|
||||
,
|
||||
get_class($entity),
|
||||
$idHash,
|
||||
get_class($this->identityMap[$className][$idHash])
|
||||
));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2140,7 +2200,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$entity,
|
||||
$managedCopy
|
||||
): void {
|
||||
if (! ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity))) {
|
||||
if (! ($class->isVersioned && ! $this->isUninitializedObject($managedCopy) && ! $this->isUninitializedObject($entity))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2158,16 +2218,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if an entity is loaded - must either be a loaded proxy or not a proxy
|
||||
*
|
||||
* @param object $entity
|
||||
*/
|
||||
private function isLoaded($entity): bool
|
||||
{
|
||||
return ! ($entity instanceof Proxy) || $entity->__isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/adds associated managed copies into the previous entity's association field
|
||||
*
|
||||
@@ -2463,7 +2513,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
private function cascadePersist($entity, array &$visited): void
|
||||
{
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
||||
if ($this->isUninitializedObject($entity)) {
|
||||
// nothing to do - proxy is not initialized, therefore we don't do anything with it
|
||||
return;
|
||||
}
|
||||
@@ -2537,13 +2587,13 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
);
|
||||
|
||||
if ($associationMappings) {
|
||||
$this->initializeObject($entity);
|
||||
}
|
||||
|
||||
$entitiesToCascade = [];
|
||||
|
||||
foreach ($associationMappings as $assoc) {
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
||||
$entity->__load();
|
||||
}
|
||||
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
|
||||
switch (true) {
|
||||
@@ -2599,9 +2649,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
||||
$entity->__load();
|
||||
}
|
||||
$this->initializeObject($entity);
|
||||
|
||||
assert($class->versionField !== null);
|
||||
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
||||
@@ -2791,7 +2839,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY];
|
||||
if (
|
||||
$unmanagedProxy !== $entity
|
||||
&& $unmanagedProxy instanceof Proxy
|
||||
&& $this->isIdentifierEquals($unmanagedProxy, $entity)
|
||||
) {
|
||||
// We will hydrate the given un-managed proxy anyway:
|
||||
@@ -2800,7 +2847,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
||||
if ($this->isUninitializedObject($entity)) {
|
||||
$entity->__setInitialized(true);
|
||||
} else {
|
||||
if (
|
||||
@@ -2817,25 +2864,20 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
$this->originalEntityData[$oid] = $data;
|
||||
|
||||
if ($entity instanceof NotifyPropertyChanged) {
|
||||
$entity->addPropertyChangedListener($this);
|
||||
}
|
||||
} else {
|
||||
$entity = $this->newInstance($class);
|
||||
$oid = spl_object_id($entity);
|
||||
|
||||
$this->entityIdentifiers[$oid] = $id;
|
||||
$this->entityStates[$oid] = self::STATE_MANAGED;
|
||||
$this->originalEntityData[$oid] = $data;
|
||||
|
||||
$this->identityMap[$class->rootEntityName][$idHash] = $entity;
|
||||
$this->registerManaged($entity, $id, $data);
|
||||
|
||||
if (isset($hints[Query::HINT_READ_ONLY])) {
|
||||
$this->readOnlyObjects[$oid] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity instanceof NotifyPropertyChanged) {
|
||||
$entity->addPropertyChangedListener($this);
|
||||
}
|
||||
|
||||
foreach ($data as $field => $value) {
|
||||
if (isset($class->fieldMappings[$field])) {
|
||||
$class->reflFields[$field]->setValue($entity, $value);
|
||||
@@ -2951,8 +2993,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER &&
|
||||
isset($hints[self::HINT_DEFEREAGERLOAD]) &&
|
||||
! $targetClass->isIdentifierComposite &&
|
||||
$newValue instanceof Proxy &&
|
||||
$newValue->__isInitialized() === false
|
||||
$this->isUninitializedObject($newValue)
|
||||
) {
|
||||
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
|
||||
}
|
||||
@@ -2993,20 +3034,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
break;
|
||||
}
|
||||
|
||||
// PERF: Inlined & optimized code from UnitOfWork#registerManaged()
|
||||
$newValueOid = spl_object_id($newValue);
|
||||
$this->entityIdentifiers[$newValueOid] = $associatedId;
|
||||
$this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
|
||||
|
||||
if (
|
||||
$newValue instanceof NotifyPropertyChanged &&
|
||||
( ! $newValue instanceof Proxy || $newValue->__isInitialized())
|
||||
) {
|
||||
$newValue->addPropertyChangedListener($this);
|
||||
}
|
||||
|
||||
$this->entityStates[$newValueOid] = self::STATE_MANAGED;
|
||||
// make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
|
||||
$this->registerManaged($newValue, $associatedId, []);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3363,7 +3391,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
$this->addToIdentityMap($entity);
|
||||
|
||||
if ($entity instanceof NotifyPropertyChanged && ( ! $entity instanceof Proxy || $entity->__isInitialized())) {
|
||||
if ($entity instanceof NotifyPropertyChanged && ! $this->isUninitializedObject($entity)) {
|
||||
$entity->addPropertyChangedListener($this);
|
||||
}
|
||||
}
|
||||
@@ -3471,7 +3499,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
public function initializeObject($obj)
|
||||
{
|
||||
if ($obj instanceof Proxy) {
|
||||
if ($obj instanceof InternalProxy) {
|
||||
$obj->__load();
|
||||
|
||||
return;
|
||||
@@ -3482,6 +3510,18 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a value is an uninitialized entity.
|
||||
*
|
||||
* @param mixed $obj
|
||||
*
|
||||
* @psalm-assert-if-true InternalProxy $obj
|
||||
*/
|
||||
public function isUninitializedObject($obj): bool
|
||||
{
|
||||
return $obj instanceof InternalProxy && ! $obj->__isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to show an object as string.
|
||||
*
|
||||
@@ -3632,13 +3672,11 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
private function mergeEntityStateIntoManagedCopy($entity, $managedCopy): void
|
||||
{
|
||||
if (! $this->isLoaded($entity)) {
|
||||
if ($this->isUninitializedObject($entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->isLoaded($managedCopy)) {
|
||||
$managedCopy->__load();
|
||||
}
|
||||
$this->initializeObject($managedCopy);
|
||||
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
@@ -3659,7 +3697,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ($other === null) {
|
||||
$prop->setValue($managedCopy, null);
|
||||
} else {
|
||||
if ($other instanceof Proxy && ! $other->__isInitialized()) {
|
||||
if ($this->isUninitializedObject($other)) {
|
||||
// do not merge fields marked lazy that have not been fetched.
|
||||
continue;
|
||||
}
|
||||
@@ -3669,14 +3707,18 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$targetClass = $this->em->getClassMetadata($assoc2['targetEntity']);
|
||||
$relatedId = $targetClass->getIdentifierValues($other);
|
||||
|
||||
if ($targetClass->subClasses) {
|
||||
$other = $this->em->find($targetClass->name, $relatedId);
|
||||
} else {
|
||||
$other = $this->em->getProxyFactory()->getProxy(
|
||||
$assoc2['targetEntity'],
|
||||
$relatedId
|
||||
);
|
||||
$this->registerManaged($other, $relatedId, []);
|
||||
$other = $this->tryGetById($relatedId, $targetClass->name);
|
||||
|
||||
if (! $other) {
|
||||
if ($targetClass->subClasses) {
|
||||
$other = $this->em->find($targetClass->name, $relatedId);
|
||||
} else {
|
||||
$other = $this->em->getProxyFactory()->getProxy(
|
||||
$assoc2['targetEntity'],
|
||||
$relatedId
|
||||
);
|
||||
$this->registerManaged($other, $relatedId, []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3819,4 +3861,30 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
return $normalizedAssociatedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a post-insert generated ID to an entity
|
||||
*
|
||||
* This is used by EntityPersisters after they inserted entities into the database.
|
||||
* It will place the assigned ID values in the entity's fields and start tracking
|
||||
* the entity in the identity map.
|
||||
*
|
||||
* @param object $entity
|
||||
* @param mixed $generatedId
|
||||
*/
|
||||
final public function assignPostInsertId($entity, $generatedId): void
|
||||
{
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
$idField = $class->getSingleIdentifierFieldName();
|
||||
$idValue = $this->convertSingleFieldIdentifierToPHPValue($class, $generatedId);
|
||||
$oid = spl_object_id($entity);
|
||||
|
||||
$class->reflFields[$idField]->setValue($entity, $idValue);
|
||||
|
||||
$this->entityIdentifiers[$oid] = [$idField => $idValue];
|
||||
$this->entityStates[$oid] = self::STATE_MANAGED;
|
||||
$this->originalEntityData[$oid][$idField] = $idValue;
|
||||
|
||||
$this->addToIdentityMap($entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ parameters:
|
||||
path: lib/Doctrine/ORM/Proxy/ProxyFactory.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Proxy\\:\\:\\$__isCloning\\.$#"
|
||||
message: "#^Call to an undefined method Doctrine\\\\Common\\\\Proxy\\\\Proxy\\:\\:__construct\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Proxy/ProxyFactory.php
|
||||
|
||||
@@ -395,21 +395,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Query/Parser.php
|
||||
|
||||
-
|
||||
message: """
|
||||
#^PHPDoc tag @return has invalid value \\(AST\\\\BetweenExpression\\|
|
||||
AST\\\\CollectionMemberExpression\\|
|
||||
AST\\\\ComparisonExpression\\|
|
||||
AST\\\\EmptyCollectionComparisonExpression\\|
|
||||
AST\\\\ExistsExpression\\|
|
||||
AST\\\\InExpression\\|
|
||||
AST\\\\InstanceOfExpression\\|
|
||||
AST\\\\LikeExpression\\|
|
||||
AST\\\\NullComparisonExpression\\)\\: Unexpected token "\\\\n \\* ", expected type at offset 344$#
|
||||
"""
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Query/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$stringPattern of class Doctrine\\\\ORM\\\\Query\\\\AST\\\\LikeExpression constructor expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\Functions\\\\FunctionNode\\|Doctrine\\\\ORM\\\\Query\\\\AST\\\\InputParameter\\|Doctrine\\\\ORM\\\\Query\\\\AST\\\\Literal\\|Doctrine\\\\ORM\\\\Query\\\\AST\\\\PathExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -34,10 +34,5 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php
|
||||
|
||||
-
|
||||
message: '/^Call to an undefined method Doctrine\\Persistence\\Proxy::__setInitialized\(\)\.$/'
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/UnitOfWork.php
|
||||
|
||||
# Symfony cache supports passing a key prefix to the clear method.
|
||||
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="5.12.0@f90118cdeacd0088e7215e64c0c99ceca819e176">
|
||||
<files psalm-version="5.13.1@086b94371304750d1c673315321a55d15fc59015">
|
||||
<file src="lib/Doctrine/ORM/AbstractQuery.php">
|
||||
<DeprecatedClass>
|
||||
<code>IterableResult</code>
|
||||
@@ -213,11 +213,6 @@
|
||||
<code>CacheProvider</code>
|
||||
</MoreSpecificReturnType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Cache/Region/FileLockRegion.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code><![CDATA[sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)]]></code>
|
||||
</ArgumentTypeCoercion>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Cache/RegionsConfiguration.php">
|
||||
<RedundantCastGivenDocblockType>
|
||||
<code>(int) $defaultLifetime</code>
|
||||
@@ -493,7 +488,6 @@
|
||||
<code>getValue</code>
|
||||
<code>setValue</code>
|
||||
<code>setValue</code>
|
||||
<code>setValue</code>
|
||||
</PossiblyNullReference>
|
||||
<PossiblyUndefinedArrayOffset>
|
||||
<code><![CDATA[$class->associationMappings[$class->identifier[0]]['joinColumns']]]></code>
|
||||
@@ -1386,17 +1380,17 @@
|
||||
<code>$columnList</code>
|
||||
</PossiblyUndefinedVariable>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Proxy/Proxy.php">
|
||||
<MissingTemplateParam>
|
||||
<code>BaseProxy</code>
|
||||
</MissingTemplateParam>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Proxy/ProxyFactory.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code>$classMetadata</code>
|
||||
<code>$classMetadata</code>
|
||||
<code>$classMetadata</code>
|
||||
</ArgumentTypeCoercion>
|
||||
<DirectConstructorCall>
|
||||
<code><![CDATA[$proxy->__construct(static function (InternalProxy $object) use ($initializer, $proxy): void {
|
||||
$initializer($object, $proxy);
|
||||
})]]></code>
|
||||
</DirectConstructorCall>
|
||||
<InvalidArgument>
|
||||
<code><![CDATA[$classMetadata->getReflectionProperties()]]></code>
|
||||
<code><![CDATA[$em->getMetadataFactory()]]></code>
|
||||
@@ -1405,7 +1399,6 @@
|
||||
<NoInterfaceProperties>
|
||||
<code><![CDATA[$metadata->isEmbeddedClass]]></code>
|
||||
<code><![CDATA[$metadata->isMappedSuperclass]]></code>
|
||||
<code><![CDATA[$proxy->__isCloning]]></code>
|
||||
</NoInterfaceProperties>
|
||||
<PossiblyNullPropertyFetch>
|
||||
<code><![CDATA[$property->name]]></code>
|
||||
@@ -1416,6 +1409,7 @@
|
||||
<code>setAccessible</code>
|
||||
</PossiblyNullReference>
|
||||
<UndefinedInterfaceMethod>
|
||||
<code>__construct</code>
|
||||
<code>__wakeup</code>
|
||||
</UndefinedInterfaceMethod>
|
||||
</file>
|
||||
@@ -2013,19 +2007,10 @@
|
||||
<code>$factors[0]</code>
|
||||
<code>$primary</code>
|
||||
<code>$terms[0]</code>
|
||||
<code><![CDATA[$this->CollectionMemberExpression()]]></code>
|
||||
<code><![CDATA[$this->ComparisonExpression()]]></code>
|
||||
<code><![CDATA[$this->EmptyCollectionComparisonExpression()]]></code>
|
||||
<code><![CDATA[$this->ExistsExpression()]]></code>
|
||||
<code><![CDATA[$this->InExpression()]]></code>
|
||||
<code><![CDATA[$this->InstanceOfExpression()]]></code>
|
||||
<code><![CDATA[$this->LikeExpression()]]></code>
|
||||
<code><![CDATA[$this->NullComparisonExpression()]]></code>
|
||||
</InvalidReturnStatement>
|
||||
<InvalidReturnType>
|
||||
<code>AST\ArithmeticFactor</code>
|
||||
<code>AST\ArithmeticTerm</code>
|
||||
<code>AST\BetweenExpression|</code>
|
||||
<code>AST\SimpleArithmeticExpression|AST\ArithmeticTerm</code>
|
||||
</InvalidReturnType>
|
||||
<InvalidStringClass>
|
||||
@@ -2456,9 +2441,6 @@
|
||||
</MissingTemplateParam>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code><![CDATA[$path . '/*.yml']]></code>
|
||||
</ArgumentTypeCoercion>
|
||||
<PossiblyUndefinedArrayOffset>
|
||||
<code><![CDATA[$column['type']]]></code>
|
||||
</PossiblyUndefinedArrayOffset>
|
||||
@@ -2811,7 +2793,6 @@
|
||||
<code>setValue</code>
|
||||
</PossiblyNullReference>
|
||||
<PossiblyUndefinedArrayOffset>
|
||||
<code><![CDATA[$assoc['joinColumns']]]></code>
|
||||
<code><![CDATA[$assoc['orphanRemoval']]]></code>
|
||||
<code><![CDATA[$assoc['targetToSourceKeyColumns']]]></code>
|
||||
</PossiblyUndefinedArrayOffset>
|
||||
@@ -2820,10 +2801,6 @@
|
||||
<code>unwrap</code>
|
||||
<code>unwrap</code>
|
||||
</PossiblyUndefinedMethod>
|
||||
<RedundantCondition>
|
||||
<code><![CDATA[$i >= 0 && $this->entityDeletions]]></code>
|
||||
<code><![CDATA[$this->entityDeletions]]></code>
|
||||
</RedundantCondition>
|
||||
<RedundantConditionGivenDocblockType>
|
||||
<code>is_array($entity)</code>
|
||||
</RedundantConditionGivenDocblockType>
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
<referencedClass name="Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand"/>
|
||||
<referencedClass name="Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper"/>
|
||||
<referencedClass name="Doctrine\ORM\Tools\Console\EntityManagerProvider\HelperSetManagerProvider"/>
|
||||
<referencedClass name="Doctrine\ORM\Internal\CommitOrder\Edge"/>
|
||||
<referencedClass name="Doctrine\ORM\Internal\CommitOrder\Vertex"/>
|
||||
<referencedClass name="Doctrine\ORM\Internal\CommitOrder\VertexState"/>
|
||||
<referencedClass name="Doctrine\ORM\Internal\CommitOrderCalculator"/>
|
||||
</errorLevel>
|
||||
</DeprecatedClass>
|
||||
<DeprecatedConstant>
|
||||
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\LazyLoading;
|
||||
|
||||
use Doctrine\ORM\Proxy\InternalProxy as Proxy;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Performance\Mock\NonProxyLoadingEntityManager;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\CMS\CmsEmployee;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ abstract class DoctrineTestCase extends TestCase
|
||||
'assertMatchesRegularExpression' => 'assertRegExp', // can be removed when PHPUnit 9 is minimum
|
||||
'assertDoesNotMatchRegularExpression' => 'assertNotRegExp', // can be removed when PHPUnit 9 is minimum
|
||||
'assertFileDoesNotExist' => 'assertFileNotExists', // can be removed PHPUnit 9 is minimum
|
||||
'expectExceptionMessageMatches' => 'expectExceptionMessageRegExp', // can be removed when PHPUnit 8 is minimum
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,8 +11,6 @@ use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\EntityResult;
|
||||
use Doctrine\ORM\Mapping\FieldResult;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\JoinTable;
|
||||
use Doctrine\ORM\Mapping\ManyToMany;
|
||||
@@ -67,14 +65,6 @@ use Doctrine\ORM\Mapping\SqlResultSetMappings;
|
||||
#[ORM\Entity]
|
||||
class CompanyFlexContract extends CompanyContract
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
* @var int
|
||||
|
||||
@@ -101,7 +101,7 @@ class ConfigurationTest extends DoctrineTestCase
|
||||
$paths = [__DIR__];
|
||||
$reflectionClass = new ReflectionClass(ConfigurationTestAnnotationReaderChecker::class);
|
||||
|
||||
$annotationDriver = $this->configuration->newDefaultAnnotationDriver($paths, false);
|
||||
$annotationDriver = $this->configuration->newDefaultAnnotationDriver($paths, false, true);
|
||||
$reader = $annotationDriver->getReader();
|
||||
$annotation = $reader->getMethodAnnotation(
|
||||
$reflectionClass->getMethod('namespacedAnnotationMethod'),
|
||||
|
||||
@@ -8,17 +8,19 @@ use Doctrine\ORM\EntityNotFoundException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\IterableTester;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsArticle;
|
||||
use Doctrine\Tests\Models\CMS\CmsComment;
|
||||
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
use function get_class;
|
||||
|
||||
@@ -144,7 +146,7 @@ class BasicFunctionalTest extends OrmFunctionalTestCase
|
||||
|
||||
// Address has been eager-loaded because it cant be lazy
|
||||
self::assertInstanceOf(CmsAddress::class, $user2->address);
|
||||
self::assertNotInstanceOf(Proxy::class, $user2->address);
|
||||
self::assertFalse($this->isUninitializedObject($user2->address));
|
||||
}
|
||||
|
||||
/** @group DDC-1230 */
|
||||
@@ -515,42 +517,76 @@ class BasicFunctionalTest extends OrmFunctionalTestCase
|
||||
self::assertEquals(4, $gblanco2->getPhonenumbers()->count());
|
||||
}
|
||||
|
||||
public function testSetSetAssociationWithGetReference(): void
|
||||
public function testSetToOneAssociationWithGetReference(): void
|
||||
{
|
||||
$user = new CmsUser();
|
||||
$user->name = 'Guilherme';
|
||||
$user->username = 'gblanco';
|
||||
$user->status = 'developer';
|
||||
$this->_em->persist($user);
|
||||
|
||||
$address = new CmsAddress();
|
||||
$address->country = 'Germany';
|
||||
$address->city = 'Berlin';
|
||||
$address->zip = '12345';
|
||||
$this->_em->persist($address);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear(CmsAddress::class);
|
||||
|
||||
self::assertFalse($this->_em->contains($address));
|
||||
self::assertTrue($this->_em->contains($user));
|
||||
|
||||
// Assume we only got the identifier of the address and now want to attach
|
||||
// that address to the user without actually loading it, using getReference().
|
||||
$addressRef = $this->_em->getReference(CmsAddress::class, $address->getId());
|
||||
|
||||
$user->setAddress($addressRef); // Ugh! Initializes address 'cause of $address->setUser($user)!
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
// Assume we only got the identifier of the user and now want to attach
|
||||
// the article to the user without actually loading it, using getReference().
|
||||
$userRef = $this->_em->getReference(CmsUser::class, $user->getId());
|
||||
self::assertTrue($this->isUninitializedObject($userRef));
|
||||
|
||||
$article = new CmsArticle();
|
||||
$article->topic = 'topic';
|
||||
$article->text = 'text';
|
||||
$article->setAuthor($userRef);
|
||||
|
||||
$this->_em->persist($article);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertFalse($userRef->__isInitialized());
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
// Check with a fresh load that the association is indeed there
|
||||
$query = $this->_em->createQuery("select u, a from Doctrine\Tests\Models\CMS\CmsUser u join u.address a where u.username='gblanco'");
|
||||
$query = $this->_em->createQuery("select u, a from Doctrine\Tests\Models\CMS\CmsUser u join u.articles a where u.username='gblanco'");
|
||||
$gblanco = $query->getSingleResult();
|
||||
|
||||
self::assertInstanceOf(CmsUser::class, $gblanco);
|
||||
self::assertInstanceOf(CmsAddress::class, $gblanco->getAddress());
|
||||
self::assertEquals('Berlin', $gblanco->getAddress()->getCity());
|
||||
self::assertInstanceOf(CmsArticle::class, $gblanco->articles[0]);
|
||||
self::assertSame($article->id, $gblanco->articles[0]->id);
|
||||
self::assertSame('text', $gblanco->articles[0]->text);
|
||||
}
|
||||
|
||||
public function testAddToToManyAssociationWithGetReference(): void
|
||||
{
|
||||
$group = new CmsGroup();
|
||||
$group->name = 'admins';
|
||||
$this->_em->persist($group);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
// Assume we only got the identifier of the user and now want to attach
|
||||
// the article to the user without actually loading it, using getReference().
|
||||
$groupRef = $this->_em->getReference(CmsGroup::class, $group->id);
|
||||
self::assertTrue($this->isUninitializedObject($groupRef));
|
||||
|
||||
$user = new CmsUser();
|
||||
$user->name = 'Guilherme';
|
||||
$user->username = 'gblanco';
|
||||
$user->groups->add($groupRef);
|
||||
|
||||
$this->_em->persist($user);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertFalse($groupRef->__isInitialized());
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
// Check with a fresh load that the association is indeed there
|
||||
$query = $this->_em->createQuery("select u, a from Doctrine\Tests\Models\CMS\CmsUser u join u.groups a where u.username='gblanco'");
|
||||
$gblanco = $query->getSingleResult();
|
||||
|
||||
self::assertInstanceOf(CmsUser::class, $gblanco);
|
||||
self::assertInstanceOf(CmsGroup::class, $gblanco->groups[0]);
|
||||
self::assertSame($group->id, $gblanco->groups[0]->id);
|
||||
self::assertSame('admins', $gblanco->groups[0]->name);
|
||||
}
|
||||
|
||||
public function testOneToManyCascadeRemove(): void
|
||||
@@ -707,9 +743,8 @@ class BasicFunctionalTest extends OrmFunctionalTestCase
|
||||
->setParameter('user', $userRef)
|
||||
->getSingleResult();
|
||||
|
||||
self::assertInstanceOf(Proxy::class, $address2->getUser());
|
||||
self::assertTrue($userRef === $address2->getUser());
|
||||
self::assertFalse($userRef->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($userRef));
|
||||
self::assertEquals('Germany', $address2->country);
|
||||
self::assertEquals('Berlin', $address2->city);
|
||||
self::assertEquals('12345', $address2->zip);
|
||||
@@ -1006,8 +1041,8 @@ class BasicFunctionalTest extends OrmFunctionalTestCase
|
||||
->setParameter(1, $article->id)
|
||||
->setFetchMode(CmsArticle::class, 'user', ClassMetadata::FETCH_EAGER)
|
||||
->getSingleResult();
|
||||
self::assertInstanceOf(Proxy::class, $article->user, 'It IS a proxy, ...');
|
||||
self::assertTrue($article->user->__isInitialized(), '...but its initialized!');
|
||||
self::assertInstanceOf(InternalProxy::class, $article->user, 'It IS a proxy, ...');
|
||||
self::assertFalse($this->isUninitializedObject($article->user), '...but its initialized!');
|
||||
$this->assertQueryCount(2);
|
||||
}
|
||||
|
||||
@@ -1291,4 +1326,33 @@ class BasicFunctionalTest extends OrmFunctionalTestCase
|
||||
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
public function testItThrowsWhenReferenceUsesIdAssignedByDatabase(): void
|
||||
{
|
||||
$user = new CmsUser();
|
||||
$user->name = 'test';
|
||||
$user->username = 'test';
|
||||
$this->_em->persist($user);
|
||||
$this->_em->flush();
|
||||
|
||||
// Obtain a reference object for the next ID. This is a user error - references
|
||||
// should be fetched only for existing IDs
|
||||
$ref = $this->_em->getReference(CmsUser::class, $user->id + 1);
|
||||
|
||||
$user2 = new CmsUser();
|
||||
$user2->name = 'test2';
|
||||
$user2->username = 'test2';
|
||||
|
||||
// Now the database will assign an ID to the $user2 entity, but that place
|
||||
// in the identity map is already taken by user error.
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessageMatches('/another object .* was already present for the same ID/');
|
||||
|
||||
// depending on ID generation strategy, the ID may be asssigned already here
|
||||
// and the entity be put in the identity map
|
||||
$this->_em->persist($user2);
|
||||
|
||||
// post insert IDs will be assigned during flush
|
||||
$this->_em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
use DateTime;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\IterableTester;
|
||||
use Doctrine\Tests\Models\Company\CompanyAuction;
|
||||
use Doctrine\Tests\Models\Company\CompanyEmployee;
|
||||
@@ -300,7 +299,7 @@ class ClassTableInheritanceTest extends OrmFunctionalTestCase
|
||||
$mainEvent = $result[0]->getMainEvent();
|
||||
// mainEvent should have been loaded because it can't be lazy
|
||||
self::assertInstanceOf(CompanyAuction::class, $mainEvent);
|
||||
self::assertNotInstanceOf(Proxy::class, $mainEvent);
|
||||
self::assertFalse($this->isUninitializedObject($mainEvent));
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
@@ -432,13 +431,13 @@ class ClassTableInheritanceTest extends OrmFunctionalTestCase
|
||||
$this->_em->clear();
|
||||
|
||||
$ref = $this->_em->getReference(CompanyPerson::class, $manager->getId());
|
||||
self::assertNotInstanceOf(Proxy::class, $ref, 'Cannot Request a proxy from a class that has subclasses.');
|
||||
self::assertFalse($this->isUninitializedObject($ref), 'Cannot Request a proxy from a class that has subclasses.');
|
||||
self::assertInstanceOf(CompanyPerson::class, $ref);
|
||||
self::assertInstanceOf(CompanyEmployee::class, $ref, 'Direct fetch of the reference has to load the child class Employee directly.');
|
||||
$this->_em->clear();
|
||||
|
||||
$ref = $this->_em->getReference(CompanyManager::class, $manager->getId());
|
||||
self::assertInstanceOf(Proxy::class, $ref, 'A proxy can be generated only if no subclasses exists for the requested reference.');
|
||||
self::assertTrue($this->isUninitializedObject($ref), 'A proxy can be generated only if no subclasses exists for the requested reference.');
|
||||
}
|
||||
|
||||
/** @group DDC-992 */
|
||||
|
||||
@@ -43,7 +43,7 @@ class DefaultValuesTest extends OrmFunctionalTestCase
|
||||
$user2 = $this->_em->getReference(get_class($user), $userId);
|
||||
|
||||
$this->_em->flush();
|
||||
self::assertFalse($user2->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($user2));
|
||||
|
||||
$a = new DefaultValueAddress();
|
||||
$a->country = 'de';
|
||||
@@ -55,7 +55,7 @@ class DefaultValuesTest extends OrmFunctionalTestCase
|
||||
$this->_em->persist($a);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertFalse($user2->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($user2));
|
||||
$this->_em->clear();
|
||||
|
||||
$a2 = $this->_em->find(get_class($a), $a->id);
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsArticle;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
@@ -150,16 +149,13 @@ class DetachedEntityTest extends OrmFunctionalTestCase
|
||||
$this->_em->clear();
|
||||
|
||||
$address2 = $this->_em->find(get_class($address), $address->id);
|
||||
self::assertInstanceOf(Proxy::class, $address2->user);
|
||||
self::assertFalse($address2->user->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($address2->user));
|
||||
$detachedAddress2 = unserialize(serialize($address2));
|
||||
self::assertInstanceOf(Proxy::class, $detachedAddress2->user);
|
||||
self::assertFalse($detachedAddress2->user->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($detachedAddress2->user));
|
||||
|
||||
$managedAddress2 = $this->_em->merge($detachedAddress2);
|
||||
self::assertInstanceOf(Proxy::class, $managedAddress2->user);
|
||||
self::assertFalse($managedAddress2->user === $detachedAddress2->user);
|
||||
self::assertFalse($managedAddress2->user->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($managedAddress2->user));
|
||||
}
|
||||
|
||||
/** @group DDC-822 */
|
||||
|
||||
@@ -36,7 +36,7 @@ class EnumTest extends OrmFunctionalTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_em = $this->getEntityManager(null, new AttributeDriver([dirname(__DIR__, 2) . '/Models/Enums']));
|
||||
$this->_em = $this->getEntityManager(null, new AttributeDriver([dirname(__DIR__, 2) . '/Models/Enums'], true));
|
||||
$this->_schemaTool = new SchemaTool($this->_em);
|
||||
|
||||
if ($this->isSecondLevelCacheEnabled) {
|
||||
@@ -260,6 +260,78 @@ class EnumTest extends OrmFunctionalTestCase
|
||||
self::assertEqualsCanonicalizing([Unit::Gram, Unit::Meter], $result[0]->supportedUnits);
|
||||
}
|
||||
|
||||
public function testEnumSingleEntityChangeSetsSimpleObjectHydrator(): void
|
||||
{
|
||||
$this->setUpEntitySchema([Card::class]);
|
||||
|
||||
$card = new Card();
|
||||
$card->suit = Suit::Clubs;
|
||||
|
||||
$this->_em->persist($card);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$result = $this->_em->find(Card::class, $card->id);
|
||||
|
||||
$this->_em->getUnitOfWork()->recomputeSingleEntityChangeSet(
|
||||
$this->_em->getClassMetadata(Card::class),
|
||||
$result
|
||||
);
|
||||
|
||||
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForUpdate($result));
|
||||
|
||||
$result->suit = Suit::Hearts;
|
||||
|
||||
$this->_em->getUnitOfWork()->recomputeSingleEntityChangeSet(
|
||||
$this->_em->getClassMetadata(Card::class),
|
||||
$result
|
||||
);
|
||||
|
||||
self::assertTrue($this->_em->getUnitOfWork()->isScheduledForUpdate($result));
|
||||
}
|
||||
|
||||
public function testEnumSingleEntityChangeSetsObjectHydrator(): void
|
||||
{
|
||||
$this->setUpEntitySchema([Card::class]);
|
||||
|
||||
$card = new Card();
|
||||
$card->suit = Suit::Clubs;
|
||||
|
||||
$this->_em->persist($card);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$result = $this->_em->find(Card::class, $card->id);
|
||||
|
||||
$this->_em->getUnitOfWork()->recomputeSingleEntityChangeSet(
|
||||
$this->_em->getClassMetadata(Card::class),
|
||||
$result
|
||||
);
|
||||
|
||||
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForUpdate($result));
|
||||
}
|
||||
|
||||
public function testEnumArraySingleEntityChangeSets(): void
|
||||
{
|
||||
$this->setUpEntitySchema([Scale::class]);
|
||||
|
||||
$scale = new Scale();
|
||||
$scale->supportedUnits = [Unit::Gram];
|
||||
|
||||
$this->_em->persist($scale);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$result = $this->_em->find(Scale::class, $scale->id);
|
||||
|
||||
$this->_em->getUnitOfWork()->recomputeSingleEntityChangeSet(
|
||||
$this->_em->getClassMetadata(Scale::class),
|
||||
$result
|
||||
);
|
||||
|
||||
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForUpdate($result));
|
||||
}
|
||||
|
||||
public function testEnumChangeSetsSimpleObjectHydrator(): void
|
||||
{
|
||||
$this->setUpEntitySchema([Card::class]);
|
||||
|
||||
135
tests/Doctrine/Tests/ORM/Functional/GH7877Test.php
Normal file
135
tests/Doctrine/Tests/ORM/Functional/GH7877Test.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function uniqid;
|
||||
|
||||
/**
|
||||
* @group GH7877
|
||||
*/
|
||||
class GH7877Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(
|
||||
GH7877ApplicationGeneratedIdEntity::class,
|
||||
GH7877EntityWithNullableAssociation::class
|
||||
);
|
||||
}
|
||||
|
||||
public function testSelfReferenceWithApplicationGeneratedIdMayBeNotNullable(): void
|
||||
{
|
||||
$entity = new GH7877ApplicationGeneratedIdEntity();
|
||||
$entity->parent = $entity;
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
public function testCrossReferenceWithApplicationGeneratedIdMayBeNotNullable(): void
|
||||
{
|
||||
$entity1 = new GH7877ApplicationGeneratedIdEntity();
|
||||
$entity1->parent = $entity1;
|
||||
$entity2 = new GH7877ApplicationGeneratedIdEntity();
|
||||
$entity2->parent = $entity1;
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
// As long as we do not have entity-level commit order computation
|
||||
// (see https://github.com/doctrine/orm/pull/10547),
|
||||
// this only works when the UoW processes $entity1 before $entity2,
|
||||
// so that the foreign key constraint E2 -> E1 can be satisfied.
|
||||
|
||||
$this->_em->persist($entity1);
|
||||
$this->_em->persist($entity2);
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
public function testNullableForeignKeysMakeInsertOrderLessRelevant(): void
|
||||
{
|
||||
$entity1 = new GH7877EntityWithNullableAssociation();
|
||||
$entity1->parent = $entity1;
|
||||
$entity2 = new GH7877EntityWithNullableAssociation();
|
||||
$entity2->parent = $entity1;
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
// In contrast to the previous test, this case demonstrates that with NULLable
|
||||
// associations, even without entity-level commit order computation
|
||||
// (see https://github.com/doctrine/orm/pull/10547), we can get away with an
|
||||
// insertion order of E2 before E1. That is because the UoW will schedule an extra
|
||||
// update that saves the day - the foreign key reference will established only after
|
||||
// all insertions have been performed.
|
||||
|
||||
$this->_em->persist($entity2);
|
||||
$this->_em->persist($entity1);
|
||||
$this->_em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH7877ApplicationGeneratedIdEntity
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="string")
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* (!) Note this uses "nullable=false"
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="GH7877ApplicationGeneratedIdEntity")
|
||||
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=false)
|
||||
*
|
||||
* @var self
|
||||
*/
|
||||
public $parent;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = uniqid();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GH7877EntityWithNullableAssociation
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="string")
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="GH7877EntityWithNullableAssociation")
|
||||
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
|
||||
*
|
||||
* @var self
|
||||
*/
|
||||
public $parent;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = uniqid();
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,7 @@ class ManyToManyBidirectionalAssociationTest extends AbstractManyToManyAssociati
|
||||
/** @psalm-return list<ECommerceProduct> */
|
||||
protected function findProducts(): array
|
||||
{
|
||||
$query = $this->_em->createQuery('SELECT p, c FROM Doctrine\Tests\Models\ECommerce\ECommerceProduct p LEFT JOIN p.categories c ORDER BY p.id, c.id');
|
||||
//$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
|
||||
$query = $this->_em->createQuery('SELECT p, c FROM Doctrine\Tests\Models\ECommerce\ECommerceProduct p LEFT JOIN p.categories c ORDER BY p.id, c.id');
|
||||
$result = $query->getResult();
|
||||
self::assertCount(2, $result);
|
||||
$cats1 = $result[0]->getCategories();
|
||||
@@ -126,8 +125,7 @@ class ManyToManyBidirectionalAssociationTest extends AbstractManyToManyAssociati
|
||||
/** @psalm-return list<ECommerceCategory> */
|
||||
protected function findCategories(): array
|
||||
{
|
||||
$query = $this->_em->createQuery('SELECT c, p FROM Doctrine\Tests\Models\ECommerce\ECommerceCategory c LEFT JOIN c.products p ORDER BY c.id, p.id');
|
||||
//$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
|
||||
$query = $this->_em->createQuery('SELECT c, p FROM Doctrine\Tests\Models\ECommerce\ECommerceCategory c LEFT JOIN c.products p ORDER BY c.id, p.id');
|
||||
$result = $query->getResult();
|
||||
self::assertCount(2, $result);
|
||||
self::assertInstanceOf(ECommerceCategory::class, $result[0]);
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\DirectoryTree\Directory;
|
||||
use Doctrine\Tests\Models\DirectoryTree\File;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
@@ -46,7 +45,7 @@ class MappedSuperclassTest extends OrmFunctionalTestCase
|
||||
$cleanFile = $this->_em->find(get_class($file), $file->getId());
|
||||
|
||||
self::assertInstanceOf(Directory::class, $cleanFile->getParent());
|
||||
self::assertInstanceOf(Proxy::class, $cleanFile->getParent());
|
||||
self::assertTrue($this->isUninitializedObject($cleanFile->getParent()));
|
||||
self::assertEquals($directory->getId(), $cleanFile->getParent()->getId());
|
||||
self::assertInstanceOf(Directory::class, $cleanFile->getParent()->getParent());
|
||||
self::assertEquals($root->getId(), $cleanFile->getParent()->getParent()->getId());
|
||||
|
||||
@@ -11,7 +11,6 @@ use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\DbalExtensions\Connection;
|
||||
use Doctrine\Tests\DbalExtensions\QueryLog;
|
||||
use Doctrine\Tests\Models\Generic\DateTimeModel;
|
||||
@@ -48,8 +47,8 @@ class MergeProxiesTest extends OrmFunctionalTestCase
|
||||
|
||||
self::assertSame($managed, $this->_em->merge($detachedUninitialized));
|
||||
|
||||
self::assertFalse($managed->__isInitialized());
|
||||
self::assertFalse($detachedUninitialized->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($managed));
|
||||
self::assertTrue($this->isUninitializedObject($detachedUninitialized));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,8 +70,8 @@ class MergeProxiesTest extends OrmFunctionalTestCase
|
||||
$this->_em->merge(unserialize(serialize($this->_em->merge($detachedUninitialized))))
|
||||
);
|
||||
|
||||
self::assertFalse($managed->__isInitialized());
|
||||
self::assertFalse($detachedUninitialized->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($managed));
|
||||
self::assertTrue($this->isUninitializedObject($detachedUninitialized));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +86,7 @@ class MergeProxiesTest extends OrmFunctionalTestCase
|
||||
|
||||
self::assertSame($managed, $this->_em->merge($managed));
|
||||
|
||||
self::assertFalse($managed->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($managed));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,13 +108,12 @@ class MergeProxiesTest extends OrmFunctionalTestCase
|
||||
|
||||
$managed = $this->_em->getReference(DateTimeModel::class, $date->id);
|
||||
|
||||
self::assertInstanceOf(Proxy::class, $managed);
|
||||
self::assertFalse($managed->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($managed));
|
||||
|
||||
$date->date = $dateTime = new DateTime();
|
||||
|
||||
self::assertSame($managed, $this->_em->merge($date));
|
||||
self::assertTrue($managed->__isInitialized());
|
||||
self::assertFalse($this->isUninitializedObject($managed));
|
||||
self::assertSame($dateTime, $managed->date, 'Data was merged into the proxy after initialization');
|
||||
}
|
||||
|
||||
@@ -150,8 +148,8 @@ class MergeProxiesTest extends OrmFunctionalTestCase
|
||||
self::assertNotSame($proxy1, $merged2);
|
||||
self::assertSame($proxy2, $merged2);
|
||||
|
||||
self::assertFalse($proxy1->__isInitialized());
|
||||
self::assertFalse($proxy2->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($proxy1));
|
||||
self::assertTrue($this->isUninitializedObject($proxy2));
|
||||
|
||||
$proxy1->__load();
|
||||
|
||||
@@ -207,9 +205,8 @@ class MergeProxiesTest extends OrmFunctionalTestCase
|
||||
$unManagedProxy = $em1->getReference(DateTimeModel::class, $file1->id);
|
||||
$mergedInstance = $em2->merge($unManagedProxy);
|
||||
|
||||
self::assertNotInstanceOf(Proxy::class, $mergedInstance);
|
||||
self::assertNotSame($unManagedProxy, $mergedInstance);
|
||||
self::assertFalse($unManagedProxy->__isInitialized());
|
||||
self::assertFalse($this->isUninitializedObject($mergedInstance));
|
||||
self::assertTrue($this->isUninitializedObject($unManagedProxy));
|
||||
|
||||
self::assertCount(
|
||||
0,
|
||||
@@ -242,7 +239,9 @@ class MergeProxiesTest extends OrmFunctionalTestCase
|
||||
|
||||
TestUtil::configureProxies($config);
|
||||
$config->setMetadataDriverImpl(ORMSetup::createDefaultAnnotationDriver(
|
||||
[realpath(__DIR__ . '/../../Models/Cache')]
|
||||
[realpath(__DIR__ . '/../../Models/Cache')],
|
||||
null,
|
||||
true
|
||||
));
|
||||
|
||||
// always runs on sqlite to prevent multi-connection race-conditions with the test suite
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceFeature;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
@@ -90,12 +89,12 @@ class OneToManyBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
$features = $product->getFeatures();
|
||||
|
||||
self::assertInstanceOf(ECommerceFeature::class, $features[0]);
|
||||
self::assertNotInstanceOf(Proxy::class, $features[0]->getProduct());
|
||||
self::assertFalse($this->isUninitializedObject($features[0]->getProduct()));
|
||||
self::assertSame($product, $features[0]->getProduct());
|
||||
self::assertEquals('Model writing tutorial', $features[0]->getDescription());
|
||||
self::assertInstanceOf(ECommerceFeature::class, $features[1]);
|
||||
self::assertSame($product, $features[1]->getProduct());
|
||||
self::assertNotInstanceOf(Proxy::class, $features[1]->getProduct());
|
||||
self::assertFalse($this->isUninitializedObject($features[1]->getProduct()));
|
||||
self::assertEquals('Annotations examples', $features[1]->getDescription());
|
||||
}
|
||||
|
||||
@@ -126,11 +125,10 @@ class OneToManyBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
$features = $query->getResult();
|
||||
|
||||
$product = $features[0]->getProduct();
|
||||
self::assertInstanceOf(Proxy::class, $product);
|
||||
self::assertInstanceOf(ECommerceProduct::class, $product);
|
||||
self::assertFalse($product->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($product));
|
||||
self::assertSame('Doctrine Cookbook', $product->getName());
|
||||
self::assertTrue($product->__isInitialized());
|
||||
self::assertFalse($this->isUninitializedObject($product));
|
||||
}
|
||||
|
||||
public function testLazyLoadsObjectsOnTheInverseSide2(): void
|
||||
@@ -141,7 +139,7 @@ class OneToManyBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
$features = $query->getResult();
|
||||
|
||||
$product = $features[0]->getProduct();
|
||||
self::assertNotInstanceOf(Proxy::class, $product);
|
||||
self::assertFalse($this->isUninitializedObject($product));
|
||||
self::assertInstanceOf(ECommerceProduct::class, $product);
|
||||
self::assertSame('Doctrine Cookbook', $product->getName());
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceCart;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceCustomer;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
@@ -100,7 +99,7 @@ class OneToOneBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
|
||||
self::assertNull($customer->getMentor());
|
||||
self::assertInstanceOf(ECommerceCart::class, $customer->getCart());
|
||||
self::assertNotInstanceOf(Proxy::class, $customer->getCart());
|
||||
self::assertFalse($this->isUninitializedObject($customer->getCart()));
|
||||
self::assertEquals('paypal', $customer->getCart()->getPayment());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function get_class;
|
||||
@@ -52,7 +51,7 @@ class OneToOneEagerLoadingTest extends OrmFunctionalTestCase
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
|
||||
$train = $this->_em->find(get_class($train), $train->id);
|
||||
self::assertNotInstanceOf(Proxy::class, $train->driver);
|
||||
self::assertFalse($this->isUninitializedObject($train->driver));
|
||||
self::assertEquals('Benjamin', $train->driver->name);
|
||||
|
||||
$this->assertQueryCount(1);
|
||||
@@ -70,7 +69,6 @@ class OneToOneEagerLoadingTest extends OrmFunctionalTestCase
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
|
||||
$train = $this->_em->find(get_class($train), $train->id);
|
||||
self::assertNotInstanceOf(Proxy::class, $train->driver);
|
||||
self::assertNull($train->driver);
|
||||
|
||||
$this->assertQueryCount(1);
|
||||
@@ -88,9 +86,9 @@ class OneToOneEagerLoadingTest extends OrmFunctionalTestCase
|
||||
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
|
||||
$driver = $this->_em->find(get_class($owner), $owner->id);
|
||||
self::assertNotInstanceOf(Proxy::class, $owner->train);
|
||||
self::assertNotNull($owner->train);
|
||||
$this->_em->find(get_class($owner), $owner->id);
|
||||
self::assertFalse($this->isUninitializedObject($owner->train));
|
||||
self::assertInstanceOf(Train::class, $owner->train);
|
||||
|
||||
$this->assertQueryCount(1);
|
||||
}
|
||||
@@ -109,7 +107,6 @@ class OneToOneEagerLoadingTest extends OrmFunctionalTestCase
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
|
||||
$driver = $this->_em->find(get_class($driver), $driver->id);
|
||||
self::assertNotInstanceOf(Proxy::class, $driver->train);
|
||||
self::assertNull($driver->train);
|
||||
|
||||
$this->assertQueryCount(1);
|
||||
@@ -126,8 +123,8 @@ class OneToOneEagerLoadingTest extends OrmFunctionalTestCase
|
||||
$this->_em->clear();
|
||||
|
||||
$waggon = $this->_em->find(get_class($waggon), $waggon->id);
|
||||
self::assertNotInstanceOf(Proxy::class, $waggon->train);
|
||||
self::assertNotNull($waggon->train);
|
||||
self::assertFalse($this->isUninitializedObject($waggon->train));
|
||||
self::assertInstanceOf(Train::class, $waggon->train);
|
||||
}
|
||||
|
||||
/** @group non-cacheable */
|
||||
|
||||
@@ -11,7 +11,6 @@ use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceCustomer;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
@@ -69,14 +68,16 @@ class OneToOneSelfReferentialAssociationTest extends OrmFunctionalTestCase
|
||||
$id = $this->createFixture();
|
||||
|
||||
$customer = $this->_em->find(ECommerceCustomer::class, $id);
|
||||
self::assertNotInstanceOf(Proxy::class, $customer->getMentor());
|
||||
self::assertFalse($this->isUninitializedObject($customer->getMentor()));
|
||||
}
|
||||
|
||||
public function testEagerLoadsAssociation(): void
|
||||
{
|
||||
$this->createFixture();
|
||||
$customerId = $this->createFixture();
|
||||
|
||||
$query = $this->_em->createQuery('select c, m from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c left join c.mentor m where c.id = :id');
|
||||
$query->setParameter('id', $customerId);
|
||||
|
||||
$query = $this->_em->createQuery('select c, m from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c left join c.mentor m order by c.id asc');
|
||||
$result = $query->getResult();
|
||||
$customer = $result[0];
|
||||
$this->assertLoadingOfAssociation($customer);
|
||||
|
||||
@@ -13,6 +13,7 @@ use Generator;
|
||||
use ReflectionMethod;
|
||||
|
||||
use function file_get_contents;
|
||||
use function rtrim;
|
||||
use function serialize;
|
||||
use function unserialize;
|
||||
|
||||
@@ -56,8 +57,8 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
|
||||
/** @return Generator<string, array{string}> */
|
||||
public static function provideSerializedSingleSelectResults(): Generator
|
||||
{
|
||||
yield '2.14.3' => [file_get_contents(__DIR__ . '/ParserResults/single_select_2_14_3.txt')];
|
||||
yield '2.15.0' => [file_get_contents(__DIR__ . '/ParserResults/single_select_2_15_0.txt')];
|
||||
yield '2.14.3' => [rtrim(file_get_contents(__DIR__ . '/ParserResults/single_select_2_14_3.txt'), "\n")];
|
||||
yield '2.15.0' => [rtrim(file_get_contents(__DIR__ . '/ParserResults/single_select_2_15_0.txt'), "\n")];
|
||||
}
|
||||
|
||||
private static function parseQuery(Query $query): ParserResult
|
||||
|
||||
@@ -59,7 +59,7 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
{
|
||||
// Considering case (a)
|
||||
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => 123]);
|
||||
$proxy->__setInitialized(true);
|
||||
|
||||
$proxy->id = null;
|
||||
$proxy->username = 'ocra';
|
||||
$proxy->name = 'Marco';
|
||||
@@ -84,7 +84,7 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
|
||||
$this->_em->persist($uninitializedProxy);
|
||||
$this->_em->flush();
|
||||
self::assertFalse($uninitializedProxy->__isInitialized(), 'Proxy didn\'t get initialized during flush operations');
|
||||
self::assertTrue($this->isUninitializedObject($uninitializedProxy), 'Proxy didn\'t get initialized during flush operations');
|
||||
self::assertEquals($userId, $uninitializedProxy->getId());
|
||||
$this->_em->remove($uninitializedProxy);
|
||||
$this->_em->flush();
|
||||
@@ -95,7 +95,7 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
*/
|
||||
public function testProxyAsDqlParameterPersist(): void
|
||||
{
|
||||
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => $this->user->getId()]);
|
||||
$proxy = $this->_em->getReference(CmsUser::class, ['id' => $this->user->getId()]);
|
||||
$proxy->id = $this->user->getId();
|
||||
$result = $this
|
||||
->_em
|
||||
|
||||
@@ -13,7 +13,6 @@ use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\UnexpectedResultException;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\IterableTester;
|
||||
use Doctrine\Tests\Models\CMS\CmsArticle;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
@@ -624,8 +623,7 @@ class QueryTest extends OrmFunctionalTestCase
|
||||
self::assertEquals(1, count($result));
|
||||
self::assertInstanceOf(CmsArticle::class, $result[0]);
|
||||
self::assertEquals('dr. dolittle', $result[0]->topic);
|
||||
self::assertInstanceOf(Proxy::class, $result[0]->user);
|
||||
self::assertFalse($result[0]->user->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($result[0]->user));
|
||||
}
|
||||
|
||||
/** @group DDC-952 */
|
||||
@@ -653,7 +651,7 @@ class QueryTest extends OrmFunctionalTestCase
|
||||
|
||||
self::assertCount(10, $articles);
|
||||
foreach ($articles as $article) {
|
||||
self::assertNotInstanceOf(Proxy::class, $article);
|
||||
self::assertFalse($this->isUninitializedObject($article));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ class ReadonlyPropertiesTest extends OrmFunctionalTestCase
|
||||
}
|
||||
|
||||
$this->_em = $this->getEntityManager(null, new AttributeDriver(
|
||||
[dirname(__DIR__, 2) . '/Models/ReadonlyProperties']
|
||||
[dirname(__DIR__, 2) . '/Models/ReadonlyProperties'],
|
||||
true
|
||||
));
|
||||
$this->_schemaTool = new SchemaTool($this->_em);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Proxy\Proxy as CommonProxy;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\Tests\Models\Company\CompanyAuction;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
|
||||
@@ -120,9 +120,9 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
$entity = $this->_em->getReference(ECommerceProduct::class, $id);
|
||||
assert($entity instanceof ECommerceProduct);
|
||||
|
||||
self::assertFalse($entity->__isInitialized(), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertTrue($this->isUninitializedObject($entity), 'Pre-Condition: Object is unitialized proxy.');
|
||||
$this->_em->getUnitOfWork()->initializeObject($entity);
|
||||
self::assertTrue($entity->__isInitialized(), 'Should be initialized after called UnitOfWork::initializeObject()');
|
||||
self::assertFalse($this->isUninitializedObject($entity), 'Should be initialized after called UnitOfWork::initializeObject()');
|
||||
}
|
||||
|
||||
/** @group DDC-1163 */
|
||||
@@ -167,9 +167,9 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
$entity = $this->_em->getReference(ECommerceProduct::class, $id);
|
||||
assert($entity instanceof ECommerceProduct);
|
||||
|
||||
self::assertFalse($entity->__isInitialized(), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertTrue($this->isUninitializedObject($entity), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertEquals($id, $entity->getId());
|
||||
self::assertFalse($entity->__isInitialized(), "Getting the identifier doesn't initialize the proxy.");
|
||||
self::assertTrue($this->isUninitializedObject($entity), "Getting the identifier doesn't initialize the proxy.");
|
||||
}
|
||||
|
||||
/** @group DDC-1625 */
|
||||
@@ -180,9 +180,9 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
$entity = $this->_em->getReference(CompanyAuction::class, $id);
|
||||
assert($entity instanceof CompanyAuction);
|
||||
|
||||
self::assertFalse($entity->__isInitialized(), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertTrue($this->isUninitializedObject($entity), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertEquals($id, $entity->getId());
|
||||
self::assertFalse($entity->__isInitialized(), "Getting the identifier doesn't initialize the proxy when extending.");
|
||||
self::assertTrue($this->isUninitializedObject($entity), "Getting the identifier doesn't initialize the proxy when extending.");
|
||||
}
|
||||
|
||||
public function testDoNotInitializeProxyOnGettingTheIdentifierAndReturnTheRightType(): void
|
||||
@@ -202,10 +202,10 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
$product = $this->_em->getRepository(ECommerceProduct::class)->find($product->getId());
|
||||
|
||||
$entity = $product->getShipping();
|
||||
self::assertFalse($entity->__isInitialized(), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertTrue($this->isUninitializedObject($entity), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertEquals($id, $entity->getId());
|
||||
self::assertSame($id, $entity->getId(), "Check that the id's are the same value, and type.");
|
||||
self::assertFalse($entity->__isInitialized(), "Getting the identifier doesn't initialize the proxy.");
|
||||
self::assertTrue($this->isUninitializedObject($entity), "Getting the identifier doesn't initialize the proxy.");
|
||||
}
|
||||
|
||||
public function testInitializeProxyOnGettingSomethingOtherThanTheIdentifier(): void
|
||||
@@ -215,9 +215,9 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
$entity = $this->_em->getReference(ECommerceProduct::class, $id);
|
||||
assert($entity instanceof ECommerceProduct);
|
||||
|
||||
self::assertFalse($entity->__isInitialized(), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertTrue($this->isUninitializedObject($entity), 'Pre-Condition: Object is unitialized proxy.');
|
||||
self::assertEquals('Doctrine Cookbook', $entity->getName());
|
||||
self::assertTrue($entity->__isInitialized(), 'Getting something other than the identifier initializes the proxy.');
|
||||
self::assertFalse($this->isUninitializedObject($entity), 'Getting something other than the identifier initializes the proxy.');
|
||||
}
|
||||
|
||||
/** @group DDC-1604 */
|
||||
@@ -229,8 +229,8 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
assert($entity instanceof ECommerceProduct);
|
||||
$className = ClassUtils::getClass($entity);
|
||||
|
||||
self::assertInstanceOf(Proxy::class, $entity);
|
||||
self::assertFalse($entity->__isInitialized());
|
||||
self::assertInstanceOf(InternalProxy::class, $entity);
|
||||
self::assertTrue($this->isUninitializedObject($entity));
|
||||
self::assertEquals(ECommerceProduct::class, $className);
|
||||
|
||||
$restName = str_replace($this->_em->getConfiguration()->getProxyNamespace(), '', get_class($entity));
|
||||
@@ -239,6 +239,6 @@ class ReferenceProxyTest extends OrmFunctionalTestCase
|
||||
self::assertTrue(file_exists($proxyFileName), 'Proxy file name cannot be found generically.');
|
||||
|
||||
$entity->__load();
|
||||
self::assertTrue($entity->__isInitialized());
|
||||
self::assertFalse($this->isUninitializedObject($entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use Doctrine\ORM\Cache\EntityCacheEntry;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\Exception\CacheException;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\Cache\Attraction;
|
||||
use Doctrine\Tests\Models\Cache\City;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
@@ -938,7 +938,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertNotNull($state1->getCountry());
|
||||
$this->assertQueryCount(1);
|
||||
self::assertInstanceOf(State::class, $state1);
|
||||
self::assertInstanceOf(Proxy::class, $state1->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $state1->getCountry());
|
||||
self::assertEquals($countryName, $state1->getCountry()->getName());
|
||||
self::assertEquals($stateId, $state1->getId());
|
||||
|
||||
@@ -956,7 +956,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertNotNull($state2->getCountry());
|
||||
$this->assertQueryCount(0);
|
||||
self::assertInstanceOf(State::class, $state2);
|
||||
self::assertInstanceOf(Proxy::class, $state2->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $state2->getCountry());
|
||||
self::assertEquals($countryName, $state2->getCountry()->getName());
|
||||
self::assertEquals($stateId, $state2->getId());
|
||||
}
|
||||
@@ -1030,7 +1030,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheFunctionalTestCase
|
||||
|
||||
$this->assertQueryCount(1);
|
||||
self::assertInstanceOf(State::class, $state1);
|
||||
self::assertInstanceOf(Proxy::class, $state1->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $state1->getCountry());
|
||||
self::assertInstanceOf(City::class, $state1->getCities()->get(0));
|
||||
self::assertInstanceOf(State::class, $state1->getCities()->get(0)->getState());
|
||||
self::assertSame($state1, $state1->getCities()->get(0)->getState());
|
||||
@@ -1047,7 +1047,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheFunctionalTestCase
|
||||
|
||||
$this->assertQueryCount(0);
|
||||
self::assertInstanceOf(State::class, $state2);
|
||||
self::assertInstanceOf(Proxy::class, $state2->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $state2->getCountry());
|
||||
self::assertInstanceOf(City::class, $state2->getCities()->get(0));
|
||||
self::assertInstanceOf(State::class, $state2->getCities()->get(0)->getState());
|
||||
self::assertSame($state2, $state2->getCities()->get(0)->getState());
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
|
||||
@@ -197,8 +197,8 @@ class SecondLevelCacheRepositoryTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertInstanceOf(State::class, $entities[1]);
|
||||
self::assertInstanceOf(Country::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Country::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[1]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[1]->getCountry());
|
||||
|
||||
// load from cache
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
@@ -211,8 +211,8 @@ class SecondLevelCacheRepositoryTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertInstanceOf(State::class, $entities[1]);
|
||||
self::assertInstanceOf(Country::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Country::class, $entities[1]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[1]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[1]->getCountry());
|
||||
|
||||
// invalidate cache
|
||||
$this->_em->persist(new State('foo', $this->_em->find(Country::class, $this->countries[0]->getId())));
|
||||
@@ -230,8 +230,8 @@ class SecondLevelCacheRepositoryTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertInstanceOf(State::class, $entities[1]);
|
||||
self::assertInstanceOf(Country::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Country::class, $entities[1]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[1]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[1]->getCountry());
|
||||
|
||||
// load from cache
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
@@ -244,7 +244,7 @@ class SecondLevelCacheRepositoryTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertInstanceOf(State::class, $entities[1]);
|
||||
self::assertInstanceOf(Country::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Country::class, $entities[1]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(Proxy::class, $entities[1]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[0]->getCountry());
|
||||
self::assertInstanceOf(InternalProxy::class, $entities[1]->getCountry());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Doctrine\Tests\ORM\Functional;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Persisters\MatchingAssociationFieldRequiresObject;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\Company\CompanyContract;
|
||||
use Doctrine\Tests\Models\Company\CompanyEmployee;
|
||||
use Doctrine\Tests\Models\Company\CompanyFixContract;
|
||||
@@ -397,13 +396,14 @@ class SingleTableInheritanceTest extends OrmFunctionalTestCase
|
||||
$this->loadFullFixture();
|
||||
|
||||
$ref = $this->_em->getReference(CompanyContract::class, $this->fix->getId());
|
||||
self::assertNotInstanceOf(Proxy::class, $ref, 'Cannot Request a proxy from a class that has subclasses.');
|
||||
self::assertFalse($this->isUninitializedObject($ref), 'Cannot Request a proxy from a class that has subclasses.');
|
||||
self::assertInstanceOf(CompanyContract::class, $ref);
|
||||
self::assertInstanceOf(CompanyFixContract::class, $ref, 'Direct fetch of the reference has to load the child class Employee directly.');
|
||||
$this->_em->clear();
|
||||
|
||||
$ref = $this->_em->getReference(CompanyFixContract::class, $this->fix->getId());
|
||||
self::assertInstanceOf(Proxy::class, $ref, 'A proxy can be generated only if no subclasses exists for the requested reference.');
|
||||
|
||||
self::assertTrue($this->isUninitializedObject($ref), 'A proxy can be generated only if no subclasses exists for the requested reference.');
|
||||
}
|
||||
|
||||
/** @group DDC-952 */
|
||||
@@ -417,6 +417,6 @@ class SingleTableInheritanceTest extends OrmFunctionalTestCase
|
||||
->setParameter(1, $this->fix->getId())
|
||||
->getSingleResult();
|
||||
|
||||
self::assertNotInstanceOf(Proxy::class, $contract->getSalesPerson());
|
||||
self::assertFalse($this->isUninitializedObject($contract->getSalesPerson()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\JoinColumns;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function assert;
|
||||
@@ -92,7 +91,7 @@ class DDC1163Test extends OrmFunctionalTestCase
|
||||
assert($specialProduct instanceof DDC1163SpecialProduct);
|
||||
|
||||
self::assertInstanceOf(DDC1163SpecialProduct::class, $specialProduct);
|
||||
self::assertInstanceOf(Proxy::class, $specialProduct);
|
||||
self::assertTrue($this->isUninitializedObject($specialProduct));
|
||||
|
||||
$specialProduct->setSubclassProperty('foobar');
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class DDC1193Test extends OrmFunctionalTestCase
|
||||
$company = $this->_em->find(get_class($company), $companyId);
|
||||
|
||||
self::assertTrue($this->_em->getUnitOfWork()->isInIdentityMap($company), 'Company is in identity map.');
|
||||
self::assertFalse($company->member->__isInitialized(), 'Pre-Condition');
|
||||
self::assertTrue($this->isUninitializedObject($company->member), 'Pre-Condition');
|
||||
self::assertTrue($this->_em->getUnitOfWork()->isInIdentityMap($company->member), 'Member is in identity map.');
|
||||
|
||||
$this->_em->remove($company);
|
||||
|
||||
@@ -38,9 +38,9 @@ class DDC1228Test extends OrmFunctionalTestCase
|
||||
|
||||
$user = $this->_em->find(DDC1228User::class, $user->id);
|
||||
|
||||
self::assertFalse($user->getProfile()->__isInitialized(), 'Proxy is not initialized');
|
||||
self::assertTrue($this->isUninitializedObject($user->getProfile()), 'Proxy is not initialized');
|
||||
$user->getProfile()->setName('Bar');
|
||||
self::assertTrue($user->getProfile()->__isInitialized(), 'Proxy is not initialized');
|
||||
self::assertFalse($this->isUninitializedObject($user->getProfile()), 'Proxy is not initialized');
|
||||
|
||||
self::assertEquals('Bar', $user->getProfile()->getName());
|
||||
self::assertEquals(['id' => 1, 'name' => 'Foo'], $this->_em->getUnitOfWork()->getOriginalEntityData($user->getProfile()));
|
||||
|
||||
@@ -56,7 +56,7 @@ class DDC1238Test extends OrmFunctionalTestCase
|
||||
|
||||
$user2 = $this->_em->getReference(DDC1238User::class, $userId);
|
||||
|
||||
$user->__load();
|
||||
//$user->__load();
|
||||
|
||||
self::assertIsInt($user->getId(), 'Even if a proxy is detached, it should still have an identifier');
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
@@ -54,7 +53,7 @@ class DDC1452Test extends OrmFunctionalTestCase
|
||||
$results = $this->_em->createQuery($dql)->setMaxResults(1)->getResult();
|
||||
|
||||
self::assertSame($results[0], $results[0]->entitiesB[0]->entityAFrom);
|
||||
self::assertNotInstanceOf(Proxy::class, $results[0]->entitiesB[0]->entityATo);
|
||||
self::assertFalse($this->isUninitializedObject($results[0]->entitiesB[0]->entityATo));
|
||||
self::assertInstanceOf(Collection::class, $results[0]->entitiesB[0]->entityATo->getEntitiesB());
|
||||
}
|
||||
|
||||
@@ -82,12 +81,12 @@ class DDC1452Test extends OrmFunctionalTestCase
|
||||
$data = $this->_em->createQuery($dql)->getResult();
|
||||
$this->_em->clear();
|
||||
|
||||
self::assertNotInstanceOf(Proxy::class, $data[0]->user);
|
||||
self::assertFalse($this->isUninitializedObject($data[0]->user));
|
||||
|
||||
$dql = 'SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.address a';
|
||||
$data = $this->_em->createQuery($dql)->getResult();
|
||||
|
||||
self::assertNotInstanceOf(Proxy::class, $data[0]->address);
|
||||
self::assertFalse($this->isUninitializedObject($data[0]->address));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\Persistence\NotifyPropertyChanged;
|
||||
use Doctrine\Persistence\PropertyChangedListener;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function count;
|
||||
@@ -57,9 +56,10 @@ class DDC1690Test extends OrmFunctionalTestCase
|
||||
$child = $this->_em->find(DDC1690Child::class, $childId);
|
||||
|
||||
self::assertEquals(1, count($parent->listeners));
|
||||
self::assertInstanceOf(Proxy::class, $child, 'Verifying that $child is a proxy before using proxy API');
|
||||
self::assertCount(0, $child->listeners);
|
||||
$child->__load();
|
||||
|
||||
$this->_em->getUnitOfWork()->initializeObject($child);
|
||||
|
||||
self::assertCount(1, $child->listeners);
|
||||
unset($parent, $child);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\Tests\Models\CMS\CmsGroup;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
@@ -37,8 +37,7 @@ class DDC1734Test extends OrmFunctionalTestCase
|
||||
|
||||
$proxy = $this->getProxy($group);
|
||||
|
||||
self::assertInstanceOf(Proxy::class, $proxy);
|
||||
self::assertFalse($proxy->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($proxy));
|
||||
|
||||
$this->_em->detach($proxy);
|
||||
$this->_em->clear();
|
||||
@@ -67,8 +66,7 @@ class DDC1734Test extends OrmFunctionalTestCase
|
||||
|
||||
$proxy = $this->getProxy($group);
|
||||
|
||||
self::assertInstanceOf(Proxy::class, $proxy);
|
||||
self::assertFalse($proxy->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($proxy));
|
||||
|
||||
$this->_em->detach($proxy);
|
||||
$serializedProxy = serialize($proxy);
|
||||
@@ -79,7 +77,7 @@ class DDC1734Test extends OrmFunctionalTestCase
|
||||
}
|
||||
|
||||
/** @param object $object */
|
||||
private function getProxy($object): Proxy
|
||||
private function getProxy($object): InternalProxy
|
||||
{
|
||||
$metadataFactory = $this->_em->getMetadataFactory();
|
||||
$className = get_class($object);
|
||||
|
||||
@@ -12,7 +12,6 @@ use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\Persistence\NotifyPropertyChanged;
|
||||
use Doctrine\Persistence\PropertyChangedListener;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function assert;
|
||||
@@ -47,10 +46,8 @@ class DDC2230Test extends OrmFunctionalTestCase
|
||||
$mergedUser = $this->_em->merge($user);
|
||||
|
||||
$address = $mergedUser->address;
|
||||
assert($address instanceof Proxy);
|
||||
|
||||
self::assertInstanceOf(Proxy::class, $address);
|
||||
self::assertFalse($address->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($address));
|
||||
}
|
||||
|
||||
public function testNotifyTrackingCalledOnProxyInitialization(): void
|
||||
@@ -62,12 +59,12 @@ class DDC2230Test extends OrmFunctionalTestCase
|
||||
$this->_em->clear();
|
||||
|
||||
$addressProxy = $this->_em->getReference(DDC2230Address::class, $insertedAddress->id);
|
||||
assert($addressProxy instanceof Proxy || $addressProxy instanceof DDC2230Address);
|
||||
assert($addressProxy instanceof DDC2230Address);
|
||||
|
||||
self::assertFalse($addressProxy->__isInitialized());
|
||||
self::assertTrue($this->isUninitializedObject($addressProxy));
|
||||
self::assertNull($addressProxy->listener);
|
||||
|
||||
$addressProxy->__load();
|
||||
$this->_em->getUnitOfWork()->initializeObject($addressProxy);
|
||||
|
||||
self::assertSame($this->_em->getUnitOfWork(), $addressProxy->listener);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user