Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix inherited embeddables and nesting after AnnotationDriver change #8006 #8036

Merged
merged 7 commits into from Mar 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the time has come to move the whole expression of this if condition into a function :-)

||
$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;
}
}