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

[POC] Split property selection from graph navigator #964

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 13 additions & 6 deletions src/Accessor/AccessorStrategyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace JMS\Serializer\Accessor;

use JMS\Serializer\DeserializationContext;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\SerializationContext;

/**
* @author Asmir Mustafic <[email protected]>
Expand All @@ -13,16 +16,20 @@ interface AccessorStrategyInterface
{
/**
* @param object $object
* @param PropertyMetadata $metadata
* @return mixed
* @param ClassMetadata $metadata
* @param PropertyMetadata[] $properties
* @param SerializationContext $context
* @return mixed[]
*/
public function getValue(object $object, PropertyMetadata $metadata);
public function getValues(object $object, ClassMetadata $metadata, array $properties, SerializationContext $context):array;

/**
* @param object $object
* @param mixed $value
* @param PropertyMetadata $metadata
* @param mixed[] $values
* @param ClassMetadata $metadata
* @param PropertyMetadata[] $properties
* @param DeserializationContext $context
* @return void
*/
public function setValue(object $object, $value, PropertyMetadata $metadata): void;
public function setValues(object $object, array $values, ClassMetadata $metadata, array $properties, DeserializationContext $context): void;
}
59 changes: 48 additions & 11 deletions src/Accessor/DefaultAccessorStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

namespace JMS\Serializer\Accessor;

use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
use JMS\Serializer\DeserializationContext;
use JMS\Serializer\Exception\LogicException;
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Metadata\StaticPropertyMetadata;
use JMS\Serializer\SerializationContext;

/**
* @author Asmir Mustafic <[email protected]>
Expand All @@ -19,28 +21,63 @@ final class DefaultAccessorStrategy implements AccessorStrategyInterface
private $readAccessors = [];
private $writeAccessors = [];
private $propertyReflectionCache = [];

/**
* @var ExpressionEvaluatorInterface
*/
private $evaluator;

public function __construct(ExpressionEvaluatorInterface $evaluator = null)
{
public function __construct(
ExpressionEvaluatorInterface $evaluator = null
) {
$this->evaluator = $evaluator;
}

public function getValue(object $object, PropertyMetadata $metadata)
public function getValues(object $data, ClassMetadata $metadata, array $properties, SerializationContext $context): array
{
if ($metadata instanceof StaticPropertyMetadata) {
return $metadata->getValue(null);
$shouldSerializeNull = $context->shouldSerializeNull();

$values = [];
foreach ($properties as $propertyMetadata) {

$v = $this->getValue($data, $propertyMetadata, $context);

if (null === $v && $shouldSerializeNull !== true) {
continue;
}

$values[] = $v;
}

if ($metadata instanceof ExpressionPropertyMetadata) {
if ($this->evaluator === null) {
throw new ExpressionLanguageRequiredException(sprintf('The property %s on %s requires the expression accessor strategy to be enabled.', $metadata->name, $metadata->class));
return $values;
}

/**
* @param object $object
* @param mixed[] $values
* @param PropertyMetadata[] $properties
* @param DeserializationContext $context
* @return void
*/
public function setValues(object $object, array $values, ClassMetadata $metadata, array $properties, DeserializationContext $context): void
{
$values = [];
foreach ($properties as $i => $propertyMetadata) {

if (!array_key_exists($i, $values)) {
continue;
}

$this->setValue($object, $values[$i], $propertyMetadata, $context);
}
}

private function getValue(object $object, PropertyMetadata $metadata, SerializationContext $context)
{
if ($metadata instanceof StaticPropertyMetadata) {
return $metadata->getValue();
}

if ($metadata instanceof ExpressionPropertyMetadata) {
return $this->evaluator->evaluate($metadata->expression, ['object' => $object]);
}

Expand Down Expand Up @@ -71,7 +108,7 @@ public function getValue(object $object, PropertyMetadata $metadata)
return $object->{$metadata->getter}();
}

public function setValue(object $object, $value, PropertyMetadata $metadata): void
private function setValue(object $object, $value, PropertyMetadata $metadata, DeserializationContext $context): void
{
if ($metadata->readOnly) {
throw new LogicException(sprintf('%s on %s is read only.', $metadata->name, $metadata->class));
Expand Down
36 changes: 14 additions & 22 deletions src/GraphNavigator/DeserializationGraphNavigator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use JMS\Serializer\Handler\HandlerRegistryInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\NullAwareVisitorInterface;
use JMS\Serializer\Selector\PropertySelectorInterface;
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
use Metadata\MetadataFactoryInterface;

Expand Down Expand Up @@ -58,23 +59,25 @@ final class DeserializationGraphNavigator extends GraphNavigator implements Grap
* @var AccessorStrategyInterface
*/
private $accessor;
/**
* @var PropertySelectorInterface
*/
private $selector;

public function __construct(
MetadataFactoryInterface $metadataFactory,
HandlerRegistryInterface $handlerRegistry,
ObjectConstructorInterface $objectConstructor,
AccessorStrategyInterface $accessor,
EventDispatcherInterface $dispatcher = null,
ExpressionEvaluatorInterface $expressionEvaluator = null
PropertySelectorInterface $selector,
EventDispatcherInterface $dispatcher = null
) {
$this->dispatcher = $dispatcher ?: new EventDispatcher();
$this->metadataFactory = $metadataFactory;
$this->handlerRegistry = $handlerRegistry;
$this->objectConstructor = $objectConstructor;
$this->accessor = $accessor;
if ($expressionEvaluator) {
$this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
}
$this->selector = $selector;
}

/**
Expand Down Expand Up @@ -147,10 +150,6 @@ public function accept($data, array $type = null)
/** @var $metadata ClassMetadata */
$metadata = $this->metadataFactory->getMetadataForClass($type['name']);

if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
}

if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
$metadata = $this->resolveMetadata($data, $metadata);
}
Expand All @@ -166,29 +165,22 @@ public function accept($data, array $type = null)
$object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context);

$this->visitor->startVisitingObject($metadata, $object, $type);
foreach ($metadata->propertyMetadata as $propertyMetadata) {
if ($this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
continue;
}

if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
continue;
}

if ($propertyMetadata->readOnly) {
continue;
}
$properties = $this->selector->select($metadata);
$values = [];

foreach ($properties as $i => $propertyMetadata) {
$this->context->pushPropertyMetadata($propertyMetadata);
try {
$v = $this->visitor->visitProperty($propertyMetadata, $data);
$this->accessor->setValue($object, $v, $propertyMetadata);
$values[$i] = $this->visitor->visitProperty($propertyMetadata, $data);
} catch (NotAcceptableException $e) {

}
$this->context->popPropertyMetadata();
}

$this->accessor->setValues($data, $values, $metadata, $properties, $this->context);

$rs = $this->visitor->endVisitingObject($metadata, $data, $type);
$this->afterVisitingObject($metadata, $rs, $type);

Expand Down
48 changes: 13 additions & 35 deletions src/GraphNavigator/SerializationGraphNavigator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use JMS\Serializer\Exception\CircularReferenceDetectedException;
use JMS\Serializer\Exception\ExcludedClassException;
use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
use JMS\Serializer\Exception\NotAcceptableException;
use JMS\Serializer\Exception\RuntimeException;
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\HandlerRegistryInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\NullAwareVisitorInterface;
use JMS\Serializer\Selector\DefaultPropertySelector;
use JMS\Serializer\Selector\PropertySelectorInterface;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\Visitor\SerializationVisitorInterface;
use JMS\Serializer\VisitorInterface;
Expand All @@ -41,45 +40,38 @@ final class SerializationGraphNavigator extends GraphNavigator implements GraphN
* @var SerializationVisitorInterface
*/
protected $visitor;

/**
* @var SerializationContext
*/
protected $context;

/**
* @var ExpressionLanguageExclusionStrategy
*/
private $expressionExclusionStrategy;

private $dispatcher;
private $metadataFactory;
private $handlerRegistry;
/**
* @var AccessorStrategyInterface
*/
private $accessor;

/**
* @var bool
*/
private $shouldSerializeNull;
/**
* @var PropertySelectorInterface
*/
private $selector;

public function __construct(
MetadataFactoryInterface $metadataFactory,
HandlerRegistryInterface $handlerRegistry,
AccessorStrategyInterface $accessor,
EventDispatcherInterface $dispatcher = null,
ExpressionEvaluatorInterface $expressionEvaluator = null
PropertySelectorInterface $selector,
EventDispatcherInterface $dispatcher = null
) {
$this->dispatcher = $dispatcher ?: new EventDispatcher();
$this->metadataFactory = $metadataFactory;
$this->handlerRegistry = $handlerRegistry;
$this->accessor = $accessor;

if ($expressionEvaluator) {
$this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
}
$this->selector = $selector;
}

public function initialize(VisitorInterface $visitor, Context $context): void
Expand Down Expand Up @@ -189,10 +181,6 @@ public function accept($data, array $type = null)
/** @var $metadata ClassMetadata */
$metadata = $this->metadataFactory->getMetadataForClass($type['name']);

if ($metadata->usingExpression && $this->expressionExclusionStrategy === null) {
throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
}

if ($this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
$this->context->stopVisiting($data);

Expand All @@ -206,23 +194,13 @@ public function accept($data, array $type = null)
}

$this->visitor->startVisitingObject($metadata, $data, $type);
foreach ($metadata->propertyMetadata as $propertyMetadata) {
if ($this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
continue;
}

if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
continue;
}

$v = $this->accessor->getValue($data, $propertyMetadata);

if (null === $v && $this->shouldSerializeNull !== true) {
continue;
}
$properties = $this->selector->select($metadata);
$values = $this->accessor->getValues($data, $metadata, $properties, $this->context);

foreach ($properties as $i => $propertyMetadata) {
$this->context->pushPropertyMetadata($propertyMetadata);
$this->visitor->visitProperty($propertyMetadata, $v);
$this->visitor->visitProperty($propertyMetadata, $values[$i]);
$this->context->popPropertyMetadata();
}

Expand Down
Loading