Skip to content

Commit

Permalink
Merge pull request #10612 from doctrine/embedded-class-mapping-dto
Browse files Browse the repository at this point in the history
  • Loading branch information
greg0ire authored Apr 3, 2023
2 parents e22592f + 9d3ae4a commit 545070f
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 66 deletions.
2 changes: 2 additions & 0 deletions lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public function setEmbeddable(): static
/**
* Adds and embedded class
*
* @param class-string $class
*
* @return $this
*/
public function addEmbedded(string $fieldName, string $class, string|false|null $columnPrefix = null): static
Expand Down
36 changes: 9 additions & 27 deletions lib/Doctrine/ORM/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,6 @@
* type: int,
* unique?: bool,
* }
* @psalm-type EmbeddedClassMapping = array{
* class: class-string,
* columnPrefix: string|null,
* declaredField: string|null,
* originalField: string|null,
* inherited?: class-string,
* declared?: class-string,
* }
*/
class ClassMetadata implements PersistenceClassMetadata, Stringable
{
Expand Down Expand Up @@ -364,22 +356,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* READ-ONLY: The names of all embedded classes based on properties.
*
* The value (definition) array may contain, among others, the following values:
*
* - <b>'inherited'</b> (string, optional)
* This is set when this embedded-class field is inherited by this class from another (inheritance) parent
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
* mapping information for this field. (If there are transient classes in the
* class hierarchy, these are ignored, so the class property may in fact come
* from a class further up in the PHP class hierarchy.)
* Fields initially declared in mapped superclasses are
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
*
* - <b>'declared'</b> (string, optional)
* This is set when the embedded-class field does not appear for the first time in this class, but is originally
* declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
* of the topmost non-transient class that contains mapping information for this field.
*
* @psalm-var array<string, EmbeddedClassMapping>
*/
public array $embeddedClasses = [];
Expand Down Expand Up @@ -2822,7 +2798,13 @@ public function getMetadataValue(string $name): mixed
/**
* Map Embedded Class
*
* @psalm-param array<string, mixed> $mapping
* @psalm-param array{
* fieldName: string,
* class?: class-string,
* declaredField?: string,
* columnPrefix?: string|false|null,
* originalField?: string
* } $mapping
*
* @throws MappingException
*/
Expand All @@ -2845,12 +2827,12 @@ public function mapEmbedded(array $mapping): void

assert($fqcn !== null);

$this->embeddedClasses[$mapping['fieldName']] = [
$this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([
'class' => $fqcn,
'columnPrefix' => $mapping['columnPrefix'] ?? null,
'declaredField' => $mapping['declaredField'] ?? null,
'originalField' => $mapping['originalField'] ?? null,
];
]);
}

/**
Expand Down
24 changes: 12 additions & 12 deletions lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
*
* @extends AbstractClassMetadataFactory<ClassMetadata>
* @psalm-import-type AssociationMapping from ClassMetadata
* @psalm-import-type EmbeddedClassMapping from ClassMetadata
*/
class ClassMetadataFactory extends AbstractClassMetadataFactory
{
Expand Down Expand Up @@ -384,11 +383,11 @@ private function getShortName(string $className): string
/**
* Puts the `inherited` and `declared` values into mapping information for fields, associations
* and embedded classes.
*
* @param AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping
*/
private function addMappingInheritanceInformation(array|FieldMapping &$mapping, ClassMetadata $parentClass): void
{
private function addMappingInheritanceInformation(
array|EmbeddedClassMapping|FieldMapping &$mapping,
ClassMetadata $parentClass,
): void {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
Expand Down Expand Up @@ -440,8 +439,9 @@ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $p
private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->embeddedClasses as $field => $embeddedClass) {
$this->addMappingInheritanceInformation($embeddedClass, $parentClass);
$subClass->embeddedClasses[$field] = $embeddedClass;
$subClassMapping = clone $embeddedClass;
$this->addMappingInheritanceInformation($subClassMapping, $parentClass);
$subClass->embeddedClasses[$field] = $subClassMapping;
}
}

Expand All @@ -462,17 +462,17 @@ private function addNestedEmbeddedClasses(
continue;
}

$embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
$embeddableMetadata = $this->getMetadataFor($embeddableClass->class);

$parentClass->mapEmbedded(
[
'fieldName' => $prefix . '.' . $property,
'class' => $embeddableMetadata->name,
'columnPrefix' => $embeddableClass['columnPrefix'],
'declaredField' => $embeddableClass['declaredField']
? $prefix . '.' . $embeddableClass['declaredField']
'columnPrefix' => $embeddableClass->columnPrefix,
'declaredField' => $embeddableClass->declaredField
? $prefix . '.' . $embeddableClass->declaredField
: $prefix,
'originalField' => $embeddableClass['originalField'] ?: $property,
'originalField' => $embeddableClass->originalField ?: $property,
],
);
}
Expand Down
91 changes: 91 additions & 0 deletions lib/Doctrine/ORM/Mapping/EmbeddedClassMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Mapping;

use ArrayAccess;

use function property_exists;

/** @template-implements ArrayAccess<string, mixed> */
final class EmbeddedClassMapping implements ArrayAccess
{
use ArrayAccessImplementation;

public string|false|null $columnPrefix = null;
public string|null $declaredField = null;
public string|null $originalField = null;

/**
* This is set when this embedded-class field is inherited by this class
* from another (inheritance) parent <em>entity</em> class. The value is
* the FQCN of the topmost entity class that contains mapping information
* for this field. (If there are transient classes in the class hierarchy,
* these are ignored, so the class property may in fact come from a class
* further up in the PHP class hierarchy.) Fields initially declared in
* mapped superclasses are <em>not</em> considered 'inherited' in the
* nearest entity subclasses.
*
* @var class-string|null
*/
public string|null $inherited = null;

/**
* This is set when the embedded-class field does not appear for the first
* time in this class, but is originally declared in another parent
* <em>entity or mapped superclass</em>. The value is the FQCN of the
* topmost non-transient class that contains mapping information for this
* field.
*
* @var class-string|null
*/
public string|null $declared = null;

/** @param class-string $class */
public function __construct(public string $class)
{
}

/**
* @psalm-param array{
* class: class-string,
* columnPrefix?: false|string|null,
* declaredField?: string|null,
* originalField?: string|null
* } $mappingArray
*/
public static function fromMappingArray(array $mappingArray): self
{
$mapping = new self($mappingArray['class']);
foreach ($mappingArray as $key => $value) {
if ($key === 'class') {
continue;
}

if (property_exists($mapping, $key)) {
$mapping->$key = $value;
}
}

return $mapping;
}

/** @return list<string> */
public function __sleep(): array
{
$serialized = ['class'];

if ($this->columnPrefix) {
$serialized[] = 'columnPrefix';
}

foreach (['declaredField', 'originalField', 'inherited', 'declared'] as $property) {
if ($this->$property !== null) {
$serialized[] = $property;
}
}

return $serialized;
}
}
2 changes: 2 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@
</file>
<file src="lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php">
<InvalidArgument>
<code>$mapping</code>
<code><![CDATA[[
'sequenceName' => $seqGeneratorAttribute->sequenceName,
'allocationSize' => $seqGeneratorAttribute->allocationSize,
Expand Down Expand Up @@ -553,6 +554,7 @@
</ArgumentTypeCoercion>
<InvalidArgument>
<code>$columnDef</code>
<code>$mapping</code>
<code><![CDATA[$this->cacheToArray($manyToManyElement->cache)]]></code>
<code><![CDATA[$this->cacheToArray($manyToOneElement->cache)]]></code>
<code><![CDATA[$this->cacheToArray($oneToManyElement->cache)]]></code>
Expand Down
17 changes: 9 additions & 8 deletions tests/Doctrine/Tests/ORM/Mapping/ClassMetadataBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Doctrine\ORM\Mapping\Builder\FieldBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\DiscriminatorColumnMapping;
use Doctrine\ORM\Mapping\EmbeddedClassMapping;
use Doctrine\ORM\Mapping\FieldMapping;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
Expand Down Expand Up @@ -51,12 +52,12 @@ public function testAddEmbeddedWithOnlyRequiredParams(): void

self::assertEquals(
[
'name' => [
'name' => EmbeddedClassMapping::fromMappingArray([
'class' => Name::class,
'columnPrefix' => null,
'declaredField' => null,
'originalField' => null,
],
]),
],
$this->cm->embeddedClasses,
);
Expand All @@ -74,12 +75,12 @@ public function testAddEmbeddedWithPrefix(): void

self::assertEquals(
[
'name' => [
'name' => EmbeddedClassMapping::fromMappingArray([
'class' => Name::class,
'columnPrefix' => 'nm_',
'declaredField' => null,
'originalField' => null,
],
]),
],
$this->cm->embeddedClasses,
);
Expand All @@ -94,12 +95,12 @@ public function testCreateEmbeddedWithoutExtraParams(): void

$this->assertIsFluent($embeddedBuilder->build());
self::assertEquals(
[
EmbeddedClassMapping::fromMappingArray([
'class' => Name::class,
'columnPrefix' => null,
'declaredField' => null,
'originalField' => null,
],
]),
$this->cm->embeddedClasses['name'],
);
}
Expand All @@ -113,12 +114,12 @@ public function testCreateEmbeddedWithColumnPrefix(): void
$this->assertIsFluent($embeddedBuilder->build());

self::assertEquals(
[
EmbeddedClassMapping::fromMappingArray([
'class' => Name::class,
'columnPrefix' => 'nm_',
'declaredField' => null,
'originalField' => null,
],
]),
$this->cm->embeddedClasses['name'],
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public function testItSurvivesSerialization(): void
$resurrectedMapping = unserialize(serialize($mapping));
assert($resurrectedMapping instanceof DiscriminatorColumnMapping);

self::assertSame($resurrectedMapping->length, 255);
self::assertSame($resurrectedMapping->columnDefinition, 'VARCHAR(255)');
self::assertSame($resurrectedMapping->enumType, 'MyEnum');
self::assertSame($resurrectedMapping->options, ['foo' => 'bar']);
self::assertSame(255, $resurrectedMapping->length);
self::assertSame('VARCHAR(255)', $resurrectedMapping->columnDefinition);
self::assertSame('MyEnum', $resurrectedMapping->enumType);
self::assertSame(['foo' => 'bar'], $resurrectedMapping->options);
}
}
34 changes: 34 additions & 0 deletions tests/Doctrine/Tests/ORM/Mapping/EmbeddedClassMappingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Mapping;

use Doctrine\ORM\Mapping\EmbeddedClassMapping;
use PHPUnit\Framework\TestCase;

use function assert;
use function serialize;
use function unserialize;

final class EmbeddedClassMappingTest extends TestCase
{
public function testItSurvivesSerialization(): void
{
$mapping = new EmbeddedClassMapping(self::class);
$mapping->columnPrefix = 'these';
$mapping->declaredField = 'values';
$mapping->originalField = 'make';
$mapping->inherited = self::class; // no
$mapping->declared = self::class; // sense

$resurrectedMapping = unserialize(serialize($mapping));
assert($resurrectedMapping instanceof EmbeddedClassMapping);

self::assertSame('these', $resurrectedMapping->columnPrefix);
self::assertSame('values', $resurrectedMapping->declaredField);
self::assertSame('make', $resurrectedMapping->originalField);
self::assertSame(self::class, $resurrectedMapping->inherited);
self::assertSame(self::class, $resurrectedMapping->declared);
}
}
26 changes: 13 additions & 13 deletions tests/Doctrine/Tests/ORM/Mapping/FieldMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,25 @@ public function testItSurvivesSerialization(): void
$resurrectedMapping = unserialize(serialize($mapping));
assert($resurrectedMapping instanceof FieldMapping);

self::assertSame($resurrectedMapping->length, 255);
self::assertSame(255, $resurrectedMapping->length);
self::assertTrue($resurrectedMapping->id);
self::assertTrue($resurrectedMapping->nullable);
self::assertTrue($resurrectedMapping->notInsertable);
self::assertTrue($resurrectedMapping->notUpdatable);
self::assertSame($resurrectedMapping->columnDefinition, 'VARCHAR(255)');
self::assertSame($resurrectedMapping->generated, ClassMetadata::GENERATOR_TYPE_AUTO);
self::assertSame($resurrectedMapping->enumType, 'MyEnum');
self::assertSame($resurrectedMapping->precision, 10);
self::assertSame($resurrectedMapping->scale, 2);
self::assertSame('VARCHAR(255)', $resurrectedMapping->columnDefinition);
self::assertSame(ClassMetadata::GENERATOR_TYPE_AUTO, $resurrectedMapping->generated);
self::assertSame('MyEnum', $resurrectedMapping->enumType);
self::assertSame(10, $resurrectedMapping->precision);
self::assertSame(2, $resurrectedMapping->scale);
self::assertTrue($resurrectedMapping->unique);
self::assertSame($resurrectedMapping->inherited, self::class);
self::assertSame($resurrectedMapping->originalClass, self::class);
self::assertSame($resurrectedMapping->originalField, 'id');
self::assertSame(self::class, $resurrectedMapping->inherited);
self::assertSame(self::class, $resurrectedMapping->originalClass);
self::assertSame('id', $resurrectedMapping->originalField);
self::assertTrue($resurrectedMapping->quoted);
self::assertSame($resurrectedMapping->declared, self::class);
self::assertSame($resurrectedMapping->declaredField, 'id');
self::assertSame($resurrectedMapping->options, ['foo' => 'bar']);
self::assertSame(self::class, $resurrectedMapping->declared);
self::assertSame('id', $resurrectedMapping->declaredField);
self::assertSame(['foo' => 'bar'], $resurrectedMapping->options);
self::assertTrue($resurrectedMapping->version);
self::assertSame($resurrectedMapping->default, 'foo');
self::assertSame('foo', $resurrectedMapping->default);
}
}
Loading

0 comments on commit 545070f

Please sign in to comment.