Compare commits

...

26 Commits

Author SHA1 Message Date
Grégoire Paris
17d28b5c4c Merge pull request #11917 from stof/lazy_ghost_postload
Fix the initialization of lazy-ghost proxies with postLoad listeners
2025-05-02 19:07:53 +02:00
Christophe Coevoet
a2d510c6f4 Fix the initialization of lazy-ghost proxies with postLoad listeners
PostLoad listeners might initialize values for transient properties, so
the proxy should not skip initialization when using transient
properties.

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
2025-05-02 15:27:59 +02:00
Christophe Coevoet
2a4ebca90e Refactor tests to avoid using instance properties to track postLoad
The old proxy implementation of doctrine/common was triggered by public
methods rather than access to properties (making public properties
unsupported in entities), so tests could use public instance properties
to track the state of postLoad lifecycle callbacks without triggering
the proxy initialization when reading that state (which then changes the
state of triggering the postLoad callback).
As the new proxy implementation hooks into properties instead, the tests
now use a static method (ensuring it is reset properly before loading
the instance for which we care about the tracking) instead of an
instance property.
2025-04-22 17:39:29 +02:00
Grégoire Paris
cc29ae0d36 Merge pull request #11891 from mpdude/expression-matching-caveats
Add more detailed caveats for using the Collection filtering API
2025-03-29 11:31:36 +01:00
Grégoire Paris
bd4a053d29 Merge pull request #11894 from DavidPetrasek/3.3.x
Fix URL's in xml-mapping.rst
2025-03-27 20:59:26 +01:00
David Petrásek
52fbfb3785 Revert to http for namespace name
These URLs are meant as identifiers rather than actual urls intended to
be used to perform an HTTP request.
2025-03-27 17:39:10 +01:00
Matthias Pigulla
c259371e5f Remove property hooks mention 2025-03-26 18:58:17 +01:00
Matthias Pigulla
dcdd58b642 working-with-associations.rst aktualisieren
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2025-03-26 18:14:01 +01:00
Matthias Pigulla
7b9c53854f Add more detailed caveats for using the Collection filtering API 2025-03-26 13:38:52 +01:00
Grégoire Paris
cdc5fe11dd Merge pull request #11889 from Rixafy/docs-typo-fix
Fix docs typo (nulable -> nullable)
2025-03-25 23:16:01 +01:00
Rixafy
69ece00564 Fix docs typo (nulable -> nullable) 2025-03-25 22:25:00 +01:00
Grégoire Paris
c679d1b007 Merge pull request #11885 from greg0ire/no-triple-stars
Avoid triple stars
2025-03-25 07:51:08 +01:00
Grégoire Paris
1e15b22dcb Avoid triple stars
They don't have a special meaning, and are rendered like this:
<strong>*REQUIRED</strong>*.
2025-03-25 07:49:53 +01:00
Grégoire Paris
44057b4683 Merge pull request #11845 from lacatoire/update-message-annotation-to-attribute
Update message of `ORMInvalidArgumentException`
2025-03-24 10:58:02 +01:00
Grégoire Paris
013df03795 Upgrade to doctrine/coding-standard 13 (#11881) 2025-03-24 07:18:07 +01:00
Louis-Arnaud
2d2a34407c Use attributes in exception message 2025-03-23 16:07:31 +01:00
Matteo Beccati
3303cd3b5d Fix non-deterministic test (#11866) 2025-03-14 00:09:36 +01:00
Grégoire Paris
afcf91e839 Merge pull request #11863 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.2.2
Bump doctrine/.github from 7.2.1 to 7.2.2
2025-03-10 09:24:12 +01:00
dependabot[bot]
c61a9b3b6d Bump doctrine/.github from 7.2.1 to 7.2.2
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.2.1 to 7.2.2.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.2.1...7.2.2)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 06:43:02 +00:00
Louis-Arnaud
3aed6912a3 Update ORMInvalidArgumentException.php
update message to use attribute instead of annotation
2025-02-21 10:10:22 +01:00
Grégoire Paris
158605bf24 Merge pull request #11833 from HypeMC/fix-dql
Fix DQL example with composite key
2025-02-13 23:49:22 +01:00
Grégoire Paris
2c2ef65817 Merge pull request #11826 from aprat84/gh-11741
Clone query hints and parameters in `LimitSubqueryOutputWalker` constructor
2025-02-12 08:52:33 +01:00
HypeMC
1c33a86983 Fix DQL example with composite key 2025-02-11 16:10:59 +01:00
Albert Prat
310fe1cccb Clone query hints and parameters in LimitSubqueryOutputWalker constructor
This fixes a bug that arises when using Pagination and an entity relation is mapped with fetch-mode EAGER but setFetchMode LAZY (or anything that is not EAGER) has been used on the query. If the query use WITH condition, an exception is incorrectly raised (Associations with fetch-mode=EAGER may not be using WITH conditions).
The class LimitSubqueryOutputWalker clones the query, but not its parameters and hints, so the generated subquery does not know that fetch-mode has been overridden.

Fixes #11741
2025-02-11 10:48:14 +01:00
Grégoire Paris
a67f677747 Merge pull request #11707 from jorenMartens/2.20.x
[DDC-551] fix, add filter support in oneToOne relation 2.20.x
2025-02-07 08:23:53 +01:00
Joren Martens
14866461c5 [DDC-551] fix, add filter support in oneToOne relation 2024-11-07 10:48:16 +01:00
36 changed files with 373 additions and 91 deletions

View File

@@ -24,4 +24,4 @@ on:
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.2.1"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.2.2"

View File

@@ -17,4 +17,4 @@ on:
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@7.2.1"
uses: "doctrine/.github/.github/workflows/documentation.yml@7.2.2"

View File

@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.2.1"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.2.2"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

View File

@@ -41,14 +41,14 @@
},
"require-dev": {
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^12.0",
"doctrine/coding-standard": "^9.0.2 || ^13.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/extension-installer": "~1.1.0 || ^1.4",
"phpstan/phpstan": "~1.4.10 || 2.0.3",
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"squizlabs/php_codesniffer": "3.12.0",
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0"

View File

@@ -71,8 +71,8 @@ Configuration Options
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy Directory (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -85,8 +85,8 @@ classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy Namespace (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -98,8 +98,8 @@ Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Metadata Driver (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -144,8 +144,8 @@ accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
Metadata Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Metadata Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -166,8 +166,8 @@ For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Query Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -189,8 +189,8 @@ For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL Logger (**Optional**)
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -202,8 +202,8 @@ Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Auto-generating Proxy Classes (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
@@ -446,7 +446,7 @@ correctly if sub-namespaces use different metadata driver
implementations.
Default Repository (***OPTIONAL***)
Default Repository (**OPTIONAL**)
-----------------------------------
Specifies the FQCN of a subclass of the EntityRepository.
@@ -461,7 +461,7 @@ That will be available for all entities without a custom repository class.
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (***OPTIONAL***)
Ignoring entities (**OPTIONAL**)
-----------------------------------
Specifies the Entity FQCNs to ignore.

View File

@@ -1426,7 +1426,7 @@ Is essentially the same as following:
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" nulable=false />
<join-column name="shipment_id" referenced-column-name="id" nullable=false />
</one-to-one>
</entity>
</doctrine-mapping>

View File

@@ -299,7 +299,7 @@ specific to a particular entity class's lifecycle.
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -323,7 +323,7 @@ level cache region.
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">
@@ -431,7 +431,7 @@ It caches the primary keys of association and cache each element will be cached
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -736,6 +736,35 @@ methods:
.. note::
There is a limitation on the compatibility of Criteria comparisons.
You have to use scalar values only as the value in a comparison or
the behaviour between different backends is not the same.
Depending on whether the collection has already been loaded from the
database or not, criteria matching may happen at the database/SQL level
or on objects in memory. This may lead to different results and come
surprising, for example when a code change in one place leads to a collection
becoming initialized and, as a side effect, returning a different result
or even breaking a ``matching()`` call somewhere else. Also, collection
initialization state in practical use cases may differ from the one covered
in unit tests.
Database level comparisons are based on scalar representations of the values
stored in entity properties. The field names passed to expressions correspond
to property names. Comparison and sorting may be affected by
database-specific behavior. For example, MySQL enum types sort by index position,
not lexicographically by value.
In-memory handling is based on the ``Selectable`` API of `Doctrine Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html#matching>`.
In this case, field names passed to expressions are being used to derive accessor
method names. Strict type comparisons are used for equal and not-equal checks,
and generally PHP language rules are being used for other comparison operators
or sorting.
As a general guidance, for consistent results use the Criteria API with scalar
values only. Note that ``DateTime`` and ``DateTimeImmutable`` are two predominant
examples of value objects that are *not* scalars.
Refrain from using special database-level column types or custom Doctrine Types
that may lead to database-specific comparison or sorting rules being applied, or
to database-level values being different from object field values.
Provide accessor methods for all entity fields used in criteria expressions,
and implement those methods in a way that their return value is the
same as the database-level value.

View File

@@ -17,7 +17,7 @@ setup for the latest code in trunk.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">
@@ -103,7 +103,7 @@ of several common elements:
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">
@@ -770,7 +770,7 @@ entity relationship. You can define this in XML with the "association-key" attri
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -86,7 +86,7 @@ and year of production as primary keys:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">
@@ -127,11 +127,12 @@ And for querying you can use arrays to both DQL and EntityRepositories:
namespace VehicleCatalogue\Model;
// $em is the EntityManager
$audi = $em->find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010));
$audi = $em->find("VehicleCatalogue\Model\Car", ["name" => "Audi A8", "year" => 2010]);
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.name = ?1 AND c.year = ?2";
$audi = $em->createQuery($dql)
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
->setParameter(1, "Audi A8")
->setParameter(2, 2010)
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
@@ -268,7 +269,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -87,7 +87,7 @@ switch to extra lazy as shown in these examples:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -558,7 +558,7 @@ methods, but you only need to choose one.
<!-- config/xml/Product.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">
@@ -1139,7 +1139,7 @@ the ``Product`` before:
<!-- config/xml/Bug.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">
@@ -1294,7 +1294,7 @@ Finally, we'll add metadata mappings for the ``User`` entity.
<!-- config/xml/User.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">
@@ -1818,7 +1818,7 @@ we have to adjust the metadata slightly.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -128,7 +128,7 @@ here are the code and mappings for it:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://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">

View File

@@ -1278,7 +1278,6 @@ class ClassMetadataInfo implements ClassMetadata
/**
* @param string $fieldName
* @param array $cache
* @phpstan-param array{usage?: int|null, region?: string|null} $cache
*
* @return int[]|string[]

View File

@@ -309,7 +309,7 @@ EXCEPTION
. ' configured to cascade persist operations for entity: ' . self::objToStr($entity) . '.'
. ' To solve this issue: Either explicitly call EntityManager#persist()'
. ' on this unknown entity or configure cascade persist'
. ' this association in the mapping for example @ManyToOne(..,cascade={"persist"}).'
. ' this association in the mapping for example #[ORM\ManyToOne(..., cascade: [\'persist\'])].'
. (method_exists($entity, '__toString')
? ''
: ' If you cannot find out which entity causes the problem implement \''

View File

@@ -1374,6 +1374,12 @@ class BasicEntityPersister implements EntityPersister
$joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = '
. $this->getSQLTableAlias($association['targetEntity']) . '.' . $targetCol;
}
// Add filter SQL
$filterSql = $this->generateFilterConditionSQL($eagerEntity, $joinTableAlias);
if ($filterSql) {
$joinCondition[] = $filterSql;
}
}
$this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON ';

View File

@@ -378,7 +378,7 @@ EOPHP;
$class = $entityPersister->getClassMetadata();
foreach ($class->getReflectionProperties() as $property) {
if (isset($identifier[$property->name]) || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
if (isset($identifier[$property->name])) {
continue;
}
@@ -448,7 +448,7 @@ EOPHP;
foreach ($reflector->getProperties($filter) as $property) {
$name = $property->name;
if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) {
if ($property->isStatic() || ! isset($identifiers[$name])) {
continue;
}

View File

@@ -105,17 +105,24 @@ class LimitSubqueryOutputWalker extends SqlOutputWalker
$this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
$this->rsm = $parserResult->getResultSetMapping();
$query = clone $query;
$cloneQuery = clone $query;
$cloneQuery->setParameters(clone $query->getParameters());
$cloneQuery->setCacheable(false);
foreach ($query->getHints() as $name => $value) {
$cloneQuery->setHint($name, $value);
}
// Reset limit and offset
$this->firstResult = $query->getFirstResult();
$this->maxResults = $query->getMaxResults();
$query->setFirstResult(0)->setMaxResults(null);
$this->firstResult = $cloneQuery->getFirstResult();
$this->maxResults = $cloneQuery->getMaxResults();
$cloneQuery->setFirstResult(0)->setMaxResults(null);
$this->em = $query->getEntityManager();
$this->em = $cloneQuery->getEntityManager();
$this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();
parent::__construct($query, $parserResult, $queryComponents);
parent::__construct($cloneQuery, $parserResult, $queryComponents);
}
/**

View File

@@ -49,6 +49,7 @@ use Doctrine\Persistence\PropertyChangedListener;
use Exception;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\VarExporter\Hydrator;
use UnexpectedValueException;
use function array_chunk;
@@ -2944,6 +2945,11 @@ EXCEPTION
if ($this->isUninitializedObject($entity)) {
$entity->__setInitialized(true);
if ($this->em->getConfiguration()->isLazyGhostObjectEnabled()) {
// Initialize properties that have default values to their default value (similar to what
Hydrator::hydrate($entity, (array) $class->reflClass->newInstanceWithoutConstructor());
}
} else {
if (
! isset($hints[Query::HINT_REFRESH])

View File

@@ -118,13 +118,11 @@ class ConnectionMock extends Connection
$this->_platformMock = $platform;
}
/** @return array */
public function getExecuteStatements(): array
{
return $this->_executeStatements;
}
/** @return array */
public function getDeletes(): array
{
return $this->_deletes;

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH11524;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="gh11524_entities")
*/
class GH11524Entity
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*
* @var int|null
*/
public $id = null;
/**
* @ORM\ManyToOne(targetEntity="GH11524Relation")
* @ORM\JoinColumn(name="relation_id", referencedColumnName="id", nullable=true)
*
* @var GH11524Relation|null
*/
public $relation = null;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH11524;
use Doctrine\ORM\Event\PostLoadEventArgs;
class GH11524Listener
{
public function postLoad(PostloadEventArgs $eventArgs): void
{
$object = $eventArgs->getObject();
if (! $object instanceof GH11524Relation) {
return;
}
$object->setCurrentLocale('en');
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH11524;
use Doctrine\ORM\Mapping as ORM;
use LogicException;
/**
* @ORM\Entity
* @ORM\Table(name="gh11524_relations")
*/
class GH11524Relation
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*
* @var int|null
*/
public $id;
/**
* @ORM\Column(type="string")
*
* @var string
*/
public $name;
/**
* @var string|null
*/
private $currentLocale;
public function setCurrentLocale(string $locale): void
{
$this->currentLocale = $locale;
}
public function getTranslation(): string
{
if ($this->currentLocale === null) {
throw new LogicException('The current locale must be set to retrieve translation.');
}
return 'fake';
}
}

View File

@@ -7,9 +7,11 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Tests\OrmFunctionalTestCase;
use function count;
use function iterator_to_array;
class EagerFetchCollectionTest extends OrmFunctionalTestCase
{
@@ -96,6 +98,16 @@ class EagerFetchCollectionTest extends OrmFunctionalTestCase
$this->assertIsString($query->getSql());
}
public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEagerPaginator(): void
{
$query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
$query->setMaxResults(1);
$query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);
$paginator = new Paginator($query, true);
$this->assertIsArray(iterator_to_array($paginator));
}
public function testEagerFetchWithIterable(): void
{
$this->createOwnerWithChildren(2);

View File

@@ -66,10 +66,11 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
self::assertTrue($entity->postPersistCallbackInvoked);
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$query = $this->_em->createQuery('select e from Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity e');
$result = $query->getResult();
self::assertTrue($result[0]->postLoadCallbackInvoked);
self::assertTrue($result[0]::$postLoadCallbackInvoked);
$result[0]->value = 'hello again';
@@ -130,12 +131,14 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
$id = $entity->getId();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$reference = $this->_em->getReference(LifecycleCallbackTestEntity::class, $id);
self::assertFalse($reference->postLoadCallbackInvoked);
self::assertFalse($reference::$postLoadCallbackInvoked);
$this->assertTrue($this->isUninitializedObject($reference));
$reference->getValue(); // trigger proxy load
self::assertTrue($reference->postLoadCallbackInvoked);
self::assertTrue($reference::$postLoadCallbackInvoked);
}
/** @group DDC-958 */
@@ -148,13 +151,14 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
$id = $entity->getId();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$reference = $this->_em->find(LifecycleCallbackTestEntity::class, $id);
self::assertTrue($reference->postLoadCallbackInvoked);
$reference->postLoadCallbackInvoked = false;
self::assertTrue($reference::$postLoadCallbackInvoked);
$reference::$postLoadCallbackInvoked = false;
$this->_em->refresh($reference);
self::assertTrue($reference->postLoadCallbackInvoked, 'postLoad should be invoked when refresh() is called.');
self::assertTrue($reference::$postLoadCallbackInvoked, 'postLoad should be invoked when refresh() is called.');
}
/** @group DDC-113 */
@@ -197,6 +201,7 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$dql = <<<'DQL'
SELECT
@@ -214,9 +219,9 @@ DQL;
->createQuery(sprintf($dql, $e1->getId(), $e2->getId()))
->getResult();
self::assertTrue(current($entities)->postLoadCallbackInvoked);
self::assertTrue(current($entities)::$postLoadCallbackInvoked);
self::assertTrue(current($entities)->postLoadCascaderNotNull);
self::assertTrue(current($entities)->cascader->postLoadCallbackInvoked);
self::assertTrue(current($entities)->cascader::$postLoadCallbackInvoked);
self::assertEquals(current($entities)->cascader->postLoadEntitiesCount, 2);
}
@@ -239,6 +244,8 @@ DQL;
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
LifecycleCallbackCascader::$postLoadCallbackInvoked = false;
$dql = <<<'DQL'
SELECT
@@ -256,7 +263,7 @@ DQL;
$result = iterator_to_array($query->iterate());
foreach ($result as $entity) {
self::assertTrue($entity[0]->postLoadCallbackInvoked);
self::assertTrue($entity[0]::$postLoadCallbackInvoked);
self::assertFalse($entity[0]->postLoadCascaderNotNull);
break;
@@ -265,7 +272,7 @@ DQL;
$iterableResult = iterator_to_array($query->toIterable());
foreach ($iterableResult as $entity) {
self::assertTrue($entity->postLoadCallbackInvoked);
self::assertTrue($entity::$postLoadCallbackInvoked);
self::assertFalse($entity->postLoadCascaderNotNull);
break;
@@ -283,6 +290,7 @@ DQL;
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$query = $this->_em->createQuery(
'SELECT e FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e'
@@ -291,7 +299,7 @@ DQL;
$result = iterator_to_array($query->iterate(null, Query::HYDRATE_SIMPLEOBJECT));
foreach ($result as $entity) {
self::assertTrue($entity[0]->postLoadCallbackInvoked);
self::assertTrue($entity[0]::$postLoadCallbackInvoked);
self::assertFalse($entity[0]->postLoadCascaderNotNull);
break;
@@ -300,7 +308,7 @@ DQL;
$result = iterator_to_array($query->toIterable([], Query::HYDRATE_SIMPLEOBJECT));
foreach ($result as $entity) {
self::assertTrue($entity->postLoadCallbackInvoked);
self::assertTrue($entity::$postLoadCallbackInvoked);
self::assertFalse($entity->postLoadCascaderNotNull);
break;
@@ -325,6 +333,8 @@ DQL;
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
LifecycleCallbackCascader::$postLoadCallbackInvoked = false;
$dql = <<<'DQL'
SELECT
@@ -342,9 +352,9 @@ DQL;
->createQuery($dql)->setParameter('entA_id', $entA->getId())
->getOneOrNullResult();
self::assertTrue($fetchedA->postLoadCallbackInvoked);
self::assertTrue($fetchedA::$postLoadCallbackInvoked);
foreach ($fetchedA->entities as $fetchJoinedEntB) {
self::assertTrue($fetchJoinedEntB->postLoadCallbackInvoked);
self::assertTrue($fetchJoinedEntB::$postLoadCallbackInvoked);
}
}
@@ -492,7 +502,7 @@ class LifecycleCallbackTestEntity
public $postPersistCallbackInvoked = false;
/** @var bool */
public $postLoadCallbackInvoked = false;
public static $postLoadCallbackInvoked = false;
/** @var bool */
public $postLoadCascaderNotNull = false;
@@ -546,7 +556,7 @@ class LifecycleCallbackTestEntity
/** @PostLoad */
public function doStuffOnPostLoad(): void
{
$this->postLoadCallbackInvoked = true;
self::$postLoadCallbackInvoked = true;
$this->postLoadCascaderNotNull = isset($this->cascader);
}
@@ -572,7 +582,7 @@ class LifecycleCallbackCascader
{
/* test stuff */
/** @var bool */
public $postLoadCallbackInvoked = false;
public static $postLoadCallbackInvoked = false;
/** @var int */
public $postLoadEntitiesCount = 0;
@@ -599,7 +609,7 @@ class LifecycleCallbackCascader
/** @PostLoad */
public function doStuffOnPostLoad(): void
{
$this->postLoadCallbackInvoked = true;
self::$postLoadCallbackInvoked = true;
$this->postLoadEntitiesCount = count($this->entities);
}

View File

@@ -558,7 +558,9 @@ class QueryTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$data = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u')
$query = 'SELECT u FROM ' . CmsUser::class . ' u ORDER BY u.username';
$data = $this->_em->createQuery($query)
->setFirstResult(1)
->setMaxResults(2)
->getResult();
@@ -567,7 +569,7 @@ class QueryTest extends OrmFunctionalTestCase
self::assertEquals('gblanco1', $data[0]->username);
self::assertEquals('gblanco2', $data[1]->username);
$data = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u')
$data = $this->_em->createQuery($query)
->setFirstResult(3)
->setMaxResults(2)
->getResult();
@@ -576,7 +578,7 @@ class QueryTest extends OrmFunctionalTestCase
self::assertEquals('gblanco3', $data[0]->username);
self::assertEquals('gblanco4', $data[1]->username);
$data = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u')
$data = $this->_em->createQuery($query)
->setFirstResult(3)
->setMaxResults(2)
->getScalarResult();

View File

@@ -537,6 +537,21 @@ class SQLFilterTest extends OrmFunctionalTestCase
self::assertEquals(2, count($query->getResult()));
}
public function testOneToOneInverseSideWithFilter(): void
{
$this->loadFixtureData();
$conf = $this->_em->getConfiguration();
$conf->addFilter('country', '\Doctrine\Tests\ORM\Functional\CMSCountryFilter');
$this->_em->getFilters()->enable('country')->setParameterList('country', ['Germany'], Types::STRING);
$user = $this->_em->find(CmsUser::class, $this->userId);
self::assertNotEmpty($user->address);
$user2 = $this->_em->find(CmsUser::class, $this->userId2);
self::assertEmpty($user2->address);
}
public function testManyToManyFilter(): void
{
$this->loadFixtureData();

View File

@@ -51,15 +51,20 @@ class DDC1690Test extends OrmFunctionalTestCase
$parentId = $parent->getId();
$childId = $child->getId();
unset($parent, $child);
DDC1690Parent::$addPropertyChangedListenerInvoked = false;
DDC1690Child::$addPropertyChangedListenerInvoked = false;
$parent = $this->_em->find(DDC1690Parent::class, $parentId);
$child = $this->_em->find(DDC1690Child::class, $childId);
self::assertTrue($parent::$addPropertyChangedListenerInvoked);
self::assertEquals(1, count($parent->listeners));
self::assertCount(0, $child->listeners);
$this->assertTrue($this->isUninitializedObject($child));
self::assertFalse($child::$addPropertyChangedListenerInvoked);
$this->_em->getUnitOfWork()->initializeObject($child);
self::assertTrue($child::$addPropertyChangedListenerInvoked);
self::assertCount(1, $child->listeners);
unset($parent, $child);
@@ -106,6 +111,11 @@ class NotifyBaseEntity implements NotifyPropertyChanged
*/
class DDC1690Parent extends NotifyBaseEntity
{
/**
* @var bool
*/
public static $addPropertyChangedListenerInvoked = false;
/**
* @var int
* @Id
@@ -151,11 +161,23 @@ class DDC1690Parent extends NotifyBaseEntity
{
return $this->child;
}
public function addPropertyChangedListener(PropertyChangedListener $listener): void
{
self::$addPropertyChangedListenerInvoked = true;
parent::addPropertyChangedListener($listener);
}
}
/** @Entity */
class DDC1690Child extends NotifyBaseEntity
{
/**
* @var bool
*/
public static $addPropertyChangedListenerInvoked = false;
/**
* @var int
* @Id
@@ -201,4 +223,11 @@ class DDC1690Child extends NotifyBaseEntity
{
return $this->parent;
}
public function addPropertyChangedListener(PropertyChangedListener $listener): void
{
self::$addPropertyChangedListenerInvoked = true;
parent::addPropertyChangedListener($listener);
}
}

View File

@@ -57,16 +57,17 @@ class DDC2230Test extends OrmFunctionalTestCase
$this->_em->persist($insertedAddress);
$this->_em->flush();
$this->_em->clear();
DDC2230Address::$listener = null; // Reset the tracking state
$addressProxy = $this->_em->getReference(DDC2230Address::class, $insertedAddress->id);
assert($addressProxy instanceof DDC2230Address);
self::assertTrue($this->isUninitializedObject($addressProxy));
self::assertNull($addressProxy->listener);
self::assertNull($addressProxy::$listener);
$this->_em->getUnitOfWork()->initializeObject($addressProxy);
self::assertSame($this->_em->getUnitOfWork(), $addressProxy->listener);
self::assertSame($this->_em->getUnitOfWork(), $addressProxy::$listener);
}
}
@@ -102,12 +103,12 @@ class DDC2230Address implements NotifyPropertyChanged
*/
public $id;
/** @var \Doctrine\Common\PropertyChangedListener */
public $listener;
/** @var \Doctrine\Common\PropertyChangedListener|null */
public static $listener;
/** {@inheritDoc} */
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listener = $listener;
self::$listener = $listener;
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Events;
use Doctrine\Tests\Models\GH11524\GH11524Entity;
use Doctrine\Tests\Models\GH11524\GH11524Listener;
use Doctrine\Tests\Models\GH11524\GH11524Relation;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11524Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
GH11524Entity::class,
GH11524Relation::class
);
$this->_em->getEventManager()->addEventListener(Events::postLoad, new GH11524Listener());
}
public function testPostLoadCalledOnProxy(): void
{
$relation = new GH11524Relation();
$relation->name = 'test';
$this->_em->persist($relation);
$entity = new GH11524Entity();
$entity->relation = $relation;
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$reloadedEntity = $this->_em->find(GH11524Entity::class, $entity->id);
$reloadedRelation = $reloadedEntity->relation;
$this->assertTrue($this->isUninitializedObject($reloadedRelation));
$this->assertSame('fake', $reloadedRelation->getTranslation(), 'The property set by the postLoad listener must get initialized on usage.');
}
}

View File

@@ -84,7 +84,7 @@ class ReflectionPropertiesGetterTest extends TestCase
public function testPropertyGetterIsIdempotent(): void
{
$getter = (new ReflectionPropertiesGetter(new RuntimeReflectionService()));
$getter = new ReflectionPropertiesGetter(new RuntimeReflectionService());
self::assertSame(
$getter->getProperties(ClassWithMixedProperties::class),
@@ -110,7 +110,7 @@ class ReflectionPropertiesGetterTest extends TestCase
->expects(self::atLeastOnce())
->method('getAccessibleProperty');
$getter = (new ReflectionPropertiesGetter($reflectionService));
$getter = new ReflectionPropertiesGetter($reflectionService);
self::assertEmpty($getter->getProperties(ClassWithMixedProperties::class));
}
@@ -127,7 +127,7 @@ class ReflectionPropertiesGetterTest extends TestCase
$reflectionService->expects(self::never())->method('getAccessibleProperty');
$getter = (new ReflectionPropertiesGetter($reflectionService));
$getter = new ReflectionPropertiesGetter($reflectionService);
self::assertEmpty($getter->getProperties(ClassWithMixedProperties::class));
}

View File

@@ -85,7 +85,7 @@ class ORMInvalidArgumentExceptionTest extends TestCase
. 'persist operations for entity: stdClass@' . spl_object_id($entity1)
. '. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity '
. 'or configure cascade persist this association in the mapping for example '
. '@ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem '
. '#[ORM\ManyToOne(..., cascade: [\'persist\'])]. If you cannot find out which entity causes the problem '
. 'implement \'baz1#__toString()\' to get a clue.',
],
'two entities found' => [
@@ -104,13 +104,13 @@ class ORMInvalidArgumentExceptionTest extends TestCase
. 'cascade persist operations for entity: stdClass@' . spl_object_id($entity1) . '. '
. 'To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity '
. 'or configure cascade persist this association in the mapping for example '
. '@ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem '
. '#[ORM\ManyToOne(..., cascade: [\'persist\'])]. If you cannot find out which entity causes the problem '
. 'implement \'baz1#__toString()\' to get a clue.' . "\n"
. ' * A new entity was found through the relationship \'foo2#bar2\' that was not configured to '
. 'cascade persist operations for entity: stdClass@' . spl_object_id($entity2) . '. To solve '
. 'this issue: Either explicitly call EntityManager#persist() on this unknown entity or '
. 'configure cascade persist this association in the mapping for example '
. '@ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem '
. '#[ORM\ManyToOne(..., cascade: [\'persist\'])]. If you cannot find out which entity causes the problem '
. 'implement \'baz2#__toString()\' to get a clue.',
],
'two entities found, one is stringable' => [
@@ -124,7 +124,7 @@ class ORMInvalidArgumentExceptionTest extends TestCase
. 'persist operations for entity: ThisIsAStringRepresentationOfEntity3'
. '. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity '
. 'or configure cascade persist this association in the mapping for example '
. '@ManyToOne(..,cascade={"persist"}).',
. '#[ORM\ManyToOne(..., cascade: [\'persist\'])].',
],
];
}

View File

@@ -46,6 +46,24 @@ final class LimitSubqueryOutputWalkerTest extends PaginationTestCase
$this->entityManager->getConnection()->setDatabasePlatform($platform);
}
public function testSubqueryClonedCompletely(): void
{
$query = $this->createQuery('SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p');
$query->setParameter('dummy-param', 123);
$query->setHint('dummy-hint', 'dummy-value');
$query->setCacheable(true);
$walker = new LimitSubqueryOutputWalker($query, new Query\ParserResult(), []);
self::assertNotSame($query, $walker->getQuery());
self::assertTrue($walker->getQuery()->hasHint('dummy-hint'));
self::assertSame('dummy-value', $walker->getQuery()->getHint('dummy-hint'));
self::assertNotSame($query->getParameters(), $walker->getQuery()->getParameters());
self::assertInstanceOf(Query\Parameter::class, $param = $walker->getQuery()->getParameter('dummy-param'));
self::assertSame(123, $param->getValue());
self::assertFalse($walker->getQuery()->isCacheable());
}
public function testLimitSubquery(): void
{
$query = $this->createQuery('SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN p.category c JOIN p.author a');

View File

@@ -679,8 +679,6 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
}
/**
* @param array $classNames
*
* @throws RuntimeException
*/
protected function setUpEntitySchema(array $classNames): void