diff --git a/docs/en/reference/annotations-reference.rst b/docs/en/reference/annotations-reference.rst index 22686dbe7f0..1bcd69f85f8 100644 --- a/docs/en/reference/annotations-reference.rst +++ b/docs/en/reference/annotations-reference.rst @@ -350,7 +350,7 @@ in order to specify that it is an embedded class. Required attributes: -- **class**: The embeddable class +- **class**: The embeddable class. You can omit this value if you use a PHP property type instead. .. code-block:: php diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index 94795e439b6..30db18d0ac8 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -309,7 +309,7 @@ - + diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 6c2c6bcbf37..21b43e571c8 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -3605,9 +3605,16 @@ public function mapEmbedded(array $mapping) { $this->assertFieldNotMapped($mapping['fieldName']); + if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) { + $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); + if ($type) { + $mapping['class'] = $type->getName(); + } + } + $this->embeddedClasses[$mapping['fieldName']] = [ 'class' => $this->fullyQualifiedClassName($mapping['class']), - 'columnPrefix' => $mapping['columnPrefix'], + 'columnPrefix' => $mapping['columnPrefix'] ?? null, 'declaredField' => $mapping['declaredField'] ?? null, 'originalField' => $mapping['originalField'] ?? null, ]; diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 96261749292..dae779b88b2 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -322,9 +322,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) ? $this->evaluateBoolean($embeddedMapping['use-column-prefix']) : true; + $class = isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null; + $mapping = [ 'fieldName' => (string) $embeddedMapping['name'], - 'class' => (string) $embeddedMapping['class'], + 'class' => $class, 'columnPrefix' => $useColumnPrefix ? $columnPrefix : false, ]; diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 668bfe57802..70914d2f891 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -408,7 +408,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) foreach ($element['embedded'] as $name => $embeddedMapping) { $mapping = [ 'fieldName' => $name, - 'class' => $embeddedMapping['class'], + 'class' => $embeddedMapping['class'] ?? null, 'columnPrefix' => $embeddedMapping['columnPrefix'] ?? null, ]; $metadata->mapEmbedded($mapping); diff --git a/lib/Doctrine/ORM/Mapping/Embedded.php b/lib/Doctrine/ORM/Mapping/Embedded.php index 6090f75836c..cfb0acaa980 100644 --- a/lib/Doctrine/ORM/Mapping/Embedded.php +++ b/lib/Doctrine/ORM/Mapping/Embedded.php @@ -31,16 +31,13 @@ #[Attribute(Attribute::TARGET_PROPERTY)] final class Embedded implements Annotation { - /** - * @Required - * @var string - */ + /** @var string|null */ public $class; /** @var string|bool|null */ public $columnPrefix; - public function __construct(string $class, $columnPrefix = null) + public function __construct(?string $class = null, $columnPrefix = null) { $this->class = $class; $this->columnPrefix = $columnPrefix; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4010d04e08d..6e6a4db46c3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -557,7 +557,7 @@ parameters: - message: "#^Call to an undefined method ReflectionProperty\\:\\:getType\\(\\)\\.$#" - count: 2 + count: 3 path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php - diff --git a/tests/Doctrine/Tests/Models/TypedProperties/Contact.php b/tests/Doctrine/Tests/Models/TypedProperties/Contact.php new file mode 100644 index 00000000000..e99dd3ad0e6 --- /dev/null +++ b/tests/Doctrine/Tests/Models/TypedProperties/Contact.php @@ -0,0 +1,18 @@ +setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE); @@ -131,5 +136,7 @@ public static function loadMetadata(ClassMetadataInfo $metadata): void $metadata->mapManyToOne( ['fieldName' => 'mainEmail'] ); + + $metadata->mapEmbedded(['fieldName' => 'contact']); } } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 28a2abefc05..14d186e2b04 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -42,6 +42,7 @@ use Doctrine\Tests\Models\DDC889\DDC889Entity; use Doctrine\Tests\Models\DDC964\DDC964Admin; use Doctrine\Tests\Models\DDC964\DDC964Guest; +use Doctrine\Tests\Models\TypedProperties\Contact; use Doctrine\Tests\Models\TypedProperties\UserTyped; use Doctrine\Tests\OrmTestCase; @@ -281,6 +282,7 @@ public function testFieldIsNullableByType(): void $this->assertEquals(CmsEmail::class, $class->getAssociationMapping('email')['targetEntity']); $this->assertEquals(CmsEmail::class, $class->getAssociationMapping('mainEmail')['targetEntity']); + $this->assertEquals(Contact::class, $class->embeddedClasses['contact']['class']); } public function testFieldTypeFromReflection(): void diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index ae142ad70c0..1abf4945d64 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -133,6 +133,9 @@ public function testFieldIsNullableByType(): void $cm->mapManyToOne(['fieldName' => 'mainEmail']); $this->assertEquals(CmsEmail::class, $cm->getAssociationMapping('mainEmail')['targetEntity']); + + $cm->mapEmbedded(['fieldName' => 'contact']); + $this->assertEquals(TypedProperties\Contact::class, $cm->embeddedClasses['contact']['class']); } public function testFieldTypeFromReflection(): void diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.TypedProperties.UserTyped.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.TypedProperties.UserTyped.php index 9fbcbe55d44..0888a78d07d 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.TypedProperties.UserTyped.php +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.TypedProperties.UserTyped.php @@ -61,3 +61,5 @@ $metadata->mapManyToOne( ['fieldName' => 'mainEmail'] ); + +$metadata->mapEmbedded(['fieldName' => 'contact']); diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.xml index 90dd5a46362..a9547af4ab3 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.xml @@ -25,5 +25,7 @@ + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.yml index 503a67fb34d..7bc39f55804 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.yml @@ -24,3 +24,5 @@ Doctrine\Tests\Models\TypedProperties\UserTyped: joinColumn: [] manyToOne: mainEmail: [] + embedded: + contact: ~