Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[2.7][Form] Refactored choice lists to support dynamic labels, values and attributes #12148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@

namespace Symfony\Bridge\Doctrine\Form\ChoiceList;

use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\Form\Exception\StringCastException;
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;

/**
* A choice list presenting a list of Doctrine entities as choices.
*
* @author Bernhard Schussek <[email protected]>
*
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
* Use {@link EntityChoiceLoader} instead.
*/
class EntityChoiceList extends ObjectChoiceList
{
Expand Down
267 changes: 267 additions & 0 deletions src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Form\ChoiceList;

use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\Exception\RuntimeException;

/**
* Loads choices using a Doctrine object manager.
*
* @author Bernhard Schussek <[email protected]>
*/
class EntityChoiceLoader implements ChoiceLoaderInterface
{
/**
* @var ChoiceListFactoryInterface
*/
private $factory;

/**
* @var ObjectManager
*/
private $manager;

/**
* @var string
*/
private $class;

/**
* @var ClassMetadata
*/
private $classMetadata;

/**
* @var null|EntityLoaderInterface
*/
private $entityLoader;

/**
* The identifier field, unless the identifier is composite
*
* @var null|string
*/
private $idField = null;

/**
* Whether to use the identifier for value generation
*
* @var bool
*/
private $compositeId = true;

/**
* @var ChoiceListInterface
*/
private $choiceList;

/**
* Returns the value of the identifier field of an entity.
*
* Doctrine must know about this entity, that is, the entity must already
* be persisted or added to the identity map before. Otherwise an
* exception is thrown.
*
* This method assumes that the entity has a single-column identifier and
* will return a single value instead of an array.
*
* @param object $object The entity for which to get the identifier
*
* @return int|string The identifier value
*
* @throws RuntimeException If the entity does not exist in Doctrine's identity map
*
* @internal Should not be accessed by user-land code. This method is public
* only to be usable as callback.
*/
public static function getIdValue(ObjectManager $om, ClassMetadata $classMetadata, $object)
{
if (!$om->contains($object)) {
throw new RuntimeException(
'Entities passed to the choice field must be managed. Maybe '.
'persist them in the entity manager?'
);
}

$om->initializeObject($object);

return current($classMetadata->getIdentifierValues($object));
}

/**
* Creates a new choice loader.
*
* Optionally, an implementation of {@link EntityLoaderInterface} can be
* passed which optimizes the entity loading for one of the Doctrine
* mapper implementations.
*
* @param ChoiceListFactoryInterface $factory The factory for creating
* the loaded choice list
* @param ObjectManager $manager The object manager
* @param string $class The entity class name
* @param null|EntityLoaderInterface $entityLoader The entity loader
*/
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, EntityLoaderInterface $entityLoader = null)
{
$this->factory = $factory;
$this->manager = $manager;
$this->classMetadata = $manager->getClassMetadata($class);
$this->class = $this->classMetadata->getName();
$this->entityLoader = $entityLoader;

$identifier = $this->classMetadata->getIdentifierFieldNames();

if (1 === count($identifier)) {
$this->idField = $identifier[0];
$this->compositeId = false;
}
}

/**
* {@inheritdoc}
*/
public function loadChoiceList($value = null)
{
if ($this->choiceList) {
return $this->choiceList;
}

$entities = $this->entityLoader
? $this->entityLoader->getEntities()
: $this->manager->getRepository($this->class)->findAll();

// If the class has a multi-column identifier, we cannot index the
// entities by their IDs
if ($this->compositeId) {
$this->choiceList = $this->factory->createListFromChoices($entities, $value);

return $this->choiceList;
}

// Index the entities by ID
$entitiesById = array();

foreach ($entities as $entity) {
$id = self::getIdValue($this->manager, $this->classMetadata, $entity);
$entitiesById[$id] = $entity;
}

$this->choiceList = $this->factory->createListFromChoices($entitiesById, $value);

return $this->choiceList;
}

/**
* Loads the values corresponding to the given entities.
*
* The values are returned with the same keys and in the same order as the
* corresponding entities in the given array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the entity as first and the array key as the second
* argument.
*
* @param array $entities An array of entities. Non-existing entities
* in this array are ignored
* @param null|callable $value The callable generating the choice values
*
* @return string[] An array of choice values
*/
public function loadValuesForChoices(array $entities, $value = null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you use inheritdoc I guess the parameter names must stay the same. So $choices instead of $entities.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added explicit doc blocks

{
// Performance optimization
if (empty($entities)) {
return array();
}

// Optimize performance for single-field identifiers. We already
// know that the IDs are used as values

// Attention: This optimization does not check choices for existence
if (!$this->choiceList && !$this->compositeId) {
$values = array();

// Maintain order and indices of the given entities
foreach ($entities as $i => $entity) {
if ($entity instanceof $this->class) {
// Make sure to convert to the right format
$values[$i] = (string) self::getIdValue($this->manager, $this->classMetadata, $entity);
}
}

return $values;
}

return $this->loadChoiceList($value)->getValuesForChoices($entities);
}

/**
* Loads the entities corresponding to the given values.
*
* The entities are returned with the same keys and in the same order as the
* corresponding values in the given array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the entity as first and the array key as the second
* argument.
*
* @param string[] $values An array of choice values. Non-existing
* values in this array are ignored
* @param null|callable $value The callable generating the choice values
*
* @return array An array of entities
*/
public function loadChoicesForValues(array $values, $value = null)
{
// Performance optimization
// Also prevents the generation of "WHERE id IN ()" queries through the
// entity loader. At least with MySQL and on the development machine
// this was tested on, no exception was thrown for such invalid
// statements, consequently no test fails when this code is removed.
// https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
if (empty($values)) {
return array();
}

// Optimize performance in case we have an entity loader and
// a single-field identifier
if (!$this->choiceList && !$this->compositeId && $this->entityLoader) {
$unorderedEntities = $this->entityLoader->getEntitiesByIds($this->idField, $values);
$entitiesById = array();
$entities = array();

// Maintain order and indices from the given $values
// An alternative approach to the following loop is to add the
// "INDEX BY" clause to the Doctrine query in the loader,
// but I'm not sure whether that's doable in a generic fashion.
foreach ($unorderedEntities as $entity) {
$id = self::getIdValue($this->manager, $this->classMetadata, $entity);
$entitiesById[$id] = $entity;
}

foreach ($values as $i => $id) {
if (isset($entitiesById[$id])) {
$entities[$i] = $entitiesById[$id];
}
}

return $entities;
}

return $this->loadChoiceList($value)->getChoicesForValues($values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
use Doctrine\ORM\EntityManager;

/**
* Getting Entities through the ORM QueryBuilder.
* Loads entities using a {@link QueryBuilder} instance.
*
* @author Benjamin Eberlei <[email protected]>
* @author Bernhard Schussek <[email protected]>
*/
class ORMQueryBuilderLoader implements EntityLoaderInterface
{
Expand Down
21 changes: 19 additions & 2 deletions src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,38 @@
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

class DoctrineOrmExtension extends AbstractExtension
{
protected $registry;

public function __construct(ManagerRegistry $registry)
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;

/**
* @var ChoiceListFactoryInterface
*/
private $choiceListFactory;

public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
{
$this->registry = $registry;
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
}

protected function loadTypes()
{
return array(
new EntityType($this->registry, PropertyAccess::createPropertyAccessor()),
new EntityType($this->registry, $this->propertyAccessor, $this->choiceListFactory),
);
}

Expand Down
Loading