diff --git a/src/Builder/DefaultDriverFactory.php b/src/Builder/DefaultDriverFactory.php index e893a9355..5b53daf68 100644 --- a/src/Builder/DefaultDriverFactory.php +++ b/src/Builder/DefaultDriverFactory.php @@ -6,10 +6,10 @@ use Doctrine\Common\Annotations\Reader; use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface; -use JMS\Serializer\Metadata\Driver\AnnotationDriver; -use JMS\Serializer\Metadata\Driver\AttributeDriver; +use JMS\Serializer\Metadata\Driver\AnnotationOrAttributeDriver; use JMS\Serializer\Metadata\Driver\DefaultValuePropertyDriver; use JMS\Serializer\Metadata\Driver\EnumPropertiesDriver; +use JMS\Serializer\Metadata\Driver\NullDriver; use JMS\Serializer\Metadata\Driver\TypedPropertiesDriver; use JMS\Serializer\Metadata\Driver\XmlDriver; use JMS\Serializer\Metadata\Driver\YamlDriver; @@ -56,11 +56,9 @@ public function enableEnumSupport(bool $enableEnumSupport = true): void public function createDriver(array $metadataDirs, Reader $annotationReader): DriverInterface { - if (PHP_VERSION_ID >= 80000) { - $annotationReader = new AttributeDriver\AttributeReader($annotationReader); - } - - $driver = new AnnotationDriver($annotationReader, $this->propertyNamingStrategy, $this->typeParser); + $driver = new DriverChain([ + new AnnotationOrAttributeDriver($this->propertyNamingStrategy, $this->typeParser, $this->expressionEvaluator, $annotationReader), + ]); if (!empty($metadataDirs)) { $fileLocator = new FileLocator($metadataDirs); @@ -71,6 +69,8 @@ public function createDriver(array $metadataDirs, Reader $annotationReader): Dri ]); } + $driver->addDriver(new NullDriver($this->propertyNamingStrategy)); + if ($this->enableEnumSupport) { $driver = new EnumPropertiesDriver($driver); } diff --git a/src/Metadata/Driver/AnnotationDriver.php b/src/Metadata/Driver/AnnotationDriver.php index 86f5a5bf4..d2fbb9138 100644 --- a/src/Metadata/Driver/AnnotationDriver.php +++ b/src/Metadata/Driver/AnnotationDriver.php @@ -18,7 +18,7 @@ class AnnotationDriver extends AnnotationOrAttributeDriver public function __construct(Reader $reader, PropertyNamingStrategyInterface $namingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null) { - parent::__construct($namingStrategy, $typeParser, $expressionEvaluator); + parent::__construct($namingStrategy, $typeParser, $expressionEvaluator, $reader); $this->reader = $reader; } diff --git a/src/Metadata/Driver/AnnotationOrAttributeDriver.php b/src/Metadata/Driver/AnnotationOrAttributeDriver.php index 461d831c1..8b506add4 100644 --- a/src/Metadata/Driver/AnnotationOrAttributeDriver.php +++ b/src/Metadata/Driver/AnnotationOrAttributeDriver.php @@ -4,6 +4,7 @@ namespace JMS\Serializer\Metadata\Driver; +use Doctrine\Common\Annotations\Reader; use JMS\Serializer\Annotation\Accessor; use JMS\Serializer\Annotation\AccessorOrder; use JMS\Serializer\Annotation\AccessType; @@ -47,7 +48,7 @@ use Metadata\Driver\DriverInterface; use Metadata\MethodMetadata; -abstract class AnnotationOrAttributeDriver implements DriverInterface +class AnnotationOrAttributeDriver implements DriverInterface { use ExpressionMetadataTrait; @@ -61,15 +62,23 @@ abstract class AnnotationOrAttributeDriver implements DriverInterface */ private $namingStrategy; - public function __construct(PropertyNamingStrategyInterface $namingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null) + /** + * @var Reader + */ + private $reader; + + public function __construct(PropertyNamingStrategyInterface $namingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null, ?Reader $reader = null) { $this->typeParser = $typeParser ?: new Parser(); $this->namingStrategy = $namingStrategy; $this->expressionEvaluator = $expressionEvaluator; + $this->reader = $reader; } public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadata { + $configured = false; + $classMetadata = new ClassMetadata($name = $class->name); $fileResource = $class->getFilename(); @@ -86,6 +95,8 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat $readOnlyClass = false; foreach ($this->getClassAnnotations($class) as $annot) { + $configured = true; + if ($annot instanceof ExclusionPolicy) { $exclusionPolicy = $annot->policy; } elseif ($annot instanceof XmlRoot) { @@ -135,6 +146,8 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat $methodAnnotations = $this->getMethodAnnotations($method); foreach ($methodAnnotations as $annot) { + $configured = true; + if ($annot instanceof PreSerialize) { $classMetadata->addPreSerializeMethod(new MethodMetadata($name, $method->name)); continue 2; @@ -174,6 +187,8 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat $propertyAnnotations = $propertiesAnnotations[$propertyKey]; foreach ($propertyAnnotations as $annot) { + $configured = true; + if ($annot instanceof Since) { $propertyMetadata->sinceVersion = $annot->version; } elseif ($annot instanceof Until) { @@ -274,21 +289,79 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat } } + if (!$configured) { + return null; + } + return $classMetadata; } /** * @return list */ - abstract protected function getClassAnnotations(\ReflectionClass $class): array; + protected function getClassAnnotations(\ReflectionClass $class): array + { + $annotations = []; + + if (PHP_VERSION_ID >= 80000) { + $annotations = array_map( + static function (\ReflectionAttribute $attribute): object { + return $attribute->newInstance(); + }, + $class->getAttributes() + ); + } + + if (null !== $this->reader) { + $annotations = array_merge($annotations, $this->reader->getClassAnnotations($class)); + } + + return $annotations; + } /** * @return list */ - abstract protected function getMethodAnnotations(\ReflectionMethod $method): array; + protected function getMethodAnnotations(\ReflectionMethod $method): array + { + $annotations = []; + + if (PHP_VERSION_ID >= 80000) { + $annotations = array_map( + static function (\ReflectionAttribute $attribute): object { + return $attribute->newInstance(); + }, + $method->getAttributes() + ); + } + + if (null !== $this->reader) { + $annotations = array_merge($annotations, $this->reader->getMethodAnnotations($method)); + } + + return $annotations; + } /** * @return list */ - abstract protected function getPropertyAnnotations(\ReflectionProperty $property): array; + protected function getPropertyAnnotations(\ReflectionProperty $property): array + { + $annotations = []; + + if (PHP_VERSION_ID >= 80000) { + $annotations = array_map( + static function (\ReflectionAttribute $attribute): object { + return $attribute->newInstance(); + }, + $property->getAttributes() + ); + } + + if (null !== $this->reader) { + $annotations = array_merge($annotations, $this->reader->getPropertyAnnotations($property)); + } + + return $annotations; + } } diff --git a/src/Metadata/Driver/NullDriver.php b/src/Metadata/Driver/NullDriver.php index fbbf750f0..30bc387a4 100644 --- a/src/Metadata/Driver/NullDriver.php +++ b/src/Metadata/Driver/NullDriver.php @@ -5,11 +5,23 @@ namespace JMS\Serializer\Metadata\Driver; use JMS\Serializer\Metadata\ClassMetadata; +use JMS\Serializer\Metadata\PropertyMetadata; +use JMS\Serializer\Naming\PropertyNamingStrategyInterface; use Metadata\ClassMetadata as BaseClassMetadata; use Metadata\Driver\DriverInterface; class NullDriver implements DriverInterface { + /** + * @var PropertyNamingStrategyInterface + */ + private $namingStrategy; + + public function __construct(PropertyNamingStrategyInterface $namingStrategy) + { + $this->namingStrategy = $namingStrategy; + } + public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadata { $classMetadata = new ClassMetadata($name = $class->name); @@ -18,6 +30,20 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat $classMetadata->fileResources[] = $fileResource; } + foreach ($class->getProperties() as $property) { + if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) { + continue; + } + + $propertyMetadata = new PropertyMetadata($name, $property->getName()); + + if (!$propertyMetadata->serializedName) { + $propertyMetadata->serializedName = $this->namingStrategy->translateName($propertyMetadata); + } + + $classMetadata->addPropertyMetadata($propertyMetadata); + } + return $classMetadata; } } diff --git a/tests/Fixtures/AuthorDeprecatedReadOnly.php b/tests/Fixtures/AuthorDeprecatedReadOnly.php index 3e31e1dc2..487c2de7a 100644 --- a/tests/Fixtures/AuthorDeprecatedReadOnly.php +++ b/tests/Fixtures/AuthorDeprecatedReadOnly.php @@ -19,9 +19,10 @@ class AuthorDeprecatedReadOnly { /** - * @JMS\Serializer\Annotation\ReadOnly + * @ReadOnly * @SerializedName("id") */ + #[\JMS\Serializer\Annotation\DeprecatedReadOnly] #[SerializedName(name: 'id')] private $id; diff --git a/tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php b/tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php index cff0e6e88..494929ed1 100644 --- a/tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php +++ b/tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php @@ -17,12 +17,14 @@ * @ReadOnly */ #[XmlRoot(name: 'author')] +#[\JMS\Serializer\Annotation\DeprecatedReadOnly] class AuthorDeprecatedReadOnlyPerClass { /** * @ReadOnly * @SerializedName("id") */ + #[\JMS\Serializer\Annotation\DeprecatedReadOnly] #[SerializedName(name: 'id')] private $id; @@ -41,6 +43,7 @@ public function __construct($id, $name) #[Type(name: 'string')] #[SerializedName(name: 'full_name')] #[Accessor(getter: 'getName')] + #[\JMS\Serializer\Annotation\DeprecatedReadOnly(readOnly: false)] private $name; public function getId() diff --git a/tests/Fixtures/AuthorList.php b/tests/Fixtures/AuthorList.php index 695beb569..23969b6ff 100644 --- a/tests/Fixtures/AuthorList.php +++ b/tests/Fixtures/AuthorList.php @@ -43,8 +43,7 @@ public function count(): int /** * @see ArrayAccess */ - #[\ReturnTypeWillChange] - public function offsetExists($offset) + public function offsetExists($offset): bool { return isset($this->authors[$offset]); } @@ -52,8 +51,7 @@ public function offsetExists($offset) /** * @see ArrayAccess */ - #[\ReturnTypeWillChange] - public function offsetGet($offset) + public function offsetGet($offset): ?Author { return $this->authors[$offset] ?? null; } @@ -61,8 +59,7 @@ public function offsetGet($offset) /** * @see ArrayAccess */ - #[\ReturnTypeWillChange] - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { if (null === $offset) { $this->authors[] = $value; @@ -74,8 +71,7 @@ public function offsetSet($offset, $value) /** * @see ArrayAccess */ - #[\ReturnTypeWillChange] - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->authors[$offset]); } diff --git a/tests/Fixtures/Doctrine/PersistendCollection/SmartPhone.php b/tests/Fixtures/Doctrine/PersistendCollection/SmartPhone.php index f6a5911a5..8622d5874 100644 --- a/tests/Fixtures/Doctrine/PersistendCollection/SmartPhone.php +++ b/tests/Fixtures/Doctrine/PersistendCollection/SmartPhone.php @@ -21,6 +21,7 @@ class SmartPhone * * @var string */ + #[Serializer\SerializedName(name: 'id')] #[Serializer\Type(name: 'string')] protected $id; @@ -40,6 +41,7 @@ class SmartPhone * * @var ArrayCollection */ + #[Serializer\SerializedName(name: 'applications')] #[Serializer\Type(name: 'ArrayCollection')] private $apps; diff --git a/tests/Fixtures/ObjectWithLifecycleCallbacks.php b/tests/Fixtures/ObjectWithLifecycleCallbacks.php index fb8359481..e991ae992 100644 --- a/tests/Fixtures/ObjectWithLifecycleCallbacks.php +++ b/tests/Fixtures/ObjectWithLifecycleCallbacks.php @@ -39,6 +39,7 @@ public function __construct($firstname = 'Foo', $lastname = 'Bar') /** * @PreSerialize */ + #[PreSerialize] private function prepareForSerialization() { $this->name = $this->firstname . ' ' . $this->lastname; @@ -47,6 +48,7 @@ private function prepareForSerialization() /** * @PostSerialize */ + #[PostSerialize] private function cleanUpAfterSerialization() { $this->name = null; @@ -55,6 +57,7 @@ private function cleanUpAfterSerialization() /** * @PostDeserialize */ + #[PostDeserialize] private function afterDeserialization() { [$this->firstname, $this->lastname] = explode(' ', $this->name); diff --git a/tests/Metadata/Driver/AnnotationDriverTest.php b/tests/Metadata/Driver/AnnotationDriverTest.php index 6ae989d90..b03b6ab57 100644 --- a/tests/Metadata/Driver/AnnotationDriverTest.php +++ b/tests/Metadata/Driver/AnnotationDriverTest.php @@ -6,13 +6,20 @@ use Doctrine\Common\Annotations\AnnotationReader; use JMS\Serializer\Metadata\Driver\AnnotationDriver; +use JMS\Serializer\Metadata\Driver\NullDriver; use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; +use Metadata\Driver\DriverChain; use Metadata\Driver\DriverInterface; class AnnotationDriverTest extends BaseAnnotationOrAttributeDriverTestCase { protected function getDriver(?string $subDir = null, bool $addUnderscoreDir = true): DriverInterface { - return new AnnotationDriver(new AnnotationReader(), new IdenticalPropertyNamingStrategy(), null, $this->getExpressionEvaluator()); + $namingStrategy = new IdenticalPropertyNamingStrategy(); + + return new DriverChain([ + new AnnotationDriver(new AnnotationReader(), $namingStrategy, null, $this->getExpressionEvaluator()), + new NullDriver($namingStrategy), + ]); } } diff --git a/tests/Metadata/Driver/AttributeDriverTest.php b/tests/Metadata/Driver/AttributeDriverTest.php index b2255b6da..5dcc3c4d8 100644 --- a/tests/Metadata/Driver/AttributeDriverTest.php +++ b/tests/Metadata/Driver/AttributeDriverTest.php @@ -5,7 +5,9 @@ namespace JMS\Serializer\Tests\Metadata\Driver; use JMS\Serializer\Metadata\Driver\AttributeDriver; +use JMS\Serializer\Metadata\Driver\NullDriver; use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; +use Metadata\Driver\DriverChain; use Metadata\Driver\DriverInterface; class AttributeDriverTest extends BaseAnnotationOrAttributeDriverTestCase @@ -21,6 +23,11 @@ protected function setUp(): void protected function getDriver(?string $subDir = null, bool $addUnderscoreDir = true): DriverInterface { - return new AttributeDriver(new IdenticalPropertyNamingStrategy(), null, $this->getExpressionEvaluator()); + $namingStrategy = new IdenticalPropertyNamingStrategy(); + + return new DriverChain([ + new AttributeDriver($namingStrategy, null, $this->getExpressionEvaluator()), + new NullDriver($namingStrategy), + ]); } } diff --git a/tests/Metadata/Driver/BaseAnnotationOrAttributeDriverTestCase.php b/tests/Metadata/Driver/BaseAnnotationOrAttributeDriverTestCase.php index 111da5737..686895727 100644 --- a/tests/Metadata/Driver/BaseAnnotationOrAttributeDriverTestCase.php +++ b/tests/Metadata/Driver/BaseAnnotationOrAttributeDriverTestCase.php @@ -5,12 +5,9 @@ namespace JMS\Serializer\Tests\Metadata\Driver; use JMS\Serializer\Tests\Fixtures\AllExcludedObject; -use Metadata\Driver\DriverInterface; abstract class BaseAnnotationOrAttributeDriverTestCase extends BaseDriverTestCase { - abstract protected function getDriver(?string $subDir = null, bool $addUnderscoreDir = true): DriverInterface; - public function testAllExcluded(): void { $a = new AllExcludedObject(); diff --git a/tests/Metadata/Driver/DocBlockDriverTest.php b/tests/Metadata/Driver/DocBlockDriverTest.php index e608e78a0..74ada8afa 100644 --- a/tests/Metadata/Driver/DocBlockDriverTest.php +++ b/tests/Metadata/Driver/DocBlockDriverTest.php @@ -8,6 +8,7 @@ use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Metadata\Driver\AnnotationDriver; use JMS\Serializer\Metadata\Driver\DocBlockDriver; +use JMS\Serializer\Metadata\Driver\NullDriver; use JMS\Serializer\Metadata\Driver\TypedPropertiesDriver; use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; use JMS\Serializer\Tests\Fixtures\DocBlockType\AlternativePHPDocsNames; @@ -47,19 +48,25 @@ use JMS\Serializer\Tests\Fixtures\DocBlockType\SingleClassFromGlobalNamespaceTypeHint; use JMS\Serializer\Tests\Fixtures\DocBlockType\UnionTypedDocBLockProperty; use JMS\Serializer\Tests\Fixtures\DocBlockType\VirtualPropertyGetter; +use Metadata\Driver\DriverChain; use PHPUnit\Framework\TestCase; class DocBlockDriverTest extends TestCase { private function resolve(string $classToResolve): ClassMetadata { + $namingStrategy = new IdenticalPropertyNamingStrategy(); + + $driver = new DriverChain([ + new AnnotationDriver(new AnnotationReader(), $namingStrategy), + new NullDriver($namingStrategy), + ]); + if (PHP_VERSION_ID > 70400) { - $baseDriver = new TypedPropertiesDriver(new AnnotationDriver(new AnnotationReader(), new IdenticalPropertyNamingStrategy())); - } else { - $baseDriver = new AnnotationDriver(new AnnotationReader(), new IdenticalPropertyNamingStrategy()); + $driver = new TypedPropertiesDriver($driver); } - $driver = new DocBlockDriver($baseDriver); + $driver = new DocBlockDriver($driver); $m = $driver->loadMetadataForClass(new \ReflectionClass($classToResolve)); self::assertNotNull($m); diff --git a/tests/Metadata/Driver/NullDriverTest.php b/tests/Metadata/Driver/NullDriverTest.php index 3a0c15f55..9c83dad17 100644 --- a/tests/Metadata/Driver/NullDriverTest.php +++ b/tests/Metadata/Driver/NullDriverTest.php @@ -6,13 +6,14 @@ use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Metadata\Driver\NullDriver; +use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; use PHPUnit\Framework\TestCase; class NullDriverTest extends TestCase { public function testReturnsValidMetadata() { - $driver = new NullDriver(); + $driver = new NullDriver(new IdenticalPropertyNamingStrategy()); $metadata = $driver->loadMetadataForClass(new \ReflectionClass('stdClass')); diff --git a/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php b/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php index f4d1363f6..c3a6357e3 100644 --- a/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php +++ b/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php @@ -7,9 +7,11 @@ use Doctrine\Common\Annotations\AnnotationReader; use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Metadata\Driver\AnnotationDriver; +use JMS\Serializer\Metadata\Driver\NullDriver; use JMS\Serializer\Metadata\Driver\TypedPropertiesDriver; use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; use JMS\Serializer\Tests\Fixtures\TypedProperties\UnionTypedProperties; +use Metadata\Driver\DriverChain; use PHPUnit\Framework\TestCase; use ReflectionClass; @@ -34,8 +36,14 @@ public function testInferUnionTypesShouldResultInNoType() private function resolve(string $classToResolve): ClassMetadata { - $baseDriver = new AnnotationDriver(new AnnotationReader(), new IdenticalPropertyNamingStrategy()); - $driver = new TypedPropertiesDriver($baseDriver); + $namingStrategy = new IdenticalPropertyNamingStrategy(); + + $driver = new DriverChain([ + new AnnotationDriver(new AnnotationReader(), $namingStrategy), + new NullDriver($namingStrategy), + ]); + + $driver = new TypedPropertiesDriver($driver); $m = $driver->loadMetadataForClass(new ReflectionClass($classToResolve)); self::assertNotNull($m); diff --git a/tests/Serializer/GraphNavigatorTest.php b/tests/Serializer/GraphNavigatorTest.php index a06c07f94..8425be6d5 100644 --- a/tests/Serializer/GraphNavigatorTest.php +++ b/tests/Serializer/GraphNavigatorTest.php @@ -21,11 +21,13 @@ use JMS\Serializer\Handler\HandlerRegistry; use JMS\Serializer\Handler\SubscribingHandlerInterface; use JMS\Serializer\Metadata\Driver\AnnotationDriver; +use JMS\Serializer\Metadata\Driver\NullDriver; use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; use JMS\Serializer\SerializationContext; use JMS\Serializer\Visitor\DeserializationVisitorInterface; use JMS\Serializer\Visitor\SerializationVisitorInterface; use JMS\Serializer\VisitorInterface; +use Metadata\Driver\DriverChain; use Metadata\MetadataFactory; use PHPUnit\Framework\TestCase; @@ -243,7 +245,14 @@ protected function setUp(): void $this->handlerRegistry = new HandlerRegistry(); $this->objectConstructor = new UnserializeObjectConstructor(); - $this->metadataFactory = new MetadataFactory(new AnnotationDriver(new AnnotationReader(), new IdenticalPropertyNamingStrategy())); + $namingStrategy = new IdenticalPropertyNamingStrategy(); + + $driver = new DriverChain([ + new AnnotationDriver(new AnnotationReader(), $namingStrategy), + new NullDriver($namingStrategy), + ]); + + $this->metadataFactory = new MetadataFactory($driver); $this->serializationNavigator = new SerializationGraphNavigator($this->metadataFactory, $this->handlerRegistry, $this->accessor, $this->dispatcher); $this->serializationNavigator->initialize($this->serializationVisitor, $this->context);