Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Necessary refactorings for Property hooks #11659

Draft
wants to merge 17 commits into
base: 3.4.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/Internal/Hydration/ObjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private function initRelatedCollection(
): PersistentCollection {
$oid = spl_object_id($entity);
$relation = $class->associationMappings[$fieldName];
$value = $class->reflFields[$fieldName]->getValue($entity);
$value = $class->propertyAccessors[$fieldName]->getValue($entity);

if ($value === null || is_array($value)) {
$value = new ArrayCollection((array) $value);
Expand All @@ -186,7 +186,7 @@ private function initRelatedCollection(
);
$value->setOwner($entity, $relation);

$class->reflFields[$fieldName]->setValue($entity, $value);
$class->propertyAccessors[$fieldName]->setValue($entity, $value);
$this->uow->setOriginalEntityProperty($oid, $fieldName, $value);

$this->initializedCollections[$oid . $fieldName] = $value;
Expand Down Expand Up @@ -346,7 +346,7 @@ protected function hydrateRowData(array $row, array &$result): void
$parentClass = $this->metadataCache[$this->resultSetMapping()->aliasMap[$parentAlias]];
$relationField = $this->resultSetMapping()->relationMap[$dqlAlias];
$relation = $parentClass->associationMappings[$relationField];
$reflField = $parentClass->reflFields[$relationField];
$reflField = $parentClass->propertyAccessors[$relationField];

// Get a reference to the parent object to which the joined element belongs.
if ($this->resultSetMapping()->isMixed && isset($this->rootAliases[$parentAlias])) {
Expand Down Expand Up @@ -446,13 +446,13 @@ protected function hydrateRowData(array $row, array &$result): void
if ($relation->inversedBy !== null) {
$inverseAssoc = $targetClass->associationMappings[$relation->inversedBy];
if ($inverseAssoc->isToOne()) {
$targetClass->reflFields[$inverseAssoc->fieldName]->setValue($element, $parentObject);
$targetClass->propertyAccessors[$inverseAssoc->fieldName]->setValue($element, $parentObject);
$this->uow->setOriginalEntityProperty(spl_object_id($element), $inverseAssoc->fieldName, $parentObject);
}
}
} else {
// For sure bidirectional, as there is no inverse side in unidirectional mappings
$targetClass->reflFields[$relation->mappedBy]->setValue($element, $parentObject);
$targetClass->propertyAccessors[$relation->mappedBy]->setValue($element, $parentObject);
$this->uow->setOriginalEntityProperty(spl_object_id($element), $relation->mappedBy, $parentObject);
}

Expand Down
132 changes: 81 additions & 51 deletions src/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@
use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\ORM\Mapping\PropertyAccessors\EmbeddablePropertyAccessor;
use Doctrine\ORM\Mapping\PropertyAccessors\EnumPropertyAccessor;
use Doctrine\ORM\Mapping\PropertyAccessors\ObjectCastPropertyAccessor;
use Doctrine\ORM\Mapping\PropertyAccessors\PropertyAccessor;
use Doctrine\ORM\Mapping\PropertyAccessors\ReadonlyAccessor;
use Doctrine\ORM\Mapping\PropertyAccessors\TypedNoDefaultPropertyAccessor;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\ReflectionService;
use Doctrine\Persistence\Reflection\EnumReflectionProperty;
use InvalidArgumentException;
use LogicException;
use ReflectionClass;
Expand Down Expand Up @@ -532,9 +537,12 @@
/**
* The ReflectionProperty instances of the mapped class.
*
* @var array<string, ReflectionProperty|null>
* @var LegacyReflectionFields<string, ReflectionProperty>|array<string, ReflectionProperty>
*/
public array $reflFields = [];
public LegacyReflectionFields|array $reflFields = [];

Check failure on line 542 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (default)

TooManyTemplateParams

src/Mapping/ClassMetadata.php:542:5: TooManyTemplateParams: Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty> has too many template params, expecting 0 (see https://psalm.dev/184)

Check failure on line 542 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (default)

TooManyTemplateParams

src/Mapping/ClassMetadata.php:542:12: TooManyTemplateParams: Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty> has too many template params, expecting 0 (see https://psalm.dev/184)

Check failure on line 542 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (3.8.2)

TooManyTemplateParams

src/Mapping/ClassMetadata.php:542:5: TooManyTemplateParams: Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty> has too many template params, expecting 0 (see https://psalm.dev/184)

Check failure on line 542 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (3.8.2)

TooManyTemplateParams

src/Mapping/ClassMetadata.php:542:12: TooManyTemplateParams: Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty> has too many template params, expecting 0 (see https://psalm.dev/184)

/** @var array<string, PropertyAccessors\PropertyAccessor> */
public array $propertyAccessors = [];

private InstantiatorInterface|null $instantiator = null;

Expand All @@ -559,13 +567,23 @@
* Gets the ReflectionProperties of the mapped class.
*
* @return ReflectionProperty[]|null[] An array of ReflectionProperty instances.
* @psalm-return array<ReflectionProperty|null>

Check failure on line 570 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (default)

InvalidReturnType

src/Mapping/ClassMetadata.php:570:22: InvalidReturnType: The declared return type 'array<array-key, ReflectionProperty|null>' for Doctrine\ORM\Mapping\ClassMetadata::getReflectionProperties is incorrect, got 'Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty>|array<string, ReflectionProperty>' (see https://psalm.dev/011)

Check failure on line 570 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (3.8.2)

InvalidReturnType

src/Mapping/ClassMetadata.php:570:22: InvalidReturnType: The declared return type 'array<array-key, ReflectionProperty|null>' for Doctrine\ORM\Mapping\ClassMetadata::getReflectionProperties is incorrect, got 'Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty>|array<string, ReflectionProperty>' (see https://psalm.dev/011)
*/
public function getReflectionProperties(): array
{
return $this->reflFields;

Check failure on line 574 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (default)

InvalidReturnStatement

src/Mapping/ClassMetadata.php:574:16: InvalidReturnStatement: The inferred type 'Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty>|array<string, ReflectionProperty>' does not match the declared return type 'array<array-key, ReflectionProperty|null>' for Doctrine\ORM\Mapping\ClassMetadata::getReflectionProperties (see https://psalm.dev/128)

Check failure on line 574 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (3.8.2)

InvalidReturnStatement

src/Mapping/ClassMetadata.php:574:16: InvalidReturnStatement: The inferred type 'Doctrine\ORM\Mapping\LegacyReflectionFields<string, ReflectionProperty>|array<string, ReflectionProperty>' does not match the declared return type 'array<array-key, ReflectionProperty|null>' for Doctrine\ORM\Mapping\ClassMetadata::getReflectionProperties (see https://psalm.dev/128)
}

/**
* Gets the ReflectionProperties of the mapped class.
*
* @return PropertyAccessor[] An array of PropertyAccessor instances.
*/
public function getPropertyAccessors(): array
{
return $this->propertyAccessors;
}

/**
* Gets a ReflectionProperty for a specific field of the mapped class.
*/
Expand All @@ -574,9 +592,12 @@
return $this->reflFields[$name];
}

public function getPropertyAccessor(string $name): PropertyAccessor|null
{
return $this->propertyAccessors[$name] ?? null;
}

/**

Check failure on line 600 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / coding-standards / Coding Standards (8.3)

Found multi-line doc comment with single line content, use one-line doc comment instead.
* Gets the ReflectionProperty for the single identifier field.
*
* @throws BadMethodCallException If the class has a composite identifier.
*/
public function getSingleIdReflectionProperty(): ReflectionProperty|null
Expand All @@ -588,6 +609,18 @@
return $this->reflFields[$this->identifier[0]];
}

/**

Check failure on line 612 in src/Mapping/ClassMetadata.php

View workflow job for this annotation

GitHub Actions / coding-standards / Coding Standards (8.3)

Found multi-line doc comment with single line content, use one-line doc comment instead.
* @throws BadMethodCallException If the class has a composite identifier.
*/
public function getSingleIdPropertyAccessor(): PropertyAccessor|null

Check warning on line 615 in src/Mapping/ClassMetadata.php

View check run for this annotation

Codecov / codecov/patch

src/Mapping/ClassMetadata.php#L615

Added line #L615 was not covered by tests
{
if ($this->isIdentifierComposite) {
throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.');

Check warning on line 618 in src/Mapping/ClassMetadata.php

View check run for this annotation

Codecov / codecov/patch

src/Mapping/ClassMetadata.php#L617-L618

Added lines #L617 - L618 were not covered by tests
}

return $this->propertyAccessors[$this->identifier[0]];

Check warning on line 621 in src/Mapping/ClassMetadata.php

View check run for this annotation

Codecov / codecov/patch

src/Mapping/ClassMetadata.php#L621

Added line #L621 was not covered by tests
}

/**
* Extracts the identifier values of an entity of this class.
*
Expand All @@ -602,7 +635,7 @@
$id = [];

foreach ($this->identifier as $idField) {
$value = $this->reflFields[$idField]->getValue($entity);
$value = $this->propertyAccessors[$idField]->getValue($entity);

if ($value !== null) {
$id[$idField] = $value;
Expand All @@ -613,7 +646,7 @@
}

$id = $this->identifier[0];
$value = $this->reflFields[$id]->getValue($entity);
$value = $this->propertyAccessors[$id]->getValue($entity);

if ($value === null) {
return [];
Expand All @@ -632,7 +665,7 @@
public function setIdentifierValues(object $entity, array $id): void
{
foreach ($id as $idField => $idValue) {
$this->reflFields[$idField]->setValue($entity, $idValue);
$this->propertyAccessors[$idField]->setValue($entity, $idValue);
}
}

Expand All @@ -641,15 +674,15 @@
*/
public function setFieldValue(object $entity, string $field, mixed $value): void
{
$this->reflFields[$field]->setValue($entity, $value);
$this->propertyAccessors[$field]->setValue($entity, $value);
}

/**
* Gets the specified field's value off the given entity.
*/
public function getFieldValue(object $entity, string $field): mixed
{
return $this->reflFields[$field]->getValue($entity);
return $this->propertyAccessors[$field]->getValue($entity);
}

/**
Expand Down Expand Up @@ -783,76 +816,74 @@
{
// Restore ReflectionClass and properties
$this->reflClass = $reflService->getClass($this->name);
$this->reflFields = new LegacyReflectionFields($this, $reflService);
$this->instantiator = $this->instantiator ?: new Instantiator();

$parentReflFields = [];
$parentAccessors = [];

foreach ($this->embeddedClasses as $property => $embeddedClass) {
if (isset($embeddedClass->declaredField)) {
assert($embeddedClass->originalField !== null);
$childProperty = $this->getAccessibleProperty(
$reflService,
$childAccessor = $this->createPropertyAccessor(
$this->embeddedClasses[$embeddedClass->declaredField]->class,
$embeddedClass->originalField,
);
assert($childProperty !== null);
$parentReflFields[$property] = new ReflectionEmbeddedProperty(
$parentReflFields[$embeddedClass->declaredField],
$childProperty,

$parentAccessors[$property] = new EmbeddablePropertyAccessor(
$parentAccessors[$embeddedClass->declaredField],
$childAccessor,
$this->embeddedClasses[$embeddedClass->declaredField]->class,
);

continue;
}

$fieldRefl = $this->getAccessibleProperty(
$reflService,
$accessor = $this->createPropertyAccessor(
$embeddedClass->declared ?? $this->name,
$property,
);

$parentReflFields[$property] = $fieldRefl;
$this->reflFields[$property] = $fieldRefl;
$parentAccessors[$property] = $accessor;
$this->propertyAccessors[$property] = $accessor;
}

foreach ($this->fieldMappings as $field => $mapping) {
if (isset($mapping->declaredField) && isset($parentReflFields[$mapping->declaredField])) {
if (isset($mapping->declaredField) && isset($parentAccessors[$mapping->declaredField])) {
assert($mapping->originalField !== null);
assert($mapping->originalClass !== null);
$childProperty = $this->getAccessibleProperty($reflService, $mapping->originalClass, $mapping->originalField);
assert($childProperty !== null);
$accessor = $this->createPropertyAccessor($mapping->originalClass, $mapping->originalField);

if (isset($mapping->enumType)) {
$childProperty = new EnumReflectionProperty(
$childProperty,
if ($mapping->enumType !== null) {
$accessor = new EnumPropertyAccessor(
$accessor,
$mapping->enumType,
);
}

$this->reflFields[$field] = new ReflectionEmbeddedProperty(
$parentReflFields[$mapping->declaredField],
$childProperty,
$this->propertyAccessors[$field] = new EmbeddablePropertyAccessor(
$parentAccessors[$mapping->declaredField],
$accessor,
$mapping->originalClass,
);
continue;
}

$this->reflFields[$field] = isset($mapping->declared)
? $this->getAccessibleProperty($reflService, $mapping->declared, $field)
: $this->getAccessibleProperty($reflService, $this->name, $field);
$this->propertyAccessors[$field] = isset($mapping->declared)
? $this->createPropertyAccessor($mapping->declared, $field)
: $this->createPropertyAccessor($this->name, $field);

if (isset($mapping->enumType) && $this->reflFields[$field] !== null) {
$this->reflFields[$field] = new EnumReflectionProperty(
$this->reflFields[$field],
if ($mapping->enumType !== null) {
$this->propertyAccessors[$field] = new EnumPropertyAccessor(
$this->propertyAccessors[$field],
$mapping->enumType,
);
}
}

foreach ($this->associationMappings as $field => $mapping) {
$this->reflFields[$field] = isset($mapping->declared)
? $this->getAccessibleProperty($reflService, $mapping->declared, $field)
: $this->getAccessibleProperty($reflService, $this->name, $field);
$this->propertyAccessors[$field] = isset($mapping->declared)
? $this->createPropertyAccessor($mapping->declared, $field)
: $this->createPropertyAccessor($this->name, $field);
}
}

Expand Down Expand Up @@ -2633,21 +2664,20 @@
return $sequencePrefix;
}

/** @psalm-param class-string $class */
private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ReflectionProperty|null
/** @psalm-param class-string $className */
private function createPropertyAccessor(string $className, string $propertyName): PropertyAccessor
{
$reflectionProperty = $reflService->getAccessibleProperty($class, $field);
if ($reflectionProperty?->isReadOnly()) {
$declaringClass = $reflectionProperty->class;
if ($declaringClass !== $class) {
$reflectionProperty = $reflService->getAccessibleProperty($declaringClass, $field);
}
$reflectionProperty = new ReflectionProperty($className, $propertyName);
$accessor = ObjectCastPropertyAccessor::fromReflectionProperty($reflectionProperty);

if ($reflectionProperty !== null) {
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
}
if ($reflectionProperty->hasType() && ! $reflectionProperty->getType()->allowsNull()) {
$accessor = new TypedNoDefaultPropertyAccessor($accessor, $reflectionProperty);
}

if ($reflectionProperty->isReadOnly()) {
$accessor = new ReadonlyAccessor($accessor, $reflectionProperty);
}

return $reflectionProperty;
return $accessor;
}
}
4 changes: 2 additions & 2 deletions src/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,8 @@ private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $pare
$subClass->addInheritedFieldMapping($subClassMapping);
}

foreach ($parentClass->reflFields as $name => $field) {
$subClass->reflFields[$name] = $field;
foreach ($parentClass->propertyAccessors as $name => $field) {
$subClass->propertyAccessors[$name] = $field;
}
}

Expand Down
Loading
Loading