Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't return a metadata object unless there is explicit annotation or attribute configuration #1494

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
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 @@
#[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
Loading