-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[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
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -129,6 +129,8 @@ public function __construct(ObjectManager $manager, $class, $labelPath = null, E | |||
628C } | ||||
|
||||
parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor); | ||||
|
||||
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader instead.', E_USER_DEPRECATED); | ||||
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. shouldn't we move this near the namespace declaration ? 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 do you mean? 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 way the trigger happens the first time the class is autoloaded 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. Fixed in #14201. |
||||
} | ||||
|
||||
/** | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,9 +37,14 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface | |
/** | ||
* Construct an ORM Query Builder Loader. | ||
* | ||
* @param QueryBuilder|\Closure $queryBuilder | ||
* @param EntityManager $manager | ||
* @param string $class | ||
* @param QueryBuilder|\Closure $queryBuilder The query builder or a closure | ||
* for creating the query builder. | ||
* Passing a closure is | ||
* deprecated and will not be | ||
* supported anymore as of | ||
* Symfony 3.0. | ||
* @param EntityManager $manager Deprecated. | ||
* @param string $class Deprecated. | ||
* | ||
* @throws UnexpectedTypeException | ||
*/ | ||
|
@@ -51,13 +56,16 @@ public function __construct($queryBuilder, $manager = null, $class = null) | |
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure'); | ||
} | ||
|
||
// This block is not executed anymore since Symfony 2.7. The query | ||
// builder closure is already invoked in DoctrineType | ||
if ($queryBuilder instanceof \Closure) { | ||
trigger_error('Passing a QueryBuilder closure to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); | ||
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 a very hard deprecation. All my entity fields in my project are relying on this feature for instance. Providing an alternative for the upgrade is absolutely necessary. The message does not explain what to do instead 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 only for the ORMQueryBuilderLoader which you probably do not use standalone. If you pass a closure to the correspoding option, it still works. 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. OK, nevermind. the form itself still accepts closures. the resolution has only been moved to a different place |
||
|
||
if (!$manager instanceof EntityManager) { | ||
throw new UnexpectedTypeException($manager, 'Doctrine\ORM\EntityManager'); | ||
} | ||
|
||
trigger_error('Passing an EntityManager to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); | ||
trigger_error('Passing a class to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); | ||
|
||
$queryBuilder = $queryBuilder($manager->getRepository($class)); | ||
|
||
if (!$queryBuilder instanceof QueryBuilder) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -116,27 +116,10 @@ public function configureOptions(OptionsResolver $resolver) | |
$choiceLoaders = &$this->choiceLoaders; | ||
$type = $this; | ||
|
||
$idReader = function (Options $options) use (&$idReaders) { | ||
$hash = CachingFactoryDecorator::generateHash(array( | ||
$options['em'], | ||
$options['class'], | ||
)); | ||
|
||
// The ID reader is a utility that is needed to read the object IDs | ||
// when generating the field values. The callback generating the | ||
// field values has no access to the object manager or the class | ||
// of the field, so we store that information in the reader. | ||
// The reader is cached so that two choice lists for the same class | ||
// (and hence with the same reader) can successfully be cached. | ||
if (!isset($idReaders[$hash])) { | ||
$classMetadata = $options['em']->getClassMetadata($options['class']); | ||
$idReaders[$hash] = new IdReader($options['em'], $classMetadata); | ||
} | ||
|
||
return $idReaders[$hash]; | ||
}; | ||
|
||
$choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders, $type) { | ||
// This closure and the "query_builder" options should be pushed to | ||
// EntityType in Symfony 3.0 as they are specific to the ORM | ||
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. Are they ? the ODM type also allows to pass a query builder (your new logic to build the cache is specific to the ORM indeed, but this is a BC break) 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, because the ODM query builder is of a different class. Both types should support the "query_builder" option, but both with their own logic. 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. but the DoctrineType is the class holding the logic being shared by all Doctrine O*M projects. If it is specific to the ORM, it must go in the EntityType 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, in fact I'd like to move "query_builder" to EntityType - but that would break BC. |
||
|
||
// Unless the choices are given explicitly, load them on demand | ||
if (null === $options['choices']) { | ||
// We consider two query builders with an equal SQL string and | ||
|
@@ -243,6 +226,13 @@ public function configureOptions(OptionsResolver $resolver) | |
return $em; | ||
}; | ||
|
||
// deprecation note | ||
$propertyNormalizer = function (Options $options, $propertyName) { | ||
trigger_error('The "property" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_label" instead.', E_USER_DEPRECATED); | ||
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 normalizer is called for BC by default in the choice_label option. So this will trigger the deprecation notice even if not used. I guess we have to test 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. Same for the other option deprecations. 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. fixed |
||
|
||
return $propertyName; | ||
}; | ||
|
||
// Invoke the query builder closure so that we can cache choice lists | ||
// for equal query builders | ||
$queryBuilderNormalizer = function (Options $options, $queryBuilder) { | ||
|
@@ -257,6 +247,35 @@ public function configureOptions(OptionsResolver $resolver) | |
return $queryBuilder; | ||
}; | ||
|
||
// deprecation note | ||
$loaderNormalizer = function (Options $options, $loader) { | ||
trigger_error('The "loader" option is deprecated since version 2.7 and will be removed in 3.0. Override getLoader() instead.', E_USER_DEPRECATED); | ||
|
||
return $loader; | ||
}; | ||
|
||
// Set the "id_reader" option via the normalizer. This option is not | ||
// supposed to be set by the user. | ||
$idReaderNormalizer = function (Options $options) use (&$idReaders) { | ||
$hash = CachingFactoryDecorator::generateHash(array( | ||
$options['em'], | ||
$options['class'], | ||
)); | ||
|
||
// The ID reader is a utility that is needed to read the object IDs | ||
// when generating the field values. The callback generating the | ||
// field values has no access to the object manager or the class | ||
// of the field, so we store that information in the reader. | ||
// The reader is cached so that two choice lists for the same class | ||
// (and hence with the same reader) can successfully be cached. | ||
if (!isset($idReaders[$hash])) { | ||
$classMetadata = $options['em']->getClassMetadata($options['class']); | ||
$idReaders[$hash] = new IdReader($options['em'], $classMetadata); | ||
} | ||
|
||
return $idReaders[$hash]; | ||
}; | ||
|
||
$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 |
||
|
@@ -268,17 +287,19 @@ public function configureOptions(OptionsResolver $resolver) | |
'choice_label' => $choiceLabel, | ||
'choice_name' => $choiceName, | ||
'choice_value' => $choiceValue, | ||
'id_reader' => $idReader, | ||
'id_reader' => null, // internal | ||
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. Would it not make more sense to implement this as a method just like 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. The problem is that we need to store the
Attributes don't work, since we need to access the ID reader already before the 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 my first suggestion wasn't ideal. But I don't see why it should not be possible to use that idReader closure (which is just about reusing code) without exposing it as option. Maybe I'm missing something, but how about:
This way there is no exposed option and it should still do the same. 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. Then the ID reader would be created three times, as it is used in three closures. You could of course implement some kind of caching, but that makes the code of each closure even more complex. The "id_reader" option is marked as internal, so it should not be used. I think that should be sufficient. |
||
)); | ||
|
||
$resolver->setRequired(array('class')); | ||
|
||
$resolver->setNormalizer('em', $emNormalizer); | ||
$resolver->setNormalizer('property', $propertyNormalizer); | ||
$resolver->setNormalizer('query_builder', $queryBuilderNormalizer); | ||
$resolver->setNormalizer('loader', $loaderNormalizer); | ||
$resolver->setNormalizer('id_reader', $idReaderNormalizer); | ||
|
||
$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 comment
The reason will be displayed to describe this comment to others. Learn more.
inheritdoc? same for loadValuesForChoices
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