vendor/jms/serializer/src/JMS/Serializer/GraphNavigator.php line 85

Open in your IDE?
  1. <?php
  2. namespace JMS\Serializer;
  3. use JMS\Serializer\Construction\ObjectConstructorInterface;
  4. use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
  5. use JMS\Serializer\EventDispatcher\ObjectEvent;
  6. use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
  7. use JMS\Serializer\EventDispatcher\PreSerializeEvent;
  8. use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
  9. use JMS\Serializer\Exception\InvalidArgumentException;
  10. use JMS\Serializer\Exception\RuntimeException;
  11. use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
  12. use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
  13. use JMS\Serializer\Handler\HandlerRegistryInterface;
  14. use JMS\Serializer\Metadata\ClassMetadata;
  15. use Metadata\MetadataFactoryInterface;
  16. /**
  17.  * Handles traversal along the object graph.
  18.  *
  19.  * This class handles traversal along the graph, and calls different methods
  20.  * on visitors, or custom handlers to process its nodes.
  21.  *
  22.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  23.  */
  24. final class GraphNavigator implements GraphNavigatorInterface
  25. {
  26.     /**
  27.      * @var ExpressionLanguageExclusionStrategy
  28.      */
  29.     private $expressionExclusionStrategy;
  30.     private $dispatcher;
  31.     private $metadataFactory;
  32.     private $handlerRegistry;
  33.     private $objectConstructor;
  34.     /**
  35.      * Parses a direction string to one of the direction constants.
  36.      *
  37.      * @param string $dirStr
  38.      *
  39.      * @return integer
  40.      */
  41.     public static function parseDirection($dirStr)
  42.     {
  43.         switch (strtolower($dirStr)) {
  44.             case 'serialization':
  45.                 return self::DIRECTION_SERIALIZATION;
  46.             case 'deserialization':
  47.                 return self::DIRECTION_DESERIALIZATION;
  48.             default:
  49.                 throw new InvalidArgumentException(sprintf('The direction "%s" does not exist.'$dirStr));
  50.         }
  51.     }
  52.     public function __construct(
  53.         MetadataFactoryInterface $metadataFactory,
  54.         HandlerRegistryInterface $handlerRegistry,
  55.         ObjectConstructorInterface $objectConstructor,
  56.         EventDispatcherInterface $dispatcher null,
  57.         ExpressionEvaluatorInterface $expressionEvaluator null
  58.     )
  59.     {
  60.         $this->dispatcher $dispatcher;
  61.         $this->metadataFactory $metadataFactory;
  62.         $this->handlerRegistry $handlerRegistry;
  63.         $this->objectConstructor $objectConstructor;
  64.         if ($expressionEvaluator) {
  65.             $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
  66.         }
  67.     }
  68.     /**
  69.      * Called for each node of the graph that is being traversed.
  70.      *
  71.      * @param mixed $data the data depends on the direction, and type of visitor
  72.      * @param null|array $type array has the format ["name" => string, "params" => array]
  73.      * @param Context $context
  74.      * @return mixed the return value depends on the direction, and type of visitor
  75.      */
  76.     public function accept($data, array $type nullContext $context)
  77.     {
  78.         $visitor $context->getVisitor();
  79.         // If the type was not given, we infer the most specific type from the
  80.         // input data in serialization mode.
  81.         if (null === $type) {
  82.             if ($context instanceof DeserializationContext) {
  83.                 throw new RuntimeException('The type must be given for all properties when deserializing.');
  84.             }
  85.             $typeName = \gettype($data);
  86.             if ('object' === $typeName) {
  87.                 $typeName = \get_class($data);
  88.             }
  89.             $type = array('name' => $typeName'params' => array());
  90.         }
  91.         // If the data is null, we have to force the type to null regardless of the input in order to
  92.         // guarantee correct handling of null values, and not have any internal auto-casting behavior.
  93.         else if ($context instanceof SerializationContext && null === $data) {
  94.             $type = array('name' => 'NULL''params' => array());
  95.         }
  96.         // Sometimes data can convey null but is not of a null type.
  97.         // Visitors can have the power to add this custom null evaluation
  98.         if ($visitor instanceof NullAwareVisitorInterface && $visitor->isNull($data) === true) {
  99.             $type = array('name' => 'NULL''params' => array());
  100.         }
  101.         switch ($type['name']) {
  102.             case 'NULL':
  103.                 return $visitor->visitNull($data$type$context);
  104.             case 'string':
  105.                 return $visitor->visitString($data$type$context);
  106.             case 'int':
  107.             case 'integer':
  108.                 return $visitor->visitInteger($data$type$context);
  109.             case 'bool':
  110.             case 'boolean':
  111.                 return $visitor->visitBoolean($data$type$context);
  112.             case 'double':
  113.             case 'float':
  114.                 return $visitor->visitDouble($data$type$context);
  115.             case 'array':
  116.                 return $visitor->visitArray($data$type$context);
  117.             case 'resource':
  118.                 $msg 'Resources are not supported in serialized data.';
  119.                 if ($context instanceof SerializationContext && null !== $path $context->getPath()) {
  120.                     $msg .= ' Path: ' $path;
  121.                 }
  122.                 throw new RuntimeException($msg);
  123.             default:
  124.                 // TODO: The rest of this method needs some refactoring.
  125.                 if ($context instanceof SerializationContext) {
  126.                     if (null !== $data) {
  127.                         if ($context->isVisiting($data)) {
  128.                             return null;
  129.                         }
  130.                         $context->startVisiting($data);
  131.                     }
  132.                     // If we're serializing a polymorphic type, then we'll be interested in the
  133.                     // metadata for the actual type of the object, not the base class.
  134.                     if (class_exists($type['name'], false) || interface_exists($type['name'], false)) {
  135.                         if (is_subclass_of($data$type['name'], false)) {
  136.                             $type = array('name' => \get_class($data), 'params' => array());
  137.                         }
  138.                     }
  139.                 } elseif ($context instanceof DeserializationContext) {
  140.                     $context->increaseDepth();
  141.                 }
  142.                 // Trigger pre-serialization callbacks, and listeners if they exist.
  143.                 // Dispatch pre-serialization event before handling data to have ability change type in listener
  144.                 if ($context instanceof SerializationContext) {
  145.                     if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.pre_serialize'$type['name'], $context->getFormat())) {
  146.                         $this->dispatcher->dispatch('serializer.pre_serialize'$type['name'], $context->getFormat(), $event = new PreSerializeEvent($context$data$type));
  147.                         $type $event->getType();
  148.                     }
  149.                 } elseif ($context instanceof DeserializationContext) {
  150.                     if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.pre_deserialize'$type['name'], $context->getFormat())) {
  151.                         $this->dispatcher->dispatch('serializer.pre_deserialize'$type['name'], $context->getFormat(), $event = new PreDeserializeEvent($context$data$type));
  152.                         $type $event->getType();
  153.                         $data $event->getData();
  154.                     }
  155.                 }
  156.                 // First, try whether a custom handler exists for the given type. This is done
  157.                 // before loading metadata because the type name might not be a class, but
  158.                 // could also simply be an artifical type.
  159.                 if (null !== $handler $this->handlerRegistry->getHandler($context->getDirection(), $type['name'], $context->getFormat())) {
  160.                     $rs = \call_user_func($handler$visitor$data$type$context);
  161.                     $this->leaveScope($context$data);
  162.                     return $rs;
  163.                 }
  164.                 $exclusionStrategy $context->getExclusionStrategy();
  165.                 /** @var $metadata ClassMetadata */
  166.                 $metadata $this->metadataFactory->getMetadataForClass($type['name']);
  167.                 if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
  168.                     throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
  169.                 }
  170.                 if ($context instanceof DeserializationContext && !empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
  171.                     $metadata $this->resolveMetadata($data$metadata);
  172.                 }
  173.                 if (null !== $exclusionStrategy && $exclusionStrategy->shouldSkipClass($metadata$context)) {
  174.                     $this->leaveScope($context$data);
  175.                     return null;
  176.                 }
  177.                 $context->pushClassMetadata($metadata);
  178.                 if ($context instanceof SerializationContext) {
  179.                     foreach ($metadata->preSerializeMethods as $method) {
  180.                         $method->invoke($data);
  181.                     }
  182.                 }
  183.                 $object $data;
  184.                 if ($context instanceof DeserializationContext) {
  185.                     $object $this->objectConstructor->construct($visitor$metadata$data$type$context);
  186.                 }
  187.                 if (isset($metadata->handlerCallbacks[$context->getDirection()][$context->getFormat()])) {
  188.                     $rs $object->{$metadata->handlerCallbacks[$context->getDirection()][$context->getFormat()]}(
  189.                         $visitor,
  190.                         $context instanceof SerializationContext null $data,
  191.                         $context
  192.                     );
  193.                     $this->afterVisitingObject($metadata$object$type$context);
  194.                     return $context instanceof SerializationContext $rs $object;
  195.                 }
  196.                 $visitor->startVisitingObject($metadata$object$type$context);
  197.                 foreach ($metadata->propertyMetadata as $propertyMetadata) {
  198.                     if (null !== $exclusionStrategy && $exclusionStrategy->shouldSkipProperty($propertyMetadata$context)) {
  199.                         continue;
  200.                     }
  201.                     if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata$context)) {
  202.                         continue;
  203.                     }
  204.                     if ($context instanceof DeserializationContext && $propertyMetadata->readOnly) {
  205.                         continue;
  206.                     }
  207.                     $context->pushPropertyMetadata($propertyMetadata);
  208.                     $visitor->visitProperty($propertyMetadata$data$context);
  209.                     $context->popPropertyMetadata();
  210.                 }
  211.                 if ($context instanceof SerializationContext) {
  212.                     $this->afterVisitingObject($metadata$data$type$context);
  213.                     return $visitor->endVisitingObject($metadata$data$type$context);
  214.                 }
  215.                 $rs $visitor->endVisitingObject($metadata$data$type$context);
  216.                 $this->afterVisitingObject($metadata$rs$type$context);
  217.                 return $rs;
  218.         }
  219.     }
  220.     private function resolveMetadata($dataClassMetadata $metadata)
  221.     {
  222.         switch (true) {
  223.             case \is_array($data) && isset($data[$metadata->discriminatorFieldName]):
  224.                 $typeValue = (string)$data[$metadata->discriminatorFieldName];
  225.                 break;
  226.             // Check XML attribute without namespace for discriminatorFieldName
  227.             case \is_object($data) && $metadata->xmlDiscriminatorAttribute && null === $metadata->xmlDiscriminatorNamespace && isset($data->attributes()->{$metadata->discriminatorFieldName}):
  228.                 $typeValue = (string)$data->attributes()->{$metadata->discriminatorFieldName};
  229.                 break;
  230.             // Check XML attribute with namespace for discriminatorFieldName
  231.             case \is_object($data) && $metadata->xmlDiscriminatorAttribute && null !== $metadata->xmlDiscriminatorNamespace && isset($data->attributes($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}):
  232.                 $typeValue = (string)$data->attributes($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName};
  233.                 break;
  234.             // Check XML element with namespace for discriminatorFieldName
  235.             case \is_object($data) && !$metadata->xmlDiscriminatorAttribute && null !== $metadata->xmlDiscriminatorNamespace && isset($data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}):
  236.                 $typeValue = (string)$data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName};
  237.                 break;
  238.             // Check XML element for discriminatorFieldName
  239.             case \is_object($data) && isset($data->{$metadata->discriminatorFieldName}):
  240.                 $typeValue = (string)$data->{$metadata->discriminatorFieldName};
  241.                 break;
  242.             default:
  243.                 throw new \LogicException(sprintf(
  244.                     'The discriminator field name "%s" for base-class "%s" was not found in input data.',
  245.                     $metadata->discriminatorFieldName,
  246.                     $metadata->name
  247.                 ));
  248.         }
  249.         if (!isset($metadata->discriminatorMap[$typeValue])) {
  250.             throw new \LogicException(sprintf(
  251.                 'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
  252.                 $typeValue,
  253.                 $metadata->name,
  254.                 implode(', 'array_keys($metadata->discriminatorMap))
  255.             ));
  256.         }
  257.         return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
  258.     }
  259.     private function leaveScope(Context $context$data)
  260.     {
  261.         if ($context instanceof SerializationContext) {
  262.             $context->stopVisiting($data);
  263.         } elseif ($context instanceof DeserializationContext) {
  264.             $context->decreaseDepth();
  265.         }
  266.     }
  267.     private function afterVisitingObject(ClassMetadata $metadata$object, array $typeContext $context)
  268.     {
  269.         $this->leaveScope($context$object);
  270.         $context->popClassMetadata();
  271.         if ($context instanceof SerializationContext) {
  272.             foreach ($metadata->postSerializeMethods as $method) {
  273.                 $method->invoke($object);
  274.             }
  275.             if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.post_serialize'$metadata->name$context->getFormat())) {
  276.                 $this->dispatcher->dispatch('serializer.post_serialize'$metadata->name$context->getFormat(), new ObjectEvent($context$object$type));
  277.             }
  278.             return;
  279.         }
  280.         foreach ($metadata->postDeserializeMethods as $method) {
  281.             $method->invoke($object);
  282.         }
  283.         if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.post_deserialize'$metadata->name$context->getFormat())) {
  284.             $this->dispatcher->dispatch('serializer.post_deserialize'$metadata->name$context->getFormat(), new ObjectEvent($context$object$type));
  285.         }
  286.     }
  287. }