Skip to content

Commit 5e8b513

Browse files
committed
[DoctrineBridge][Form] Fix IdReader when indexing by primary foreign key
1 parent aa7eb67 commit 5e8b513

File tree

3 files changed

+172
-1
lines changed

3 files changed

+172
-1
lines changed

Form/ChoiceList/IdReader.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* A utility for reading object IDs.
2020
*
2121
* @since 1.0
22+
*
2223
* @author Bernhard Schussek <[email protected]>
2324
*
2425
* @internal This class is meant for internal use only.
@@ -50,6 +51,11 @@ class IdReader
5051
*/
5152
private $idField;
5253

54+
/**
55+
* @var IdReader|null
56+
*/
57+
private $associationIdReader;
58+
5359
public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
5460
{
5561
$ids = $classMetadata->getIdentifierFieldNames();
@@ -60,6 +66,16 @@ public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
6066
$this->singleId = 1 === count($ids);
6167
$this->intId = $this->singleId && in_array($idType, array('integer', 'smallint', 'bigint'));
6268
$this->idField = current($ids);
69+
70+
// single field association are resolved, since the schema column could be an int
71+
if ($this->singleId && $classMetadata->hasAssociation($this->idField)) {
72+
$this->associationIdReader = new self($om, $om->getClassMetadata(
73+
$classMetadata->getAssociationTargetClass($this->idField)
74+
));
75+
76+
$this->singleId = $this->associationIdReader->isSingleId();
77+
$this->intId = $this->associationIdReader->isIntId();
78+
}
6379
}
6480

6581
/**
@@ -108,7 +124,13 @@ public function getIdValue($object)
108124

109125
$this->om->initializeObject($object);
110126

111-
return current($this->classMetadata->getIdentifierValues($object));
127+
$idValue = current($this->classMetadata->getIdentifierValues($object));
128+
129+
if ($this->associationIdReader) {
130+
$idValue = $this->associationIdReader->getIdValue($idValue);
131+
}
132+
133+
return $idValue;
112134
}
113135

114136
/**
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
13+
14+
use Doctrine\ORM\Mapping\Id;
15+
use Doctrine\ORM\Mapping\Column;
16+
use Doctrine\ORM\Mapping\Entity;
17+
use Doctrine\ORM\Mapping\OneToOne;
18+
19+
/** @Entity */
20+
class SingleAssociationToIntIdEntity
21+
{
22+
/** @Id @OneToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"ALL"}) */
23+
protected $entity;
24+
25+
/** @Column(type="string", nullable=true) */
26+
public $name;
27+
28+
public function __construct(SingleIntIdNoToStringEntity $entity, $name)
29+
{
30+
$this->entity = $entity;
31+
$this->name = $name;
32+
}
33+
34+
public function __toString()
35+
{
36+
return (string) $this->name;
37+
}
38+
}

Tests/Form/Type/EntityTypeTest.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@
3030
use Symfony\Component\Form\Forms;
3131
use Symfony\Component\Form\Test\TypeTestCase;
3232
use Symfony\Component\PropertyAccess\PropertyAccess;
33+
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity;
34+
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity;
3335

3436
class EntityTypeTest extends TypeTestCase
3537
{
3638
const ITEM_GROUP_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity';
3739
const SINGLE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity';
40+
const SINGLE_IDENT_NO_TO_STRING_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity';
3841
const SINGLE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity';
42+
const SINGLE_ASSOC_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity';
3943
const COMPOSITE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity';
4044
const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity';
4145

@@ -60,7 +64,9 @@ protected function setUp()
6064
$classes = array(
6165
$this->em->getClassMetadata(self::ITEM_GROUP_CLASS),
6266
$this->em->getClassMetadata(self::SINGLE_IDENT_CLASS),
67+
$this->em->getClassMetadata(self::SINGLE_IDENT_NO_TO_STRING_CLASS),
6368
$this->em->getClassMetadata(self::SINGLE_STRING_IDENT_CLASS),
69+
$this->em->getClassMetadata(self::SINGLE_ASSOC_IDENT_CLASS),
6470
$this->em->getClassMetadata(self::COMPOSITE_IDENT_CLASS),
6571
$this->em->getClassMetadata(self::COMPOSITE_STRING_IDENT_CLASS),
6672
);
@@ -304,6 +310,31 @@ public function testSubmitSingleNonExpandedSingleIdentifier()
304310
$this->assertSame('2', $field->getViewData());
305311
}
306312

313+
public function testSubmitSingleNonExpandedSingleAssocIdentifier()
314+
{
315+
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
316+
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
317+
318+
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
319+
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
320+
321+
$this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2));
322+
323+
$field = $this->factory->createNamed('name', 'entity', null, array(
324+
'multiple' => false,
325+
'expanded' => false,
326+
'em' => 'default',
327+
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
328+
'choice_label' => 'name',
329+
));
330+
331+
$field->submit('2');
332+
333+
$this->assertTrue($field->isSynchronized());
334+
$this->assertSame($entity2, $field->getData());
335+
$this->assertSame('2', $field->getViewData());
336+
}
337+
307338
public function testSubmitSingleNonExpandedCompositeIdentifier()
308339
{
309340
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
@@ -352,6 +383,35 @@ public function testSubmitMultipleNonExpandedSingleIdentifier()
352383
$this->assertSame(array('1', '3'), $field->getViewData());
353384
}
354385

386+
public function testSubmitMultipleNonExpandedSingleAssocIdentifier()
387+
{
388+
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
389+
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
390+
$innerEntity3 = new SingleIntIdNoToStringEntity(3, 'InBaz');
391+
392+
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
393+
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
394+
$entity3 = new SingleAssociationToIntIdEntity($innerEntity3, 'Baz');
395+
396+
$this->persist(array($innerEntity1, $innerEntity2, $innerEntity3, $entity1, $entity2, $entity3));
397+
398+
$field = $this->factory->createNamed('name', 'entity', null, array(
399+
'multiple' => true,
400+
'expanded' => false,
401+
'em' => 'default',
402+
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
403+
'choice_label' => 'name',
404+
));
405+
406+
$field->submit(array('1', '3'));
407+
408+
$expected = new ArrayCollection(array($entity1, $entity3));
409+
410+
$this->assertTrue($field->isSynchronized());
411+
$this->assertEquals($expected, $field->getData());
412+
$this->assertSame(array('1', '3'), $field->getViewData());
413+
}
414+
355415
public function testSubmitMultipleNonExpandedSingleIdentifierForExistingData()
356416
{
357417
$entity1 = new SingleIntIdEntity(1, 'Foo');
@@ -611,6 +671,29 @@ public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier()
611671
$this->assertNull($field->getData());
612672
}
613673

674+
public function testDisallowChoicesThatAreNotIncludedChoicesSingleAssocIdentifier()
675+
{
676+
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
677+
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
678+
679+
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
680+
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
681+
682+
$this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2));
683+
684+
$field = $this->factory->createNamed('name', 'entity', null, array(
685+
'em' => 'default',
686+
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
687+
'choices' => array($entity1, $entity2),
688+
'choice_label' => 'name',
689+
));
690+
691+
$field->submit('3');
692+
693+
$this->assertFalse($field->isSynchronized());
694+
$this->assertNull($field->getData());
695+
}
696+
614697
public function testDisallowChoicesThatAreNotIncludedChoicesCompositeIdentifier()
615698
{
616699
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
@@ -656,6 +739,34 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifie
656739
$this->assertNull($field->getData());
657740
}
658741

742+
public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleAssocIdentifier()
743+
{
744+
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
745+
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
746+
$innerEntity3 = new SingleIntIdNoToStringEntity(3, 'InBaz');
747+
748+
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
749+
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
750+
$entity3 = new SingleAssociationToIntIdEntity($innerEntity3, 'Baz');
751+
752+
$this->persist(array($innerEntity1, $innerEntity2, $innerEntity3, $entity1, $entity2, $entity3));
753+
754+
$repository = $this->em->getRepository(self::SINGLE_ASSOC_IDENT_CLASS);
755+
756+
$field = $this->factory->createNamed('name', 'entity', null, array(
757+
'em' => 'default',
758+
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
759+
'query_builder' => $repository->createQueryBuilder('e')
760+
->where('e.entity IN (1, 2)'),
761+
'choice_label' => 'name',
762+
));
763+
764+
$field->submit('3');
765+
766+
$this->assertFalse($field->isSynchronized());
767+
$this->assertNull($field->getData());
768+
}
769+
659770
public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingleIdentifier()
660771
{
661772
$entity1 = new SingleIntIdEntity(1, 'Foo');

0 commit comments

Comments
 (0)