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

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 87afdea

Browse files
committed
add ExtraLazyChoiceLoader and extra_lazy option
1 parent c9e7809 commit 87afdea

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.1
5+
---
6+
7+
* Add `extra_lazy` option to `DoctrineType` for loading entities on demand
8+
49
7.0
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;
@@ -118,17 +119,23 @@ public function configureOptions(OptionsResolver $resolver): void
118119
$vary[] = $this->getQueryBuilderPartsForCachingHash($options['query_builder']) ?? $options['query_builder'];
119120
}
120121

121-
return ChoiceList::loader($this, new DoctrineChoiceLoader(
122+
$loader = new DoctrineChoiceLoader(
122123
$options['em'],
123124
$options['class'],
124125
$options['id_reader'],
125126
$this->getCachedEntityLoader(
126127
$options['em'],
127128
$options['query_builder'] ?? $options['em']->getRepository($options['class'])->createQueryBuilder('e'),
128129
$options['class'],
129-
$vary
130-
)
131-
), $vary);
130+
$vary,
131+
),
132+
);
133+
134+
if ($options['extra_lazy']) {
135+
$loader = new ExtraLazyChoiceLoader($loader);
136+
}
137+
138+
return ChoiceList::loader($this, $loader, $vary);
132139
}
133140

134141
return null;
@@ -208,6 +215,7 @@ public function configureOptions(OptionsResolver $resolver): void
208215
'choice_value' => $choiceValue,
209216
'id_reader' => null, // internal
210217
'choice_translation_domain' => false,
218+
'extra_lazy' => false,
211219
]);
212220

213221
$resolver->setRequired(['class']);
@@ -217,6 +225,7 @@ public function configureOptions(OptionsResolver $resolver): void
217225
$resolver->setNormalizer('id_reader', $idReaderNormalizer);
218226

219227
$resolver->setAllowedTypes('em', ['null', 'string', ObjectManager::class]);
228+
$resolver->setAllowedTypes('extra_lazy', 'bool');
220229
}
221230

222231
/**

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

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

src/Symfony/Bridge/Doctrine/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"symfony/dependency-injection": "^6.4|^7.0",
3030
"symfony/doctrine-messenger": "^6.4|^7.0",
3131
"symfony/expression-language": "^6.4|^7.0",
32-
"symfony/form": "^6.4|^7.0",
32+
"symfony/form": "^7.1",
3333
"symfony/http-kernel": "^6.4|^7.0",
3434
"symfony/lock": "^6.4|^7.0",
3535
"symfony/messenger": "^6.4|^7.0",
@@ 65CE -53,7 +53,7 @@
5353
"doctrine/orm": "<2.15",
5454
"symfony/cache": "<6.4",
5555
"symfony/dependency-injection": "<6.4",
56-
"symfony/form": "<6.4",
56+
"symfony/form": "<7.1",
5757
"symfony/http-foundation": "<6.4",
5858
"symfony/http-kernel": "<6.4",
5959
"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.1
5+
---
6+
7+
* Add `ExtraLazyChoiceLoader` for loading choices on demand
8+
49
7.0
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