vendor/jms/serializer/src/GraphNavigator/SerializationGraphNavigator.php line 271

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace JMS\Serializer\GraphNavigator;
  4. use JMS\Serializer\Accessor\AccessorStrategyInterface;
  5. use JMS\Serializer\Context;
  6. use JMS\Serializer\EventDispatcher\EventDispatcher;
  7. use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
  8. use JMS\Serializer\EventDispatcher\ObjectEvent;
  9. use JMS\Serializer\EventDispatcher\PreSerializeEvent;
  10. use JMS\Serializer\Exception\CircularReferenceDetectedException;
  11. use JMS\Serializer\Exception\ExcludedClassException;
  12. use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
  13. use JMS\Serializer\Exception\InvalidArgumentException;
  14. use JMS\Serializer\Exception\NotAcceptableException;
  15. use JMS\Serializer\Exception\RuntimeException;
  16. use JMS\Serializer\Exception\SkipHandlerException;
  17. use JMS\Serializer\Exception\UninitializedPropertyException;
  18. use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
  19. use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
  20. use JMS\Serializer\Functions;
  21. use JMS\Serializer\GraphNavigator;
  22. use JMS\Serializer\GraphNavigatorInterface;
  23. use JMS\Serializer\Handler\HandlerRegistryInterface;
  24. use JMS\Serializer\Metadata\ClassMetadata;
  25. use JMS\Serializer\NullAwareVisitorInterface;
  26. use JMS\Serializer\SerializationContext;
  27. use JMS\Serializer\Visitor\SerializationVisitorInterface;
  28. use JMS\Serializer\VisitorInterface;
  29. use Metadata\MetadataFactoryInterface;
  30. use function assert;
  31. /**
  32.  * Handles traversal along the object graph.
  33.  *
  34.  * This class handles traversal along the graph, and calls different methods
  35.  * on visitors, or custom handlers to process its nodes.
  36.  *
  37.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  38.  */
  39. final class SerializationGraphNavigator extends GraphNavigator
  40. {
  41.     /**
  42.      * @var SerializationVisitorInterface
  43.      */
  44.     protected $visitor;
  45.     /**
  46.      * @var SerializationContext
  47.      */
  48.     protected $context;
  49.     /**
  50.      * @var ExpressionLanguageExclusionStrategy
  51.      */
  52.     private $expressionExclusionStrategy;
  53.     /**
  54.      * @var EventDispatcherInterface
  55.      */
  56.     private $dispatcher;
  57.     /**
  58.      * @var MetadataFactoryInterface
  59.      */
  60.     private $metadataFactory;
  61.     /**
  62.      * @var HandlerRegistryInterface
  63.      */
  64.     private $handlerRegistry;
  65.     /**
  66.      * @var AccessorStrategyInterface
  67.      */
  68.     private $accessor;
  69.     /**
  70.      * @var bool
  71.      */
  72.     private $shouldSerializeNull;
  73.     public function __construct(
  74.         MetadataFactoryInterface $metadataFactory,
  75.         HandlerRegistryInterface $handlerRegistry,
  76.         AccessorStrategyInterface $accessor,
  77.         ?EventDispatcherInterface $dispatcher null,
  78.         ?ExpressionEvaluatorInterface $expressionEvaluator null
  79.     ) {
  80.         $this->dispatcher $dispatcher ?: new EventDispatcher();
  81.         $this->metadataFactory $metadataFactory;
  82.         $this->handlerRegistry $handlerRegistry;
  83.         $this->accessor $accessor;
  84.         if ($expressionEvaluator) {
  85.             $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
  86.         }
  87.     }
  88.     public function initialize(VisitorInterface $visitorContext $context): void
  89.     {
  90.         assert($context instanceof SerializationContext);
  91.         parent::initialize($visitor$context);
  92.         $this->shouldSerializeNull $context->shouldSerializeNull();
  93.     }
  94.     /**
  95.      * Called for each node of the graph that is being traversed.
  96.      *
  97.      * @param mixed $data the data depends on the direction, and type of visitor
  98.      * @param array|null $type array has the format ["name" => string, "params" => array]
  99.      *
  100.      * @return mixed the return value depends on the direction, and type of visitor
  101.      */
  102.     public function accept($data, ?array $type null)
  103.     {
  104.         // If the type was not given, we infer the most specific type from the
  105.         // input data in serialization mode.
  106.         if (null === $type) {
  107.             $typeName = \gettype($data);
  108.             if ('object' === $typeName) {
  109.                 $typeName = \get_class($data);
  110.             }
  111.             $type = ['name' => $typeName'params' => []];
  112.         } elseif (null === $data) {
  113.             // If the data is null, we have to force the type to null regardless of the input in order to
  114.             // guarantee correct handling of null values, and not have any internal auto-casting behavior.
  115.             $type = ['name' => 'NULL''params' => []];
  116.         }
  117.         // Sometimes data can convey null but is not of a null type.
  118.         // Visitors can have the power to add this custom null evaluation
  119.         if ($this->visitor instanceof NullAwareVisitorInterface && true === $this->visitor->isNull($data)) {
  120.             $type = ['name' => 'NULL''params' => []];
  121.         }
  122.         switch ($type['name']) {
  123.             case 'NULL':
  124.                 if (!$this->shouldSerializeNull && !$this->isRootNullAllowed()) {
  125.                     throw new NotAcceptableException();
  126.                 }
  127.                 return $this->visitor->visitNull($data$type);
  128.             case 'string':
  129.                 return $this->visitor->visitString((string) $data$type);
  130.             case 'int':
  131.             case 'integer':
  132.                 return $this->visitor->visitInteger((int) $data$type);
  133.             case 'bool':
  134.             case 'boolean':
  135.                 return $this->visitor->visitBoolean((bool) $data$type);
  136.             case 'double':
  137.             case 'float':
  138.                 return $this->visitor->visitDouble((float) $data$type);
  139.             case 'iterable':
  140.                 return $this->visitor->visitArray(Functions::iterableToArray($data), $type);
  141.             case 'array':
  142.             case 'list':
  143.                 return $this->visitor->visitArray((array) $data$type);
  144.             case 'resource':
  145.                 $msg 'Resources are not supported in serialized data.';
  146.                 if (null !== $path $this->context->getPath()) {
  147.                     $msg .= ' Path: ' $path;
  148.                 }
  149.                 throw new RuntimeException($msg);
  150.             default:
  151.                 if (null !== $data) {
  152.                     if ($this->context->isVisiting($data)) {
  153.                         throw new CircularReferenceDetectedException();
  154.                     }
  155.                     $this->context->startVisiting($data);
  156.                 }
  157.                 // If we're serializing a polymorphic type, then we'll be interested in the
  158.                 // metadata for the actual type of the object, not the base class.
  159.                 if (class_exists($type['name'], false) || interface_exists($type['name'], false)) {
  160.                     if (is_subclass_of($data$type['name'], false) && null === $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION$type['name'], $this->format)) {
  161.                         $type = ['name' => \get_class($data), 'params' => $type['params'] ?? []];
  162.                     }
  163.                 }
  164.                 // Trigger pre-serialization callbacks, and listeners if they exist.
  165.                 // Dispatch pre-serialization event before handling data to have ability change type in listener
  166.                 if ($this->dispatcher->hasListeners('serializer.pre_serialize'$type['name'], $this->format)) {
  167.                     $this->dispatcher->dispatch('serializer.pre_serialize'$type['name'], $this->format$event = new PreSerializeEvent($this->context$data$type));
  168.                     $type $event->getType();
  169.                 }
  170.                 // First, try whether a custom handler exists for the given type. This is done
  171.                 // before loading metadata because the type name might not be a class, but
  172.                 // could also simply be an artifical type.
  173.                 if (null !== $handler $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION$type['name'], $this->format)) {
  174.                     try {
  175.                         $rs = \call_user_func($handler$this->visitor$data$type$this->context);
  176.                         $this->context->stopVisiting($data);
  177.                         return $rs;
  178.                     } catch (SkipHandlerException $e) {
  179.                         // Skip handler, fallback to default behavior
  180.                     } catch (NotAcceptableException $e) {
  181.                         $this->context->stopVisiting($data);
  182.                         throw $e;
  183.                     }
  184.                 }
  185.                 $metadata $this->metadataFactory->getMetadataForClass($type['name']);
  186.                 \assert($metadata instanceof ClassMetadata);
  187.                 if ($metadata->usingExpression && null === $this->expressionExclusionStrategy) {
  188.                     throw new ExpressionLanguageRequiredException(sprintf('To use conditional exclude/expose in %s you must configure the expression language.'$metadata->name));
  189.                 }
  190.                 if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata$this->context)) {
  191.                     $this->context->stopVisiting($data);
  192.                     throw new ExcludedClassException();
  193.                 }
  194.                 if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipClass($metadata$this->context)) {
  195.                     $this->context->stopVisiting($data);
  196.                     throw new ExcludedClassException();
  197.                 }
  198.                 if (!is_object($data)) {
  199.                     throw new InvalidArgumentException('Value at ' $this->context->getPath() . ' is expected to be an object of class ' $type['name'] . ' but is of type ' gettype($data));
  200.                 }
  201.                 $this->context->pushClassMetadata($metadata);
  202.                 foreach ($metadata->preSerializeMethods as $method) {
  203.                     $method->invoke($data);
  204.                 }
  205.                 $this->visitor->startVisitingObject($metadata$data$type);
  206.                 foreach ($metadata->propertyMetadata as $propertyMetadata) {
  207.                     if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata$this->context)) {
  208.                         continue;
  209.                     }
  210.                     if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata$this->context)) {
  211.                         continue;
  212.                     }
  213.                     try {
  214.                         $v $this->accessor->getValue($data$propertyMetadata$this->context);
  215.                     } catch (UninitializedPropertyException $e) {
  216.                         continue;
  217.                     }
  218.                     if (null === $v && true !== $this->shouldSerializeNull) {
  219.                         continue;
  220.                     }
  221.                     $this->context->pushPropertyMetadata($propertyMetadata);
  222.                     $this->visitor->visitProperty($propertyMetadata$v);
  223.                     $this->context->popPropertyMetadata();
  224.                 }
  225.                 $this->afterVisitingObject($metadata$data$type);
  226.                 return $this->visitor->endVisitingObject($metadata$data$type);
  227.         }
  228.     }
  229.     private function isRootNullAllowed(): bool
  230.     {
  231.         return $this->context->hasAttribute('allows_root_null') && $this->context->getAttribute('allows_root_null') && === $this->context->getVisitingSet()->count();
  232.     }
  233.     private function afterVisitingObject(ClassMetadata $metadataobject $object, array $type): void
  234.     {
  235.         $this->context->stopVisiting($object);
  236.         $this->context->popClassMetadata();
  237.         foreach ($metadata->postSerializeMethods as $method) {
  238.             $method->invoke($object);
  239.         }
  240.         if ($this->dispatcher->hasListeners('serializer.post_serialize'$metadata->name$this->format)) {
  241.             $this->dispatcher->dispatch('serializer.post_serialize'$metadata->name$this->format, new ObjectEvent($this->context$object$type));
  242.         }
  243.     }
  244. }