8000 add ExtraLazyChoiceLoader and extra_lazy option · symfony/symfony@33b2f3d · GitHub
[go: up one dir, main page]

Skip to content

Commit 33b2f3d

Browse files
committed
add ExtraLazyChoiceLoader and extra_lazy option
1 parent 68a5704 commit 33b2f3d

File tree

7 files changed

+233
-6
lines changed

7 files changed

+233
-6
lines changed

src/Symfony/Bridge/Doctrine/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.2
5+
---
6+
7+
* Add `extra_lazy` option to `DoctrineType` for loading entities on demand
8+
49
7.1
510
---
611

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Form\AbstractType;
2323
use Symfony\Component\Form\ChoiceList\ChoiceList;
2424
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
25+
use Symfony\Component\Form\ChoiceList\Loader\ExtraLazyChoiceLoader;
2526
use Symfony\Component\Form\Exception\RuntimeException;
2627
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
2728
use Symfony\Component\Form\FormBuilderInterface;
@@ -116,17 +117,23 @@ public function configureOptions(OptionsResolver $resolver): void
116117
$vary[] = $this->getQueryBuilderPartsForCachingHash($options['query_builder']) ?? $options['query_builder'];
117118
}
118119

119-
return ChoiceList::loader($this, new DoctrineChoiceLoader(
120+
$loader = new DoctrineChoiceLoader(
120121
$options['em'],
121122
$options['class'],
122123
$options['id_reader'],
123124
$this->getCachedEntityLoader(
124125
$options['em'],
125126
$options['query_builder'] ?? $options['em']->getRepository($options['class'])->createQueryBuilder('e'),
126127
$options['class'],
127-
$vary
128-
)
129-
), $vary);
128+
$vary,
129+
),
130+
);
131+
132+
if ($options['extra_lazy']) {
133+
$loader = new ExtraLazyChoiceLoader($loader);
134+
}
135+
136+
return ChoiceList::loader($this, $loader, $vary);
130137
}
131138

132139
return null;
@@ -206,6 +213,7 @@ public function configureOptions(OptionsResolver $resolver): void
206213
'choice_value' => $choiceValue,
207214
'id_reader' => null, // internal
208215
'choice_translation_domain' => false,
216+
'extra_lazy' => false,
209217
]);
210218

211219
$resolver->setRequired(['class']);
@@ -215,6 +223,7 @@ public function configureOptions(OptionsResolver $resolver): void
215223
$resolver->setNormalizer('id_reader', $idReaderNormalizer);
216224

217225
$resolver->setAllowedTypes('em', ['null', 'string', ObjectManager::class]);
226+
$resolver->setAllowedTypes('extra_lazy', 'bool');
218227
}
219228

220229
/**

src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,4 +1758,108 @@ public function testWithSameLoaderAndDifferentChoiceValueCallbacks()
17581758
$this->assertSame('Foo', $view['entity_two']->vars['choices']['Foo']->value);
17591759
$this->assertSame('Bar', $view['entity_two']->vars['choices']['Bar']->value);
17601760
}
1761+
1762+
public function testEmptyChoicesWhenLazy()
1763+
{
1764+
$entity1 = new SingleIntIdEntity(1, 'Foo');
1765+
$entity2 = new SingleIntIdEntity(2, 'Bar');
1766+
$this->persist([$entity1, $entity2]);
1767+
1768+
$view = $this->factory->create(FormTypeTest::TESTED_TYPE)
1769+
->add('entity_one', self::TESTED_TYPE, [
1770+
'em' => 'default',
1771+
'class' => self::SINGLE_IDENT_CLASS,
1772+
'extra_lazy' => true,
1773+
])
1774+
->createView()
1775+
;
1776+
1777+
$this->assertCount(0, $view['entity_one']->vars['choices']);
1778+
}
1779+
1780+
public function testLoadedChoicesWhenLazyAndBoundData()
1781+
{
1782+
$entity1 = new SingleIntIdEntity(1, 'Foo');
1783+
$entity2 = new SingleIntIdEntity(2, 'Bar');
1784+
$this->persist([$entity1, $entity2]);
1785+
1786+
$view = $this->factory->create(FormTypeTest::TESTED_TYPE, ['entity_one' => $entity1])
1787+
->add('entity_one', self::TESTED_TYPE, [
1788+
'em' => 'default',
1789+
'class' => self::SINGLE_IDENT_CLASS,
1790+
'extra_lazy' => true,
1791+
])
1792+
->createView()
1793+
;
1794+
1795+
$this->assertCount(1, $view['entity_one']->vars['choices']);
1796+
$this->assertSame('1', $view['entity_one']->vars['choices'][1]->value);
1797+
}
1798+
1799+
public function testLoadedChoicesWhenLazyAndSubmittedData()
1800+
{
1801+
$entity1 = new SingleIntIdEntity(1, 'Foo');
1802+
$entity2 = new SingleIntIdEntity(2, 'Bar');
1803+
$this->persist([$entity1, $entity2]);
1804+
1805+
$view = $this->factory->create(FormTypeTest::TESTED_TYPE)
1806+
->add('entity_one', self::TESTED_TYPE, [
1807+
'em' => 'default',
1808+
'class' => self::SINGLE_IDENT_CLASS,
1809+
'extra_lazy' => true,
1810+
])
1811+
->submit(['entity_one' => '2'])
1812+
->createView()
1813+
;
1814+
1815+
$this->assertCount(1, $view['entity_one']->vars['choices']);
1816+
$this->assertSame('2', $view['entity_one']->vars['choices'][2]->value);
1817+
}
1818+
1819+
public function testEmptyChoicesWhenLazyAndEmptyDataIsSubmitted()
1820+
{
1821+
$entity1 = new SingleIntIdEntity(1, 'Foo');
1822+
$entity2 = new SingleIntIdEntity(2, 'Bar');
1823+
$this->persist([$entity1, $entity2]);
1824+
1825+
$view = $this->factory->create(FormTypeTest::TESTED_TYPE, ['entity_one' => $entity1])
1826+
->add('entity_one', self::TESTED_TYPE, [
1827+
'em' => 'default',
1828+
'class' => self::SINGLE_IDENT_CLASS,
1829+
'extra_lazy' => true,
1830+
])
1831+
->submit([])
1832+
->createView()
1833+
;
1834+
1835+
$this->assertCount(0, $view['entity_one']->vars['choices']);
1836+
}
1837+
1838+
public function testErrorOnSubmitInvalidValuesWhenLazyAndCustomQueryBuilder()
1839+
{
1840+
$entity1 = new SingleIntIdEntity(1, 'Foo');
1841+
$entity2 = new SingleIntIdEntity(2, 'Bar');
1842+
$this->persist([$entity1, $entity2]);
1843+
$qb = $this->em
1844+
->createQueryBuilder()
1845+
->select('e')
1846+
->from(self::SINGLE_IDENT_CLASS, 'e')
1847+
->where('e.id = 2')
1848+
;
1849+
1850+
$form = $this->factory->create(FormTypeTest::TESTED_TYPE, ['entity_one' => $entity2])
1851+
->add('entity_one', self::TESTED_TYPE, [
1852+
'em' => 'default',
1853+
'class' => self::SINGLE_IDENT_CLASS,
1854+
'query_builder' => $qb,
1855+
'extra_lazy' => true,
1856+
])
1857+
->submit(['entity_one' => '1'])
1858+
;
1859+
$view = $form->createView();
1860+
1861+
$this->assertCount(0, $view['entity_one']->vars['choices']);
1862+
$this->assertCount(1, $errors = $form->getErrors(true));
1863+
$this->assertSame('The selected choice is invalid.', $errors->current()->getMessage());
1864+
}
17611865
}

src/Symfony/Bridge/Doctrine/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"symfony/dependency-injection": "^6.4|^7.0",
3131
"symfony/doctrine-messenger": "^6.4|^7.0",
3232
"symfony/expression-language": "^6.4|^7.0",
33-
"symfony/form": "^6.4.6|^7.0.6",
33+
"symfony/form": "^7.2",
3434
"symfony/http-kernel": "^6.4|^7.0",
3535
"symfony/lock": "^6.4|^7.0",
3636
"symfony/messenger": "^6.4|^7.0",
@@ -55,7 +55,7 @@
5555
"doctrine/orm": "<2.15",
5656
"symfony/cache": "<6.4",
5757
"symfony/dependency-injection": "<6.4",
58-
"symfony/form": "<6.4.6|>=7,<7.0.6",
58+
"symfony/form": "<7.2",
5959
"symfony/http-foundation": "<6.4",
6060
"symfony/http-kernel": "<6.4",
6161
"symfony/lock": "<6.4",

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.2
5+
---
6+
7+
* Add `ExtraLazyChoiceLoader` for loading choices on demand
8+
49
7.1
510
---
611

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\ChoiceList\Loader;
13+
14+
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
15+
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
16+
17+
/**
18+
* A choice loader that loads its choices and values lazily, only when necessary.
19+
*
20+
* @author Yonel Ceruto <yonelceruto@gmail.com>
21+
*/
22+
class ExtraLazyChoiceLoader implements ChoiceLoaderInterface
23+
{
24+
private ?ChoiceListInterface $choiceList = null;
25+
26+
public function __construct(
27+
private readonly ChoiceLoaderInterface $loader,
28+
) {
29+
}
30+
31+
public function loadChoiceList(callable $value = null): ChoiceListInterface
32+
{
33+
return $this->choiceList ??= new ArrayChoiceList([], $value);
34+
}
35+
36+
public function loadChoicesForValues(array $values, callable $value = null): array
37+
{
38+
$choices = $this->loader->loadChoicesForValues($values, $value);
39+
$this->choiceList = new ArrayChoiceList($choices, $value);
40+
41+
return $choices;
42+
}
43+
44+
public function loadValuesForChoices(array $choices, callable $value = null): array
45+
{
46+
$values = $this->loader->loadValuesForChoices($choices, $value);
47+
48+
if ($this->choiceList?->getValuesForChoices($choices) !== $values) {
49+
$this->loadChoicesForValues($values, $value);
50+
}
51+
52+
return $values;
53+
}
54+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\ChoiceList\Loader;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Form\ChoiceList\Loader\ExtraLazyChoiceLoader;
16+
use Symfony\Component\Form\Tests\Fixtures\ArrayChoiceLoader;
17+
18+
class ExtraLazyChoiceLoaderTest extends TestCase
19+
{
20+
private ExtraLazyChoiceLoader $loader;
21+
22+
protected function setUp(): void
23+
{
24+
$this->loader = new ExtraLazyChoiceLoader(new ArrayChoiceLoader(['A', 'B', 'C']));
25+
}
26+
27+
public function testInitialEmptyChoiceListLoading()
28+
{
29+
$this->assertSame([], $this->loader->loadChoiceList()->getChoices());
30+
}
31+
32+
public function testOnDemandChoiceListAfterLoadingValuesForChoices()
33+
{
34+
$this->loader->loadValuesForChoices(['A']);
35+
$this->assertSame(['A' => 'A'], $this->loader->loadChoiceList()->getChoices());
36+
}
37+
38+
public function testOnDemandChoiceListAfterLoadingChoicesForValues()
39+
{
40+
$this->loader->loadChoicesForValues(['B']);
41+
$this->assertSame(['B' => 'B'], $this->loader->loadChoiceList()->getChoices());
42+
}
43+
44+
public function testOnDemandChoiceList()
45+
{
46+
$this->loader->loadValuesForChoices(['A']);
47+
$this->loader->loadChoicesForValues(['B']);
48+
$this->assertSame(['B' => 'B'], $this->loader->loadChoiceList()->getChoices());
49+
}
50+
}

0 commit comments

Comments
 (0)
0