Skip to content

Commit

Permalink
* Add special inclusion strategy interface that would accept a value …
Browse files Browse the repository at this point in the history
…also

* Adapted the getExclusionStrategy (in the Context.php) to return DisjunctionExclusionStratgy interface so it could also accept calls to methods with other interfaces beside the ExclusionStrategyInterface
* Null value could be eventually adapted also with this kind of Exclusion strategy
  • Loading branch information
slava-v authored and Veaceslav Vasilache committed May 24, 2022
1 parent ac31cd1 commit 02d8e87
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 35 deletions.
18 changes: 6 additions & 12 deletions src/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
use JMS\Serializer\Exclusion\SkipWhenEmptyExclusionStrategy;
use JMS\Serializer\Exclusion\ValueExclusionStrategyInterface;
use JMS\Serializer\Exclusion\VersionExclusionStrategy;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
Expand Down Expand Up @@ -107,7 +108,7 @@ public function getNavigator(): GraphNavigatorInterface
return $this->navigator;
}

public function getExclusionStrategy(): ?ExclusionStrategyInterface
public function getExclusionStrategy(): ?DisjunctExclusionStrategy
{
return $this->exclusionStrategy;
}
Expand Down Expand Up @@ -148,28 +149,21 @@ final protected function assertMutable(): void
}

/**
* @param ExclusionStrategyInterface|ValueExclusionStrategyInterface $strategy
*
* @return $this
*/
public function addExclusionStrategy(ExclusionStrategyInterface $strategy): self
public function addExclusionStrategy($strategy): self
{
$this->assertMutable();

if (null === $this->exclusionStrategy) {
$this->exclusionStrategy = $strategy;

return $this;
}

if ($this->exclusionStrategy instanceof DisjunctExclusionStrategy) {
$this->exclusionStrategy->addStrategy($strategy);

return $this;
}

$this->exclusionStrategy = new DisjunctExclusionStrategy([
$this->exclusionStrategy,
$strategy,
]);
$this->exclusionStrategy = new DisjunctExclusionStrategy([$strategy]);

return $this;
}
Expand Down
40 changes: 34 additions & 6 deletions src/Exclusion/DisjunctExclusionStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class DisjunctExclusionStrategy implements ExclusionStrategyInterface
final class DisjunctExclusionStrategy implements ExclusionStrategyInterface, ValueExclusionStrategyInterface
{
/**
* @var ExclusionStrategyInterface[]
Expand All @@ -30,8 +30,24 @@ public function __construct(array $delegates = [])
$this->delegates = $delegates;
}

public function addStrategy(ExclusionStrategyInterface $strategy): void
/**
* @param ExclusionStrategyInterface|ValueExclusionStrategyInterface $strategy
*/
public function addStrategy($strategy): void
{
if (
!($strategy instanceof ExclusionStrategyInterface)
|| !($strategy instanceof ValueExclusionStrategyInterface)
) {
throw new \InvalidArgumentException(
sprintf(
'Strategy should be one of %s, %s instances',
ExclusionStrategyInterface::class,
ValueExclusionStrategyInterface::class
)
);
}

$this->delegates[] = $strategy;
}

Expand All @@ -41,8 +57,7 @@ public function addStrategy(ExclusionStrategyInterface $strategy): void
public function shouldSkipClass(ClassMetadata $metadata, Context $context): bool
{
foreach ($this->delegates as $delegate) {
\assert($delegate instanceof ExclusionStrategyInterface);
if ($delegate->shouldSkipClass($metadata, $context)) {
if ($delegate instanceof ExclusionStrategyInterface && $delegate->shouldSkipClass($metadata, $context)) {
return true;
}
}
Expand All @@ -56,8 +71,21 @@ public function shouldSkipClass(ClassMetadata $metadata, Context $context): bool
public function shouldSkipProperty(PropertyMetadata $property, Context $context): bool
{
foreach ($this->delegates as $delegate) {
\assert($delegate instanceof ExclusionStrategyInterface);
if ($delegate->shouldSkipProperty($property, $context)) {
if ($delegate instanceof ExclusionStrategyInterface && $delegate->shouldSkipProperty($property, $context)) {
return true;
}
}

return false;
}

/**
* Whether the property should be skipped.
*/
public function shouldSkipPropertyWithValue(PropertyMetadata $property, Context $context, $value): bool
{
foreach ($this->delegates as $delegate) {
if ($delegate instanceof ValueExclusionStrategyInterface && $delegate->shouldSkipPropertyWithValue($property, $context, $value)) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Exclusion/ExpressionLanguageExclusionStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* @author Asmir Mustafic <goetas@gmail.com>
*/
final class ExpressionLanguageExclusionStrategy
final class ExpressionLanguageExclusionStrategy implements ExclusionStrategyInterface
{
/**
* @var ExpressionEvaluatorInterface
Expand Down
28 changes: 17 additions & 11 deletions src/Exclusion/SkipWhenEmptyExclusionStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,30 @@
namespace JMS\Serializer\Exclusion;

use JMS\Serializer\Context;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;

class SkipWhenEmptyExclusionStrategy implements ExclusionStrategyInterface
class SkipWhenEmptyExclusionStrategy implements ValueExclusionStrategyInterface
{
public function shouldSkipClass(ClassMetadata $metadata, Context $context): bool
/**
* @inheritDoc
*/
public function shouldSkipPropertyWithValue(PropertyMetadata $property, Context $context, $value): bool
{
return false;
}

public function shouldSkipProperty(PropertyMetadata $property, Context $context): bool
{
if (!$property->skipWhenEmpty
&& $context->hasAttribute(Context::ATTR_SKIP_WHEN_EMPTY)
if (
$property->skipWhenEmpty
|| (
$context->hasAttribute(Context::ATTR_SKIP_WHEN_EMPTY)
&& $context->getAttribute(Context::ATTR_SKIP_WHEN_EMPTY)
) {
)
) {
if ($value instanceof \ArrayObject || \is_array($value) && 0 === count($value)) {
return true;
}

// This would be used for T object types, later, in the visitor->visitProperty
$property->skipWhenEmpty = true;
}

return false;
}
}
27 changes: 27 additions & 0 deletions src/Exclusion/ValueExclusionStrategyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Exclusion;

use JMS\Serializer\Context;
use JMS\Serializer\Metadata\PropertyMetadata;

/**
* Interface for exclusion strategies including the values
*
* @author Veaceslav vasilache <slava.dev@gmail.com>
*/
interface ValueExclusionStrategyInterface
{
/**
* Whether the property should be skipped, using the value also
*
* @param PropertyMetadata $property
* @param Context $context
* @param mixed $value
*
* @return bool
*/
public function shouldSkipPropertyWithValue(PropertyMetadata $property, Context $context, $value): bool;
}
4 changes: 4 additions & 0 deletions src/GraphNavigator/SerializationGraphNavigator.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ public function accept($data, ?array $type = null)
continue;
}

if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipPropertyWithValue($propertyMetadata, $this->context, $v)) {
continue;
}

$this->context->pushPropertyMetadata($propertyMetadata);
$this->visitor->visitProperty($propertyMetadata, $v);
$this->context->popPropertyMetadata();
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/ObjectWithEmptyArrayAndHashNotAnnotated.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class ObjectWithEmptyArrayAndHashNotAnnotated

private $object = [];

/**
* @Serializer\SkipWhenEmpty()
*/
private $objectAnnotated = [];

/**
* @Serializer\Type("array<string>")
* @Serializer\SkipWhenEmpty()
Expand Down
3 changes: 2 additions & 1 deletion tests/Handler/ArrayCollectionHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace JMS\Serializer\Tests\Handler;

use Doctrine\Common\Collections\ArrayCollection;
use JMS\Serializer\Exclusion\DisjunctExclusionStrategy;
use JMS\Serializer\Handler\ArrayCollectionHandler;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\SerializationContext;
Expand Down Expand Up @@ -48,7 +49,7 @@ public function testSerializeArraySkipByExclusionStrategy()
$factoryMock = $this->getMockBuilder(MetadataFactoryInterface::class)->getMock();
$factoryMock->method('getMetadataForClass')->willReturn(new ClassMetadata(ArrayCollection::class));

$context->method('getExclusionStrategy')->willReturn(new AlwaysExcludeExclusionStrategy());
$context->method('getExclusionStrategy')->willReturn(new DisjunctExclusionStrategy([new AlwaysExcludeExclusionStrategy()]));
$context->method('getMetadataFactory')->willReturn($factoryMock);

$type = ['name' => 'ArrayCollection', 'params' => []];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use JMS\Serializer\EventDispatcher\EventDispatcher;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber;
use JMS\Serializer\Exclusion\DisjunctExclusionStrategy;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Tests\Fixtures\ExclusionStrategy\AlwaysExcludeExclusionStrategy;
use JMS\Serializer\Tests\Fixtures\SimpleObject;
Expand Down Expand Up @@ -49,7 +50,7 @@ public function testDoesNotRewriteCustomType()

public function testExcludedPropDoesNotGetInitialized()
{
$this->context->method('getExclusionStrategy')->willReturn(new AlwaysExcludeExclusionStrategy());
$this->context->method('getExclusionStrategy')->willReturn(new DisjunctExclusionStrategy([new AlwaysExcludeExclusionStrategy()]));
$this->context->method('getMetadataFactory')->willReturn(new class implements MetadataFactoryInterface
{
public function getMetadataForClass($className)
Expand Down Expand Up @@ -83,7 +84,7 @@ public function testProxyLoadingCanBeSkippedByExclusionStrategy()
$factoryMock = $this->getMockBuilder(MetadataFactoryInterface::class)->getMock();
$factoryMock->method('getMetadataForClass')->willReturn(new ClassMetadata(SimpleObject::class));

$this->context->method('getExclusionStrategy')->willReturn(new AlwaysExcludeExclusionStrategy());
$this->context->method('getExclusionStrategy')->willReturn(new DisjunctExclusionStrategy([new AlwaysExcludeExclusionStrategy()]));
$this->context->method('getMetadataFactory')->willReturn($factoryMock);

$event = $this->createEvent($obj = new SimpleObjectProxy('a', 'b'), ['name' => SimpleObjectProxy::class, 'params' => []]);
Expand Down
5 changes: 3 additions & 2 deletions tests/Serializer/GraphNavigatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use JMS\Serializer\Exception\NotAcceptableException;
use JMS\Serializer\Exception\RuntimeException;
use JMS\Serializer\Exception\SkipHandlerException;
use JMS\Serializer\Exclusion\DisjunctExclusionStrategy;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\GraphNavigator\DeserializationGraphNavigator;
use JMS\Serializer\GraphNavigator\SerializationGraphNavigator;
Expand Down Expand Up @@ -80,7 +81,7 @@ public function testNavigatorPassesInstanceOnSerialization()

$this->context->expects($this->once())
->method('getExclusionStrategy')
->will($this->returnValue($exclusionStrategy));
->will($this->returnValue(new DisjunctExclusionStrategy([$exclusionStrategy])));

$navigator = new SerializationGraphNavigator($this->metadataFactory, $this->handlerRegistry, $this->accessor, $this->dispatcher);
$navigator->initialize($this->serializationVisitor, $this->context);
Expand Down Expand Up @@ -110,7 +111,7 @@ public function testNavigatorPassesNullOnDeserialization()

$this->context->expects($this->once())
->method('getExclusionStrategy')
->will($this->returnValue($exclusionStrategy));
->will($this->returnValue(new DisjunctExclusionStrategy([$exclusionStrategy])));

$navigator = new DeserializationGraphNavigator($this->metadataFactory, $this->handlerRegistry, $this->objectConstructor, $this->accessor, $this->dispatcher);
$navigator->initialize($this->deserializationVisitor, $this->context);
Expand Down

0 comments on commit 02d8e87

Please sign in to comment.