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

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