-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make Annotations/Attribute mapping drivers report fields for the clas…
…ses where they are declared This PR will make the annotations and attribute mapping drivers report mapping configuration for the classes where it is declared, instead of omitting it and reporting it for subclasses only. This is necessary to be able to catch mis-configurations in `ClassMetadataFactory`. Fixes #10417, closes #10450, closes #10454. ####⚠️ Summary for users getting `MappingExceptions` with the new mode When you set the `$reportFieldsWhereDeclared` constructor parameters to `true` for the AnnotationDriver and/or AttributesDriver and get `MappingExceptions`, you may be doing one of the following: * Using `private` fields with the same name in different classes of an entity inheritance hierarchy (see #10450) * Redeclaring/overwriting mapped properties inherited from mapped superclasses and/or other entities (see #10454) As explained in these two PRs, the ORM cannot (or at least, was not designed to) support such configurations. Unfortunately, due to the old – now deprecated – driver behaviour, the misconfigurations could not be detected, and due to previously missing tests, this in turn was not noticed. #### Current situation The annotations mapping driver has the following condition to skip properties that are reported by the PHP reflection API: https://github.com/doctrine/orm/blob/69c7791ba256d947ddb1aafe5f2439ab31704937/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php#L345-L357 This code has been there basically unchanged since the initial 2.0 release. The same condition can be found in the attribute driver, probably it has been copied when attributes were added. I _think_ what the driver tries to do here is to deal with the fact that Reflection will also report `public`/`protected` properties inherited from parent classes. This is supported by the observation (see #5744) that e. g. YAML and XML drivers do not contain this logic. The conditions are not precise enough for edge cases. They lead to some fields and configuration values not even being reported by the driver. Only since the fields would be "discovered" again when reflecting on subclasses, they eventually end up in class metadata structures for the subclasses. In one case of inherited ID generator mappings, the `ClassMetadataFactory` would also rely on this behaviour. Two potential bugs that can result from this are demonstrated in #10450 and #10454. #### Suggested solution In order to find a more reliable way of separating properties that are merely reported again in subclasses from those that are actual re-declarations, use the information already available in `ClassMetadata`. In particular, `declared` tells us in which non-transient class a "field" was first seen. Make the mapping driver skip only those properties for which we already know that they have been declared in parent classes, and skip them only when the observed declaring class matches the expectation. For all other properties, report them to `ClassMetadataFactory` and let that deal with consistency checking/error handling. #10450 and #10454 are merged into this PR to show that they pass now. #### Soft deprecation strategy To avoid throwing new/surprising exceptions (even for misconfigurations) during a minor version upgrade, the new driver mode is opt-in. Users will have to set the `$reportFieldsWhereDeclared` constructor parameters to `true` for the `AnnotationDriver` and/or `AttributesDriver`. Unless they do so, a deprecation warning will be raised. In 3.0, the "new" mode will become the default. The constructor parameter can be deprecated (as of ORM 3.1, probably) and is a no-op. We need to follow up in other places (DoctrineBundle, ... – what else?) to make this driver parameter an easy-to-change configuration setting.
- Loading branch information
Showing
21 changed files
with
413 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM\Mapping\Driver; | ||
|
||
use Doctrine\ORM\Mapping\ClassMetadata; | ||
use ReflectionProperty; | ||
|
||
/** @internal */ | ||
trait ReflectionBasedDriver | ||
{ | ||
/** @var bool */ | ||
private $reportFieldsWhereDeclared = false; | ||
|
||
/** | ||
* Helps to deal with the case that reflection may report properties inherited from parent classes. | ||
* When we know about the fields already (inheritance has been anticipated in ClassMetadataFactory), | ||
* the driver must skip them. | ||
* | ||
* The declaring classes may mismatch when there are private properties: The same property name may be | ||
* reported multiple times, but since it is private, it is in fact multiple (different) properties in | ||
* different classes. In that case, report the property as an individual field. (ClassMetadataFactory will | ||
* probably fail in that case, though.) | ||
*/ | ||
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool | ||
{ | ||
if (! $this->reportFieldsWhereDeclared) { | ||
return $metadata->isMappedSuperclass && ! $property->isPrivate() | ||
|| $metadata->isInheritedField($property->name) | ||
|| $metadata->isInheritedAssociation($property->name) | ||
|| $metadata->isInheritedEmbeddedClass($property->name); | ||
} | ||
|
||
$declaringClass = $property->getDeclaringClass()->getName(); | ||
|
||
if ( | ||
isset($metadata->fieldMappings[$property->name]['declared']) | ||
&& $metadata->fieldMappings[$property->name]['declared'] === $declaringClass | ||
) { | ||
return true; | ||
} | ||
|
||
if ( | ||
isset($metadata->associationMappings[$property->name]['declared']) | ||
&& $metadata->associationMappings[$property->name]['declared'] === $declaringClass | ||
) { | ||
return true; | ||
} | ||
|
||
return isset($metadata->embeddedClasses[$property->name]['declared']) | ||
&& $metadata->embeddedClasses[$property->name]['declared'] === $declaringClass; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.