8000 [JsonEncoder] Introduce component · symfony/symfony@aed69bb · GitHub
[go: up one dir, main page]

Skip to content

Commit aed69bb

Browse files
committed
[JsonEncoder] Introduce component
1 parent 73d4904 commit aed69bb

File tree

196 files changed

+8451
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

196 files changed

+8451
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@
77
/src/Symfony/Component/Translation/Bridge export-ignore
88
/src/Symfony/Component/Emoji/Resources/data/* linguist-generated=true
99
/src/Symfony/Component/Intl/Resources/data/*/* linguist-generated=true
10+
/src/Symfony/Component/JsonEncoder/Tests/Fixtures/encoder/* linguist-generated=true
11+
/src/Symfony/Component/JsonEncoder/Tests/Fixtures/decoder/* linguist-generated=true
1012
/src/Symfony/**/.github/workflows/close-pull-request.yml linguist-generated=true
1113
/src/Symfony/**/.github/PULL_REQUEST_TEMPLATE.md linguist-generated=true

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"symfony/http-foundation": "self.version",
8484
"symfony/http-kernel": "self.version",
8585
"symfony/intl": "self.version",
86+
"symfony/json-encoder": "self.version",
8687
"symfony/ldap": "self.version",
8788
"symfony/lock": "self.version",
8889
"symfony/mailer": "self.version",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\JsonEncoder\Attribute;
13+
14+
/**
15+
* Defines a callable or a {@see \Symfony\Component\JsonEncoder\Decode\Denormalizer\DenormalizerInterface} service id
16+
* that will be used to denormalize the property data during decoding.
17+
*
18+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
19+
*
20+
* @experimental
21+
*/
22+
#[\Attribute(\Attribute::TARGET_PROPERTY)]
23+
class Denormalizer
24+
{
25+
private string|\Closure $denormalizer;
26+
27+
/**
28+
* @param string|(callable(mixed, array<string, mixed>?): mixed)|(callable(mixed): mixed) $denormalizer
29+
*/
30+
public function __construct(mixed $denormalizer)
31+
{
32+
if (\is_callable($denormalizer)) {
33+
$denormalizer = \Closure::fromCallable($denormalizer);
34+
}
35+
36+
$this->denormalizer = $denormalizer;
37+
}
38+
39+
public function getDenormalizer(): string|\Closure
40+
{
41+
return $this->denormalizer;
42+
}
43+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\JsonEncoder\Attribute;
13+
14+
/**
15+
* Defines the encoded property name.
16+
*
17+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
18+
*
19+
* @experimental
20+
*/
21+
#[\Attribute(\Attribute::TARGET_PROPERTY)]
22+
final class EncodedName
23+
{
24+
public function __construct(
25+
private string $name,
26+
) {
27+
}
28+
29+
public function getName(): string
30+
{
31+
return $this->name;
32+
}
33+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\JsonEncoder\Attribute;
13+
14+
/**
15+
* Defines a callable or a {@see \Symfony\Component\JsonEncoder\Encode\Normalizer\NormalizerInterface} service id
16+
* that will be used to normalize the property data during encoding.
17+
*
18+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
19+
*
20+
* @experimental
21+
*/
22+
#[\Attribute(\Attribute::TARGET_PROPERTY)]
23+
class Normalizer
24+
{
25+
private string|\Closure $normalizer;
26+
27+
/**
28+
* @param string|(callable(mixed, array<string, mixed>?): mixed)|(callable(mixed): mixed) $normalizer
29+
*/
30+
public function __construct(mixed $normalizer)
31+
{
32+
if (\is_callable($normalizer)) {
33+
$normalizer = \Closure::fromCallable($normalizer);
34+
}
35+
36+
$this->normalizer = $normalizer;
37+
}
38+
39+
public function getNormalizer(): string|\Closure
40+
{
41+
return $this->normalizer;
42+
}
43+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
7.3
5+
---
6+
7+
* Introduce the component as experimental
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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\JsonEncoder\CacheWarmer;
13+
14+
use Psr\Log\LoggerInterface;
15+
use Psr\Log\NullLogger;
16+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
17+
use Symfony\Component\JsonEncoder\Decode\DecoderGenerator;
18+
use Symfony\Component\JsonEncoder\Encode\EncoderGenerator;
19+
use Symfony\Component\JsonEncoder\Exception\ExceptionInterface;
20+
use Symfony\Component\JsonEncoder\Mapping\PropertyMetadataLoaderInterface;
21+
use Symfony\Component\TypeInfo\Type;
22+
23+
/**
24+
* Generates encoders and decoders PHP files.
25+
*
26+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
27+
*
28+
* @internal
29+
*/
30+
final class EncoderDecoderCacheWarmer implements CacheWarmerInterface
31+
{
32+
private EncoderGenerator $encoderGenerator;
33+
private DecoderGenerator $decoderGenerator;
34+
35+
/**
36+
* @param iterable<class-string> $encodableClassNames
37+
*/
38+
public function __construct(
39+
private iterable $encodableClassNames,
40+
PropertyMetadataLoaderInterface $encodePropertyMetadataLoader,
41+
PropertyMetadataLoaderInterface $decodePropertyMetadataLoader,
42+
string $encodersDir,
43+
string $decodersDir,
44+
bool $forceEncodeChunks = false,
45+
private LoggerInterface $logger = new NullLogger(),
46+
) {
47+
$this->encoderGenerator = new EncoderGenerator($encodePropertyMetadataLoader, $encodersDir, $forceEncodeChunks);
48+
$this->decoderGenerator = new DecoderGenerator($decodePropertyMetadataLoader, $decodersDir);
49+
}
50+
51+
public function warmUp(string $cacheDir, ?string $buildDir = null): array
52+
{
53+
foreach ($this->encodableClassNames as $className) {
54+
$type = Type::object($className);
55+
56+
$this->warmUpEncoder($type);
57+
$this->warmUpDecoders($type);
58+
}
59+
60+
return [];
61+
}
62+
63+
public function isOptional(): bool
64+
{
65+
return true;
66+
}
67+
68+
private function warmUpEncoder(Type $type): void
69+
{
70+
try {
71+
$this->encoderGenerator->generate($type);
72+
} catch (ExceptionInterface $e) {
73+
$this->logger->debug('Cannot generate "json" encoder for "{type}": {exception}', ['type' => (string) $type, 'exception' => $e]);
74+
}
75+
}
76+
77+
private function warmUpDecoders(Type $type): void
78+
{
79+
try {
80+
$this->decoderGenerator->generate($type, decodeFromStream: false);
81+
} catch (ExceptionInterface $e) {
82+
$this->logger->debug('Cannot generate "json" decoder for "{type}": {exception}', ['type' => (string) $type, 'exception' => $e]);
83+
}
84+
85+
try {
86+
$this->decoderGenerator->generate($type, decodeFromStream: true);
87+
} catch (ExceptionInterface $e) {
88+
$this->logger->debug('Cannot generate "json" streaming decoder for "{type}": {exception}', ['type' => (string) $type, 'exception' => $e]);
89+
}
90+
}
91+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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\JsonEncoder\CacheWarmer;
13+
14+
use Symfony\Component\Filesystem\Filesystem;
15+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer;
16+
use Symfony\Component\JsonEncoder\Exception\RuntimeException;
17+
use Symfony\Component\VarExporter\ProxyHelper;
18+
19+
/**
20+
* Generates lazy ghost {@see \Symfony\Component\VarExporter\LazyGhostTrait}
21+
* PHP files for $encodable types.
22+
*
23+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
24+
*
25+
* @internal
26+
*/
27+
final class LazyGhostCacheWarmer extends CacheWarmer
28+
{
29+
private Filesystem $fs;
30+
31+
/**
32+
* @param iterable<class-string> $encodableClassNames
33+
*/
34+
public function __construct(
35+
private iterable $encodableClassNames,
36+
private string $lazyGhostsDir,
37+
) {
38+
$this->fs = new Filesystem();
39+
}
40+
41+
public function warmUp(string $cacheDir, ?string $buildDir = null): array
42+
{
43+
if (!$this->fs->exists($this->lazyGhostsDir)) {
44+
$this->fs->mkdir($this->lazyGhostsDir);
45+
}
46+
47+
foreach ($this->encodableClassNames as $className) {
48+
$this->warmClassLazyGhost($className);
49+
}
50+
51+
return [];
52+
}
53+
54+
public function isOptional(): bool
55+
{
56+
return true;
57+
}
58+
59+
/**
60+
* @param class-string $className
61+
*/
62+
private function warmClassLazyGhost(string $className): void
63+
{
64+
$path = \sprintf('%s%s%s.php', $this->lazyGhostsDir, \DIRECTORY_SEPARATOR, hash('xxh128', $className));
65+
66+
try {
67+
$classReflection = new \ReflectionClass($className);
68+
} catch (\ReflectionException $e) {
69+
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
70+
}
71+
72+
$this->writeCacheFile($path, \sprintf(
73+
'class %s%s',
74+
\sprintf('%sGhost', preg_replace('/\\\\/', '', $className)),
75+
ProxyHelper::generateLazyGhost($classReflection),
76+
));
77+
}
78+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\JsonEncoder\DataModel;
13+
14+
use PhpParser\Node\Expr;
15+
16+
/**
17+
* Represents a way to access data on PHP.
18+
*
19+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
20+
*
21+
* @internal
22+
*/
23+
interface DataAccessorInterface
24+
{
25+
/**
26+
* Converts to "nikic/php-parser" PHP expression.
27+
*/
28+
public function toPhpExpr(): Expr;
29+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\JsonEncoder\DataModel\Decode;
13+
14+
use Symfony\Component\TypeInfo\Type\BackedEnumType;
15+
16+
/**
17+
* Represents a backed enum in the data model graph representation.
18+
*
19+
* Backed enums are leaves in the data model tree.
20+
*
21+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
22+
*
23+
* @internal
24+
*/
25+
final class BackedEnumNode implements DataModelNodeInterface
26+
{
27+
public function __construct(
28+
public BackedEnumType $type,
29+
) {
30+
}
31+
32+
public function getIdentifier(): string
33+
{
34+
return (string) $this->type;
35+
}
36+
37+
public function getType(): BackedEnumType
38+
{
39+
return $this->type;
40+
}
41+
}

0 commit comments

Comments
 (0)
0