Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/2.20.x' into 3.3.x
Browse files Browse the repository at this point in the history
  • Loading branch information
greg0ire committed Feb 4, 2025
2 parents aff82af + 19912de commit c9557c5
Show file tree
Hide file tree
Showing 19 changed files with 283 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coding-standards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ on:

jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.1.0"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.2.1"
2 changes: 1 addition & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ on:
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@7.1.0"
uses: "doctrine/.github/.github/workflows/documentation.yml@7.2.1"
2 changes: 1 addition & 1 deletion .github/workflows/release-on-milestone-closed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.1.0"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.2.1"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
Expand Down
7 changes: 3 additions & 4 deletions docs/en/reference/association-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -903,8 +903,7 @@ defaults to "id", just as in one-to-one or many-to-one mappings.

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

.. configuration-block::

Expand All @@ -931,7 +930,7 @@ Is essentially the same as following:
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment $shipment;
.. code-block:: annotation
Expand All @@ -940,7 +939,7 @@ Is essentially the same as following:
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private Shipment $shipment;
Expand Down
5 changes: 4 additions & 1 deletion src/Cache/CollectionCacheKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ public function __construct(
public readonly string $entityClass,
public readonly string $association,
array $ownerIdentifier,
string $filterHash = '',
) {
ksort($ownerIdentifier);

$this->ownerIdentifier = $ownerIdentifier;

parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
$filterHash = $filterHash === '' ? '' : '_' . $filterHash;

parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association . $filterHash);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\UnitOfWork;

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

protected string $regionName;
protected FilterCollection $filters;
protected CollectionHydrator $hydrator;
protected CacheLogger|null $cacheLogger;

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

$this->region = $region;
$this->persister = $persister;
$this->association = $association;
$this->filters = $em->getFilters();
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
Expand Down Expand Up @@ -135,7 +141,7 @@ public function containsKey(PersistentCollection $collection, mixed $key): bool
public function count(PersistentCollection $collection): int
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
$entry = $this->region->get($key);

if ($entry !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function afterTransactionRolledBack(): void
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());

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

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

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

// Invalidate non initialized collections OR ordered collection
if ($isDirty && ! $isInitialized || $this->association->isOrdered()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function afterTransactionRolledBack(): void
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
$lock = $this->region->lock($key);

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

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

if ($lock === null) {
Expand Down
15 changes: 11 additions & 4 deletions src/Cache/Persister/Entity/AbstractEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;

use function array_merge;
use function func_get_args;
use function serialize;
use function sha1;

Expand All @@ -43,6 +45,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
protected TimestampCacheKey $timestampKey;
protected EntityHydrator $hydrator;
protected Cache $cache;
protected FilterCollection $filters;
protected CacheLogger|null $cacheLogger = null;
protected string $regionName;

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

$this->cache = $em->getCache();
$this->filters = $em->getFilters();
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
Expand Down Expand Up @@ -215,7 +219,7 @@ protected function getHash(
? $this->persister->expandCriteriaParameters($criteria)
: $this->persister->expandParameters($criteria);

return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $this->filters->getHash());
}

/**
Expand Down Expand Up @@ -472,7 +476,7 @@ public function loadManyToManyCollection(
}

$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
$list = $persister->loadCollectionCache($collection, $key);

if ($list !== null) {
Expand Down Expand Up @@ -503,7 +507,7 @@ public function loadOneToManyCollection(
}

$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
$list = $persister->loadCollectionCache($collection, $key);

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

/** @param array<string, mixed> $ownerId */
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId): CollectionCacheKey
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, /* string $filterHash */): CollectionCacheKey
{
$filterHash = (string) (func_get_args()[2] ?? ''); // todo: move to argument in next major release

return new CollectionCacheKey(
$this->metadataFactory->getMetadataFor($association->sourceEntity)->rootEntityName,
$association->fieldName,
$ownerId,
$filterHash,
);
}
}
14 changes: 11 additions & 3 deletions src/Internal/Hydration/SimpleObjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use function assert;
use function count;
use function in_array;
use function is_array;
use function key;
use function reset;
use function sprintf;
Expand Down Expand Up @@ -138,14 +139,21 @@ protected function hydrateRowData(array $row, array &$result): void
}

if ($value !== null && isset($cacheKeyInfo['enumType'])) {
$originalValue = $value;
$originalValue = $currentValue = $value;
try {
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
if (! is_array($originalValue)) {
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
} else {
$value = [];
foreach ($originalValue as $i => $currentValue) {
$value[$i] = $this->buildEnum($currentValue, $cacheKeyInfo['enumType']);
}
}
} catch (ValueError $e) {
throw MappingException::invalidEnumValue(
$entityName,
$cacheKeyInfo['fieldName'],
(string) $originalValue,
(string) $currentValue,
$cacheKeyInfo['enumType'],
$e,
);
Expand Down
5 changes: 5 additions & 0 deletions src/Mapping/Driver/ReflectionBasedDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ trait ReflectionBasedDriver
*/
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool
{
/** @var class-string $declaringClass */
$declaringClass = $property->class;

if ($this->isTransient($declaringClass)) {
return isset($metadata->fieldMappings[$property->name]);
}

if (
isset($metadata->fieldMappings[$property->name]->declared)
&& $metadata->fieldMappings[$property->name]->declared === $declaringClass
Expand Down
10 changes: 9 additions & 1 deletion src/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -1500,7 +1500,15 @@ protected function getSelectColumnSQL(string $field, ClassMetadata $class, strin
$tableAlias = $this->getSQLTableAlias($class->name, $root);
$fieldMapping = $class->fieldMappings[$field];
$sql = sprintf('%s.%s', $tableAlias, $this->quoteStrategy->getColumnName($field, $class, $this->platform));
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);

$columnAlias = null;
if ($this->currentPersisterContext->rsm->hasColumnAliasByField($alias, $field)) {
$columnAlias = $this->currentPersisterContext->rsm->getColumnAliasByField($alias, $field);
}

if ($columnAlias === null) {
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
}

$this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field);
if (! empty($fieldMapping->enumType)) {
Expand Down
26 changes: 25 additions & 1 deletion src/Query/ResultSetMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ class ResultSetMapping
*/
public array $fieldMappings = [];

/**
* Map field names for each class to alias
*
* @var array<class-string, array<string, array<string, string>>>
*/
public array $columnAliasMappings = [];

/**
* Maps column names in the result set to the alias/field name to use in the mapped result.
*
Expand Down Expand Up @@ -328,7 +335,10 @@ public function addFieldResult(string $alias, string $columnName, string $fieldN
// column name => alias of owner
$this->columnOwnerMap[$columnName] = $alias;
// field name => class name of declaring class
$this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
$declaringClass = $declaringClass ?: $this->aliasMap[$alias];
$this->declaringClasses[$columnName] = $declaringClass;

$this->columnAliasMappings[$declaringClass][$alias][$fieldName] = $columnName;

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

public function hasColumnAliasByField(string $alias, string $fieldName): bool
{
$declaringClass = $this->aliasMap[$alias];

return isset($this->columnAliasMappings[$declaringClass][$alias][$fieldName]);
}

public function getColumnAliasByField(string $alias, string $fieldName): string
{
$declaringClass = $this->aliasMap[$alias];

return $this->columnAliasMappings[$declaringClass][$alias][$fieldName];
}

/**
* Adds a joined entity result.
*
Expand Down
32 changes: 32 additions & 0 deletions tests/Tests/ORM/Functional/Ticket/GH10450Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ public static function classesThatOverrideFieldNames(): Generator
yield 'Entity class that redeclares a protected field inherited from a base entity' => [GH10450EntityChildProtected::class];
yield 'Entity class that redeclares a protected field inherited from a mapped superclass' => [GH10450MappedSuperclassChildProtected::class];
}

public function testFieldsOfTransientClassesAreNotConsideredDuplicate(): void
{
$em = $this->getTestEntityManager();

$metadata = $em->getClassMetadata(GH10450Cat::class);

self::assertArrayHasKey('id', $metadata->fieldMappings);
}
}

#[ORM\Entity]
Expand Down Expand Up @@ -113,3 +122,26 @@ class GH10450MappedSuperclassChildProtected extends GH10450BaseMappedSuperclassP
#[ORM\Column(type: 'text', name: 'child')]
protected string $field;
}

abstract class GH10450AbstractEntity
{
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue]
protected int $id;
}

#[ORM\Entity]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorMap(['cat' => GH10450Cat::class])]
#[ORM\DiscriminatorColumn(name: 'type')]
abstract class GH10450Animal extends GH10450AbstractEntity
{
#[ORM\Column(type: 'text', name: 'base')]
private string $field;
}

#[ORM\Entity]
class GH10450Cat extends GH10450Animal
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilterAndIndexedRelation;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'Category_Master')]
class Category
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
public int $id;

#[ORM\Column(type: 'string')]
public string $name;

#[ORM\Column(type: 'string')]
public string $type;

public function __construct(string $name, string $type)
{
$this->name = $name;
$this->type = $type;
}
}
Loading

0 comments on commit c9557c5

Please sign in to comment.