vendor/symfony/serializer/Normalizer/AbstractNormalizer.php line 187

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Serializer\Normalizer;
  11. use Symfony\Component\Serializer\Exception\CircularReferenceException;
  12. use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  13. use Symfony\Component\Serializer\Exception\LogicException;
  14. use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
  15. use Symfony\Component\Serializer\Exception\RuntimeException;
  16. use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
  17. use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
  18. use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
  19. use Symfony\Component\Serializer\SerializerAwareInterface;
  20. use Symfony\Component\Serializer\SerializerAwareTrait;
  21. /**
  22.  * Normalizer implementation.
  23.  *
  24.  * @author Kévin Dunglas <dunglas@gmail.com>
  25.  */
  26. abstract class AbstractNormalizer implements NormalizerInterfaceDenormalizerInterfaceSerializerAwareInterfaceCacheableSupportsMethodInterface
  27. {
  28.     use ObjectToPopulateTrait;
  29.     use SerializerAwareTrait;
  30.     /* constants to configure the context */
  31.     /**
  32.      * How many loops of circular reference to allow while normalizing.
  33.      *
  34.      * The default value of 1 means that when we encounter the same object a
  35.      * second time, we consider that a circular reference.
  36.      *
  37.      * You can raise this value for special cases, e.g. in combination with the
  38.      * max depth setting of the object normalizer.
  39.      */
  40.     public const CIRCULAR_REFERENCE_LIMIT 'circular_reference_limit';
  41.     /**
  42.      * Instead of creating a new instance of an object, update the specified object.
  43.      *
  44.      * If you have a nested structure, child objects will be overwritten with
  45.      * new instances unless you set DEEP_OBJECT_TO_POPULATE to true.
  46.      */
  47.     public const OBJECT_TO_POPULATE 'object_to_populate';
  48.     /**
  49.      * Only (de)normalize attributes that are in the specified groups.
  50.      */
  51.     public const GROUPS 'groups';
  52.     /**
  53.      * Limit (de)normalize to the specified names.
  54.      *
  55.      * For nested structures, this list needs to reflect the object tree.
  56.      */
  57.     public const ATTRIBUTES 'attributes';
  58.     /**
  59.      * If ATTRIBUTES are specified, and the source has fields that are not part of that list,
  60.      * either ignore those attributes (true) or throw an ExtraAttributesException (false).
  61.      */
  62.     public const ALLOW_EXTRA_ATTRIBUTES 'allow_extra_attributes';
  63.     /**
  64.      * Hashmap of default values for constructor arguments.
  65.      *
  66.      * The names need to match the parameter names in the constructor arguments.
  67.      */
  68.     public const DEFAULT_CONSTRUCTOR_ARGUMENTS 'default_constructor_arguments';
  69.     /**
  70.      * Hashmap of field name => callable to (de)normalize this field.
  71.      *
  72.      * The callable is called if the field is encountered with the arguments:
  73.      *
  74.      * - mixed         $attributeValue value of this field
  75.      * - object|string $object         the whole object being normalized or the object's class being denormalized
  76.      * - string        $attributeName  name of the attribute being (de)normalized
  77.      * - string        $format         the requested format
  78.      * - array         $context        the serialization context
  79.      */
  80.     public const CALLBACKS 'callbacks';
  81.     /**
  82.      * Handler to call when a circular reference has been detected.
  83.      *
  84.      * If you specify no handler, a CircularReferenceException is thrown.
  85.      *
  86.      * The method will be called with ($object, $format, $context) and its
  87.      * return value is returned as the result of the normalize call.
  88.      */
  89.     public const CIRCULAR_REFERENCE_HANDLER 'circular_reference_handler';
  90.     /**
  91.      * Skip the specified attributes when normalizing an object tree.
  92.      *
  93.      * This list is applied to each element of nested structures.
  94.      *
  95.      * Note: The behaviour for nested structures is different from ATTRIBUTES
  96.      * for historical reason. Aligning the behaviour would be a BC break.
  97.      */
  98.     public const IGNORED_ATTRIBUTES 'ignored_attributes';
  99.     /**
  100.      * @internal
  101.      */
  102.     protected const CIRCULAR_REFERENCE_LIMIT_COUNTERS 'circular_reference_limit_counters';
  103.     protected $defaultContext = [
  104.         self::ALLOW_EXTRA_ATTRIBUTES => true,
  105.         self::CIRCULAR_REFERENCE_LIMIT => 1,
  106.         self::IGNORED_ATTRIBUTES => [],
  107.     ];
  108.     /**
  109.      * @deprecated since Symfony 4.2
  110.      */
  111.     protected $circularReferenceLimit 1;
  112.     /**
  113.      * @deprecated since Symfony 4.2
  114.      *
  115.      * @var callable|null
  116.      */
  117.     protected $circularReferenceHandler;
  118.     /**
  119.      * @var ClassMetadataFactoryInterface|null
  120.      */
  121.     protected $classMetadataFactory;
  122.     /**
  123.      * @var NameConverterInterface|null
  124.      */
  125.     protected $nameConverter;
  126.     /**
  127.      * @deprecated since Symfony 4.2
  128.      */
  129.     protected $callbacks = [];
  130.     /**
  131.      * @deprecated since Symfony 4.2
  132.      */
  133.     protected $ignoredAttributes = [];
  134.     /**
  135.      * @deprecated since Symfony 4.2
  136.      */
  137.     protected $camelizedAttributes = [];
  138.     /**
  139.      * Sets the {@link ClassMetadataFactoryInterface} to use.
  140.      */
  141.     public function __construct(ClassMetadataFactoryInterface $classMetadataFactory nullNameConverterInterface $nameConverter null, array $defaultContext = [])
  142.     {
  143.         $this->classMetadataFactory $classMetadataFactory;
  144.         $this->nameConverter $nameConverter;
  145.         $this->defaultContext array_merge($this->defaultContext$defaultContext);
  146.         $this->validateCallbackContext($this->defaultContext'default');
  147.         if (isset($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER]) && !\is_callable($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER])) {
  148.             throw new InvalidArgumentException(sprintf('Invalid callback found in the "%s" default context option.'self::CIRCULAR_REFERENCE_HANDLER));
  149.         }
  150.     }
  151.     /**
  152.      * Sets circular reference limit.
  153.      *
  154.      * @deprecated since Symfony 4.2
  155.      *
  156.      * @param int $circularReferenceLimit Limit of iterations for the same object
  157.      *
  158.      * @return self
  159.      */
  160.     public function setCircularReferenceLimit($circularReferenceLimit)
  161.     {
  162.         @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "circular_reference_limit" key of the context instead.'__METHOD__), \E_USER_DEPRECATED);
  163.         $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] = $this->circularReferenceLimit $circularReferenceLimit;
  164.         return $this;
  165.     }
  166.     /**
  167.      * Sets circular reference handler.
  168.      *
  169.      * @deprecated since Symfony 4.2
  170.      *
  171.      * @return self
  172.      */
  173.     public function setCircularReferenceHandler(callable $circularReferenceHandler)
  174.     {
  175.         @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "circular_reference_handler" key of the context instead.'__METHOD__), \E_USER_DEPRECATED);
  176.         $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER] = $this->circularReferenceHandler $circularReferenceHandler;
  177.         return $this;
  178.     }
  179.     /**
  180.      * Sets (de)normalization callbacks.
  181.      *
  182.      * @deprecated since Symfony 4.2
  183.      *
  184.      * @param callable[] $callbacks Help (de)normalize the result
  185.      *
  186.      * @return self
  187.      *
  188.      * @throws InvalidArgumentException if a non-callable callback is set
  189.      */
  190.     public function setCallbacks(array $callbacks)
  191.     {
  192.         @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "callbacks" key of the context instead.'__METHOD__), \E_USER_DEPRECATED);
  193.         foreach ($callbacks as $attribute => $callback) {
  194.             if (!\is_callable($callback)) {
  195.                 throw new InvalidArgumentException(sprintf('The given callback for attribute "%s" is not callable.'$attribute));
  196.             }
  197.         }
  198.         $this->defaultContext[self::CALLBACKS] = $this->callbacks $callbacks;
  199.         return $this;
  200.     }
  201.     /**
  202.      * Sets ignored attributes for normalization and denormalization.
  203.      *
  204.      * @deprecated since Symfony 4.2
  205.      *
  206.      * @return self
  207.      */
  208.     public function setIgnoredAttributes(array $ignoredAttributes)
  209.     {
  210.         @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "ignored_attributes" key of the context instead.'__METHOD__), \E_USER_DEPRECATED);
  211.         $this->defaultContext[self::IGNORED_ATTRIBUTES] = $this->ignoredAttributes $ignoredAttributes;
  212.         return $this;
  213.     }
  214.     /**
  215.      * {@inheritdoc}
  216.      */
  217.     public function hasCacheableSupportsMethod(): bool
  218.     {
  219.         return false;
  220.     }
  221.     /**
  222.      * Detects if the configured circular reference limit is reached.
  223.      *
  224.      * @param object $object
  225.      * @param array  $context
  226.      *
  227.      * @return bool
  228.      *
  229.      * @throws CircularReferenceException
  230.      */
  231.     protected function isCircularReference($object, &$context)
  232.     {
  233.         $objectHash spl_object_hash($object);
  234.         $circularReferenceLimit $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->circularReferenceLimit;
  235.         if (isset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash])) {
  236.             if ($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] >= $circularReferenceLimit) {
  237.                 unset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]);
  238.                 return true;
  239.             }
  240.             ++$context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash];
  241.         } else {
  242.             $context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] = 1;
  243.         }
  244.         return false;
  245.     }
  246.     /**
  247.      * Handles a circular reference.
  248.      *
  249.      * If a circular reference handler is set, it will be called. Otherwise, a
  250.      * {@class CircularReferenceException} will be thrown.
  251.      *
  252.      * @final since Symfony 4.2
  253.      *
  254.      * @param object      $object
  255.      * @param string|null $format
  256.      * @param array       $context
  257.      *
  258.      * @return mixed
  259.      *
  260.      * @throws CircularReferenceException
  261.      */
  262.     protected function handleCircularReference($object/*, string $format = null, array $context = []*/)
  263.     {
  264.         if (\func_num_args() < && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this__FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) {
  265.             @trigger_error(sprintf('The "%s()" method will have two new "string $format = null" and "array $context = []" arguments in version 5.0, not defining it is deprecated since Symfony 4.2.'__METHOD__), \E_USER_DEPRECATED);
  266.         }
  267.         $format = \func_num_args() > func_get_arg(1) : null;
  268.         $context = \func_num_args() > func_get_arg(2) : [];
  269.         $circularReferenceHandler $context[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->circularReferenceHandler;
  270.         if ($circularReferenceHandler) {
  271.             return $circularReferenceHandler($object$format$context);
  272.         }
  273.         throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', \get_class($object), $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->circularReferenceLimit));
  274.     }
  275.     /**
  276.      * Gets attributes to normalize using groups.
  277.      *
  278.      * @param string|object $classOrObject
  279.      * @param bool          $attributesAsString If false, return an array of {@link AttributeMetadataInterface}
  280.      *
  281.      * @throws LogicException if the 'allow_extra_attributes' context variable is false and no class metadata factory is provided
  282.      *
  283.      * @return string[]|AttributeMetadataInterface[]|bool
  284.      */
  285.     protected function getAllowedAttributes($classOrObject, array $context$attributesAsString false)
  286.     {
  287.         $allowExtraAttributes $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES];
  288.         if (!$this->classMetadataFactory) {
  289.             if (!$allowExtraAttributes) {
  290.                 throw new LogicException(sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.'self::ALLOW_EXTRA_ATTRIBUTES));
  291.             }
  292.             return false;
  293.         }
  294.         $tmpGroups $context[self::GROUPS] ?? $this->defaultContext[self::GROUPS] ?? null;
  295.         $groups = (\is_array($tmpGroups) || is_scalar($tmpGroups)) ? (array) $tmpGroups false;
  296.         if (false === $groups && $allowExtraAttributes) {
  297.             return false;
  298.         }
  299.         $allowedAttributes = [];
  300.         foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) {
  301.             $name $attributeMetadata->getName();
  302.             if (
  303.                 (false === $groups || array_intersect($attributeMetadata->getGroups(), $groups)) &&
  304.                 $this->isAllowedAttribute($classOrObject$namenull$context)
  305.             ) {
  306.                 $allowedAttributes[] = $attributesAsString $name $attributeMetadata;
  307.             }
  308.         }
  309.         return $allowedAttributes;
  310.     }
  311.     /**
  312.      * Is this attribute allowed?
  313.      *
  314.      * @param object|string $classOrObject
  315.      * @param string        $attribute
  316.      * @param string|null   $format
  317.      *
  318.      * @return bool
  319.      */
  320.     protected function isAllowedAttribute($classOrObject$attribute$format null, array $context = [])
  321.     {
  322.         $ignoredAttributes $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES] ?? $this->ignoredAttributes;
  323.         if (\in_array($attribute$ignoredAttributes)) {
  324.             return false;
  325.         }
  326.         $attributes $context[self::ATTRIBUTES] ?? $this->defaultContext[self::ATTRIBUTES] ?? null;
  327.         if (isset($attributes[$attribute])) {
  328.             // Nested attributes
  329.             return true;
  330.         }
  331.         if (\is_array($attributes)) {
  332.             return \in_array($attribute$attributestrue);
  333.         }
  334.         return true;
  335.     }
  336.     /**
  337.      * Normalizes the given data to an array. It's particularly useful during
  338.      * the denormalization process.
  339.      *
  340.      * @param object|array $data
  341.      *
  342.      * @return array
  343.      */
  344.     protected function prepareForDenormalization($data)
  345.     {
  346.         return (array) $data;
  347.     }
  348.     /**
  349.      * Returns the method to use to construct an object. This method must be either
  350.      * the object constructor or static.
  351.      *
  352.      * @param string     $class
  353.      * @param array|bool $allowedAttributes
  354.      *
  355.      * @return \ReflectionMethod|null
  356.      */
  357.     protected function getConstructor(array &$data$class, array &$context, \ReflectionClass $reflectionClass$allowedAttributes)
  358.     {
  359.         return $reflectionClass->getConstructor();
  360.     }
  361.     /**
  362.      * Instantiates an object using constructor parameters when needed.
  363.      *
  364.      * This method also allows to denormalize data into an existing object if
  365.      * it is present in the context with the object_to_populate. This object
  366.      * is removed from the context before being returned to avoid side effects
  367.      * when recursively normalizing an object graph.
  368.      *
  369.      * @param string     $class
  370.      * @param array|bool $allowedAttributes
  371.      *
  372.      * @return object
  373.      *
  374.      * @throws RuntimeException
  375.      * @throws MissingConstructorArgumentsException
  376.      */
  377.     protected function instantiateObject(array &$data$class, array &$context, \ReflectionClass $reflectionClass$allowedAttributesstring $format null)
  378.     {
  379.         if (null !== $object $this->extractObjectToPopulate($class$contextself::OBJECT_TO_POPULATE)) {
  380.             unset($context[self::OBJECT_TO_POPULATE]);
  381.             return $object;
  382.         }
  383.         // clean up even if no match
  384.         unset($context[static::OBJECT_TO_POPULATE]);
  385.         $constructor $this->getConstructor($data$class$context$reflectionClass$allowedAttributes);
  386.         if ($constructor) {
  387.             if (true !== $constructor->isPublic()) {
  388.                 return $reflectionClass->newInstanceWithoutConstructor();
  389.             }
  390.             $constructorParameters $constructor->getParameters();
  391.             $params = [];
  392.             foreach ($constructorParameters as $constructorParameter) {
  393.                 $paramName $constructorParameter->name;
  394.                 $key $this->nameConverter $this->nameConverter->normalize($paramName$class$format$context) : $paramName;
  395.                 $allowed false === $allowedAttributes || \in_array($paramName$allowedAttributes);
  396.                 $ignored = !$this->isAllowedAttribute($class$paramName$format$context);
  397.                 if ($constructorParameter->isVariadic()) {
  398.                     if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key$data))) {
  399.                         if (!\is_array($data[$paramName])) {
  400.                             throw new RuntimeException(sprintf('Cannot create an instance of "%s" from serialized data because the variadic parameter "%s" can only accept an array.'$class$constructorParameter->name));
  401.                         }
  402.                         $variadicParameters = [];
  403.                         foreach ($data[$paramName] as $parameterData) {
  404.                             $variadicParameters[] = $this->denormalizeParameter($reflectionClass$constructorParameter$paramName$parameterData$context$format);
  405.                         }
  406.                         $params array_merge($params$variadicParameters);
  407.                         unset($data[$key]);
  408.                     }
  409.                 } elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key$data))) {
  410.                     $parameterData $data[$key];
  411.                     if (null === $parameterData && $constructorParameter->allowsNull()) {
  412.                         $params[] = null;
  413.                         // Don't run set for a parameter passed to the constructor
  414.                         unset($data[$key]);
  415.                         continue;
  416.                     }
  417.                     // Don't run set for a parameter passed to the constructor
  418.                     $params[] = $this->denormalizeParameter($reflectionClass$constructorParameter$paramName$parameterData$context$format);
  419.                     unset($data[$key]);
  420.                 } elseif (\array_key_exists($key$context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
  421.                     $params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
  422.                 } elseif (\array_key_exists($key$this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
  423.                     $params[] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
  424.                 } elseif ($constructorParameter->isDefaultValueAvailable()) {
  425.                     $params[] = $constructorParameter->getDefaultValue();
  426.                 } elseif ($constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) {
  427.                     $params[] = null;
  428.                 } else {
  429.                     throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.'$class$constructorParameter->name));
  430.                 }
  431.             }
  432.             if ($constructor->isConstructor()) {
  433.                 return $reflectionClass->newInstanceArgs($params);
  434.             } else {
  435.                 return $constructor->invokeArgs(null$params);
  436.             }
  437.         }
  438.         return new $class();
  439.     }
  440.     /**
  441.      * @internal
  442.      */
  443.     protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter$parameterName$parameterData, array $context$format null)
  444.     {
  445.         try {
  446.             if (($parameterType $parameter->getType()) instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) {
  447.                 $parameterClass $parameterType->getName();
  448.                 new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist
  449.                 if (!$this->serializer instanceof DenormalizerInterface) {
  450.                     throw new LogicException(sprintf('Cannot create an instance of "%s" from serialized data because the serializer inject in "%s" is not a denormalizer.'$parameterClass, static::class));
  451.                 }
  452.                 $parameterData $this->serializer->denormalize($parameterData$parameterClass$format$this->createChildContext($context$parameterName$format));
  453.             }
  454.         } catch (\ReflectionException $e) {
  455.             throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".'$parameterName), 0$e);
  456.         } catch (MissingConstructorArgumentsException $e) {
  457.             if (!$parameter->getType()->allowsNull()) {
  458.                 throw $e;
  459.             }
  460.             return null;
  461.         }
  462.         return $this->applyCallbacks($parameterData$class->getName(), $parameterName$format$context);
  463.     }
  464.     /**
  465.      * @param string $attribute Attribute name
  466.      *
  467.      * @internal
  468.      */
  469.     protected function createChildContext(array $parentContext$attribute/*, ?string $format */): array
  470.     {
  471.         if (\func_num_args() < 3) {
  472.             @trigger_error(sprintf('Method "%s::%s()" will have a third "?string $format" argument in version 5.0; not defining it is deprecated since Symfony 4.3.', static::class, __FUNCTION__), \E_USER_DEPRECATED);
  473.         }
  474.         if (isset($parentContext[self::ATTRIBUTES][$attribute])) {
  475.             $parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute];
  476.         } else {
  477.             unset($parentContext[self::ATTRIBUTES]);
  478.         }
  479.         return $parentContext;
  480.     }
  481.     /**
  482.      * Validate callbacks set in context.
  483.      *
  484.      * @param string $contextType Used to specify which context is invalid in exceptions
  485.      *
  486.      * @throws InvalidArgumentException
  487.      */
  488.     final protected function validateCallbackContext(array $contextstring $contextType ''): void
  489.     {
  490.         if (!isset($context[self::CALLBACKS])) {
  491.             return;
  492.         }
  493.         if (!\is_array($context[self::CALLBACKS])) {
  494.             throw new InvalidArgumentException(sprintf('The "%s"%s context option must be an array of callables.'self::CALLBACKS'' !== $contextType $contextType''));
  495.         }
  496.         foreach ($context[self::CALLBACKS] as $attribute => $callback) {
  497.             if (!\is_callable($callback)) {
  498.                 throw new InvalidArgumentException(sprintf('Invalid callback found for attribute "%s" in the "%s"%s context option.'$attributeself::CALLBACKS'' !== $contextType $contextType''));
  499.             }
  500.         }
  501.     }
  502.     /**
  503.      * Apply callbacks set in context.
  504.      *
  505.      * @param mixed         $value
  506.      * @param object|string $object Can be either the object being normalizing or the object's class being denormalized
  507.      *
  508.      * @return mixed
  509.      */
  510.     final protected function applyCallbacks($value$objectstring $attribute, ?string $format, array $context)
  511.     {
  512.         /**
  513.          * @var callable|null
  514.          */
  515.         $callback $context[self::CALLBACKS][$attribute] ?? $this->defaultContext[self::CALLBACKS][$attribute] ?? $this->callbacks[$attribute] ?? null;
  516.         return $callback $callback($value$object$attribute$format$context) : $value;
  517.     }
  518. }