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:
commit
d3a1cb1837
@ -19,6 +19,7 @@ use Symfony\Component\Form\Exception\RuntimeException;
|
|||||||
* A utility for reading object IDs.
|
* A utility for reading object IDs.
|
||||||
*
|
*
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
|
*
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
*
|
*
|
||||||
* @internal This class is meant for internal use only.
|
* @internal This class is meant for internal use only.
|
||||||
@ -50,6 +51,11 @@ class IdReader
|
|||||||
*/
|
*/
|
||||||
private $idField;
|
private $idField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var IdReader|null
|
||||||
|
*/
|
||||||
|
private $associationIdReader;
|
||||||
|
|
||||||
public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
|
public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
|
||||||
{
|
{
|
||||||
$ids = $classMetadata->getIdentifierFieldNames();
|
$ids = $classMetadata->getIdentifierFieldNames();
|
||||||
@ -60,6 +66,16 @@ class IdReader
|
|||||||
$this->singleId = 1 === count($ids);
|
$this->singleId = 1 === count($ids);
|
||||||
$this->intId = $this->singleId && in_array($idType, array('integer', 'smallint', 'bigint'));
|
$this->intId = $this->singleId && in_array($idType, array('integer', 'smallint', 'bigint'));
|
||||||
$this->idField = current($ids);
|
$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);
|
$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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,12 +30,16 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
|||||||
use Symfony\Component\Form\Forms;
|
use Symfony\Component\Form\Forms;
|
||||||
use Symfony\Component\Form\Test\TypeTestCase;
|
use Symfony\Component\Form\Test\TypeTestCase;
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||||
|
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity;
|
||||||
|
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity;
|
||||||
|
|
||||||
class EntityTypeTest extends TypeTestCase
|
class EntityTypeTest extends TypeTestCase
|
||||||
{
|
{
|
||||||
const ITEM_GROUP_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity';
|
const ITEM_GROUP_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity';
|
||||||
const SINGLE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity';
|
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_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_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity';
|
||||||
const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity';
|
const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity';
|
||||||
|
|
||||||
@ -60,7 +64,9 @@ class EntityTypeTest extends TypeTestCase
|
|||||||
$classes = array(
|
$classes = array(
|
||||||
$this->em->getClassMetadata(self::ITEM_GROUP_CLASS),
|
$this->em->getClassMetadata(self::ITEM_GROUP_CLASS),
|
||||||
$this->em->getClassMetadata(self::SINGLE_IDENT_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_STRING_IDENT_CLASS),
|
||||||
|
$this->em->getClassMetadata(self::SINGLE_ASSOC_IDENT_CLASS),
|
||||||
$this->em->getClassMetadata(self::COMPOSITE_IDENT_CLASS),
|
$this->em->getClassMetadata(self::COMPOSITE_IDENT_CLASS),
|
||||||
$this->em->getClassMetadata(self::COMPOSITE_STRING_IDENT_CLASS),
|
$this->em->getClassMetadata(self::COMPOSITE_STRING_IDENT_CLASS),
|
||||||
);
|
);
|
||||||
@ -304,6 +310,31 @@ class EntityTypeTest extends TypeTestCase
|
|||||||
$this->assertSame('2', $field->getViewData());
|
$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()
|
public function testSubmitSingleNonExpandedCompositeIdentifier()
|
||||||
{
|
{
|
||||||
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
|
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
|
||||||
@ -352,6 +383,35 @@ class EntityTypeTest extends TypeTestCase
|
|||||||
$this->assertSame(array('1', '3'), $field->getViewData());
|
$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()
|
public function testSubmitMultipleNonExpandedSingleIdentifierForExistingData()
|
||||||
{
|
{
|
||||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||||
@ -636,6 +696,29 @@ class EntityTypeTest extends TypeTestCase
|
|||||||
$this->assertNull($field->getData());
|
$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()
|
public function testDisallowChoicesThatAreNotIncludedChoicesCompositeIdentifier()
|
||||||
{
|
{
|
||||||
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
|
$entity1 = new CompositeIntIdEntity(10, 20, 'Foo');
|
||||||
@ -681,6 +764,34 @@ class EntityTypeTest extends TypeTestCase
|
|||||||
$this->assertNull($field->getData());
|
$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()
|
public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingleIdentifier()
|
||||||
{
|
{
|
||||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||||
|
Reference in New Issue
Block a user