Skip to content

Commit 6a4470c

Browse files
committed
[JsonStreamer] Rebuild cache on class update
1 parent 4798ba5 commit 6a4470c

File tree

15 files changed

+253
-244
lines changed

15 files changed

+253
-244
lines changed

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,10 +2120,6 @@ private function registerJsonStreamerConfiguration(array $config, ContainerBuild
21202120
$container->registerAliasForArgument('json_streamer.stream_writer', StreamWriterInterface::class, 'json.stream_writer');
21212121
$container->registerAliasForArgument('json_streamer.stream_reader', StreamReaderInterface::class, 'json.stream_reader');
21222122

2123-
$container->setParameter('.json_streamer.stream_writers_dir', '%kernel.cache_dir%/json_streamer/stream_writer');
2124-
$container->setParameter('.json_streamer.stream_readers_dir', '%kernel.cache_dir%/json_streamer/stream_reader');
2125-
$container->setParameter('.json_streamer.lazy_ghosts_dir', '%kernel.cache_dir%/json_streamer/lazy_ghost');
2126-
21272123
if (\PHP_VERSION_ID >= 80400) {
21282124
$container->removeDefinition('.json_streamer.cache_warmer.lazy_ghost');
21292125
}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@
3131
->args([
3232
tagged_locator('json_streamer.value_transformer'),
3333
service('json_streamer.write.property_metadata_loader'),
34-
param('.json_streamer.stream_writers_dir'),
34+
param('kernel.cache_dir').'/json_streamer',
3535
])
36+
->call('setConfigCacheFactory', [service('config_cache_factory')])
3637
->set('json_streamer.stream_reader', JsonStreamReader::class)
3738
->args([
3839
tagged_locator('json_streamer.value_transformer'),
3940
service('json_streamer.read.property_metadata_loader'),
40-
param('.json_streamer.stream_readers_dir'),
41-
param('.json_streamer.lazy_ghosts_dir'),
41+
param('kernel.cache_dir').'/json_streamer',
4242
])
43+
->call('setConfigCacheFactory', [service('config_cache_factory')])
4344
->alias(JsonStreamWriter::class, 'json_streamer.stream_writer')
4445
->alias(JsonStreamReader::class, 'json_streamer.stream_reader')
4546

@@ -101,18 +102,16 @@
101102
->set('.json_streamer.cache_warmer.streamer', StreamerCacheWarmer::class)
102103
->args([
103104
abstract_arg('streamable'),
104-
service('json_streamer.write.property_metadata_loader'),
105-
service('json_streamer.read.property_metadata_loader'),
106-
param('.json_streamer.stream_writers_dir'),
107-
param('.json_streamer.stream_readers_dir'),
105+
service('json_streamer.stream_reader'),
106+
service('json_streamer.stream_writer'),
108107
service('logger')->ignoreOnInvalid(),
109108
])
110109
->tag('kernel.cache_warmer')
111110

112111
->set('.json_streamer.cache_warmer.lazy_ghost', LazyGhostCacheWarmer::class)
113112
->args([
114113
abstract_arg('streamable class names'),
115-
param('.json_streamer.lazy_ghosts_dir'),
114+
param('kernel.cache_dir').'/json_streamer',
116115
])
117116
->tag('kernel.cache_warmer')
118117
;

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/JsonStreamerTest.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,38 @@ public function testWarmupStreamableClasses()
5252
/** @var Filesystem $fs */
5353
$fs = static::getContainer()->get('filesystem');
5454

55-
$streamWritersDir = \sprintf('%s/json_streamer/stream_writer/', static::getContainer()->getParameter('kernel.cache_dir'));
55+
$cacheDir = \sprintf('%s/json_streamer/', static::getContainer()->getParameter('kernel.cache_dir'));
5656

5757
// clear already created stream writers
58-
if ($fs->exists($streamWritersDir)) {
59-
$fs->remove($streamWritersDir);
58+
if ($fs->exists($cacheDir)) {
59+
$fs->remove($cacheDir);
6060
}
6161

6262
static::getContainer()->get('json_streamer.cache_warmer.streamer.alias')->warmUp(static::getContainer()->getParameter('kernel.cache_dir'));
6363

64-
$this->assertFileExists($streamWritersDir);
65-
$this->assertCount(2, glob($streamWritersDir.'/*'));
64+
$cacheFiles = array_map(basename(...), glob($cacheDir.'/*'));
65+
sort($cacheFiles);
66+
67+
$this->assertFileExists($cacheDir);
68+
$this->assertSame([
69+
'1c85daa6f45414f9b87147e5de5581cd.json.read.php',
70+
'1c85daa6f45414f9b87147e5de5581cd.json.read.php.meta',
71+
'1c85daa6f45414f9b87147e5de5581cd.json.read.php.meta.json',
72+
'1c85daa6f45414f9b87147e5de5581cd.json.read_stream.php',
73+
'1c85daa6f45414f9b87147e5de5581cd.json.read_stream.php.meta',
74+
'1c85daa6f45414f9b87147e5de5581cd.json.read_stream.php.meta.json',
75+
'1c85daa6f45414f9b87147e5de5581cd.json.write.php',
76+
'1c85daa6f45414f9b87147e5de5581cd.json.write.php.meta',
77+
'1c85daa6f45414f9b87147e5de5581cd.json.write.php.meta.json',
78+
'6e60c0954544526d2341f561cd07e5bc.json.read.php',
79+
'6e60c0954544526d2341f561cd07e5bc.json.read.php.meta',
80+
'6e60c0954544526d2341f561cd07e5bc.json.read.php.meta.json',
81+
'6e60c0954544526d2341f561cd07e5bc.json.read_stream.php',
82+
'6e60c0954544526d2341f561cd07e5bc.json.read_stream.php.meta',
83+
'6e60c0954544526d2341f561cd07e5bc.json.read_stream.php.meta.json',
84+
'6e60c0954544526d2341f561cd07e5bc.json.write.php',
85+
'6e60c0954544526d2341f561cd07e5bc.json.write.php.meta',
86+
'6e60c0954544526d2341f561cd07e5bc.json.write.php.meta.json',
87+
], $cacheFiles);
6688
}
6789
}

src/Symfony/Component/JsonStreamer/CacheWarmer/StreamerCacheWarmer.php

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
use Psr\Log\NullLogger;
1616
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
1717
use Symfony\Component\JsonStreamer\Exception\ExceptionInterface;
18-
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoaderInterface;
19-
use Symfony\Component\JsonStreamer\Read\StreamReaderGenerator;
20-
use Symfony\Component\JsonStreamer\Write\StreamWriterGenerator;
18+
use Symfony\Component\JsonStreamer\JsonStreamReader;
19+
use Symfony\Component\JsonStreamer\JsonStreamWriter;
2120
use Symfony\Component\TypeInfo\Type;
2221

2322
/**
@@ -29,22 +28,15 @@
2928
*/
3029
final class StreamerCacheWarmer implements CacheWarmerInterface
3130
{
32-
private StreamWriterGenerator $streamWriterGenerator;
33-
private StreamReaderGenerator $streamReaderGenerator;
34-
3531
/**
3632
* @param iterable<class-string, array{object: bool, list: bool}> $streamable
3733
*/
3834
public function __construct(
3935
private iterable $streamable,
40-
PropertyMetadataLoaderInterface $streamWriterPropertyMetadataLoader,
41-
PropertyMetadataLoaderInterface $streamReaderPropertyMetadataLoader,
42-
string $streamWritersDir,
43-
string $streamReadersDir,
36+
private JsonStreamReader $reader,
37+
private JsonStreamWriter $writer,
4438
private LoggerInterface $logger = new NullLogger(),
4539
) {
46-
$this->streamWriterGenerator = new StreamWriterGenerator($streamWriterPropertyMetadataLoader, $streamWritersDir);
47-
$this->streamReaderGenerator = new StreamReaderGenerator($streamReaderPropertyMetadataLoader, $streamReadersDir);
4840
}
4941

5042
public function warmUp(string $cacheDir, ?string $buildDir = null): array
@@ -76,7 +68,7 @@ public function isOptional(): bool
7668
private function warmUpStreamWriter(Type $type): void
7769
{
7870
try {
79-
$this->streamWriterGenerator->generate($type);
71+
$this->writer->dumpStreamer($type);
8072
} catch (ExceptionInterface $e) {
8173
$this->logger->debug('Cannot generate "json" stream writer for "{type}": {exception}', ['type' => (string) $type, 'exception' => $e]);
8274
}
@@ -85,13 +77,13 @@ private function warmUpStreamWriter(Type $type): void
8577
private function warmUpStreamReaders(Type $type): void
8678
{
8779
try {
88-
$this->streamReaderGenerator->generate($type, false);
80+
$this->reader->dumpStreamer($type, false);
8981
} catch (ExceptionInterface $e) {
9082
$this->logger->debug('Cannot generate "json" string stream reader for "{type}": {exception}', ['type' => (string) $type, 'exception' => $e]);
9183
}
9284

9385
try {
94-
$this->streamReaderGenerator->generate($type, true);
86+
$this->reader->dumpStreamer($type, true);
9587
} catch (ExceptionInterface $e) {
9688
$this->logger->debug('Cannot generate "json" resource stream reader for "{type}": {exception}', ['type' => (string) $type, 'exception' => $e]);
9789
}

src/Symfony/Component/JsonStreamer/JsonStreamReader.php

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
use PHPStan\PhpDocParser\Parser\PhpDocParser;
1515
use Psr\Container\ContainerInterface;
16+
use Symfony\Component\Config\ConfigCacheFactoryInterface;
17+
use Symfony\Component\Config\ConfigCacheInterface;
18+
use Symfony\Component\Config\Resource\ReflectionClassResource;
19+
use Symfony\Component\Filesystem\Filesystem;
1620
use Symfony\Component\JsonStreamer\Mapping\GenericTypePropertyMetadataLoader;
1721
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoader;
1822
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoaderInterface;
@@ -24,6 +28,7 @@
2428
use Symfony\Component\JsonStreamer\ValueTransformer\StringToDateTimeValueTransformer;
2529
use Symfony\Component\JsonStreamer\ValueTransformer\ValueTransformerInterface;
2630
use Symfony\Component\TypeInfo\Type;
31+
use Symfony\Component\TypeInfo\Type\ObjectType;
2732
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
2833
use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver;
2934
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
@@ -41,32 +46,35 @@ final class JsonStreamReader implements StreamReaderInterface
4146
private Instantiator $instantiator;
4247
private LazyInstantiator $lazyInstantiator;
4348

49+
private ?ConfigCacheFactoryInterface $configCacheFactory = null;
50+
private ?Filesystem $fs = null;
51+
4452
public function __construct(
4553
private ContainerInterface $valueTransformers,
4654
PropertyMetadataLoaderInterface $propertyMetadataLoader,
47-
string $streamReadersDir,
48-
?string $lazyGhostsDir = null,
55+
private string $cacheDir,
4956
) {
50-
$this->streamReaderGenerator = new StreamReaderGenerator($propertyMetadataLoader, $streamReadersDir);
57+
$this->streamReaderGenerator = new StreamReaderGenerator($propertyMetadataLoader);
5158
$this->instantiator = new Instantiator();
52-
$this->lazyInstantiator = new LazyInstantiator($lazyGhostsDir);
59+
$this->lazyInstantiator = new LazyInstantiator($cacheDir);
5360
}
5461

5562
public function read($input, Type $type, array $options = []): mixed
5663
{
5764
$isStream = \is_resource($input);
58-
$path = $this->streamReaderGenerator->generate($type, $isStream, $options);
65+
$path = $this->getStreamerPath($type, $isStream);
66+
$this->dumpStreamer($type, $isStream, $options);
5967

6068
return (require $path)($input, $this->valueTransformers, $isStream ? $this->lazyInstantiator : $this->instantiator, $options);
6169
}
6270

6371
/**
6472
* @param array<string, ValueTransformerInterface> $valueTransformers
6573
*/
66-
public static function create(array $valueTransformers = [], ?string $streamReadersDir = null, ?string $lazyGhostsDir = null): self
74+
public static function create(array $valueTransformers = [], ?string $cacheDir = null): self
6775
{
68-
$streamReadersDir ??= sys_get_temp_dir().'/json_streamer/read';
69-
$lazyGhostsDir ??= sys_get_temp_dir().'/json_streamer/lazy_ghost';
76+
$cacheDir ??= sys_get_temp_dir().'/json_streamer';
77+
7078
$valueTransformers += [
7179
'json_streamer.value_transformer.string_to_date_time' => new StringToDateTimeValueTransformer(),
7280
];
@@ -101,6 +109,52 @@ public function get(string $id): ValueTransformerInterface
101109
$typeContextFactory,
102110
);
103111

104-
return new self($valueTransformersContainer, $propertyMetadataLoader, $streamReadersDir, $lazyGhostsDir);
112+
return new self($valueTransformersContainer, $propertyMetadataLoader, $cacheDir);
113+
}
114+
115+
/**
116+
* @param array<string, mixed> $options
117+
*/
118+
public function dumpStreamer(Type $type, bool $decodeFromStream, array $options = []): void
119+
{
120+
$path = $this->getStreamerPath($type, $decodeFromStream);
121+
122+
if ($this->configCacheFactory) {
123+
$cache = $this->configCacheFactory->cache(
124+
$path,
125+
function (ConfigCacheInterface $cache) use ($type, $decodeFromStream, $options) {
126+
$classResources = [];
127+
foreach ($type->traverse() as $t) {
128+
if ($t instanceof ObjectType) {
129+
$classResources[] = new ReflectionClassResource(new \ReflectionClass($t->getClassName()));
130+
}
131+
}
132+
133+
$cache->write($this->streamReaderGenerator->generate($type, $decodeFromStream, $options), $classResources);
134+
},
135+
);
136+
137+
return;
138+
}
139+
140+
$this->fs ??= new Filesystem();
141+
142+
if (!$this->fs->exists($this->cacheDir)) {
143+
$this->fs->mkdir($this->cacheDir);
144+
}
145+
146+
if (!$this->fs->exists($path)) {
147+
$this->fs->dumpFile($path, $this->streamReaderGenerator->generate($type, $decodeFromStream, $options));
148+
}
149+
}
150+
151+
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory): void
152+
{
153+
$this->configCacheFactory = $configCacheFactory;
154+
}
155+
156+
private function getStreamerPath(Type $type, bool $decodeFromStream): string
157+
{
158+
return \sprintf('%s%s%s.json%s.php', $this->cacheDir, \DIRECTORY_SEPARATOR, hash('xxh128', (string) $type), $decodeFromStream ? '.read_stream' : '.read');
105159
}
106160
}

src/Symfony/Component/JsonStreamer/JsonStreamWriter.php

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
use PHPStan\PhpDocParser\Parser\PhpDocParser;
1515
use Psr\Container\ContainerInterface;
16+
use Symfony\Component\Config\ConfigCacheFactoryInterface;
17+
use Symfony\Component\Config\ConfigCacheInterface;
18+
use Symfony\Component\Config\Resource\ReflectionClassResource;
19+
use Symfony\Component\Filesystem\Filesystem;
1620
use Symfony\Component\JsonStreamer\Mapping\GenericTypePropertyMetadataLoader;
1721
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoader;
1822
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoaderInterface;
@@ -22,6 +26,7 @@
2226
use Symfony\Component\JsonStreamer\ValueTransformer\ValueTransformerInterface;
2327
use Symfony\Component\JsonStreamer\Write\StreamWriterGenerator;
2428
use Symfony\Component\TypeInfo\Type;
29+
use Symfony\Component\TypeInfo\Type\ObjectType;
2530
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
2631
use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver;
2732
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
@@ -37,17 +42,22 @@ final class JsonStreamWriter implements StreamWriterInterface
3742
{
3843
private StreamWriterGenerator $streamWriterGenerator;
3944

45+
private ?ConfigCacheFactoryInterface $configCacheFactory = null;
46+
private ?Filesystem $fs = null;
47+
4048
public function __construct(
4149
private ContainerInterface $valueTransformers,
4250
PropertyMetadataLoaderInterface $propertyMetadataLoader,
43-
string $streamWritersDir,
51+
private string $cacheDir,
4452
) {
45-
$this->streamWriterGenerator = new StreamWriterGenerator($propertyMetadataLoader, $streamWritersDir);
53+
$this->streamWriterGenerator = new StreamWriterGenerator($propertyMetadataLoader);
4654
}
4755

4856
public function write(mixed $data, Type $type, array $options = []): \Traversable&\Stringable
4957
{
50-
$path = $this->streamWriterGenerator->generate($type, $options);
58+
$path = $this->getStreamerPath($type, $this->cacheDir);
59+
$this->dumpStreamer($type, $options);
60+
5161
$chunks = (require $path)($data, $this->valueTransformers, $options);
5262

5363
return new
@@ -83,9 +93,10 @@ public function __toString(): string
8393
/**
8494
* @param array<string, ValueTransformerInterface> $valueTransformers
8595
*/
86-
public static function create(array $valueTransformers = [], ?string $streamWritersDir = null): self
96+
public static function create(array $valueTransformers = [], ?string $cacheDir = null): self
8797
{
88-
$streamWritersDir ??= sys_get_temp_dir().'/json_streamer/write';
98+
$cacheDir ??= sys_get_temp_dir().'/json_streamer';
99+
89100
$valueTransformers += [
90101
'json_streamer.value_transformer.date_time_to_string' => new DateTimeToStringValueTransformer(),
91102
];
@@ -120,6 +131,52 @@ public function get(string $id): ValueTransformerInterface
120131
$typeContextFactory,
121132
);
122133

123-
return new self($valueTransformersContainer, $propertyMetadataLoader, $streamWritersDir);
134+
return new self($valueTransformersContainer, $propertyMetadataLoader, $cacheDir);
135+
}
136+
137+
/**
138+
* @param array<string, mixed> $options
139+
*/
140+
public function dumpStreamer(Type $type, array $options = []): void
141+
{
142+
$path = $this->getStreamerPath($type, $this->cacheDir);
143+
144+
if ($this->configCacheFactory) {
145+
$cache = $this->configCacheFactory->cache(
146+
$path,
147+
function (ConfigCacheInterface $cache) use ($type, $options) {
148+
$classResources = [];
149+
foreach ($type->traverse() as $t) {
150+
if ($t instanceof ObjectType) {
151+
$classResources[] = new ReflectionClassResource(new \ReflectionClass($t->getClassName()));
152+
}
153+
}
154+
155+
$cache->write($this->streamWriterGenerator->generate($type, $options), $classResources);
156+
},
157+
);
158+
159+
return;
160+
}
161+
162+
$this->fs ??= new Filesystem();
163+
164+
if (!$this->fs->exists($this->cacheDir)) {
165+
$this->fs->mkdir($this->cacheDir);
166+
}
167+
168+
if (!$this->fs->exists($path)) {
169+
$this->fs->dumpFile($path, $this->streamWriterGenerator->generate($type, $options));
170+
}
171+
}
172+
173+
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory): void
174+
{
175+
$this->configCacheFactory = $configCacheFactory;
176+
}
177+
178+
private function getStreamerPath(Type $type): string
179+
{
180+
return \sprintf('%s%s%s.json.write.php', $this->cacheDir, \DIRECTORY_SEPARATOR, hash('xxh128', (string) $type));
124181
}
125182
}

0 commit comments

Comments
 (0)