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 Jun 28, 2023
1 parent f87de20 commit ee9acb1
Show file tree
Hide file tree
Showing 16 changed files with 179 additions and 39 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 @@ -5,7 +5,7 @@
namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation\Accessor;
use JMS\Serializer\Annotation\ReadOnly;
use JMS\Serializer\Annotation\DeprecatedReadOnly;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\XmlRoot;
Expand All @@ -22,6 +22,7 @@ class AuthorDeprecatedReadOnly
* @JMS\Serializer\Annotation\ReadOnly
* @SerializedName("id")
*/
#[DeprecatedReadOnly]
#[SerializedName(name: 'id')]
private $id;

Expand Down
11 changes: 7 additions & 4 deletions tests/Fixtures/AuthorDeprecatedReadOnlyPerClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation\Accessor;
use JMS\Serializer\Annotation\ReadOnly;
use JMS\Serializer\Annotation\DeprecatedReadOnly;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\XmlRoot;
Expand All @@ -14,15 +14,17 @@
* @deprecated ReadOnly annotation is deprecated
*
* @XmlRoot("author")
* @ReadOnly
* @JMS\Serializer\Annotation\ReadOnly
*/
#[XmlRoot(name: 'author')]
#[DeprecatedReadOnly]
class AuthorDeprecatedReadOnlyPerClass
{
/**
* @ReadOnly
* @JMS\Serializer\Annotation\ReadOnly
* @SerializedName("id")
*/
#[DeprecatedReadOnly]
#[SerializedName(name: 'id')]
private $id;

Expand All @@ -36,11 +38,12 @@ public function __construct($id, $name)
* @Type("string")
* @SerializedName("full_name")
* @Accessor("getName")
* @ReadOnly(false)
* @JMS\Serializer\Annotation\ReadOnly(false)
*/
#[Type(name: 'string')]
#[SerializedName(name: 'full_name')]
#[Accessor(getter: 'getName')]
#[DeprecatedReadOnly(readOnly: false)]
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
Loading

0 comments on commit ee9acb1

Please sign in to comment.