diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 6369ea3634..8e3f148448 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.1.5 +----- + + * fixed caching of choice lists when EntityType is used with the "query_builder" option + 2.1.0 ----- diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php index 78594ed8ba..2e385a343f 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php @@ -392,7 +392,10 @@ class EntityChoiceList extends ObjectChoiceList private function getIdentifierValues($entity) { if (!$this->em->contains($entity)) { - throw new FormException('Entities passed to the choice field must be managed'); + throw new FormException( + 'Entities passed to the choice field must be managed. Maybe ' . + 'persist them in the entity manager?' + ); } $this->em->initializeObject($entity); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 14d88e3403..5ac205a1d7 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\Common\Persistence\ManagerRegistry; +use Symfony\Component\Form\Exception\FormException; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; @@ -130,7 +131,17 @@ abstract class DoctrineType extends AbstractType return $registry->getManager($em); } - return $registry->getManagerForClass($options['class']); + $em = $registry->getManagerForClass($options['class']); + + if (null === $em) { + throw new FormException(sprintf( + 'Class "%s" seems not to be a managed Doctrine entity. ' . + 'Did you forget to map it?', + $options['class'] + )); + } + + return $em; }; $resolver->setDefaults(array( diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 51b47be4dc..c9ffb95e9b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -11,26 +11,53 @@ namespace Symfony\Bridge\Doctrine\Form\Type; -use Doctrine\Common\Persistence\ObjectManager; +use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ORM\QueryBuilder; class EntityType extends DoctrineType { + /** + * @var array + */ + private $loaderCache = array(); + /** * Return the default loader object. * - * @param ObjectManager $manager - * @param mixed $queryBuilder - * @param string $class + * @param ObjectManager $manager + * @param QueryBuilder|\Closure $queryBuilder + * @param string $class + * * @return ORMQueryBuilderLoader + * + * @throws UnexpectedTypeException If the passed $queryBuilder is no \Closure + * and no QueryBuilder or if the closure + * does not return a QueryBuilder. */ public function getLoader(ObjectManager $manager, $queryBuilder, $class) { - return new ORMQueryBuilderLoader( - $queryBuilder, - $manager, - $class - ); + if ($queryBuilder instanceof \Closure) { + $queryBuilder = $queryBuilder($manager->getRepository($class)); + + if (!$queryBuilder instanceof QueryBuilder) { + throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); + } + } elseif (!$queryBuilder instanceof QueryBuilder) { + throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure'); + } + + // It is important to return the same loader for identical queries, + // otherwise the caching mechanism in DoctrineType does not work + // (which expects identical loaders for the cache to work) + $hash = md5($queryBuilder->getQuery()->getDQL()); + + if (!isset($this->loaderCache[$hash])) { + $this->loaderCache[$hash] = new ORMQueryBuilderLoader($queryBuilder); + } + + return $this->loaderCache[$hash]; } public function getName() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index ea068c24f3..e73dd11be0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\Type; use Symfony\Component\Form\Tests\FormPerformanceTestCase; +use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity; use Doctrine\ORM\Tools\SchemaTool; use Symfony\Bridge\Doctrine\Tests\DoctrineOrmTestCase; @@ -36,6 +37,9 @@ class EntityTypePerformanceTest extends FormPerformanceTestCase $manager->expects($this->any()) ->method('getManager') ->will($this->returnValue($this->em)); + $manager->expects($this->any()) + ->method('getManagerForClass') + ->will($this->returnValue($this->em)); return array( new CoreExtension(), @@ -109,4 +113,21 @@ class EntityTypePerformanceTest extends FormPerformanceTestCase $form->createView(); } } + + public function testCollapsedEntityFieldWithQueryBuilder() + { + $this->setMaxRunningTime(1); + + for ($i = 0; $i < 20; ++$i) { + $form = $this->factory->create('entity', null, array( + 'class' => self::ENTITY_CLASS, + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('e')->addOrderBy('e.id', 'DESC'); + } + )); + + // force loading of the choice list + $form->createView(); + } + } }