Skip to content

Commit

Permalink
- allow to compose doctrine attributes and php 8 attributes
Browse files Browse the repository at this point in the history
- enable attributes by default
- exctract attribute cache decoration to private method
  • Loading branch information
goetas committed Jul 30, 2021
1 parent fe8ba8b commit c355c35
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 37 deletions.
12 changes: 8 additions & 4 deletions src/Builder/DefaultDriverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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\TypedPropertiesDriver;
use JMS\Serializer\Metadata\Driver\XmlDriver;
use JMS\Serializer\Metadata\Driver\YamlDriver;
Expand Down Expand Up @@ -43,16 +44,19 @@ public function __construct(PropertyNamingStrategyInterface $propertyNamingStrat

public function createDriver(array $metadataDirs, Reader $annotationReader): DriverInterface
{
$driver = new AnnotationDriver($annotationReader, $this->propertyNamingStrategy, $this->typeParser);
if (PHP_VERSION_ID >= 80000) {
$attributeAnnotationDriver = new AnnotationDriver(new AttributeDriver\AttributeReader(), $this->propertyNamingStrategy, $this->typeParser);
$driver = new AttributeDriver($attributeAnnotationDriver, $driver);
}

if (!empty($metadataDirs)) {
$fileLocator = new FileLocator($metadataDirs);

$driver = new DriverChain([
new YamlDriver($fileLocator, $this->propertyNamingStrategy, $this->typeParser, $this->expressionEvaluator),
new XmlDriver($fileLocator, $this->propertyNamingStrategy, $this->typeParser, $this->expressionEvaluator),
new AnnotationDriver($annotationReader, $this->propertyNamingStrategy, $this->typeParser, $this->expressionEvaluator),
$driver,
]);
} else {
$driver = new AnnotationDriver($annotationReader, $this->propertyNamingStrategy, $this->typeParser);
}

if (PHP_VERSION_ID >= 70400) {
Expand Down
33 changes: 33 additions & 0 deletions src/Metadata/Driver/AttributeDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Metadata\Driver;

use Metadata\ClassMetadata as BaseClassMetadata;
use Metadata\Driver\DriverInterface;
use Metadata\MergeableInterface;

class AttributeDriver implements DriverInterface
{
private $annotationDriver;

private $decorated;

public function __construct(AnnotationDriver $annotationDriver, DriverInterface $decorated)
{
$this->annotationDriver = $annotationDriver;
$this->decorated = $decorated;
}

public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadata
{
$metadata = $this->decorated->loadMetadataForClass($class);
$attributeMetadata = !$class->isInternal() ? $this->annotationDriver->loadMetadataForClass($class) : null;
if ($metadata instanceof MergeableInterface && $attributeMetadata instanceof MergeableInterface) {
$metadata->merge($attributeMetadata);
}

return $metadata ?: $attributeMetadata;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace JMS\Serializer;
namespace JMS\Serializer\Metadata\Driver\AttributeDriver;

use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
Expand All @@ -16,7 +16,9 @@ public function getClassAnnotations(ReflectionClass $class)
$result = [];
$attributes = $class->getAttributes();
foreach ($attributes as $attribute) {
$result[] = $attribute->newInstance();
if (0 === strpos($attribute->getName(), 'JMS\Serializer\Annotation\\')) {
$result[] = $attribute->newInstance();
}
}

return $result;
Expand All @@ -37,7 +39,9 @@ public function getMethodAnnotations(ReflectionMethod $method)
$result = [];
$attributes = $method->getAttributes();
foreach ($attributes as $attribute) {
$result[] = $attribute->newInstance();
if (0 === strpos($attribute->getName(), 'JMS\Serializer\Annotation\\')) {
$result[] = $attribute->newInstance();
}
}

return $result;
Expand All @@ -58,7 +62,9 @@ public function getPropertyAnnotations(ReflectionProperty $property)
$result = [];
$attributes = $property->getAttributes();
foreach ($attributes as $attribute) {
$result[] = $attribute->newInstance();
if (0 === strpos($attribute->getName(), 'JMS\Serializer\Annotation\\')) {
$result[] = $attribute->newInstance();
}
}

return $result;
Expand Down
42 changes: 19 additions & 23 deletions src/SerializerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ final class SerializerBuilder
*/
private $eventDispatcher;

/**
* @var bool
*/
private $enableAttributes = false;

/**
* @var bool
*/
Expand Down Expand Up @@ -188,14 +183,13 @@ public static function create(...$args): self
return new static(...$args);
}

public function __construct(?HandlerRegistryInterface $handlerRegistry = null, ?EventDispatcherInterface $eventDispatcher = null, bool $enableAttributes = false)
public function __construct(?HandlerRegistryInterface $handlerRegistry = null, ?EventDispatcherInterface $eventDispatcher = null)
{
$this->typeParser = new Parser();
$this->handlerRegistry = $handlerRegistry ?: new HandlerRegistry();
$this->eventDispatcher = $eventDispatcher ?: new EventDispatcher();
$this->serializationVisitors = [];
$this->deserializationVisitors = [];
$this->enableAttributes = $enableAttributes;

if ($handlerRegistry) {
$this->handlersConfigured = true;
Expand Down Expand Up @@ -529,22 +523,8 @@ public function build(): Serializer
{
$annotationReader = $this->annotationReader;
if (null === $annotationReader) {
if (PHP_VERSION_ID >= 80000 && true === $this->enableAttributes) {
$annotationReader = new AttributeReader();
} else {
$annotationReader = new AnnotationReader();

if (null !== $this->cacheDir) {
$this->createDir($this->cacheDir . '/annotations');
if (class_exists(FilesystemAdapter::class)) {
$annotationsCache = new FilesystemAdapter('', 0, $this->cacheDir . '/annotations');
$annotationReader = new PsrCachedReader($annotationReader, $annotationsCache, $this->debug);
} else {
$annotationsCache = new FilesystemCache($this->cacheDir . '/annotations');
$annotationReader = new CachedReader($annotationReader, $annotationsCache, $this->debug);
}
}
}
$annotationReader = new AnnotationReader();
$annotationReader = $this->decorateAnnotationReader($annotationReader);
}

if (null === $this->driverFactory) {
Expand Down Expand Up @@ -643,4 +623,20 @@ private function createDir(string $dir): void
throw new RuntimeException(sprintf('Could not create directory "%s".', $dir));
}
}

private function decorateAnnotationReader(Reader $annotationReader): Reader
{
if (null !== $this->cacheDir) {
$this->createDir($this->cacheDir . '/annotations');
if (class_exists(FilesystemAdapter::class)) {
$annotationsCache = new FilesystemAdapter('', 0, $this->cacheDir . '/annotations');
$annotationReader = new PsrCachedReader($annotationReader, $annotationsCache, $this->debug);
} else {
$annotationsCache = new FilesystemCache($this->cacheDir . '/annotations');
$annotationReader = new CachedReader($annotationReader, $annotationsCache, $this->debug);
}
}

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

declare(strict_types=1);

namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation\Exclude;
use JMS\Serializer\Annotation\PostDeserialize;
use JMS\Serializer\Annotation\PostSerialize;
use JMS\Serializer\Annotation\PreSerialize;
use JMS\Serializer\Annotation\Type;

class ObjectWithOnlyLifecycleCallbacks
{
/**
* @PreSerialize
*/
#[PreSerialize]
private function prepareForSerialization()
{

}

/**
* @PostSerialize
*/
#[PostSerialize]
private function cleanUpAfterSerialization()
{

}

/**
* @PostDeserialize
*/
#[PostDeserialize]
private function afterDeserialization()
{

}
}
4 changes: 2 additions & 2 deletions tests/Fixtures/SimpleInternalObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
use JMS\Serializer\Annotation as Serializer;

/**
* @Serializer\Exclude("NONE")
* @Serializer\ExclusionPolicy("ALL")
*/
#[Serializer\Exclude('NONE')]
#[Serializer\ExclusionPolicy(policy: "ALL")]
class SimpleInternalObject extends \Exception
{
private $bar;
Expand Down
25 changes: 25 additions & 0 deletions tests/Metadata/Driver/AttributeDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
namespace JMS\Serializer\Tests\Metadata\Driver;

use Doctrine\Common\Annotations\AnnotationReader;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\Driver\AnnotationDriver;
use JMS\Serializer\Metadata\Driver\AttributeDriver;
use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy;
use JMS\Serializer\Tests\Fixtures\ObjectWithOnlyLifecycleCallbacks;
use Metadata\Driver\DriverInterface;
use Metadata\MethodMetadata;

class AttributeDriverTest extends AnnotationDriverTest
{
Expand All @@ -21,6 +24,28 @@ protected function setUp(): void
parent::setUp();
}


public function testLifeCycleCallbacks()
{
/**
* @var ClassMetadata $m
*/
$m = $this->getDriver()->loadMetadataForClass(new \ReflectionClass(ObjectWithOnlyLifecycleCallbacks::class));

$c = new ClassMetadata(ObjectWithOnlyLifecycleCallbacks::class);
$c->preSerializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'prepareForSerialization');
$c->preSerializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'prepareForSerialization');
self::assertEquals($c->preSerializeMethods, $m->preSerializeMethods);

$c->postSerializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'cleanUpAfterSerialization');
$c->postSerializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'cleanUpAfterSerialization');
self::assertEquals($c->postSerializeMethods, $m->postSerializeMethods);

$c->postDeserializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'afterDeserialization');
$c->postDeserializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'afterDeserialization');
self::assertEquals($c->postDeserializeMethods, $m->postDeserializeMethods);
}

protected function getDriver(?string $subDir = null, bool $addUnderscoreDir = true): DriverInterface
{
$annotationsDriver = new AnnotationDriver(new AnnotationReader(), new IdenticalPropertyNamingStrategy(), null, $this->getExpressionEvaluator());
Expand Down
11 changes: 10 additions & 1 deletion tests/Metadata/Driver/AttributeDriverWithNoFallbackTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@
use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy;
use Metadata\Driver\DriverInterface;

class AttributeDriverWithNoFallbackTest extends AttributeDriverTest
class AttributeDriverWithNoFallbackTest extends AnnotationDriverTest
{
protected function setUp(): void
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped('Attributes are available only on php 8 or higher');
}

parent::setUp();
}

protected function getDriver(?string $subDir = null, bool $addUnderscoreDir = true): DriverInterface
{
$attributesAnnotationDriver = new AnnotationDriver(new AttributeDriver\AttributeReader(), new IdenticalPropertyNamingStrategy(), null, $this->getExpressionEvaluator());
Expand Down
23 changes: 23 additions & 0 deletions tests/Metadata/Driver/BaseDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
use JMS\Serializer\Tests\Fixtures\FirstClassMapCollection;
use JMS\Serializer\Tests\Fixtures\ObjectWithExpressionVirtualPropertiesAndExcludeAll;
use JMS\Serializer\Tests\Fixtures\ObjectWithInvalidExpression;
use JMS\Serializer\Tests\Fixtures\ObjectWithOnlyLifecycleCallbacks;
use JMS\Serializer\Tests\Fixtures\ObjectWithVirtualPropertiesAndDuplicatePropName;
use JMS\Serializer\Tests\Fixtures\ObjectWithVirtualPropertiesAndDuplicatePropNameExcludeAll;
use JMS\Serializer\Tests\Fixtures\ObjectWithVirtualPropertiesAndExcludeAll;
use JMS\Serializer\Tests\Fixtures\ParentSkipWithEmptyChild;
use JMS\Serializer\Tests\Fixtures\Person;
use Metadata\Driver\DriverInterface;
use Metadata\MethodMetadata;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
Expand Down Expand Up @@ -568,6 +570,27 @@ public function testExclusionIf()
self::assertEquals($p, $m->propertyMetadata['age']);
}

public function testLifeCycleCallbacks()
{
/**
* @var ClassMetadata $m
*/
$m = $this->getDriver()->loadMetadataForClass(new \ReflectionClass(ObjectWithOnlyLifecycleCallbacks::class));


$c = new ClassMetadata(ObjectWithOnlyLifecycleCallbacks::class);
$c->preSerializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'prepareForSerialization');
self::assertEquals($c->preSerializeMethods, $m->preSerializeMethods);

$c->postSerializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'cleanUpAfterSerialization');
self::assertEquals($c->postSerializeMethods, $m->postSerializeMethods);

$c->postDeserializeMethods[] = new MethodMetadata(ObjectWithOnlyLifecycleCallbacks::class, 'afterDeserialization');
self::assertEquals($c->postDeserializeMethods, $m->postDeserializeMethods);


}

public function testExclusionIfOnClass()
{
$class = 'JMS\Serializer\Tests\Fixtures\PersonAccount';
Expand Down

0 comments on commit c355c35

Please sign in to comment.