-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Form] Refactored choice lists to support dynamic label, value, index and attribute generation #14050
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
[Form] Refactored choice lists to support dynamic label, value, index and attribute generation #14050
Changes from 1 commit
03efce1
3846b37
e6739bf
a289deb
26eba76
d6179c8
1d89922
7e0960d
94d18e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
… when caching the choice loader
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
|
||
use Doctrine\Common\Persistence\ManagerRegistry; | ||
use Doctrine\Common\Persistence\ObjectManager; | ||
use Doctrine\ORM\QueryBuilder; | ||
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; | ||
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; | ||
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; | ||
|
@@ -23,6 +24,7 @@ | |
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; | ||
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; | ||
use Symfony\Component\Form\Exception\RuntimeException; | ||
use Symfony\Component\Form\Exception\UnexpectedTypeException; | ||
use Symfony\Component\Form\FormBuilderInterface; | ||
use Symfony\Component\OptionsResolver\Options; | ||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
@@ -71,20 +73,24 @@ public function configureOptions(OptionsResolver $resolver) | |
$choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders, $type) { | ||
// Unless the choices are given explicitly, load them on demand | ||
if (null === $options['choices']) { | ||
// Don't cache if the query builder is constructed dynamically | ||
if ($options['query_builder'] instanceof \Closure) { | ||
$hash = null; | ||
} else { | ||
$hash = CachingFactoryDecorator::generateHash(array( | ||
$options['em'], | ||
$options['class'], | ||
$options['query_builder'], | ||
$options['loader'], | ||
)); | ||
|
||
if (isset($choiceLoaders[$hash])) { | ||
return $choiceLoaders[$hash]; | ||
} | ||
// We consider two query builders with an equal SQL string and | ||
// equal parameters to be equal | ||
$qbParts = $options['query_builder'] | ||
? array( | ||
$options['query_builder']->getQuery()->getSQL(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. query builder option has a normalizer, so it cannot be a closure here |
||
$options['query_builder']->getParameters()->toArray(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. query builder parameters can contain objects. Is this handled properly ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the hash generation function traverses the array and replaces objects by their hashes. |
||
) | ||
: null; | ||
|
||
$hash = CachingFactoryDecorator::generateHash(array( | ||
$options['em'], | ||
$options['class'], | ||
$qbParts, | ||
$options['loader'], | ||
)); | ||
|
||
if (isset($choiceLoaders[$hash])) { | ||
return $choiceLoaders[$hash]; | ||
} | ||
|
||
if ($options['loader']) { | ||
|
@@ -96,18 +102,14 @@ public function configureOptions(OptionsResolver $resolver) | |
$entityLoader = $type->getLoader($options['em'], $queryBuilder, $options['class']); | ||
} | ||
|
||
$choiceLoader = new DoctrineChoiceLoader( | ||
$choiceLoaders[$hash] = new DoctrineChoiceLoader( | ||
$choiceListFactory, | ||
$options['em'], | ||
$options['class'], | ||
$entityLoader | ||
); | ||
|
||
if (null !== $hash) { | ||
$choiceLoaders[$hash] = $choiceLoader; | ||
} | ||
|
||
return $choiceLoader; | ||
return $choiceLoaders[$hash]; | ||
} | ||
}; | ||
|
||
|
@@ -199,6 +201,20 @@ public function configureOptions(OptionsResolver $resolver) | |
return $entitiesById; | ||
}; | ||
|
||
// Invoke the query builder closure so that we can cache choice lists | ||
// for equal query builders | ||
$queryBuilderNormalizer = function (Options $options, $queryBuilder) { | ||
if (is_callable($queryBuilder)) { | ||
$queryBuilder = call_user_func($queryBuilder, $options['em']->getRepository($options['class'])); | ||
|
||
if (!$queryBuilder instanceof QueryBuilder) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is breaking things for other Doctrine projects reusing the DoctrineType (this is why the logic doing this conversion was in the project-specific classes) |
||
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); | ||
} | ||
} | ||
|
||
return $queryBuilder; | ||
}; | ||
|
||
$resolver->setDefaults(array( | ||
'em' => null, | ||
'property' => null, // deprecated, use "choice_label" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs trigger via normalizer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
|
@@ -216,9 +232,11 @@ public function configureOptions(OptionsResolver $resolver) | |
|
||
$resolver->setNormalizer('em', $emNormalizer); | ||
$resolver->setNormalizer('choices', $choicesNormalizer); | ||
$resolver->setNormalizer('query_builder', $queryBuilderNormalizer); | ||
|
||
$resolver->setAllowedTypes('em', array('null', 'string', 'Doctrine\Common\Persistence\ObjectManager')); | ||
$resolver->setAllowedTypes('loader', array('null', 'Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface')); | ||
$resolver->setAllowedTypes('query_builder', array('null', 'callable', 'Doctrine\ORM\QueryBuilder')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BC break for other Doctrine bundles here |
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,11 +28,7 @@ class EntityType extends DoctrineType | |
*/ | ||
public function getLoader(ObjectManager $manager, $queryBuilder, $class) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO the $queryBuilder should be typehinted now. The closure is not passed to getLoader anymore, so for people relying on it, it would break anyway. Maybe we can change the signature now since people didn't overwrite the method probably. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nvm, since there is not common interface for doctrine query builders for both ORM and ODM, we cannot add a typehint I guess. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you said yourself, that would be breaking BC. |
||
{ | ||
return new ORMQueryBuilderLoader( | ||
$queryBuilder, | ||
$manager, | ||
$class | ||
); | ||
return new ORMQueryBuilderLoader($queryBuilder, $manager, $class); | ||
} | ||
|
||
public function getName() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing a closure and the params manager and class should be deprecated then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed