diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index d9d22407ed72d..31934dd32ca31 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG --- * Add support for constructor promoted properties to `Context` attribute +* Add context option `PropertyNormalizer::NORMALIZE_VISIBILITY` with bitmask flags `PropertyNormalizer::NORMALIZE_PUBLIC`, `PropertyNormalizer::NORMALIZE_PROTECTED`, `PropertyNormalizer::NORMALIZE_PRIVATE` +* Add method `withNormalizeVisibility` to `PropertyNormalizerContextBuilder` 6.1 --- diff --git a/src/Symfony/Component/Serializer/Context/Normalizer/PropertyNormalizerContextBuilder.php b/src/Symfony/Component/Serializer/Context/Normalizer/PropertyNormalizerContextBuilder.php index e0a8f2f9108e5..71d5ed04500af 100644 --- a/src/Symfony/Component/Serializer/Context/Normalizer/PropertyNormalizerContextBuilder.php +++ b/src/Symfony/Component/Serializer/Context/Normalizer/PropertyNormalizerContextBuilder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; + /** * A helper providing autocompletion for available PropertyNormalizer options. * @@ -18,4 +20,11 @@ */ final class PropertyNormalizerContextBuilder extends AbstractObjectNormalizerContextBuilder { + /** + * Configures whether fields should be output based on visibility. + */ + public function withNormalizeVisibility(int $normalizeVisibility): static + { + return $this->with(PropertyNormalizer::NORMALIZE_VISIBILITY, $normalizeVisibility); + } } diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php index 1143bbc3454f6..bd537a1b1d44f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php @@ -12,6 +12,10 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * Converts between objects and arrays by mapping properties. @@ -32,6 +36,24 @@ */ class PropertyNormalizer extends AbstractObjectNormalizer { + public const NORMALIZE_PUBLIC = 1; + public const NORMALIZE_PROTECTED = 2; + public const NORMALIZE_PRIVATE = 4; + + /** + * Flag to control whether fields should be output based on visibility. + */ + public const NORMALIZE_VISIBILITY = 'normalize_visibility'; + + public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = []) + { + parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext); + + if (!isset($this->defaultContext[self::NORMALIZE_VISIBILITY])) { + $this->defaultContext[self::NORMALIZE_VISIBILITY] = self::NORMALIZE_PUBLIC | self::NORMALIZE_PROTECTED | self::NORMALIZE_PRIVATE; + } + } + /** * {@inheritdoc} * @@ -90,14 +112,29 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr try { $reflectionProperty = $this->getReflectionProperty($classOrObject, $attribute); - if ($reflectionProperty->isStatic()) { - return false; - } } catch (\ReflectionException) { return false; } - return true; + if ($reflectionProperty->isStatic()) { + return false; + } + + $normalizeVisibility = $context[self::NORMALIZE_VISIBILITY] ?? $this->defaultContext[self::NORMALIZE_VISIBILITY]; + + if ((self::NORMALIZE_PUBLIC & $normalizeVisibility) && $reflectionProperty->isPublic()) { + return true; + } + + if ((self::NORMALIZE_PROTECTED & $normalizeVisibility) && $reflectionProperty->isProtected()) { + return true; + } + + if ((self::NORMALIZE_PRIVATE & $normalizeVisibility) && $reflectionProperty->isPrivate()) { + return true; + } + + return false; } /** diff --git a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/PropertyNormalizerContextBuilderTest.php b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/PropertyNormalizerContextBuilderTest.php new file mode 100644 index 0000000000000..41f0b89902897 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/PropertyNormalizerContextBuilderTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\PropertyNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; + +/** + * @author Antoine Lamirault + */ +class PropertyNormalizerContextBuilderTest extends TestCase +{ + private PropertyNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new PropertyNormalizerContextBuilder(); + } + + public function testWithNormalizeVisibility() + { + $context = $this->contextBuilder + ->withNormalizeVisibility(PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED) + ->toArray(); + + $this->assertSame([ + PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED, + ], $context); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index b7993e59f32df..2b9a111ed1e27 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -122,6 +122,54 @@ public function testNormalizeObjectWithLazyProperties() ); } + public function testNormalizeOnlyPublic() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['foo' => 'foo'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PUBLIC]) + ); + } + + public function testNormalizeOnlyProtected() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['camelCase' => 'camelcase'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PROTECTED]) + ); + } + + public function testNormalizeOnlyPrivate() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['bar' => 'bar'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PRIVATE]) + ); + } + + public function testNormalizePublicAndProtected() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['foo' => 'foo', 'camelCase' => 'camelcase'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED]) + ); + } + public function testDenormalize() { $obj = $this->normalizer->denormalize(