Skip to content

Commit aa66777

Browse files
committed
[JsonEncoder] Allow to warm up arbitrary types
1 parent f3fb3a5 commit aa66777

File tree

9 files changed

+92
-14
lines changed

9 files changed

+92
-14
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2595,6 +2595,14 @@ private function addJsonEncoderSection(ArrayNodeDefinition $rootNode, callable $
25952595
->arrayNode('json_encoder')
25962596
->info('JSON encoder configuration')
25972597
->{$enableIfStandalone('symfony/json-encoder', EncoderInterface::class)}()
2598+
->fixXmlConfig('type')
2599+
->children()
2600+
->arrayNode('types')
2601+
->info('Encodable/decodable types to warm up during cache generation.')
2602+
->scalarPrototype()->end()
2603+
->defaultValue([])
2604+
->end()
2605+
->end()
25982606
->end()
25992607
->end()
26002608
;

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,6 +2028,7 @@ private function registerJsonEncoderConfiguration(array $config, ContainerBuilde
20282028
$container->setParameter('.json_encoder.encoders_dir', '%kernel.cache_dir%/json_encoder/encoder');
20292029
$container->setParameter('.json_encoder.decoders_dir', '%kernel.cache_dir%/json_encoder/decoder');
20302030
$container->setParameter('.json_encoder.lazy_ghosts_dir', '%kernel.cache_dir%/json_encoder/lazy_ghost');
2031+
$container->setParameter('.json_encoder.encodable_types', $config['types']);
20312032

20322033
if (\PHP_VERSION_ID >= 80400) {
20332034
$container->removeDefinition('.json_encoder.cache_warmer.lazy_ghost');

src/Symfony/Bundle/FrameworkBundle/Resources/config/json_encoder.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@
107107
// cache
108108
->set('.json_encoder.cache_warmer.encoder_decoder', EncoderDecoderCacheWarmer::class)
109109
->args([
110-
abstract_arg('encodable class names'),
110+
abstract_arg('encodable types'),
111+
service('type_info.resolver.string')->nullOnInvalid(),
111112
service('json_encoder.encode.property_metadata_loader'),
112113
service('json_encoder.decode.property_metadata_loader'),
113114
param('.json_encoder.encoders_dir'),

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,9 @@
10071007
</xsd:complexType>
10081008

10091009
<xsd:complexType name="json-encoder">
1010+
<xsd:sequence>
1011+
<xsd:element name="type" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
1012+
</xsd:sequence>
10101013
<xsd:attribute name="enabled" type="xsd:boolean" />
10111014
</xsd:complexType>
10121015

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
973973
],
974974
'json_encoder' => [
975975
'enabled' => !class_exists(FullStack::class) && class_exists(JsonEncoder::class),
976+
'types' => [],
976977
],
977978
];
978979
}

src/Symfony/Component/JsonEncoder/CacheWarmer/EncoderDecoderCacheWarmer.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
use Symfony\Component\JsonEncoder\Decode\DecoderGenerator;
1818
use Symfony\Component\JsonEncoder\Encode\EncoderGenerator;
1919
use Symfony\Component\JsonEncoder\Exception\ExceptionInterface;
20+
use Symfony\Component\JsonEncoder\Exception\InvalidArgumentException;
2021
use Symfony\Component\JsonEncoder\Mapping\PropertyMetadataLoaderInterface;
2122
use Symfony\Component\TypeInfo\Type;
23+
use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface;
2224

2325
/**
2426
* Generates encoders and decoders PHP files.
@@ -33,10 +35,11 @@ final class EncoderDecoderCacheWarmer implements CacheWarmerInterface
3335
private DecoderGenerator $decoderGenerator;
3436

3537
/**
36-
* @param iterable<class-string> $encodableClassNames
38+
* @param iterable<string> $encodableTypes
3739
*/
3840
public function __construct(
39-
private iterable $encodableClassNames,
41+
private iterable $encodableTypes,
42+
private ?TypeResolverInterface $stringTypeResolver,
4043
PropertyMetadataLoaderInterface $encodePropertyMetadataLoader,
4144
PropertyMetadataLoaderInterface $decodePropertyMetadataLoader,
4245
string $encodersDir,
@@ -49,8 +52,16 @@ public function __construct(
4952

5053
public function warmUp(string $cacheDir, ?string $buildDir = null): array
5154
{
52-
foreach ($this->encodableClassNames as $className) {
53-
$type = Type::object($className);
55+
foreach ($this->encodableTypes as $typeString) {
56+
if ($this->stringTypeResolver) {
57+
$type = $this->stringTypeResolver->resolve($typeString);
58+
} else {
59+
if (!class_exists($typeString)) {
60+
throw new InvalidArgumentException(\sprintf('Unable to parse "%s" as string type resolver is not available. Try running "composer require phpstan/phpoc-parser".', $typeString));
61+
}
62+
63+
$type = Type::object($typeString);
64+
}
5465

5566
$this->warmUpEncoder($type);
5667
$this->warmUpDecoders($type);

src/Symfony/Component/JsonEncoder/DependencyInjection/EncodablePass.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616

1717
/**
18-
* Sets the encodable classes to the services that need them.
18+
* Sets the encodable types to the services that need them.
1919
*
2020
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
2121
*/
@@ -38,12 +38,15 @@ public function process(ContainerBuilder $container): void
3838
$container->removeDefinition($id);
3939
}
4040

41-
$container->getDefinition('.json_encoder.cache_warmer.encoder_decoder')
42-
->replaceArgument(0, $encodableClassNames);
43-
4441
if ($container->hasDefinition('.json_encoder.cache_warmer.lazy_ghost')) {
4542
$container->getDefinition('.json_encoder.cache_warmer.lazy_ghost')
4643
->replaceArgument(0, $encodableClassNames);
4744
}
45+
46+
$encodableTypes = array_merge($encodableClassNames, $container->getParameter('.json_encoder.encodable_types'));
47+
$encodableTypes = array_values(array_unique($encodableTypes));
48+
49+
$container->getDefinition('.json_encoder.cache_warmer.encoder_decoder')
50+
->replaceArgument(0, $encodableTypes);
4851
}
4952
}

src/Symfony/Component/JsonEncoder/Tests/CacheWarmer/EncoderDecoderCacheWarmerTest.php

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\JsonEncoder\CacheWarmer\EncoderDecoderCacheWarmer;
16+
use Symfony\Component\JsonEncoder\Exception\InvalidArgumentException;
1617
use Symfony\Component\JsonEncoder\Mapping\PropertyMetadataLoader;
1718
use Symfony\Component\JsonEncoder\Tests\Fixtures\Model\ClassicDummy;
19+
use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver;
1820
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
21+
use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface;
1922

2023
class EncoderDecoderCacheWarmerTest extends TestCase
2124
{
@@ -42,29 +45,55 @@ protected function setUp(): void
4245

4346
public function testWarmUp()
4447
{
45-
$this->cacheWarmer([ClassicDummy::class])->warmUp('useless');
48+
$this->cacheWarmer([ClassicDummy::class, \sprintf('list<%s>', ClassicDummy::class)])->warmUp('useless');
4649

4750
$this->assertSame([
51+
\sprintf('%s/5acb3571777e02a2712fb9a9126a338f.json.php', $this->encodersDir),
4852
\sprintf('%s/d147026bb5d25e5012afcdc1543cf097.json.php', $this->encodersDir),
4953
], glob($this->encodersDir.'/*'));
5054

5155
$this->assertSame([
56+
\sprintf('%s/5acb3571777e02a2712fb9a9126a338f.json.php', $this->decodersDir),
57+
\sprintf('%s/5acb3571777e02a2712fb9a9126a338f.json.stream.php', $this->decodersDir),
5258
\sprintf('%s/d147026bb5d25e5012afcdc1543cf097.json.php', $this->decodersDir),
5359
\sprintf('%s/d147026bb5d25e5012afcdc1543cf097.json.stream.php', $this->decodersDir),
5460
], glob($this->decodersDir.'/*'));
5561
}
5662

63+
public function testWarmupClassWithoutStringTypeResolver()
64+
{
65+
$this->cacheWarmer([ClassicDummy::class], stringTypeResolver: null)->warmUp('useless');
66+
67+
$this->assertSame([
68+
\sprintf('%s/d147026bb5d25e5012afcdc1543cf097.json.php', $this->encodersDir),
69+
], glob($this->encodersDir.'/*'));
70+
71+
$this->assertSame([
72+
\sprintf('%s/d147026bb5d25e5012afcdc1543cf097.json.php', $this->decodersDir),
73+
\sprintf('%s/d147026bb5d25e5012afcdc1543cf097.json.stream.php', $this->decodersDir),
74+
], glob($this->decodersDir.'/*'));
75+
}
76+
77+
public function testThrowWhenCannotWarmupTypeWithoutStringTypeResolver()
78+
{
79+
$this->expectException(InvalidArgumentException::class);
80+
$this->expectExceptionMessage('Unable to parse "foo" as string type resolver is not available. Try running "composer require phpstan/phpoc-parser".');
81+
82+
$this->cacheWarmer(['foo'], stringTypeResolver: null)->warmUp('useless');
83+
}
84+
5785
/**
5886
* @param list<class-string> $encodable
5987
*/
60-
private function cacheWarmer(array $encodable): EncoderDecoderCacheWarmer
88+
private function cacheWarmer(array $encodable, ?TypeResolverInterface $stringTypeResolver = new StringTypeResolver()): EncoderDecoderCacheWarmer
6189
{
62-
$typeResolver = TypeResolver::create();
90+
$propertyMetadataLoader = new PropertyMetadataLoader(TypeResolver::create());
6391

6492
return new EncoderDecoderCacheWarmer(
6593
$encodable,
66-
new PropertyMetadataLoader($typeResolver),
67-
new PropertyMetadataLoader($typeResolver),
94+
$stringTypeResolver,
95+
$propertyMetadataLoader,
96+
$propertyMetadataLoader,
6897
$this->encodersDir,
6998
$this->decodersDir,
7099
);

src/Symfony/Component/JsonEncoder/Tests/DependencyInjection/EncodablePassTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function testSetEncodableClassNames()
2424
$container->register('json_encoder.encoder');
2525
$container->register('.json_encoder.cache_warmer.encoder_decoder')->setArguments([null]);
2626
$container->register('.json_encoder.cache_warmer.lazy_ghost')->setArguments([null]);
27+
$container->setParameter('.json_encoder.encodable_types', []);
2728

2829
$container->register('encodable')->setClass('Foo')->addTag('json_encoder.encodable');
2930
$container->register('abstractEncodable')->setClass('Bar')->addTag('json_encoder.encodable')->setAbstract(true);
@@ -40,4 +41,24 @@ public function testSetEncodableClassNames()
4041
$this->assertSame($expectedEncodableClassNames, $encoderDecoderCacheWarmer->getArgument(0));
4142
$this->assertSame($expectedEncodableClassNames, $lazyGhostCacheWarmer->getArgument(0));
4243
}
44+
45+
public function testSetEncodableTypes()
46+
{
47+
$container = new ContainerBuilder();
48+
49+
$container->register('json_encoder.encoder');
50+
$container->register('.json_encoder.cache_warmer.encoder_decoder')->setArguments([null]);
51+
$container->register('.json_encoder.cache_warmer.lazy_ghost')->setArguments([null]);
52+
53+
$container->register('encodable')->setClass('Foo')->addTag('json_encoder.encodable');
54+
$container->register('encodable')->setClass('Bar')->addTag('json_encoder.encodable');
55+
$container->setParameter('.json_encoder.encodable_types', ['array<string, bool>', 'Foo', 'list<Foo>']);
56+
57+
$pass = new EncodablePass();
58+
$pass->process($container);
59+
60+
$encoderDecoderCacheWarmer = $container->getDefinition('.json_encoder.cache_warmer.encoder_decoder');
61+
62+
$this->assertSame(['Bar', 'array<string, bool>', 'Foo', 'list<Foo>'], $encoderDecoderCacheWarmer->getArgument(0));
63+
}
4364
}

0 commit comments

Comments
 (0)