Skip to content

Commit c9557c5

Browse files
committed
Merge remote-tracking branch 'origin/2.20.x' into 3.3.x
2 parents aff82af + 19912de commit c9557c5

19 files changed

+283
-22
lines changed

.github/workflows/coding-standards.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ on:
2424

2525
jobs:
2626
coding-standards:
27-
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.1.0"
27+
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.2.1"

.github/workflows/documentation.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ on:
1717
jobs:
1818
documentation:
1919
name: "Documentation"
20-
uses: "doctrine/.github/.github/workflows/documentation.yml@7.1.0"
20+
uses: "doctrine/.github/.github/workflows/documentation.yml@7.2.1"

.github/workflows/release-on-milestone-closed.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77

88
jobs:
99
release:
10-
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.1.0"
10+
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.2.1"
1111
secrets:
1212
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
1313
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

docs/en/reference/association-mapping.rst

+3-4
Original file line numberDiff line numberDiff line change
@@ -903,8 +903,7 @@ defaults to "id", just as in one-to-one or many-to-one mappings.
903903

904904
Additionally, when using typed properties with Doctrine 2.9 or newer
905905
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
906-
associations as they will be set based on type. Also ``nullable``
907-
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
906+
associations as they will be set based on type. So that:
908907

909908
.. configuration-block::
910909

@@ -931,7 +930,7 @@ Is essentially the same as following:
931930
<?php
932931
/** One Product has One Shipment. */
933932
#[OneToOne(targetEntity: Shipment::class)]
934-
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
933+
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
935934
private Shipment $shipment;
936935
937936
.. code-block:: annotation
@@ -940,7 +939,7 @@ Is essentially the same as following:
940939
/**
941940
* One Product has One Shipment.
942941
* @OneToOne(targetEntity="Shipment")
943-
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
942+
* @JoinColumn(name="shipment_id", referencedColumnName="id")
944943
*/
945944
private Shipment $shipment;
946945

src/Cache/CollectionCacheKey.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ public function __construct(
2929
public readonly string $entityClass,
3030
public readonly string $association,
3131
array $ownerIdentifier,
32+
string $filterHash = '',
3233
) {
3334
ksort($ownerIdentifier);
3435

3536
$this->ownerIdentifier = $ownerIdentifier;
3637

37-
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
38+
$filterHash = $filterHash === '' ? '' : '_' . $filterHash;
39+
40+
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association . $filterHash);
3841
}
3942
}

src/Cache/Persister/Collection/AbstractCollectionPersister.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Doctrine\ORM\PersistentCollection;
1919
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
2020
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
21+
use Doctrine\ORM\Query\FilterCollection;
2122
use Doctrine\ORM\UnitOfWork;
2223

2324
use function array_values;
@@ -35,6 +36,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
3536
protected array $queuedCache = [];
3637

3738
protected string $regionName;
39+
protected FilterCollection $filters;
3840
protected CollectionHydrator $hydrator;
3941
protected CacheLogger|null $cacheLogger;
4042

@@ -48,6 +50,10 @@ public function __construct(
4850
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
4951
$cacheFactory = $cacheConfig->getCacheFactory();
5052

53+
$this->region = $region;
54+
$this->persister = $persister;
55+
$this->association = $association;
56+
$this->filters = $em->getFilters();
5157
$this->regionName = $region->getName();
5258
$this->uow = $em->getUnitOfWork();
5359
$this->metadataFactory = $em->getMetadataFactory();
@@ -135,7 +141,7 @@ public function containsKey(PersistentCollection $collection, mixed $key): bool
135141
public function count(PersistentCollection $collection): int
136142
{
137143
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
138-
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
144+
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
139145
$entry = $this->region->get($key);
140146

141147
if ($entry !== null) {

src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function afterTransactionRolledBack(): void
3636
public function delete(PersistentCollection $collection): void
3737
{
3838
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
39-
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
39+
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
4040

4141
$this->persister->delete($collection);
4242

@@ -53,7 +53,7 @@ public function update(PersistentCollection $collection): void
5353
}
5454

5555
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
56-
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
56+
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
5757

5858
// Invalidate non initialized collections OR ordered collection
5959
if ($isDirty && ! $isInitialized || $this->association->isOrdered()) {

src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function afterTransactionRolledBack(): void
6161
public function delete(PersistentCollection $collection): void
6262
{
6363
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
64-
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
64+
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
6565
$lock = $this->region->lock($key);
6666

6767
$this->persister->delete($collection);
@@ -88,7 +88,7 @@ public function update(PersistentCollection $collection): void
8888
$this->persister->update($collection);
8989

9090
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
91-
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
91+
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
9292
$lock = $this->region->lock($key);
9393

9494
if ($lock === null) {

src/Cache/Persister/Entity/AbstractEntityPersister.php

+11-4
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
use Doctrine\ORM\PersistentCollection;
2525
use Doctrine\ORM\Persisters\Entity\EntityPersister;
2626
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
27+
use Doctrine\ORM\Query\FilterCollection;
2728
use Doctrine\ORM\Query\ResultSetMapping;
2829
use Doctrine\ORM\UnitOfWork;
2930

3031
use function array_merge;
32+
use function func_get_args;
3133
use function serialize;
3234
use function sha1;
3335

@@ -43,6 +45,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
4345
protected TimestampCacheKey $timestampKey;
4446
protected EntityHydrator $hydrator;
4547
protected Cache $cache;
48+
protected FilterCollection $filters;
4649
protected CacheLogger|null $cacheLogger = null;
4750
protected string $regionName;
4851

@@ -64,6 +67,7 @@ public function __construct(
6467
$cacheFactory = $cacheConfig->getCacheFactory();
6568

6669
$this->cache = $em->getCache();
70+
$this->filters = $em->getFilters();
6771
$this->regionName = $region->getName();
6872
$this->uow = $em->getUnitOfWork();
6973
$this->metadataFactory = $em->getMetadataFactory();
@@ -215,7 +219,7 @@ protected function getHash(
215219
? $this->persister->expandCriteriaParameters($criteria)
216220
: $this->persister->expandParameters($criteria);
217221

218-
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
222+
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $this->filters->getHash());
219223
}
220224

221225
/**
@@ -472,7 +476,7 @@ public function loadManyToManyCollection(
472476
}
473477

474478
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
475-
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
479+
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
476480
$list = $persister->loadCollectionCache($collection, $key);
477481

478482
if ($list !== null) {
@@ -503,7 +507,7 @@ public function loadOneToManyCollection(
503507
}
504508

505509
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
506-
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
510+
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
507511
$list = $persister->loadCollectionCache($collection, $key);
508512

509513
if ($list !== null) {
@@ -546,12 +550,15 @@ public function refresh(array $id, object $entity, LockMode|int|null $lockMode =
546550
}
547551

548552
/** @param array<string, mixed> $ownerId */
549-
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId): CollectionCacheKey
553+
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, /* string $filterHash */): CollectionCacheKey
550554
{
555+
$filterHash = (string) (func_get_args()[2] ?? ''); // todo: move to argument in next major release
556+
551557
return new CollectionCacheKey(
552558
$this->metadataFactory->getMetadataFor($association->sourceEntity)->rootEntityName,
553559
$association->fieldName,
554560
$ownerId,
561+
$filterHash,
555562
);
556563
}
557564
}

src/Internal/Hydration/SimpleObjectHydrator.php

+11-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use function assert;
1818
use function count;
1919
use function in_array;
20+
use function is_array;
2021
use function key;
2122
use function reset;
2223
use function sprintf;
@@ -138,14 +139,21 @@ protected function hydrateRowData(array $row, array &$result): void
138139
}
139140

140141
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
141-
$originalValue = $value;
142+
$originalValue = $currentValue = $value;
142143
try {
143-
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
144+
if (! is_array($originalValue)) {
145+
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
146+
} else {
147+
$value = [];
148+
foreach ($originalValue as $i => $currentValue) {
149+
$value[$i] = $this->buildEnum($currentValue, $cacheKeyInfo['enumType']);
150+
}
151+
}
144152
} catch (ValueError $e) {
145153
throw MappingException::invalidEnumValue(
146154
$entityName,
147155
$cacheKeyInfo['fieldName'],
148-
(string) $originalValue,
156+
(string) $currentValue,
149157
$cacheKeyInfo['enumType'],
150158
$e,
151159
);

src/Mapping/Driver/ReflectionBasedDriver.php

+5
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ trait ReflectionBasedDriver
2222
*/
2323
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool
2424
{
25+
/** @var class-string $declaringClass */
2526
$declaringClass = $property->class;
2627

28+
if ($this->isTransient($declaringClass)) {
29+
return isset($metadata->fieldMappings[$property->name]);
30+
}
31+
2732
if (
2833
isset($metadata->fieldMappings[$property->name]->declared)
2934
&& $metadata->fieldMappings[$property->name]->declared === $declaringClass

src/Persisters/Entity/BasicEntityPersister.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -1500,7 +1500,15 @@ protected function getSelectColumnSQL(string $field, ClassMetadata $class, strin
15001500
$tableAlias = $this->getSQLTableAlias($class->name, $root);
15011501
$fieldMapping = $class->fieldMappings[$field];
15021502
$sql = sprintf('%s.%s', $tableAlias, $this->quoteStrategy->getColumnName($field, $class, $this->platform));
1503-
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
1503+
1504+
$columnAlias = null;
1505+
if ($this->currentPersisterContext->rsm->hasColumnAliasByField($alias, $field)) {
1506+
$columnAlias = $this->currentPersisterContext->rsm->getColumnAliasByField($alias, $field);
1507+
}
1508+
1509+
if ($columnAlias === null) {
1510+
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
1511+
}
15041512

15051513
$this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field);
15061514
if (! empty($fieldMapping->enumType)) {

src/Query/ResultSetMapping.php

+25-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ class ResultSetMapping
6666
*/
6767
public array $fieldMappings = [];
6868

69+
/**
70+
* Map field names for each class to alias
71+
*
72+
* @var array<class-string, array<string, array<string, string>>>
73+
*/
74+
public array $columnAliasMappings = [];
75+
6976
/**
7077
* Maps column names in the result set to the alias/field name to use in the mapped result.
7178
*
@@ -328,7 +335,10 @@ public function addFieldResult(string $alias, string $columnName, string $fieldN
328335
// column name => alias of owner
329336
$this->columnOwnerMap[$columnName] = $alias;
330337
// field name => class name of declaring class
331-
$this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
338+
$declaringClass = $declaringClass ?: $this->aliasMap[$alias];
339+
$this->declaringClasses[$columnName] = $declaringClass;
340+
341+
$this->columnAliasMappings[$declaringClass][$alias][$fieldName] = $columnName;
332342

333343
if (! $this->isMixed && $this->scalarMappings) {
334344
$this->isMixed = true;
@@ -337,6 +347,20 @@ public function addFieldResult(string $alias, string $columnName, string $fieldN
337347
return $this;
338348
}
339349

350+
public function hasColumnAliasByField(string $alias, string $fieldName): bool
351+
{
352+
$declaringClass = $this->aliasMap[$alias];
353+
354+
return isset($this->columnAliasMappings[$declaringClass][$alias][$fieldName]);
355+
}
356+
357+
public function getColumnAliasByField(string $alias, string $fieldName): string
358+
{
359+
$declaringClass = $this->aliasMap[$alias];
360+
361+
return $this->columnAliasMappings[$declaringClass][$alias][$fieldName];
362+
}
363+
340364
/**
341365
* Adds a joined entity result.
342366
*

tests/Tests/ORM/Functional/Ticket/GH10450Test.php

+32
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ public static function classesThatOverrideFieldNames(): Generator
3030
yield 'Entity class that redeclares a protected field inherited from a base entity' => [GH10450EntityChildProtected::class];
3131
yield 'Entity class that redeclares a protected field inherited from a mapped superclass' => [GH10450MappedSuperclassChildProtected::class];
3232
}
33+
34+
public function testFieldsOfTransientClassesAreNotConsideredDuplicate(): void
35+
{
36+
$em = $this->getTestEntityManager();
37+
38+
$metadata = $em->getClassMetadata(GH10450Cat::class);
39+
40+
self::assertArrayHasKey('id', $metadata->fieldMappings);
41+
}
3342
}
3443

3544
#[ORM\Entity]
@@ -113,3 +122,26 @@ class GH10450MappedSuperclassChildProtected extends GH10450BaseMappedSuperclassP
113122
#[ORM\Column(type: 'text', name: 'child')]
114123
protected string $field;
115124
}
125+
126+
abstract class GH10450AbstractEntity
127+
{
128+
#[ORM\Column(type: 'integer')]
129+
#[ORM\Id]
130+
#[ORM\GeneratedValue]
131+
protected int $id;
132+
}
133+
134+
#[ORM\Entity]
135+
#[ORM\InheritanceType('SINGLE_TABLE')]
136+
#[ORM\DiscriminatorMap(['cat' => GH10450Cat::class])]
137+
#[ORM\DiscriminatorColumn(name: 'type')]
138+
abstract class GH10450Animal extends GH10450AbstractEntity
139+
{
140+
#[ORM\Column(type: 'text', name: 'base')]
141+
private string $field;
142+
}
143+
144+
#[ORM\Entity]
145+
class GH10450Cat extends GH10450Animal
146+
{
147+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilterAndIndexedRelation;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
9+
#[ORM\Entity]
10+
#[ORM\Table(name: 'Category_Master')]
11+
class Category
12+
{
13+
#[ORM\Id]
14+
#[ORM\Column(type: 'integer')]
15+
#[ORM\GeneratedValue(strategy: 'AUTO')]
16+
public int $id;
17+
18+
#[ORM\Column(type: 'string')]
19+
public string $name;
20+
21+
#[ORM\Column(type: 'string')]
22+
public string $type;
23+
24+
public function __construct(string $name, string $type)
25+
{
26+
$this->name = $name;
27+
$this->type = $type;
28+
}
29+
}

0 commit comments

Comments
 (0)