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

Skip to content

Commit 0c5b83a

Browse files
committed
feature #18359 [Form] [DoctrineBridge] optimized LazyChoiceList and DoctrineChoiceLoader (HeahDude)
This PR was merged into the 3.1-dev branch. Discussion ---------- [Form] [DoctrineBridge] optimized LazyChoiceList and DoctrineChoiceLoader | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | ~ | License | MIT | Doc PR | ~ Problem ====== Actually we got a circular dependency: | Object | Return |----------|-------------| |`DefaultChoiceListFactory::createListFromLoader()` (decorated) | `LazyChoiceList` with loader and resolved `$value` | `LazyChoiceList::get*` | `DoctrineChoiceLoader` with resolved `$value` `DoctrineChoiceLoader::loadChoiceList()` | (decorated) `DefaultChoiceListFactory` with loaded choices and resolved `$value` `DefaultChoiceListFactory::createListFromChoices()` | `ArrayChoiceList` with `resolved `$value` With this refactoring, the `DoctrineChoiceLoader` is no longer dependant to the factory, the `ChoiceLoaderInterface::loadChoiceList()` must return a `ChoiceListInterface` but should not need a decorated factory while `$value` is already resolved. It should remain lazy IMHO. Solution ====== | Object | Return | |----------|-----------| | `DefaultChoiceListFactory::createListFromLoader()` | `LazyChoiceList` with loader and resolved `$value` | `LazyChoiceList::get*()` | `DoctrineChoiceLoader` with resolved `$value` | `DoctrineChoiceLoader::loadChoiceList()` | `ArrayChoiceList` with resolved `$value`. Since `choiceListFactory` is a private property, this change should be safe regarding BC. To justify this change, I've made some blackfire profiling. You can see my [branch of SE here](https://github.com/HeahDude/symfony-standard/tree/test/optimize-doctrine_choice_loader) and the [all in file test implementation](https://github.com/HeahDude/symfony-standard/blob/test/optimize-doctrine_choice_loader/src/AppBundle/Controller/DefaultController.php). Basically it loads a form with 3 `EntityType` fields with different classes holding 50 instances each. (INIT events are profiled with an empty cache) When | What | Diff (SE => PR) --------|-------|------ INIT (1) | build form (load types) | [see](https://blackfire.io/profiles/compare/061d5d28-15c6-4e01-b8c0-3edc9cb8daf0/graph) INIT (2) | build view (load choices) | [see](https://blackfire.io/profiles/compare/04f142a8-d886-405a-be4d-636ba82d8acd/graph) CACHED | build form (load types) | [see](https://blackfire.io/profiles/compare/293b27b6-aa58-42ae-bafb-655513201505/graph) CACHED | build view (load choices) | [see](https://blackfire.io/profiles/compare/e5b37dfe-cc9e-498f-b98a-7448830ad190/graph) SUBMIT | build form (load types) | [see](https://blackfire.io/profiles/compare/7f3baea9-0d27-46b6-8c24-c577742382dc/graph) SUBMIT | handle request (load choices) | [see](https://blackfire.io/profiles/compare/8644ebfb-4397-495b-8f3d-1a6e1d7f8476/graph) SUBMIT | build view (load values) | [see](https://blackfire.io/profiles/compare/89c3cc7c-ea07-4300-91b3-99004cb58ea1/graph) (1): ![build_form-no_cache](https://cloud.githubusercontent.com/assets/10107633/14136166/b5a85eb8-f661-11e5-8556-3e0dcbfaf404.jpg) (2): ![build_view-no_cache](https://cloud.githubusercontent.com/assets/10107633/14136240/1162f3ee-f662-11e5-834a-1ed1e519dc83.jpg) It can seem like 1 and 2 balance each other but it comes clear when comparing values: | - | Build form | Build view | |-----|---------|--------------| | wall time | -88 ms | +9.71 ms | blocking I/O | -40 ms | +3.67 ms | cpu | -48 ms | +13.4 ms | memory | -4.03 MB | +236 kB | network | -203 B | +2.21 kB Commits ------- 98621f4 [Form] optimized LazyChoiceList 86b2ff1 [DoctrineBridge] optimized DoctrineChoiceLoader
2 parents d602924 + 98621f4 commit 0c5b83a

File tree

4 files changed

+154
-47
lines changed

4 files changed

+154
-47
lines changed

src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
1313

1414
use Doctrine\Common\Persistence\ObjectManager;
15+
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
1516
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
1617
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
1718
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
@@ -60,20 +61,31 @@ class DoctrineChoiceLoader implements ChoiceLoaderInterface
6061
* passed which optimizes the object loading for one of the Doctrine
6162
* mapper implementations.
6263
*
63-
* @param ChoiceListFactoryInterface $factory The factory for creating
64-
* the loaded choice list
6564
* @param ObjectManager $manager The object manager
6665
* @param string $class The class name of the
6766
* loaded objects
6867
* @param IdReader $idReader The reader for the object
6968
* IDs.
69+
* @param ChoiceListFactoryInterface $factory The factory for creating
70+
* the loaded choice list
7071
* @param null|EntityLoaderInterface $objectLoader The objects loader
7172
*/
72-
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null)
73+
public function __construct($manager, $class, $idReader = null, $objectLoader = null, $factory = null)
7374
{
75+
// BC to be removed and replace with type hints in 4.0
76+
if ($manager instanceof ChoiceListFactoryInterface) {
77+
@trigger_error(sprintf('Passing a ChoiceListFactoryInterface to %s is deprecated since version 3.1 and will no longer be supported in 4.0. You should either call "%s::loadChoiceList" or override it to return a ChoiceListInterface.', __CLASS__, __CLASS__));
78+
79+
// Provide a BC layer since $factory has changed
80+
// form first to last argument as of 3.1
81+
$this->factory = $manager;
82+
$manager = $class;
83+
$class = $idReader;
84+
$objectLoader = $factory;
85+
}
86+
7487
$classMetadata = $manager->getClassMetadata($class);
7588

76-
$this->factory = $factory;
7789
$this->manager = $manager;
7890
$this->class = $classMetadata->getName();
7991
$this->idReader = $idReader ?: new IdReader($manager, $classMetadata);
@@ -93,9 +105,7 @@ public function loadChoiceList($value = null)
93105
? $this->objectLoader->getEntities()
94106
: $this->manager->getRepository($this->class)->findAll();
95107

96-
$this->choiceList = $this->factory->createListFromChoices($objects, $value);
97-
98-
return $this->choiceList;
108+
return $this->choiceList = new ArrayChoiceList($objects, $value);
99109
}
100110

101111
/**
@@ -146,7 +156,7 @@ public function loadChoicesForValues(array $values, $value = null)
146156

147157
// Optimize performance in case we have an object loader and
148158
// a single-field identifier
149-
$optimize = null === $value || is_array($value) && $value[0] === $this->idReader;
159+
$optimize = null === $value || is_array($value) && $this->idReader === $value[0];
150160

151161
if ($optimize && !$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) {
152162
$unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values);

src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ public function configureOptions(OptionsResolver $resolver)
160160
}
161161

162162
$doctrineChoiceLoader = new DoctrineChoiceLoader(
163-
$this->choiceListFactory,
164163
$options['em'],
165164
$options['class'],
166165
$options['id_reader'],

src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,18 @@ class LazyChoiceList implements ChoiceListInterface
4545

4646
/**
4747
* @var ChoiceListInterface|null
48+
*
49+
* @deprecated Since 3.1, to be remove in 4.0. Cache the choice list in the {@link ChoiceLoaderInterface} instead.
4850
*/
4951
private $loadedList;
5052

53+
/**
54+
* @var bool
55+
*
56+
* @deprecated Flag used for BC layer since 3.1. To be removed in 4.0.
57+
*/
58+
private $loaded = false;
59+
5160
/**
5261
* Creates a lazily-loaded list using the given loader.
5362
*
@@ -70,70 +79,125 @@ public function __construct(ChoiceLoaderInterface $loader, callable $value = nul
7079
*/
7180
public function getChoices()
7281
{
73-
if (!$this->loadedList) {
74-
$this->loadedList = $this->loader->loadChoiceList($this->value);
82+
if ($this->loaded) {
83+
// We can safely invoke the {@link ChoiceLoaderInterface} assuming it has the list
84+
// in cache when the lazy list is already loaded
85+
if ($this->loadedList !== $this->loader->loadChoiceList($this->value)) {
86+
@trigger_error(sprintf('Caching the choice list in %s is deprecated since 3.1 and will not happen in 4.0. Cache the list in the %s instead.', __CLASS__, ChoiceLoaderInterface::class));
87+
}
88+
89+
return $this->loadedList->getChoices();
7590
}
7691

92+
// BC
93+
$this->loadedList = $this->loader->loadChoiceList($this->value);
94+
$this->loaded = true;
95+
7796
return $this->loadedList->getChoices();
97+
// In 4.0 keep the following line only:
98+
// return $this->loader->loadChoiceList($this->value)->getChoices()
7899
}
79100

80101
/**
81102
* {@inheritdoc}
82103
*/
83104
public function getValues()
84105
{
85-
if (!$this->loadedList) {
86-
$this->loadedList = $this->loader->loadChoiceList($this->value);
106+
if ($this->loaded) {
107+
// Check whether the loader has the same cache
108+
if ($this->loadedList !== $this->loader->loadChoiceList($this->value)) {
109+
@trigger_error(sprintf('Caching the choice list in %s is deprecated since 3.1 and will not happen in 4.0. Cache the list in the %s instead.', __CLASS__, ChoiceLoaderInterface::class));
110+
}
111+
112+
return $this->loadedList->getValues();
87113
}
88114

115+
// BC
116+
$this->loadedList = $this->loader->loadChoiceList($this->value);
117+
$this->loaded = true;
118+
89119
return $this->loadedList->getValues();
120+
// In 4.0 keep the following line only:
121+
// return $this->loader->loadChoiceList($this->value)->getValues()
90122
}
91123

92124
/**
93125
* {@inheritdoc}
94126
*/
95127
public function getStructuredValues()
96128
{
97-
if (!$this->loadedList) {
98-
$this->loadedList = $this->loader->loadChoiceList($this->value);
129+
if ($this->loaded) {
130+
// Check whether the loader has the same cache
131+
if ($this->loadedList !== $this->loader->loadChoiceList($this->value)) {
132+
@trigger_error(sprintf('Caching the choice list in %s is deprecated since 3.1 and will not happen in 4.0. Cache the list in the %s instead.', __CLASS__, ChoiceLoaderInterface::class));
133+
}
134+
135+
return $this->loadedList->getStructuredValues();
99136
}
100137

138+
// BC
139+
$this->loadedList = $this->loader->loadChoiceList($this->value);
140+
$this->loaded = true;
141+
101142
return $this->loadedList->getStructuredValues();
143+
// In 4.0 keep the following line only:
144+
// return $this->loader->loadChoiceList($this->value)->getStructuredValues();
102145
}
103146

104147
/**
105148
* {@inheritdoc}
106149
*/
107150
public function getOriginalKeys()
108151
{
109-
if (!$this->loadedList) {
110-
$this->loadedList = $this->loader->loadChoiceList($this->value);
152+
if ($this->loaded) {
153+
// Check whether the loader has the same cache
154+
if ($this->loadedList !== $this->loader->loadChoiceList($this->value)) {
155+
@trigger_error(sprintf('Caching the choice list in %s is deprecated since 3.1 and will not happen in 4.0. Cache the list in the %s instead.', __CLASS__, ChoiceLoaderInterface::class));
156+
}
157+
158+
return $this->loadedList->getOriginalKeys();
111159
}
112160

161+
// BC
162+
$this->loadedList = $this->loader->loadChoiceList($this->value);
163+
$this->loaded = true;
164+
113165
return $this->loadedList->getOriginalKeys();
166+
// In 4.0 keep the following line only:
167+
// return $this->loader->loadChoiceList($this->value)->getOriginalKeys();
114168
}
115169

116170
/**
117171
* {@inheritdoc}
118172
*/
119173
public function getChoicesForValues(array $values)
120174
{
121-
if (!$this->loadedList) {
122-
return $this->loader->loadChoicesForValues($values, $this->value);
175+
if ($this->loaded) {
176+
// Check whether the loader has the same cache
177+
if ($this->loadedList !== $this->loader->loadChoiceList($this->value)) {
178+
@trigger_error(sprintf('Caching the choice list in %s is deprecated since 3.1 and will not happen in 4.0. Cache the list in the %s instead.', __CLASS__, ChoiceLoaderInterface::class));
179+
}
180+
181+
return $this->loadedList->getChoicesForValues($values);
123182
}
124183

125-
return $this->loadedList->getChoicesForValues($values);
184+
return $this->loader->loadChoicesForValues($values, $this->value);
126185
}
127186

128187
/**
129188
* {@inheritdoc}
130189
*/
131190
public function getValuesForChoices(array $choices)
132191
{
133-
if (!$this->loadedList) {
134-
return $this->loader->loadValuesForChoices($choices, $this->value);
192+
if ($this->loaded) {
193+
// Check whether the loader has the same cache
194+
if ($this->loadedList !== $this->loader->loadChoiceList($this->value)) {
195+
@trigger_error(sprintf('Caching the choice list in %s is deprecated since 3.1 and will not happen in 4.0. Cache the list in the %s instead.', __CLASS__, ChoiceLoaderInterface::class));
196+
}
197+
198+
return $this->loadedList->getValuesForChoices($choices);
135199
}
136200

137-
return $this->loadedList->getValuesForChoices($choices);
201+
return $this->loader->loadValuesForChoices($choices, $this->value);
138202
}
139203
}

0 commit comments

Comments
 (0)