From 6d306c1946be6faf9ef17065ce18ccf4e591d405 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 18 Dec 2021 11:03:12 +0100 Subject: [PATCH] Support for nesting attributes with PHP 8.1 (#9241) * [GH-9240] Refactor Association/AttributeOverrides to use @NamedConstructorArguments with AnnotationDriver. * [GH-9240] Add support for PHP 8.1 nested attributes. Supported/new attributes are #[AttributeOverrides], #[AssociationOverrides], #[JoinTable] with nested joinColumns, inverseJoinColumns. * [GH-9240] Add support for nesting Index, UniqueCosntraint into #[Table] on PHP 8.1 * Apply review comments by gregooire. * Add documentation for new attributes. * Add docs for new nested #[JoinTable] support of join columns * Add docs for new nested #[Table] support of index, uniqueConstraints * Rename "Required/Optional atttributes" to "Required/Optional parameters" * Remove nesting for JoinTable#joinColumns and Table#indexes/uniqueConstraints again. * Hosuekeeping: phpcs/psalm * housekeeping * Remove unused function imports. --- docs/en/reference/attributes-reference.rst | 148 ++++++++++++++---- .../ORM/Mapping/AssociationOverride.php | 44 +++++- .../ORM/Mapping/AssociationOverrides.php | 26 ++- .../ORM/Mapping/AttributeOverride.php | 8 +- .../ORM/Mapping/AttributeOverrides.php | 26 ++- .../ORM/Mapping/Driver/AnnotationDriver.php | 4 +- .../ORM/Mapping/Driver/AttributeDriver.php | 68 +++++++- lib/Doctrine/ORM/Mapping/MappingException.php | 13 ++ .../Tests/Models/DDC3579/DDC3579Admin.php | 2 + .../Tests/Models/DDC3579/DDC3579Group.php | 4 + .../Tests/Models/DDC3579/DDC3579User.php | 4 + .../Models/DDC5934/DDC5934BaseContract.php | 3 + .../Tests/Models/DDC5934/DDC5934Contract.php | 2 + .../Tests/Models/DDC5934/DDC5934Member.php | 2 + .../Tests/Models/DDC964/DDC964Admin.php | 2 + .../Tests/Models/DDC964/DDC964Guest.php | 2 + .../Tests/Models/DDC964/DDC964User.php | 10 ++ .../ORM/Functional/Ticket/DDC3711Test.php | 29 ---- .../ORM/Mapping/AbstractMappingDriverTest.php | 16 ++ .../Tests/ORM/Mapping/AttributeDriverTest.php | 24 ++- .../Tests/ORM/Mapping/ClassMetadataTest.php | 13 +- .../ORM/Mapping/YamlMappingDriverTest.php | 17 ++ 22 files changed, 391 insertions(+), 76 deletions(-) delete mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index 4644889d6c7..89b1f8cad33 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -10,6 +10,8 @@ annotation metadata supported since the first version 2.0. Index ----- +- :ref:`#[AssociationOverride] ` - :ref:`#[Cache] ` - :ref:`#[ChangeTrackingPolicy ` @@ -49,6 +51,93 @@ Index Reference --------- +.. _attrref_associationoverride: + +#[AssociationOverride] +~~~~~~~~~~~~~~~~~~~~~~ + +In an inheritance hierarchy this attribute allows to override the +assocation mapping definitions of the parent mappings. It needs to be nested +within a ``#[AssociationOverrides]`` on the class level. + +Required parameters: + +- **name**: Name of the association mapping to overwrite. + +Optional parameters: + +- **joinColumns**: A list of nested ``#[JoinColumn]`` declarations. +- **joinTable**: A nested ``#[JoinTable]`` declaration in case of a many-to-many overwrite. +- **inversedBy**: The name of the inversedBy field on the target entity side. +- **fetch**: The fetch strategy, one of: EAGER, LAZY, EXTRA_LAZY. + +Examples: + +.. code-block:: php + + ` and :ref:`#[GeneratedValue(strategy: "CUSTOM")] ` are specified. -Required attributes: +Required parameters: - **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator @@ -244,13 +333,13 @@ actually instantiated as. If this attribute is not specified, the discriminator column defaults to a string column of length 255 called ``dtype``. -Required attributes: +Required parameters: - **name**: The column name of the discriminator. This name is also used during Array hydration as key to specify the class-name. -Optional attributes: +Optional parameters: - **type**: By default this is string. @@ -319,7 +408,7 @@ attribute to establish the relationship between the two classes. The embedded attribute is required on an entity's member variable, in order to specify that it is an embedded class. -Required attributes: +Required parameters: - **class**: The embeddable class @@ -331,7 +420,7 @@ Required attributes: Required attribute to mark a PHP class as an entity. Doctrine manages the persistence of all classes marked as entities. -Optional attributes: +Optional parameters: - **repositoryClass**: Specifies the FQCN of a subclass of the ``EntityRepository``. Use of repositories for entities is encouraged to keep @@ -368,7 +457,7 @@ conjunction with #[Id]. If this attribute is not specified with ``#[Id]`` the ``NONE`` strategy is used as default. -Optional attributes: +Optional parameters: - **strategy**: Set the name of the identifier generation strategy. Valid values are ``AUTO``, ``SEQUENCE``, ``IDENTITY``, ``UUID`` @@ -424,14 +513,14 @@ Attribute is used on the entity-class level. It provides a hint to the SchemaToo generate a database index on the specified table columns. It only has meaning in the ``SchemaTool`` schema generation context. -Required attributes: +Required parameters: - **name**: Name of the Index - **fields**: Array of fields. Exactly one of **fields, columns** is required. - **columns**: Array of columns. Exactly one of **fields, columns** is required. -Optional attributes: +Optional parameters: - **options**: Array of platform specific options: @@ -546,9 +635,10 @@ are missing they will be computed considering the field's name and the current The ``#[InverseJoinColumn]`` is the same as ``#[JoinColumn]`` and is used in the context of a ``#[ManyToMany]`` attribute declaration to specifiy the details of the join table's -column information used for the join to the inverse entity. +column information used for the join to the inverse entity. This is only required +on PHP 8.0, where nested attributes are not yet supported. -Optional attributes: +Optional parameters: - **name**: Column name that holds the foreign key identifier for this relation. In the context of ``#[JoinTable]`` it specifies the column @@ -596,7 +686,7 @@ details of the database join table. If you do not specify using the affected table and the column names. A notable difference to the annotation metadata support, ``#[JoinColumn]`` -and ``#[InverseJoinColumn]`` are specified at the property level and are not +and ``#[InverseJoinColumn]`` can be specified at the property level and are not nested within the ``#[JoinTable]`` attribute. Required attribute: @@ -623,14 +713,14 @@ Example: Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities. -Required attributes: +Required parameters: - **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! -Optional attributes: +Optional parameters: - **cascade**: Cascade Option @@ -659,14 +749,14 @@ additional, optional attribute that has reasonable default configuration values using the table and names of the two related entities. -Required attributes: +Required parameters: - **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! -Optional attributes: +Optional parameters: - **mappedBy**: This option specifies the property name on the @@ -720,7 +810,7 @@ The ``#[MappedSuperclass]`` attribute cannot be used in conjunction with ``#[Entity]``. See the Inheritance Mapping section for :doc:`more details on the restrictions of mapped superclasses `. -Optional attributes: +Optional parameters: - **repositoryClass**: Specifies the FQCN of a subclass of the EntityRepository. That will be inherited for all subclasses of that Mapped Superclass. @@ -756,13 +846,13 @@ be specified. When no :ref:`#[JoinColumn] ` is specified it defaults to using the target entity table and primary key column names and the current naming strategy to determine a name for the join column. -Required attributes: +Required parameters: - **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! -Optional attributes: +Optional parameters: - **cascade**: Cascade Option - **fetch**: One of LAZY or EAGER @@ -786,13 +876,13 @@ Example: #[OneToMany] ~~~~~~~~~~~~ -Required attributes: +Required parameters: - **targetEntity**: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash! -Optional attributes: +Optional parameters: - **cascade**: Cascade Option - **orphanRemoval**: Boolean that specifies if orphans, inverse @@ -916,11 +1006,11 @@ For use with ``#[GeneratedValue(strategy: "SEQUENCE")]`` this attribute allows to specify details about the sequence, such as the increment size and initial values of the sequence. -Required attributes: +Required parameters: - **sequenceName**: Name of the sequence -Optional attributes: +Optional parameters: - **allocationSize**: Increment the sequence by the allocation size when its fetched. A value larger than 1 allows optimization for @@ -954,11 +1044,11 @@ placed on the entity-class level and is optional. If it is not specified the table name will default to the entity's unqualified classname. -Required attributes: +Required parameters: - **name**: Name of the table -Optional attributes: +Optional parameters: - **schema**: Name of the schema the table lies in. @@ -985,12 +1075,12 @@ generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool schema generation context. -Required attributes: +Required parameters: - **name**: Name of the Index - **columns**: Array of columns. -Optional attributes: +Optional parameters: - **options**: Array of platform specific options: diff --git a/lib/Doctrine/ORM/Mapping/AssociationOverride.php b/lib/Doctrine/ORM/Mapping/AssociationOverride.php index a541d7ebd78..68dedb6b5ae 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationOverride.php +++ b/lib/Doctrine/ORM/Mapping/AssociationOverride.php @@ -8,6 +8,7 @@ * This annotation is used to override association mapping of property for an entity relationship. * * @Annotation + * @NamedArgumentConstructor * @Target("ANNOTATION") */ final class AssociationOverride implements Annotation @@ -22,29 +23,64 @@ final class AssociationOverride implements Annotation /** * The join column that is being mapped to the persistent attribute. * - * @var array<\Doctrine\ORM\Mapping\JoinColumn> + * @var array<\Doctrine\ORM\Mapping\JoinColumn>|null */ public $joinColumns; + /** + * The join column that is being mapped to the persistent attribute. + * + * @var array<\Doctrine\ORM\Mapping\JoinColumn>|null + */ + public $inverseJoinColumns; + /** * The join table that maps the relationship. * - * @var \Doctrine\ORM\Mapping\JoinTable + * @var \Doctrine\ORM\Mapping\JoinTable|null */ public $joinTable; /** * The name of the association-field on the inverse-side. * - * @var string + * @var ?string */ public $inversedBy; /** * The fetching strategy to use for the association. * - * @var string + * @var ?string * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch; + + /** + * @param JoinColumn|array $joinColumns + * @param JoinColumn|array $inverseJoinColumns + */ + public function __construct( + string $name, + $joinColumns = null, + $inverseJoinColumns = null, + ?JoinTable $joinTable = null, + ?string $inversedBy = null, + ?string $fetch = null + ) { + if ($joinColumns instanceof JoinColumn) { + $joinColumns = [$joinColumns]; + } + + if ($inverseJoinColumns instanceof JoinColumn) { + $inverseJoinColumns = [$inverseJoinColumns]; + } + + $this->name = $name; + $this->joinColumns = $joinColumns; + $this->inverseJoinColumns = $inverseJoinColumns; + $this->joinTable = $joinTable; + $this->inversedBy = $inversedBy; + $this->fetch = $fetch; + } } diff --git a/lib/Doctrine/ORM/Mapping/AssociationOverrides.php b/lib/Doctrine/ORM/Mapping/AssociationOverrides.php index 9f638c2205f..9105a375990 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationOverrides.php +++ b/lib/Doctrine/ORM/Mapping/AssociationOverrides.php @@ -4,12 +4,18 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + +use function is_array; + /** * This annotation is used to override association mappings of relationship properties. * * @Annotation + * @NamedArgumentConstructor() * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class AssociationOverrides implements Annotation { /** @@ -17,5 +23,23 @@ final class AssociationOverrides implements Annotation * * @var array<\Doctrine\ORM\Mapping\AssociationOverride> */ - public $value; + public $overrides = []; + + /** + * @param array|AssociationOverride $overrides + */ + public function __construct($overrides) + { + if (! is_array($overrides)) { + $overrides = [$overrides]; + } + + foreach ($overrides as $override) { + if (! ($override instanceof AssociationOverride)) { + throw MappingException::invalidOverrideType('AssociationOverride', $override); + } + + $this->overrides[] = $override; + } + } } diff --git a/lib/Doctrine/ORM/Mapping/AttributeOverride.php b/lib/Doctrine/ORM/Mapping/AttributeOverride.php index 056ba602f7f..5480cfbee13 100644 --- a/lib/Doctrine/ORM/Mapping/AttributeOverride.php +++ b/lib/Doctrine/ORM/Mapping/AttributeOverride.php @@ -10,9 +10,9 @@ * This annotation is used to override the mapping of a entity property. * * @Annotation + * @NamedArgumentConstructor * @Target("ANNOTATION") */ -#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] final class AttributeOverride implements Annotation { /** @@ -28,4 +28,10 @@ final class AttributeOverride implements Annotation * @var \Doctrine\ORM\Mapping\Column */ public $column; + + public function __construct(string $name, Column $column) + { + $this->name = $name; + $this->column = $column; + } } diff --git a/lib/Doctrine/ORM/Mapping/AttributeOverrides.php b/lib/Doctrine/ORM/Mapping/AttributeOverrides.php index c7d8371e5dc..272cc459e8d 100644 --- a/lib/Doctrine/ORM/Mapping/AttributeOverrides.php +++ b/lib/Doctrine/ORM/Mapping/AttributeOverrides.php @@ -4,12 +4,18 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + +use function is_array; + /** * This annotation is used to override the mapping of a entity property. * * @Annotation + * @NamedArgumentConstructor() * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class AttributeOverrides implements Annotation { /** @@ -17,5 +23,23 @@ final class AttributeOverrides implements Annotation * * @var array<\Doctrine\ORM\Mapping\AttributeOverride> */ - public $value; + public $overrides = []; + + /** + * @param array|AttributeOverride $overrides + */ + public function __construct($overrides) + { + if (! is_array($overrides)) { + $overrides = [$overrides]; + } + + foreach ($overrides as $override) { + if (! ($override instanceof AttributeOverride)) { + throw MappingException::invalidOverrideType('AttributeOverride', $override); + } + + $this->overrides[] = $override; + } + } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 086a3baf879..2ddd85027ef 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -411,7 +411,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) if (isset($classAnnotations[Mapping\AssociationOverrides::class])) { $associationOverridesAnnot = $classAnnotations[Mapping\AssociationOverrides::class]; - foreach ($associationOverridesAnnot->value as $associationOverride) { + foreach ($associationOverridesAnnot->overrides as $associationOverride) { $override = []; $fieldName = $associationOverride->name; @@ -463,7 +463,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) if (isset($classAnnotations[Mapping\AttributeOverrides::class])) { $attributeOverridesAnnot = $classAnnotations[Mapping\AttributeOverrides::class]; - foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) { + foreach ($attributeOverridesAnnot->overrides as $attributeOverrideAnnot) { $attributeOverride = $this->columnToArray($attributeOverrideAnnot->name, $attributeOverrideAnnot->column); $metadata->setAttributeOverride($attributeOverrideAnnot->name, $attributeOverride); diff --git a/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php index 0568d04644a..09bde9f9977 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php @@ -397,12 +397,72 @@ public function loadMetadataForClass($className, ClassMetadata $metadata): void } } + // Evaluate AssociationOverrides attribute + if (isset($classAttributes[Mapping\AssociationOverrides::class])) { + $associationOverride = $classAttributes[Mapping\AssociationOverrides::class]; + + foreach ($associationOverride->overrides as $associationOverride) { + $override = []; + $fieldName = $associationOverride->name; + + // Check for JoinColumn/JoinColumns attributes + if ($associationOverride->joinColumns) { + $joinColumns = []; + + foreach ($associationOverride->joinColumns as $joinColumn) { + $joinColumns[] = $this->joinColumnToArray($joinColumn); + } + + $override['joinColumns'] = $joinColumns; + } + + if ($associationOverride->inverseJoinColumns) { + $joinColumns = []; + + foreach ($associationOverride->inverseJoinColumns as $joinColumn) { + $joinColumns[] = $this->joinColumnToArray($joinColumn); + } + + $override['inverseJoinColumns'] = $joinColumns; + } + + // Check for JoinTable attributes + if ($associationOverride->joinTable) { + $joinTableAnnot = $associationOverride->joinTable; + $joinTable = [ + 'name' => $joinTableAnnot->name, + 'schema' => $joinTableAnnot->schema, + 'joinColumns' => $override['joinColumns'] ?? [], + 'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [], + ]; + + unset($override['joinColumns'], $override['inverseJoinColumns']); + + $override['joinTable'] = $joinTable; + } + + // Check for inversedBy + if ($associationOverride->inversedBy) { + $override['inversedBy'] = $associationOverride->inversedBy; + } + + // Check for `fetch` + if ($associationOverride->fetch) { + $override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch); + } + + $metadata->setAssociationOverride($fieldName, $override); + } + } + // Evaluate AttributeOverrides annotation - if (isset($classAttributes[Mapping\AttributeOverride::class])) { - foreach ($classAttributes[Mapping\AttributeOverride::class] as $attributeOverrideAttribute) { - $attributeOverride = $this->columnToArray($attributeOverrideAttribute->name, $attributeOverrideAttribute->column); + if (isset($classAttributes[Mapping\AttributeOverrides::class])) { + $attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class]; + + foreach ($attributeOverridesAnnot->overrides as $attributeOverride) { + $mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column); - $metadata->setAttributeOverride($attributeOverrideAttribute->name, $attributeOverride); + $metadata->setAttributeOverride($attributeOverride->name, $mapping); } } diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index bec92116851..11a8cbf0af4 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -11,6 +11,7 @@ use function array_keys; use function array_map; use function array_values; +use function get_debug_type; use function get_parent_class; use function implode; use function sprintf; @@ -941,4 +942,16 @@ public static function invalidUniqueConstraintConfiguration($className, $indexNa ) ); } + + /** + * @param mixed $givenValue + */ + public static function invalidOverrideType(string $expectdType, $givenValue): self + { + return new self(sprintf( + 'Expected %s, but %s was given.', + $expectdType, + get_debug_type($givenValue) + )); + } } diff --git a/tests/Doctrine/Tests/Models/DDC3579/DDC3579Admin.php b/tests/Doctrine/Tests/Models/DDC3579/DDC3579Admin.php index 6f900d1884a..cc0d1c75d6b 100644 --- a/tests/Doctrine/Tests/Models/DDC3579/DDC3579Admin.php +++ b/tests/Doctrine/Tests/Models/DDC3579/DDC3579Admin.php @@ -17,6 +17,8 @@ * ) * }) */ +#[Entity] +#[AssociationOverrides([new AssociationOverride(name: 'groups', inversedBy: 'admins')])] class DDC3579Admin extends DDC3579User { public static function loadMetadata($metadata): void diff --git a/tests/Doctrine/Tests/Models/DDC3579/DDC3579Group.php b/tests/Doctrine/Tests/Models/DDC3579/DDC3579Group.php index 7b0cd846934..5451c96902c 100644 --- a/tests/Doctrine/Tests/Models/DDC3579/DDC3579Group.php +++ b/tests/Doctrine/Tests/Models/DDC3579/DDC3579Group.php @@ -15,6 +15,7 @@ /** * @Entity */ +#[Entity] class DDC3579Group { /** @@ -22,18 +23,21 @@ class DDC3579Group * @GeneratedValue * @Id @Column(type="integer") */ + #[Id, GeneratedValue, Column(type: 'integer')] private $id; /** * @var string|null * @Column */ + #[Column] private $name; /** * @psalm-var Collection * @ManyToMany(targetEntity="DDC3579Admin", mappedBy="groups") */ + #[ManyToMany(targetEntity: DDC3579Admin::class, mappedBy: 'groups')] private $admins; public function __construct(?string $name = null) diff --git a/tests/Doctrine/Tests/Models/DDC3579/DDC3579User.php b/tests/Doctrine/Tests/Models/DDC3579/DDC3579User.php index 7aa2301c0a8..a5daba7e68c 100644 --- a/tests/Doctrine/Tests/Models/DDC3579/DDC3579User.php +++ b/tests/Doctrine/Tests/Models/DDC3579/DDC3579User.php @@ -15,6 +15,7 @@ /** * @MappedSuperclass */ +#[MappedSuperclass] class DDC3579User { /** @@ -23,18 +24,21 @@ class DDC3579User * @GeneratedValue * @Column(type="integer", name="user_id", length=150) */ + #[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)] protected $id; /** * @var string * @Column(name="user_name", nullable=true, unique=false, length=250) */ + #[Column(name: 'user_name', nullable: true, unique: false, length: 250)] protected $name; /** * @var ArrayCollection * @ManyToMany(targetEntity="DDC3579Group") */ + #[ManyToMany(targetEntity: DDC3579Group::class)] protected $groups; public function __construct(?string $name = null) diff --git a/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php b/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php index 4a1aef97bf7..285a4d5919f 100644 --- a/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php +++ b/tests/Doctrine/Tests/Models/DDC5934/DDC5934BaseContract.php @@ -16,6 +16,7 @@ /** * @Entity */ +#[Entity] class DDC5934BaseContract { /** @@ -24,12 +25,14 @@ class DDC5934BaseContract * @Column(name="id", type="integer") * @GeneratedValue() */ + #[Id, Column, GeneratedValue] public $id; /** * @psalm-var Collection * @ManyToMany(targetEntity="DDC5934Member", fetch="LAZY", inversedBy="contracts") */ + #[ManyToMany(targetEntity: DDC5934Member::class, fetch: 'LAZY', inversedBy: 'contracts')] public $members; public function __construct() diff --git a/tests/Doctrine/Tests/Models/DDC5934/DDC5934Contract.php b/tests/Doctrine/Tests/Models/DDC5934/DDC5934Contract.php index 88de1db620b..ab064c23127 100644 --- a/tests/Doctrine/Tests/Models/DDC5934/DDC5934Contract.php +++ b/tests/Doctrine/Tests/Models/DDC5934/DDC5934Contract.php @@ -15,6 +15,8 @@ * @AssociationOverride(name="members", fetch="EXTRA_LAZY") * ) */ +#[Entity] +#[AssociationOverrides([new AssociationOverride(name: 'members', fetch: 'EXTRA_LAZY')])] class DDC5934Contract extends DDC5934BaseContract { public static function loadMetadata(ClassMetadata $metadata): void diff --git a/tests/Doctrine/Tests/Models/DDC5934/DDC5934Member.php b/tests/Doctrine/Tests/Models/DDC5934/DDC5934Member.php index 5b1dbb6ffea..e12ff0de9d1 100644 --- a/tests/Doctrine/Tests/Models/DDC5934/DDC5934Member.php +++ b/tests/Doctrine/Tests/Models/DDC5934/DDC5934Member.php @@ -10,6 +10,7 @@ /** * @ORM\Entity() */ +#[ORM\Entity] class DDC5934Member { /** @@ -17,6 +18,7 @@ class DDC5934Member * * @var ArrayCollection */ + #[ORM\ManyToMany(targetEntity: DDC5934BaseContract::class, mappedBy: 'members')] public $contracts; public function __construct() diff --git a/tests/Doctrine/Tests/Models/DDC964/DDC964Admin.php b/tests/Doctrine/Tests/Models/DDC964/DDC964Admin.php index f201bb4a4f3..71bd1e74fbb 100644 --- a/tests/Doctrine/Tests/Models/DDC964/DDC964Admin.php +++ b/tests/Doctrine/Tests/Models/DDC964/DDC964Admin.php @@ -28,6 +28,8 @@ * ) * }) */ +#[Entity] +#[AssociationOverrides([new AssociationOverride(name: 'groups', joinTable: new JoinTable(name: 'ddc964_users_admingroups'), joinColumns: [new JoinColumn(name: 'adminuser_id')], inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')]), new AssociationOverride(name: 'address', joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')])])] class DDC964Admin extends DDC964User { public static function loadMetadata(ClassMetadataInfo $metadata): void diff --git a/tests/Doctrine/Tests/Models/DDC964/DDC964Guest.php b/tests/Doctrine/Tests/Models/DDC964/DDC964Guest.php index 181dea1effc..afe8a6c47ec 100644 --- a/tests/Doctrine/Tests/Models/DDC964/DDC964Guest.php +++ b/tests/Doctrine/Tests/Models/DDC964/DDC964Guest.php @@ -30,6 +30,8 @@ * ) * }) */ +#[Entity] +#[AttributeOverrides([new AttributeOverride(name: 'id', column: new Column(name: 'guest_id', type: 'integer', length: 140)), new AttributeOverride(name: 'name', column: new Column(name: 'guest_name', nullable: false, unique: true, length: 240))])] class DDC964Guest extends DDC964User { public static function loadMetadata(ClassMetadataInfo $metadata): void diff --git a/tests/Doctrine/Tests/Models/DDC964/DDC964User.php b/tests/Doctrine/Tests/Models/DDC964/DDC964User.php index dc3f345e7ed..627abb1aa10 100644 --- a/tests/Doctrine/Tests/Models/DDC964/DDC964User.php +++ b/tests/Doctrine/Tests/Models/DDC964/DDC964User.php @@ -10,6 +10,7 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\InverseJoinColumn; use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Mapping\JoinTable; use Doctrine\ORM\Mapping\ManyToMany; @@ -20,6 +21,7 @@ /** * @MappedSuperclass */ +#[MappedSuperclass] class DDC964User { /** @@ -28,12 +30,14 @@ class DDC964User * @GeneratedValue * @Column(type="integer", name="user_id", length=150) */ + #[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)] protected $id; /** * @var string|null * @Column(name="user_name", nullable=true, unique=false, length=250) */ + #[Column(name: 'user_name', nullable: true, unique: false, length: 250)] protected $name; /** @@ -44,6 +48,10 @@ class DDC964User * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} * ) */ + #[ManyToMany(targetEntity: DDC964Group::class, inversedBy: 'users', cascade: ['persist', 'merge', 'detach'])] + #[JoinTable(name: 'ddc964_users_groups')] + #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] + #[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')] protected $groups; /** @@ -51,6 +59,8 @@ class DDC964User * @ManyToOne(targetEntity="DDC964Address", cascade={"persist", "merge"}) * @JoinColumn(name="address_id", referencedColumnName="id") */ + #[ManyToOne(targetEntity: DDC964Address::class, cascade: ['persist', 'merge'])] + #[JoinColumn(name: 'address_id', referencedColumnName: 'id')] protected $address; public function __construct(?string $name = null) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php deleted file mode 100644 index 599e2162c71..00000000000 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php +++ /dev/null @@ -1,29 +0,0 @@ -loadDriver(); - - $em = $this->getTestEntityManager(); - $em->getConfiguration()->setMetadataDriverImpl($yamlDriver); - $factory = new ClassMetadataFactory(); - $factory->setEntityManager($em); - - $entityA = new ClassMetadata(DDC3711EntityA::class); - $entityA = $factory->getMetadataFor(DDC3711EntityA::class); - - self::assertEquals(['link_a_id1' => 'id1', 'link_a_id2' => 'id2'], $entityA->associationMappings['entityB']['relationToSourceKeyColumns']); - self::assertEquals(['link_b_id1' => 'id1', 'link_b_id2' => 'id2'], $entityA->associationMappings['entityB']['relationToTargetKeyColumns']); - } -} diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 0e79aad7c30..cf0203374e0 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -762,6 +762,10 @@ public function testSqlResultSetMapping(): void */ public function testAssociationOverridesMapping(): void { + if (PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Does not work on PHP 8.0.* due to nested attributes missing.'); + } + $factory = $this->createClassMetadataFactory(); $adminMetadata = $factory->getMetadataFor(DDC964Admin::class); $guestMetadata = $factory->getMetadataFor(DDC964Guest::class); @@ -840,6 +844,10 @@ public function testAssociationOverridesMapping(): void */ public function testInversedByOverrideMapping(): void { + if (PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Does not work on PHP 8.0.* due to nested attributes missing.'); + } + $factory = $this->createClassMetadataFactory(); $adminMetadata = $factory->getMetadataFor(DDC3579Admin::class); @@ -856,6 +864,10 @@ public function testInversedByOverrideMapping(): void */ public function testFetchOverrideMapping(): void { + if (PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Does not work on PHP 8.0.* due to nested attributes missing.'); + } + // check override metadata $contractMetadata = $this->createClassMetadataFactory()->getMetadataFor(DDC5934Contract::class); @@ -868,6 +880,10 @@ public function testFetchOverrideMapping(): void */ public function testAttributeOverridesMapping(): void { + if (PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Does not work on PHP 8.0.* due to nested attributes missing.'); + } + $factory = $this->createClassMetadataFactory(); $guestMetadata = $factory->getMetadataFor(DDC964Guest::class); $adminMetadata = $factory->getMetadataFor(DDC964Admin::class); diff --git a/tests/Doctrine/Tests/ORM/Mapping/AttributeDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AttributeDriverTest.php index 01f6d365b18..1ef6f60cc32 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AttributeDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AttributeDriverTest.php @@ -47,22 +47,38 @@ public function testSqlResultSetMapping(): void public function testAssociationOverridesMapping(): void { - self::markTestSkipped('AttributeDriver does not support association overrides.'); + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('AttributeDriver does not support association overrides.'); + } else { + parent::testAssociationOverridesMapping(); + } } public function testInversedByOverrideMapping(): void { - self::markTestSkipped('AttributeDriver does not support association overrides.'); + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('AttributeDriver does not support association overrides.'); + } else { + parent::testInversedByOverrideMapping(); + } } public function testFetchOverrideMapping(): void { - self::markTestSkipped('AttributeDriver does not support association overrides.'); + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('AttributeDriver does not support association overrides.'); + } else { + parent::testFetchOverrideMapping(); + } } public function testAttributeOverridesMapping(): void { - self::markTestSkipped('AttributeDriver does not support association overrides.'); + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('AttributeDriver does not support association overrides.'); + } else { + parent::testAttributeOverridesMapping(); + } } public function testOriginallyNestedAttributesDeclaredWithoutOriginalParent(): void diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index d452d778492..5010b6c9349 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -39,7 +39,6 @@ use function unserialize; use const CASE_UPPER; - use const PHP_VERSION_ID; require_once __DIR__ . '/../../Models/Global/GlobalNamespaceModel.php'; @@ -1147,6 +1146,10 @@ public function testInvalidCascade(): void */ public function testInvalidPropertyAssociationOverrideNameException(): void { + if (PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Does not work on PHP 8.0.* due to nested attributes missing.'); + } + $this->expectException('Doctrine\ORM\Mapping\MappingException'); $this->expectExceptionMessage('Invalid field override named \'invalidPropertyName\' for class \'Doctrine\Tests\Models\DDC964\DDC964Admin'); $cm = new ClassMetadata(DDC964Admin::class); @@ -1161,6 +1164,10 @@ public function testInvalidPropertyAssociationOverrideNameException(): void */ public function testInvalidPropertyAttributeOverrideNameException(): void { + if (PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Does not work on PHP 8.0.* due to nested attributes missing.'); + } + $this->expectException('Doctrine\ORM\Mapping\MappingException'); $this->expectExceptionMessage('Invalid field override named \'invalidPropertyName\' for class \'Doctrine\Tests\Models\DDC964\DDC964Guest\'.'); $cm = new ClassMetadata(DDC964Guest::class); @@ -1175,6 +1182,10 @@ public function testInvalidPropertyAttributeOverrideNameException(): void */ public function testInvalidOverrideAttributeFieldTypeException(): void { + if (PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Does not work on PHP 8.0.* due to nested attributes missing.'); + } + $this->expectException('Doctrine\ORM\Mapping\MappingException'); $this->expectExceptionMessage('The column type of attribute \'name\' on class \'Doctrine\Tests\Models\DDC964\DDC964Guest\' could not be changed.'); $cm = new ClassMetadata(DDC964Guest::class); diff --git a/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php index fa0b54d7d81..77db11396a4 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/YamlMappingDriverTest.php @@ -8,6 +8,7 @@ use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Mapping\Driver\YamlDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use Doctrine\Tests\Models\DDC3711\DDC3711EntityA; use Doctrine\Tests\Models\DirectoryTree\Directory; use Doctrine\Tests\Models\DirectoryTree\File; use Doctrine\Tests\Models\Generic\SerializationModel; @@ -83,6 +84,22 @@ public function testSpacesShouldBeIgnoredWhenUseExplode(): void self::assertEquals(255, $nameField['length']); self::assertEquals(255, $valueField['length']); } + + public function testCompositeKeyForJoinTableInManyToManyCreation(): void + { + $yamlDriver = $this->loadDriver(); + + $em = $this->getTestEntityManager(); + $em->getConfiguration()->setMetadataDriverImpl($yamlDriver); + $factory = new ClassMetadataFactory(); + $factory->setEntityManager($em); + + $entityA = new ClassMetadata(DDC3711EntityA::class); + $entityA = $factory->getMetadataFor(DDC3711EntityA::class); + + self::assertEquals(['link_a_id1' => 'id1', 'link_a_id2' => 'id2'], $entityA->associationMappings['entityB']['relationToSourceKeyColumns']); + self::assertEquals(['link_b_id1' => 'id1', 'link_b_id2' => 'id2'], $entityA->associationMappings['entityB']['relationToTargetKeyColumns']); + } } class DDC2069Entity