Skip to content

Commit

Permalink
Don't return a metadata object unless there is explicit annotation or…
Browse files Browse the repository at this point in the history
… attribute configuration, add the NullDriver to the default driver chain to ensure a minimally configured metadata object is created
  • Loading branch information
mbabker committed Jul 18, 2023
1 parent f87de20 commit 14531d9
Show file tree
Hide file tree
Showing 16 changed files with 175 additions and 35 deletions.
14 changes: 7 additions & 7 deletions src/Builder/DefaultDriverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Metadata/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
83 changes: 78 additions & 5 deletions src/Metadata/Driver/AnnotationOrAttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,7 +48,7 @@
use Metadata\Driver\DriverInterface;
use Metadata\MethodMetadata;

abstract class AnnotationOrAttributeDriver implements DriverInterface
class AnnotationOrAttributeDriver implements DriverInterface
{
use ExpressionMetadataTrait;

Expand All @@ -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();

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -274,21 +289,79 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat
}
}

if (!$configured) {
return null;
}

return $classMetadata;
}

/**
* @return list<object>
*/
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<object>
*/
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<object>
*/
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;
}
}
26 changes: 26 additions & 0 deletions src/Metadata/Driver/NullDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
}
3 changes: 2 additions & 1 deletion tests/Fixtures/AuthorDeprecatedReadOnly.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
class AuthorDeprecatedReadOnly
{
/**
* @JMS\Serializer\Annotation\ReadOnly
* @ReadOnly
* @SerializedName("id")
*/
#[\JMS\Serializer\Annotation\DeprecatedReadOnly]

Check failure on line 25 in tests/Fixtures/AuthorDeprecatedReadOnly.php

View workflow job for this annotation

GitHub Actions / Coding Standards (7.2)

Constant \JMS\Serializer\Annotation\DeprecatedReadOnly should not be referenced via a fully qualified name, but via a use statement.
#[SerializedName(name: 'id')]
private $id;

Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
* @ReadOnly
*/
#[XmlRoot(name: 'author')]
#[\JMS\Serializer\Annotation\DeprecatedReadOnly]

Check failure on line 20 in tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php

View workflow job for this annotation

GitHub Actions / Coding Standards (7.2)

Constant \JMS\Serializer\Annotation\DeprecatedReadOnly should not be referenced via a fully qualified name, but via a use statement.
class AuthorDeprecatedReadOnlyPerClass
{
/**
* @ReadOnly
* @SerializedName("id")
*/
#[\JMS\Serializer\Annotation\DeprecatedReadOnly]

Check failure on line 27 in tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php

View workflow job for this annotation

GitHub Actions / Coding Standards (7.2)

Constant \JMS\Serializer\Annotation\DeprecatedReadOnly should not be referenced via a fully qualified name, but via a use statement.
#[SerializedName(name: 'id')]
private $id;

Expand All @@ -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)]

Check failure on line 46 in tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php

View workflow job for this annotation

GitHub Actions / Coding Standards (7.2)

Function \JMS\Serializer\Annotation\DeprecatedReadOnly() should not be referenced via a fully qualified name, but via a use statement.
private $name;

public function getId()
Expand Down
12 changes: 4 additions & 8 deletions tests/Fixtures/AuthorList.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,23 @@ public function count(): int
/**
* @see ArrayAccess
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
public function offsetExists($offset): bool
{
return isset($this->authors[$offset]);
}

/**
* @see ArrayAccess
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
public function offsetGet($offset): ?Author
{
return $this->authors[$offset] ?? null;
}

/**
* @see ArrayAccess
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
public function offsetSet($offset, $value): void
{
if (null === $offset) {
$this->authors[] = $value;
Expand All @@ -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]);
}
Expand Down
2 changes: 2 additions & 0 deletions tests/Fixtures/Doctrine/PersistendCollection/SmartPhone.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class SmartPhone
*
* @var string
*/
#[Serializer\SerializedName(name: 'id')]
#[Serializer\Type(name: 'string')]
protected $id;

Expand All @@ -40,6 +41,7 @@ class SmartPhone
*
* @var ArrayCollection<int, App>
*/
#[Serializer\SerializedName(name: 'applications')]
#[Serializer\Type(name: 'ArrayCollection<JMS\Serializer\Tests\Fixtures\Doctrine\PersistendCollection\App>')]
private $apps;

Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/ObjectWithLifecycleCallbacks.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public function __construct($firstname = 'Foo', $lastname = 'Bar')
/**
* @PreSerialize
*/
#[PreSerialize]
private function prepareForSerialization()
{
$this->name = $this->firstname . ' ' . $this->lastname;
Expand All @@ -47,6 +48,7 @@ private function prepareForSerialization()
/**
* @PostSerialize
*/
#[PostSerialize]
private function cleanUpAfterSerialization()
{
$this->name = null;
Expand All @@ -55,6 +57,7 @@ private function cleanUpAfterSerialization()
/**
* @PostDeserialize
*/
#[PostDeserialize]
private function afterDeserialization()
{
[$this->firstname, $this->lastname] = explode(' ', $this->name);
Expand Down
9 changes: 8 additions & 1 deletion tests/Metadata/Driver/AnnotationDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
]);
}
}
Loading

0 comments on commit 14531d9

Please sign in to comment.