Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Component/JsonStreamer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* Remove `nikic/php-parser` dependency
* Add `_current_object` to the context passed to value transformers during write operations
* Add `include_null_properties` option to encode the properties with `null` value
* Add synthetic properties support

7.3
---
Expand Down
18 changes: 15 additions & 3 deletions src/Symfony/Component/JsonStreamer/Mapping/PropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,31 @@ final class PropertyMetadata
* @param list<string|\Closure> $streamToNativeValueTransformers
*/
public function __construct(
private string $name,
private ?string $name,
private Type $type,
private array $nativeToStreamValueTransformers = [],
private array $streamToNativeValueTransformers = [],
) {
}

public function getName(): string
/**
* @param list<string|\Closure> $nativeToStreamValueTransformers
* @param list<string|\Closure> $streamToNativeValueTransformers
*/
public static function createSynthetic(
Type $type,
array $nativeToStreamValueTransformers = [],
array $streamToNativeValueTransformers = [],
): self {
return new self(null, $type, $nativeToStreamValueTransformers, $streamToNativeValueTransformers);
}

public function getName(): ?string
{
return $this->name;
}

public function withName(string $name): self
public function withName(?string $name): self
{
return new self($name, $this->type, $this->nativeToStreamValueTransformers, $this->streamToNativeValueTransformers);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ public function load(string $className, array $options = [], array $context = []
$result = [];

foreach ($initialResult as $initialStreamedName => $initialMetadata) {
if (!$initialName = $initialMetadata->getName()) {
continue;
}

try {
$propertyReflection = new \ReflectionProperty($className, $initialMetadata->getName());
$propertyReflection = new \ReflectionProperty($className, $initialName);
} catch (\ReflectionException $e) {
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ public function load(string $className, array $options = [], array $context = []
$result = [];

foreach ($initialResult as $initialStreamedName => $initialMetadata) {
if (!$initialName = $initialMetadata->getName()) {
continue;
}

try {
$propertyReflection = new \ReflectionProperty($className, $initialMetadata->getName());
$propertyReflection = new \ReflectionProperty($className, $initialName);
} catch (\ReflectionException $e) {
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ private function createDataModel(Type $type, array $options = [], array $context
$propertiesMetadata = $this->propertyMetadataLoader->load($className, $options, $context);

foreach ($propertiesMetadata as $streamedName => $propertyMetadata) {
if (!$propertyMetadata->getName()) {
continue;
}

$propertiesNodes[$streamedName] = [
'name' => $propertyMetadata->getName(),
'value' => $this->createDataModel($propertyMetadata->getType(), $options, $context),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\JsonStreamer\Tests\Fixtures\Mapping;

use Symfony\Component\JsonStreamer\Mapping\PropertyMetadata;
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoaderInterface;
use Symfony\Component\TypeInfo\Type;

final class SyntheticPropertyMetadataLoader implements PropertyMetadataLoaderInterface
{
public function load(string $className, array $options = [], array $context = []): array
{
return [
'synthetic' => PropertyMetadata::createSynthetic(Type::true(), [
self::true(...),
]),
];
}

public static function true(): true
{
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\JsonStreamer\Tests\Fixtures\Model;

class DummyWithSyntheticProperties
{
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/Symfony/Component/JsonStreamer/Tests/JsonStreamReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
namespace Symfony\Component\JsonStreamer\Tests;

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\JsonStreamer\JsonStreamReader;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Mapping\SyntheticPropertyMetadataLoader;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDateTimes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithGenerics;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithPhpDoc;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithSyntheticProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithValueTransformerAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\ValueTransformer\DivideStringAndCastToIntValueTransformer;
use Symfony\Component\JsonStreamer\Tests\Fixtures\ValueTransformer\StringToBooleanValueTransformer;
Expand Down Expand Up @@ -176,6 +179,16 @@ public function testReadObjectWithDateTimes()
}, '{"interface":"2024-11-20","immutable":"2025-11-20"}', Type::object(DummyWithDateTimes::class));
}

public function testReadObjectWithSyntheticProperties()
{
$reader = new JsonStreamReader($this->createMock(ContainerInterface::class), new SyntheticPropertyMetadataLoader(), $this->streamReadersDir, $this->lazyGhostsDir);

$this->assertRead($reader, function (mixed $read) {
$this->assertInstanceOf(DummyWithSyntheticProperties::class, $read);
$this->assertSame([], get_object_vars($read));
}, '{"synthetic":true}', Type::object(DummyWithSyntheticProperties::class));
}

public function testCreateStreamReaderFile()
{
$reader = JsonStreamReader::create(streamReadersDir: $this->streamReadersDir, lazyGhostsDir: $this->lazyGhostsDir);
Expand Down
10 changes: 10 additions & 0 deletions src/Symfony/Component/JsonStreamer/Tests/JsonStreamWriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\JsonStreamer\Exception\NotEncodableValueException;
use Symfony\Component\JsonStreamer\JsonStreamWriter;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Mapping\SyntheticPropertyMetadataLoader;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithArray;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDateTimes;
Expand All @@ -25,6 +27,7 @@
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedArray;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithPhpDoc;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithSyntheticProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithValueTransformerAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummy;
Expand Down Expand Up @@ -273,6 +276,13 @@ public function testWriteObjectWithDollarNamedProperties()
$this->assertWritten('{"$foo":true,"{$foo->bar}":true}', new DummyWithDollarNamedProperties(), Type::object(DummyWithDollarNamedProperties::class));
}

public function testWriteObjectWithSyntheticProperty()
{
$writer = new JsonStreamWriter($this->createMock(ContainerInterface::class), new SyntheticPropertyMetadataLoader(), $this->streamWritersDir);

$this->assertSame('{"synthetic":true}', (string) $writer->write(new DummyWithSyntheticProperties(), Type::object(DummyWithSyntheticProperties::class)));
}

#[DataProvider('throwWhenMaxDepthIsReachedDataProvider')]
public function testThrowWhenMaxDepthIsReached(Type $type, mixed $data)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
use Symfony\Component\JsonStreamer\Read\StreamReaderGenerator;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyEnum;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Mapping\SyntheticPropertyMetadataLoader;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithOtherDummies;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithSyntheticProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithValueTransformerAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\ValueTransformer\DivideStringAndCastToIntValueTransformer;
Expand Down Expand Up @@ -53,9 +55,9 @@ protected function setUp(): void
}

#[DataProvider('generatedStreamReaderDataProvider')]
public function testGeneratedStreamReader(string $fixture, Type $type)
public function testGeneratedStreamReader(string $fixture, Type $type, ?PropertyMetadataLoaderInterface $propertyMetadataLoader = null)
{
$propertyMetadataLoader = new GenericTypePropertyMetadataLoader(
$propertyMetadataLoader ??= new GenericTypePropertyMetadataLoader(
new DateTimeTypePropertyMetadataLoader(new AttributePropertyMetadataLoader(
new PropertyMetadataLoader(TypeResolver::create()),
new ServiceContainer([
Expand All @@ -81,7 +83,7 @@ public function testGeneratedStreamReader(string $fixture, Type $type)
}

/**
* @return iterable<array{0: string, 1: Type}>
* @return iterable<array{0: string, 1: Type, 2?: PropertyMetadataLoaderInterface}>
*/
public static function generatedStreamReaderDataProvider(): iterable
{
Expand All @@ -107,6 +109,7 @@ public static function generatedStreamReaderDataProvider(): iterable
yield ['object_in_object', Type::object(DummyWithOtherDummies::class)];
yield ['object_with_nullable_properties', Type::object(DummyWithNullableProperties::class)];
yield ['object_with_value_transformer', Type::object(DummyWithValueTransformerAttributes::class)];
yield ['object_with_synthetic_properties', Type::object(DummyWithSyntheticProperties::class), new SyntheticPropertyMetadataLoader()];

yield ['union', Type::union(Type::int(), Type::list(Type::enum(DummyBackedEnum::class)), Type::object(DummyWithNameAttributes::class))];
yield ['object_with_union', Type::object(DummyWithUnionProperties::class)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
use Symfony\Component\JsonStreamer\Mapping\Write\DateTimeTypePropertyMetadataLoader;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyEnum;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Mapping\SyntheticPropertyMetadataLoader;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithArray;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDollarNamedProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedArray;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithOtherDummies;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithSyntheticProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithValueTransformerAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummy;
Expand Down Expand Up @@ -56,9 +58,9 @@ protected function setUp(): void
}

#[DataProvider('generatedStreamWriterDataProvider')]
public function testGeneratedStreamWriter(string $fixture, Type $type)
public function testGeneratedStreamWriter(string $fixture, Type $type, ?PropertyMetadataLoaderInterface $propertyMetadataLoader = null)
{
$propertyMetadataLoader = new GenericTypePropertyMetadataLoader(
$propertyMetadataLoader ??= new GenericTypePropertyMetadataLoader(
new DateTimeTypePropertyMetadataLoader(new AttributePropertyMetadataLoader(
new PropertyMetadataLoader(TypeResolver::create()),
new ServiceContainer([
Expand All @@ -79,7 +81,7 @@ public function testGeneratedStreamWriter(string $fixture, Type $type)
}

/**
* @return iterable<array{0: string, 1: Type}>
* @return iterable<array{0: string, 1: Type, 2?: PropertyMetadataLoaderInterface}>
*/
public static function generatedStreamWriterDataProvider(): iterable
{
Expand Down Expand Up @@ -111,6 +113,7 @@ public static function generatedStreamWriterDataProvider(): iterable
yield ['object_with_value_transformer', Type::object(DummyWithValueTransformerAttributes::class)];
yield ['self_referencing_object', Type::object(SelfReferencingDummy::class)];
yield ['object_with_dollar_named_properties', Type::object(DummyWithDollarNamedProperties::class)];
yield ['object_with_synthetic_properties', Type::object(DummyWithSyntheticProperties::class), new SyntheticPropertyMetadataLoader()];

yield ['union', Type::union(Type::int(), Type::list(Type::enum(DummyBackedEnum::class)), Type::object(DummyWithNameAttributes::class))];
yield ['object_with_union', Type::object(DummyWithUnionProperties::class)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private function createDataModel(Type $type, string $accessor, array $options =
$propertiesNodes = [];

foreach ($propertiesMetadata as $streamedName => $propertyMetadata) {
$propertyAccessor = $accessor.'->'.$propertyMetadata->getName();
$propertyAccessor = $propertyMetadata->getName() ? $accessor.'->'.$propertyMetadata->getName() : 'null';

foreach ($propertyMetadata->getNativeToStreamValueTransformer() as $valueTransformer) {
if (\is_string($valueTransformer)) {
Expand Down