Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/en/tutorials/embeddables.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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;
}
}