diff --git a/docs/en/reference/annotations-reference.rst b/docs/en/reference/annotations-reference.rst
index 74083b40912..ef49c29ab16 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 9dee8ab4153..24fe819ed1f 100644
--- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
+++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -3593,9 +3593,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 instanceof ReflectionNamedType) {
+ $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..e4ddcbd3830 100644
--- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
+++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
@@ -324,7 +324,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$mapping = [
'fieldName' => (string) $embeddedMapping['name'],
- 'class' => (string) $embeddedMapping['class'],
+ 'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null,
'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 792274df396..ac1fca5e6f4 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;
@@ -284,6 +285,7 @@ public function testFieldTypeFromReflection(): 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']);
}
/**
diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php
index 1d4f4d37fa9..b115823d502 100644
--- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php
+++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php
@@ -125,6 +125,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: ~