bug #15251 [DoctrineBridge][Form] Fix IdReader when indexing by primary foreign key (giosh94mhz)

This PR was merged into the 2.7 branch.

Discussion
----------

[DoctrineBridge][Form] Fix IdReader when indexing by primary foreign key

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

Port to Symfony 2.7 of PR #14372 . The `IdReader` class can now resolve association and determine the real id value.

There is still room for improvement, though. Since I've added a `new IdReader` in the constructor, it is better to declare `IdReader` as `final` just to be safe.

PS: sorry to keep you waiting, @webmozart . When merging both commit don't forget to add the `@deprecated` annotation, and that `SingleAssociationToIntIdEntity.php` is duplicated.

Commits
-------

799d0e0 [DoctrineBridge][Form] Fix IdReader when indexing by primary foreign key
This commit is contained in:
Fabien Potencier 2015-08-12 11:59:37 +02:00
commit d3a1cb1837
2 changed files with 134 additions and 1 deletions

View File

@ -19,6 +19,7 @@ use Symfony\Component\Form\Exception\RuntimeException;
* A utility for reading object IDs.
*
* @since 1.0
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal This class is meant for internal use only.
@ -50,6 +51,11 @@ class IdReader
*/
private $idField;
/**
* @var IdReader|null
*/
private $associationIdReader;
public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
{
$ids = $classMetadata->getIdentifierFieldNames();
@ -60,6 +66,16 @@ class IdReader
$this->singleId = 1 === count($ids);
$this->intId = $this->singleId && in_array($idType, array('integer', 'smallint', 'bigint'));
$this->idField = current($ids);
// single field association are resolved, since the schema column could be an int
if ($this->singleId && $classMetadata->hasAssociation($this->idField)) {
$this->associationIdReader = new self($om, $om->getClassMetadata(
$classMetadata->getAssociationTargetClass($this->idField)
));
$this->singleId = $this->associationIdReader->isSingleId();
$this->intId = $this->associationIdReader->isIntId();
}
}
/**
@ -108,7 +124,13 @@ class IdReader
$this->om->initializeObject($object);
return current($this->classMetadata->getIdentifierValues($object));
$idValue = current($this->classMetadata->getIdentifierValues($object));
if ($this->associationIdReader) {
$idValue = $this->associationIdReader->getIdValue($idValue);
}
return $idValue;
}
/**

View File

@ -30,12 +30,16 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity;
class EntityTypeTest extends TypeTestCase
{
const ITEM_GROUP_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity';
const SINGLE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity';
const SINGLE_IDENT_NO_TO_STRING_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity';
const SINGLE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity';
const SINGLE_ASSOC_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity';
const COMPOSITE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity';
const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity';
@ -60,7 +64,9 @@ class EntityTypeTest extends TypeTestCase
$classes = array(
$this->em->getClassMetadata(self::ITEM_GROUP_CLASS),
$this->em->getClassMetadata(self::SINGLE_IDENT_CLASS),
$this->em->getClassMetadata(self::SINGLE_IDENT_NO_TO_STRING_CLASS),
$this->em->getClassMetadata(self::SINGLE_STRING_IDENT_CLASS),
$this->em->getClassMetadata(self::SINGLE_ASSOC_IDENT_CLASS),
$this->em->getClassMetadata(self::COMPOSITE_IDENT_CLASS),
$this->em->getClassMetadata(self::COMPOSITE_STRING_IDENT_CLASS),
);
@ -304,6 +310,31 @@ class EntityTypeTest extends TypeTestCase
$this->assertSame('2', $field->getViewData());
}
public function testSubmitSingleNonExpandedSingleAssocIdentifier()
{
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
$this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2));
$field = $this->factory->createNamed('name', 'entity', null, array(
'multiple' => false,
'expanded' => false,
'em' => 'default',
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
'choice_label' => 'name',
));
$field->submit('2');
$this->assertTrue($field->isSynchronized());
$this->assertSame($entity2, $field->getData());
$this->assertSame('2', $field->getViewData());
}
public function testSubmitSingleNonExpandedCompositeIdentifier()
{
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
@ -352,6 +383,35 @@ class EntityTypeTest extends TypeTestCase
$this->assertSame(array('1', '3'), $field->getViewData());
}
public function testSubmitMultipleNonExpandedSingleAssocIdentifier()
{
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
$innerEntity3 = new SingleIntIdNoToStringEntity(3, 'InBaz');
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
$entity3 = new SingleAssociationToIntIdEntity($innerEntity3, 'Baz');
$this->persist(array($innerEntity1, $innerEntity2, $innerEntity3, $entity1, $entity2, $entity3));
$field = $this->factory->createNamed('name', 'entity', null, array(
'multiple' => true,
'expanded' => false,
'em' => 'default',
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
'choice_label' => 'name',
));
$field->submit(array('1', '3'));
$expected = new ArrayCollection(array($entity1, $entity3));
$this->assertTrue($field->isSynchronized());
$this->assertEquals($expected, $field->getData());
$this->assertSame(array('1', '3'), $field->getViewData());
}
public function testSubmitMultipleNonExpandedSingleIdentifierForExistingData()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');
@ -636,6 +696,29 @@ class EntityTypeTest extends TypeTestCase
$this->assertNull($field->getData());
}
public function testDisallowChoicesThatAreNotIncludedChoicesSingleAssocIdentifier()
{
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
$this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2));
$field = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
'choices' => array($entity1, $entity2),
'choice_label' => 'name',
));
$field->submit('3');
$this->assertFalse($field->isSynchronized());
$this->assertNull($field->getData());
}
public function testDisallowChoicesThatAreNotIncludedChoicesCompositeIdentifier()
{
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
@ -681,6 +764,34 @@ class EntityTypeTest extends TypeTestCase
$this->assertNull($field->getData());
}
public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleAssocIdentifier()
{
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
$innerEntity2 = new SingleIntIdNoToStringEntity(2, 'InBar');
$innerEntity3 = new SingleIntIdNoToStringEntity(3, 'InBaz');
$entity1 = new SingleAssociationToIntIdEntity($innerEntity1, 'Foo');
$entity2 = new SingleAssociationToIntIdEntity($innerEntity2, 'Bar');
$entity3 = new SingleAssociationToIntIdEntity($innerEntity3, 'Baz');
$this->persist(array($innerEntity1, $innerEntity2, $innerEntity3, $entity1, $entity2, $entity3));
$repository = $this->em->getRepository(self::SINGLE_ASSOC_IDENT_CLASS);
$field = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_ASSOC_IDENT_CLASS,
'query_builder' => $repository->createQueryBuilder('e')
->where('e.entity IN (1, 2)'),
'choice_label' => 'name',
));
$field->submit('3');
$this->assertFalse($field->isSynchronized());
$this->assertNull($field->getData());
}
public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingleIdentifier()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');