merged branch ericclemmons/1735-entity_choice_list_group_by (PR #2464)
Commits -------6cb7acf
CS - camelCase & curly bracesd9b7abb
Added EntityChoiceList test for `group_by` and invalid, deep property pathse6554d6
Removed Closure support from group_by (PropertyPath strings only)037933a
CS - (String) renamed to (string)7ad0f05
Added group_by test for EntityType882482a
Added group_by tests for EntityChoiceList040e988
`EntityChoiceList` now supports grouping of entities by property path or closureb171a6a
Added `group_by` to EntityType Discussion ---------- [Doctrine] [Form] EntityType+EntityChoiceList supports grouping choices Bug fix: no Feature addition: yes Backwards compatibility break: no Symfony2 tests pass: yes Fixes the following tickets: #1735 Per the discussion in #1735, `EntityType` does not immediately support grouping options, though I updated support for it in `EntityChoiceList` infb9d951b1d
. This PR accomplishes the following: * Adds optional `group_by` property to `EntityType` that supports either a `PropertyPath` or a `\Closure` that is evaluated on the entity choices * Support for groups is added via the constructor in `EntityChoiceList` * Groups are created prior to `EntityChoiceList#loadEntities` via a new `groupEntities` function * Added tests for `EntityChoiceList` * Added test for `EntityType` `group_by` support *There is an alternative version that only modifies `EntityType`, but that requires the addition of `EntityType#buildView(...)`, which is messy, IMO: https://github.com/ericclemmons/symfony/compare/master...1735-entity_type_group_by* --------------------------------------------------------------------------- by fabpot at 2011/10/25 01:48:23 -0700 ping @beberlei --------------------------------------------------------------------------- by beberlei at 2011/10/25 03:06:05 -0700 I didnt run the tests, but generally this looks very good and is a good extension. --------------------------------------------------------------------------- by beberlei at 2011/11/01 06:25:09 -0700 @fabpot i revewied this and it looks very good, tests all pass, i think this is a very nice addition.
This commit is contained in:
commit
3f38b9c537
@ -81,7 +81,14 @@ class EntityChoiceList extends ArrayChoiceList
|
||||
|
||||
private $propertyPath;
|
||||
|
||||
public function __construct(EntityManager $em, $class, $property = null, $queryBuilder = null, $choices = array())
|
||||
/**
|
||||
* Closure or PropertyPath string on Entity to use for grouping of entities
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $groupBy;
|
||||
|
||||
public function __construct(EntityManager $em, $class, $property = null, $queryBuilder = null, $choices = array(), $groupBy = null)
|
||||
{
|
||||
// If a query builder was passed, it must be a closure or QueryBuilder
|
||||
// instance
|
||||
@ -102,6 +109,7 @@ class EntityChoiceList extends ArrayChoiceList
|
||||
$this->queryBuilder = $queryBuilder;
|
||||
$this->unitOfWork = $em->getUnitOfWork();
|
||||
$this->identifier = $em->getClassMetadata($class)->getIdentifierFieldNames();
|
||||
$this->groupBy = $groupBy;
|
||||
|
||||
// The property option defines, which property (path) is used for
|
||||
// displaying entities as strings
|
||||
@ -138,11 +146,39 @@ class EntityChoiceList extends ArrayChoiceList
|
||||
$this->choices = array();
|
||||
$this->entities = array();
|
||||
|
||||
if ($this->groupBy) {
|
||||
$entities = $this->groupEntities($entities, $this->groupBy);
|
||||
}
|
||||
|
||||
$this->loadEntities($entities);
|
||||
|
||||
return $this->choices;
|
||||
}
|
||||
|
||||
private function groupEntities($entities, $groupBy)
|
||||
{
|
||||
$grouped = array();
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
// Get group name from property path
|
||||
try {
|
||||
$path = new PropertyPath($groupBy);
|
||||
$group = (string) $path->getValue($entity);
|
||||
} catch (UnexpectedTypeException $e) {
|
||||
// PropertyPath cannot traverse entity
|
||||
$group = null;
|
||||
}
|
||||
|
||||
if (empty($group)) {
|
||||
$grouped[] = $entity;
|
||||
} else {
|
||||
$grouped[$group][] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert entities into choices with support for groups
|
||||
*
|
||||
|
@ -48,6 +48,7 @@ class EntityType extends AbstractType
|
||||
'property' => null,
|
||||
'query_builder' => null,
|
||||
'choices' => array(),
|
||||
'group_by' => null,
|
||||
);
|
||||
|
||||
$options = array_replace($defaultOptions, $options);
|
||||
@ -58,7 +59,8 @@ class EntityType extends AbstractType
|
||||
$options['class'],
|
||||
$options['property'],
|
||||
$options['query_builder'],
|
||||
$options['choices']
|
||||
$options['choices'],
|
||||
$options['group_by']
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Bridge\Doctrine\Form\Fixtures;
|
||||
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
/** @Entity */
|
||||
class ItemGroupEntity
|
||||
{
|
||||
/** @Id @Column(type="integer") */
|
||||
protected $id;
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
public $name;
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
public $groupName;
|
||||
|
||||
public function __construct($id, $name, $groupName)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->groupName = $groupName;
|
||||
}
|
||||
}
|
@ -12,14 +12,18 @@
|
||||
namespace Symfony\Tests\Bridge\Doctrine\Form\ChoiceList;
|
||||
|
||||
require_once __DIR__.'/../DoctrineOrmTestCase.php';
|
||||
require_once __DIR__.'/../../Fixtures/ItemGroupEntity.php';
|
||||
require_once __DIR__.'/../../Fixtures/SingleIdentEntity.php';
|
||||
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\DoctrineOrmTestCase;
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\Fixtures\ItemGroupEntity;
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleIdentEntity;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
|
||||
|
||||
class EntityChoiceListTest extends DoctrineOrmTestCase
|
||||
{
|
||||
const ITEM_GROUP_CLASS = 'Symfony\Tests\Bridge\Doctrine\Form\Fixtures\ItemGroupEntity';
|
||||
|
||||
const SINGLE_IDENT_CLASS = 'Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleIdentEntity';
|
||||
|
||||
const COMPOSITE_IDENT_CLASS = 'Symfony\Tests\Bridge\Doctrine\Form\Fixtures\CompositeIdentEntity';
|
||||
@ -113,4 +117,63 @@ class EntityChoiceListTest extends DoctrineOrmTestCase
|
||||
'group2' => array(2 => 'Bar')
|
||||
), $choiceList->getChoices());
|
||||
}
|
||||
|
||||
public function testGroupBySupportsString()
|
||||
{
|
||||
$item1 = new ItemGroupEntity(1, 'Foo', 'Group1');
|
||||
$item2 = new ItemGroupEntity(2, 'Bar', 'Group1');
|
||||
$item3 = new ItemGroupEntity(3, 'Baz', 'Group2');
|
||||
$item4 = new ItemGroupEntity(4, 'Boo!', null);
|
||||
|
||||
$this->em->persist($item1);
|
||||
$this->em->persist($item2);
|
||||
$this->em->persist($item3);
|
||||
$this->em->persist($item4);
|
||||
|
||||
$choiceList = new EntityChoiceList(
|
||||
$this->em,
|
||||
self::ITEM_GROUP_CLASS,
|
||||
'name',
|
||||
null,
|
||||
array(
|
||||
$item1,
|
||||
$item2,
|
||||
$item3,
|
||||
$item4,
|
||||
),
|
||||
'groupName'
|
||||
);
|
||||
|
||||
$this->assertEquals(array(
|
||||
'Group1' => array(1 => 'Foo', '2' => 'Bar'),
|
||||
'Group2' => array(3 => 'Baz'),
|
||||
'4' => 'Boo!'
|
||||
), $choiceList->getChoices('choices'));
|
||||
}
|
||||
|
||||
public function testGroupByInvalidPropertyPathReturnsFlatChoices()
|
||||
{
|
||||
$item1 = new ItemGroupEntity(1, 'Foo', 'Group1');
|
||||
$item2 = new ItemGroupEntity(2, 'Bar', 'Group1');
|
||||
|
||||
$this->em->persist($item1);
|
||||
$this->em->persist($item2);
|
||||
|
||||
$choiceList = new EntityChoiceList(
|
||||
$this->em,
|
||||
self::ITEM_GROUP_CLASS,
|
||||
'name',
|
||||
null,
|
||||
array(
|
||||
$item1,
|
||||
$item2,
|
||||
),
|
||||
'groupName.child.that.does.not.exist'
|
||||
);
|
||||
|
||||
$this->assertEquals(array(
|
||||
1 => 'Foo',
|
||||
2 => 'Bar'
|
||||
), $choiceList->getChoices('choices'));
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Tests\Bridge\Doctrine\Form\Type;
|
||||
|
||||
require_once __DIR__.'/../DoctrineOrmTestCase.php';
|
||||
require_once __DIR__.'/../../Fixtures/ItemGroupEntity.php';
|
||||
require_once __DIR__.'/../../Fixtures/SingleIdentEntity.php';
|
||||
require_once __DIR__.'/../../Fixtures/SingleStringIdentEntity.php';
|
||||
require_once __DIR__.'/../../Fixtures/CompositeIdentEntity.php';
|
||||
@ -20,6 +21,7 @@ require_once __DIR__.'/../../Fixtures/CompositeStringIdentEntity.php';
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Tests\Component\Form\Extension\Core\Type\TypeTestCase;
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\DoctrineOrmTestCase;
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\Fixtures\ItemGroupEntity;
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleIdentEntity;
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleStringIdentEntity;
|
||||
use Symfony\Tests\Bridge\Doctrine\Form\Fixtures\CompositeIdentEntity;
|
||||
@ -31,6 +33,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class EntityTypeTest extends TypeTestCase
|
||||
{
|
||||
const ITEM_GROUP_CLASS = 'Symfony\Tests\Bridge\Doctrine\Form\Fixtures\ItemGroupEntity';
|
||||
const SINGLE_IDENT_CLASS = 'Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleIdentEntity';
|
||||
const SINGLE_STRING_IDENT_CLASS = 'Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleStringIdentEntity';
|
||||
const COMPOSITE_IDENT_CLASS = 'Symfony\Tests\Bridge\Doctrine\Form\Fixtures\CompositeIdentEntity';
|
||||
@ -50,6 +53,7 @@ class EntityTypeTest extends TypeTestCase
|
||||
|
||||
$schemaTool = new SchemaTool($this->em);
|
||||
$classes = array(
|
||||
$this->em->getClassMetadata(self::ITEM_GROUP_CLASS),
|
||||
$this->em->getClassMetadata(self::SINGLE_IDENT_CLASS),
|
||||
$this->em->getClassMetadata(self::SINGLE_STRING_IDENT_CLASS),
|
||||
$this->em->getClassMetadata(self::COMPOSITE_IDENT_CLASS),
|
||||
@ -461,6 +465,33 @@ class EntityTypeTest extends TypeTestCase
|
||||
$this->assertEquals(2, $field->getClientData());
|
||||
}
|
||||
|
||||
public function testGroupByChoices()
|
||||
{
|
||||
$item1 = new ItemGroupEntity(1, 'Foo', 'Group1');
|
||||
$item2 = new ItemGroupEntity(2, 'Bar', 'Group1');
|
||||
$item3 = new ItemGroupEntity(3, 'Baz', 'Group2');
|
||||
$item4 = new ItemGroupEntity(4, 'Boo!', null);
|
||||
|
||||
$this->persist(array($item1, $item2, $item3, $item4));
|
||||
|
||||
$field = $this->factory->createNamed('entity', 'name', null, array(
|
||||
'em' => 'default',
|
||||
'class' => self::ITEM_GROUP_CLASS,
|
||||
'choices' => array($item1, $item2, $item3, $item4),
|
||||
'property' => 'name',
|
||||
'group_by' => 'groupName',
|
||||
));
|
||||
|
||||
$field->bind('2');
|
||||
|
||||
$this->assertEquals(2, $field->getClientData());
|
||||
$this->assertEquals(array(
|
||||
'Group1' => array(1 => 'Foo', '2' => 'Bar'),
|
||||
'Group2' => array(3 => 'Baz'),
|
||||
'4' => 'Boo!'
|
||||
), $field->createView()->get('choices'));
|
||||
}
|
||||
|
||||
public function testDisallowChoicesThatAreNotIncluded_choicesSingleIdentifier()
|
||||
{
|
||||
$entity1 = new SingleIdentEntity(1, 'Foo');
|
||||
|
Reference in New Issue
Block a user