Skip to content

Commit

Permalink
Merge pull request doctrine#7054 from cb8/foreign-id
Browse files Browse the repository at this point in the history
Fix ID generation of foreign keys

Ports doctrine#6701 using v3.0 structure.
  • Loading branch information
lcobucci authored Feb 25, 2018
2 parents 3e1d124 + 5fde371 commit 91acb40
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 24 deletions.
60 changes: 38 additions & 22 deletions lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@
use Doctrine\ORM\Events;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Sequencing;
use Doctrine\ORM\Sequencing\Planning\AssociationValueGeneratorExecutor;
use Doctrine\ORM\Sequencing\Planning\ColumnValueGeneratorExecutor;
use Doctrine\ORM\Sequencing\Planning\CompositeValueGenerationPlan;
use Doctrine\ORM\Sequencing\Planning\NoopValueGenerationPlan;
use Doctrine\ORM\Sequencing\Planning\SingleValueGenerationPlan;
use Doctrine\ORM\Sequencing\Planning\ValueGenerationExecutor;
use ReflectionException;
use function array_map;
use function class_exists;
use function count;
use function end;
use function explode;
use function is_subclass_of;
use function reset;
use function sprintf;
use function strpos;
use function strtolower;
Expand Down Expand Up @@ -524,41 +525,56 @@ private function getTargetPlatform() : Platforms\AbstractPlatform

private function buildValueGenerationPlan(ClassMetadata $class) : void
{
/** @var LocalColumnMetadata[] $generatedProperties */
$generatedProperties = [];
$executors = $this->buildValueGenerationExecutorList($class);

foreach ($class->getDeclaredPropertiesIterator() as $property) {
if (! ($property instanceof LocalColumnMetadata && $property->hasValueGenerator())) {
continue;
}

$generatedProperties[] = $property;
}

switch (count($generatedProperties)) {
switch (count($executors)) {
case 0:
$class->setValueGenerationPlan(new NoopValueGenerationPlan());
break;

case 1:
$property = reset($generatedProperties);
$executor = new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));

$class->setValueGenerationPlan(new SingleValueGenerationPlan($class, $executor));
$class->setValueGenerationPlan(new SingleValueGenerationPlan($class, $executors[0]));
break;

default:
$executors = [];

foreach ($generatedProperties as $property) {
$executors[] = new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));
}

$class->setValueGenerationPlan(new CompositeValueGenerationPlan($class, $executors));
break;
}
}

/**
* @return ValueGenerationExecutor[]
*/
private function buildValueGenerationExecutorList(ClassMetadata $class) : array
{
$executors = [];

foreach ($class->getDeclaredPropertiesIterator() as $property) {
$executor = $this->buildValueGenerationExecutorForProperty($class, $property);

if ($executor instanceof ValueGenerationExecutor) {
$executors[] = $executor;
}
}

return $executors;
}

private function buildValueGenerationExecutorForProperty(
ClassMetadata $class,
Property $property
) : ?ValueGenerationExecutor {
if ($property instanceof LocalColumnMetadata && $property->hasValueGenerator()) {
return new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));
}

if ($property instanceof ToOneAssociationMetadata && $property->isPrimaryKey()) {
return new AssociationValueGeneratorExecutor();
}

return null;
}

private function createPropertyValueGenerator(
ClassMetadata $class,
LocalColumnMetadata $property
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Sequencing\Planning;

use Doctrine\ORM\EntityManagerInterface;

final class AssociationValueGeneratorExecutor implements ValueGenerationExecutor
{
/**
* {@inheritdoc}
*/
public function execute(EntityManagerInterface $entityManager, object $entity) : array
{
// value set by inverse side
return [];
}

public function isDeferred() : bool
{
return true;
}
}
4 changes: 2 additions & 2 deletions lib/Doctrine/ORM/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -962,9 +962,9 @@ private function executeInserts(ClassMetadata $class) : void
if ($generationPlan->containsDeferred()) {
// Entity has post-insert IDs
$oid = spl_object_id($entity);
$id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
$id = $persister->getIdentifier($entity);

$this->entityIdentifiers[$oid] = $id;
$this->entityIdentifiers[$oid] = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $id);
$this->entityStates[$oid] = self::STATE_MANAGED;
$this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];

Expand Down
191 changes: 191 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH6531Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<?php

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\ORM\Annotation as ORM;
use Doctrine\Common\Collections\ArrayCollection;

final class GH6531Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp() : void
{
parent::setup();

$this->setUpEntitySchema(
[
GH6531User::class,
GH6531Address::class,
GH6531Article::class,
GH6531ArticleAttribute::class,
GH6531Order::class,
GH6531OrderItem::class,
GH6531Product::class,
]
);
}

/**
* @group 6531
*/
public function testSimpleDerivedIdentity() : void
{
$user = new GH6531User();
$address = new GH6531Address();
$address->user = $user;

$this->em->persist($user);
$this->em->persist($address);
$this->em->flush();

self::assertSame($user, $this->em->find(GH6531User::class, $user->id));
self::assertSame($address, $this->em->find(GH6531Address::class, $user));
}

/**
* @group 6531
*/
public function testDynamicAttributes() : void
{
$article = new GH6531Article();
$article->addAttribute('name', 'value');

$this->em->persist($article);
$this->em->flush();

self::assertSame(
$article->attributes['name'],
$this->em->find(GH6531ArticleAttribute::class, ['article' => $article, 'attribute' => 'name'])
);
}

/**
* @group 6531
*/
public function testJoinTableWithMetadata() : void
{
$product = new GH6531Product();
$this->em->persist($product);
$this->em->flush();

$order = new GH6531Order();
$order->addItem($product, 2);

$this->em->persist($order);
$this->em->flush();

self::assertSame(
$order->items->first(),
$this->em->find(GH6531OrderItem::class, ['product' => $product, 'order' => $order])
);
}
}

/**
* @ORM\Entity
*/
class GH6531User
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
public $id;
}

/**
* @ORM\Entity
*/
class GH6531Address
{
/** @ORM\Id @ORM\OneToOne(targetEntity=GH6531User::class) */
public $user;
}

/**
* @ORM\Entity
*/
class GH6531Article
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
public $id;

/** @ORM\OneToMany(targetEntity=GH6531ArticleAttribute::class, mappedBy="article", cascade={"ALL"}, indexBy="attribute") */
public $attributes;

public function addAttribute(string $name, string $value)
{
$this->attributes[$name] = new GH6531ArticleAttribute($name, $value, $this);
}
}

/**
* @ORM\Entity
*/
class GH6531ArticleAttribute
{
/** @ORM\Id @ORM\ManyToOne(targetEntity=GH6531Article::class, inversedBy="attributes") */
public $article;

/** @ORM\Id @ORM\Column(type="string") */
public $attribute;

/** @ORM\Column(type="string") */
public $value;

public function __construct(string $name, string $value, GH6531Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}

/**
* @ORM\Entity
*/
class GH6531Order
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
public $id;

/** @ORM\OneToMany(targetEntity=GH6531OrderItem::class, mappedBy="order", cascade={"ALL"}) */
public $items;

public function __construct()
{
$this->items = new ArrayCollection();
}

public function addItem(GH6531Product $product, int $amount) : void
{
$this->items->add(new GH6531OrderItem($this, $product, $amount));
}
}

/**
* @ORM\Entity
*/
class GH6531Product
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
public $id;
}

/**
* @ORM\Entity
*/
class GH6531OrderItem
{
/** @ORM\Id @ORM\ManyToOne(targetEntity=GH6531Order::class) */
public $order;

/** @ORM\Id @ORM\ManyToOne(targetEntity=GH6531Product::class) */
public $product;

/** @ORM\Column(type="integer") */
public $amount = 1;

public function __construct(GH6531Order $order, GH6531Product $product, int $amount = 1)
{
$this->order = $order;
$this->product = $product;
$this->amount = $amount;
}
}

0 comments on commit 91acb40

Please sign in to comment.