Skip to content

Commit

Permalink
Fix inherited embeddables and nesting after AnnotationDriver change #…
Browse files Browse the repository at this point in the history
…8006 (#8036)

* Add test case

* Treat parent embeddables as mapped superclasses

* [GH-8031] Bugfix: Get working again on nested embeddables in inherited embeddables.

* Housekeeping: CS

* Update note on limitations

* [GH-8031] Verify assocations still do not work with Embeddables.

* Housekeeping: CS

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
  • Loading branch information
malarzm and beberlei committed Mar 15, 2020
1 parent cd905ff commit a9b6b72
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 8 deletions.
4 changes: 3 additions & 1 deletion docs/en/tutorials/embeddables.rst
Expand Up @@ -8,7 +8,9 @@ or address are the primary use case for this feature.

.. note::

Embeddables can only contain properties with basic ``@Column`` mapping.
Embeddables can not contain references to entities. They can however compose
other embeddables in addition to holding properties with basic ``@Column``
mapping.

For the purposes of this tutorial, we will assume that you have a ``User``
class in your application and you would like to store an address in
Expand Down
13 changes: 6 additions & 7 deletions lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Expand Up @@ -31,6 +31,7 @@
use Doctrine\ORM\Id\IdentityGenerator;
use Doctrine\ORM\ORMException;
use ReflectionException;
use function assert;

/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
Expand Down Expand Up @@ -401,10 +402,10 @@ private function getShortName($className)
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->fieldMappings as $mapping) {
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass && ! $parentClass->isEmbeddedClass) {
$mapping['inherited'] = $parentClass->name;
}
if ( ! isset($mapping['declared'])) {
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedFieldMapping($mapping);
Expand Down Expand Up @@ -469,10 +470,6 @@ private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetad
private function addNestedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass, $prefix)
{
foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
if (isset($embeddableClass['inherited'])) {
continue;
}

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

$parentClass->mapEmbedded(
Expand Down Expand Up @@ -780,7 +777,9 @@ protected function getDriver()
*/
protected function isEntity(ClassMetadataInterface $class)
{
return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
assert($class instanceof ClassMetadata);

return $class->isMappedSuperclass === false && $class->isEmbeddedClass === false;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
Expand Up @@ -277,6 +277,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
/* @var $property \ReflectionProperty */
foreach ($class->getProperties() as $property) {
if ($metadata->isMappedSuperclass && ! $property->isPrivate()
||
$metadata->isEmbeddedClass && $property->getDeclaringClass()->getName() !== $class->getName()
||
$metadata->isInheritedField($property->name)
||
Expand Down
163 changes: 163 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH8031Test.php
@@ -0,0 +1,163 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Tests\OrmFunctionalTestCase;

class GH8031Test extends OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();

$this->setUpEntitySchema([
GH8031Invoice::class,
]);
}

public function testEntityIsFetched()
{
$entity = new GH8031Invoice(new GH8031InvoiceCode(1, 2020, new GH8031Nested(10)));
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();

/** @var GH8031Invoice $fetched */
$fetched = $this->_em->find(GH8031Invoice::class, $entity->getId());
$this->assertInstanceOf(GH8031Invoice::class, $fetched);
$this->assertSame(1, $fetched->getCode()->getNumber());
$this->assertSame(2020, $fetched->getCode()->getYear());

$this->_em->clear();
$this->assertCount(
1,
$this->_em->getRepository(GH8031Invoice::class)->findBy([], ['code.number' => 'ASC'])
);
}

public function testEmbeddableWithAssociationNotAllowed()
{
$cm = $this->_em->getClassMetadata(GH8031EmbeddableWithAssociation::class);

$this->assertArrayHasKey('invoice', $cm->associationMappings);

$cm = $this->_em->getClassMetadata(GH8031Invoice::class);

$this->assertCount(0, $cm->associationMappings);
}
}

/**
* @Embeddable
*/
class GH8031EmbeddableWithAssociation
{
/** @ManyToOne(targetEntity=GH8031Invoice::class) */
public $invoice;
}

/**
* @Embeddable
*/
class GH8031Nested
{
/**
* @Column(type="integer", name="number", length=6)
* @var int
*/
protected $number;

public function __construct(int $number)
{
$this->number = $number;
}

public function getNumber() : int
{
return $this->number;
}
}

/**
* @Embeddable
*/
class GH8031InvoiceCode extends GH8031AbstractYearSequenceValue
{
}

/**
* @Embeddable
*/
abstract class GH8031AbstractYearSequenceValue
{
/**
* @Column(type="integer", name="number", length=6)
* @var int
*/
protected $number;

/**
* @Column(type="smallint", name="year", length=4)
* @var int
*/
protected $year;

/** @Embedded(class=GH8031Nested::class) */
protected $nested;

public function __construct(int $number, int $year, GH8031Nested $nested)
{
$this->number = $number;
$this->year = $year;
$this->nested = $nested;
}

public function getNumber() : int
{
return $this->number;
}

public function getYear() : int
{
return $this->year;
}
}

/**
* @Entity
*/
class GH8031Invoice
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
private $id;

/**
* @Embedded(class=GH8031InvoiceCode::class)
* @var GH8031InvoiceCode
*/
private $code;

/** @Embedded(class=GH8031EmbeddableWithAssociation::class) */
private $embeddedAssoc;

public function __construct(GH8031InvoiceCode $code)
{
$this->code = $code;
}

public function getId()
{
return $this->id;
}

public function getCode() : GH8031InvoiceCode
{
return $this->code;
}
}

0 comments on commit a9b6b72

Please sign in to comment.