class FieldItemNormalizer

Same name in this branch
  1. 10 core/modules/serialization/src/Normalizer/FieldItemNormalizer.php \Drupal\serialization\Normalizer\FieldItemNormalizer
Same name in other branches
  1. 9 core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php \Drupal\jsonapi\Normalizer\FieldItemNormalizer
  2. 9 core/modules/serialization/src/Normalizer/FieldItemNormalizer.php \Drupal\serialization\Normalizer\FieldItemNormalizer
  3. 9 core/modules/hal/src/Normalizer/FieldItemNormalizer.php \Drupal\hal\Normalizer\FieldItemNormalizer
  4. 8.9.x core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php \Drupal\jsonapi\Normalizer\FieldItemNormalizer
  5. 8.9.x core/modules/serialization/src/Normalizer/FieldItemNormalizer.php \Drupal\serialization\Normalizer\FieldItemNormalizer
  6. 8.9.x core/modules/hal/src/Normalizer/FieldItemNormalizer.php \Drupal\hal\Normalizer\FieldItemNormalizer
  7. 11.x core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php \Drupal\jsonapi\Normalizer\FieldItemNormalizer
  8. 11.x core/modules/serialization/src/Normalizer/FieldItemNormalizer.php \Drupal\serialization\Normalizer\FieldItemNormalizer

Converts the Drupal field item object to a JSON:API array structure.

@internal JSON:API maintains no PHP API since its API is the HTTP API. This class may change at any time and this will break any dependencies on it.

Hierarchy

Expanded class hierarchy of FieldItemNormalizer

See also

https://www.drupal.org/project/drupal/issues/3032787

jsonapi.api.php

1 file declares its use of FieldItemNormalizer
FieldItemNormalizerTest.php in core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php
1 string reference to 'FieldItemNormalizer'
jsonapi.services.yml in core/modules/jsonapi/jsonapi.services.yml
core/modules/jsonapi/jsonapi.services.yml
1 service uses FieldItemNormalizer
serializer.normalizer.field_item.jsonapi in core/modules/jsonapi/jsonapi.services.yml
Drupal\jsonapi\Normalizer\FieldItemNormalizer

File

core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php, line 28

Namespace

Drupal\jsonapi\Normalizer
View source
class FieldItemNormalizer extends NormalizerBase implements DenormalizerInterface {
    use SerializedColumnNormalizerTrait;
    
    /**
     * The entity type manager.
     *
     * @var \Drupal\Core\Entity\EntityTypeManagerInterface
     */
    protected $entityTypeManager;
    
    /**
     * FieldItemNormalizer constructor.
     *
     * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
     *   The entity type manager.
     */
    public function __construct(EntityTypeManagerInterface $entity_type_manager) {
        $this->entityTypeManager = $entity_type_manager;
    }
    
    /**
     * {@inheritdoc}
     *
     * This normalizer leaves JSON:API normalizer land and enters the land of
     * Drupal core's serialization system. That system was never designed with
     * cacheability in mind, and hence bubbles cacheability out of band. This must
     * catch it, and pass it to the value object that JSON:API uses.
     */
    public function normalize($field_item, $format = NULL, array $context = []) : array|string|int|float|bool|\ArrayObject|null {
        assert($field_item instanceof FieldItemInterface);
        
        /** @var \Drupal\Core\TypedData\TypedDataInterface $property */
        $values = [];
        $context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY] = new CacheableMetadata();
        if (!empty($field_item->getProperties(TRUE))) {
            // We normalize each individual value, so each can do their own casting,
            // if needed.
            $field_properties = TypedDataInternalPropertiesHelper::getNonInternalProperties($field_item);
            foreach ($field_properties as $property_name => $property) {
                $values[$property_name] = $this->serializer
                    ->normalize($property, $format, $context);
            }
            // Flatten if there is only a single property to normalize.
            $flatten = count($field_properties) === 1 && $field_item::mainPropertyName() !== NULL;
            $values = static::rasterizeValueRecursive($flatten ? reset($values) : $values);
        }
        else {
            $values = $field_item->getValue();
        }
        $normalization = new CacheableNormalization($context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY], $values);
        unset($context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY]);
        return $normalization;
    }
    
    /**
     * {@inheritdoc}
     */
    public function denormalize($data, $class, $format = NULL, array $context = []) : mixed {
        $item_definition = $context['field_definition']->getItemDefinition();
        assert($item_definition instanceof FieldItemDataDefinitionInterface);
        $field_item = $this->getFieldItemInstance($context['resource_type'], $item_definition);
        $this->checkForSerializedStrings($data, $class, $field_item);
        $property_definitions = $item_definition->getPropertyDefinitions();
        $serialized_property_names = $this->getCustomSerializedPropertyNames($field_item);
        $denormalize_property = function ($property_name, $property_value, $property_value_class, $format, $context) use ($serialized_property_names) {
            if ($this->serializer
                ->supportsDenormalization($property_value, $property_value_class, $format, $context)) {
                return $this->serializer
                    ->denormalize($property_value, $property_value_class, $format, $context);
            }
            else {
                if (in_array($property_name, $serialized_property_names, TRUE)) {
                    $property_value = serialize($property_value);
                }
                return $property_value;
            }
        };
        // Because e.g. the 'bundle' entity key field requires field values to not
        // be expanded to an array of all properties, we special-case single-value
        // properties.
        if (!is_array($data)) {
            // The NULL normalization means there is no value, hence we can return
            // early. Note that this is not just an optimization but a necessity for
            // field types without main properties (such as the "map" field type).
            if ($data === NULL) {
                return $data;
            }
            $property_value = $data;
            $property_name = $item_definition->getMainPropertyName();
            $property_value_class = $property_definitions[$property_name]->getClass();
            return $denormalize_property($property_name, $property_value, $property_value_class, $format, $context);
        }
        $data_internal = [];
        if (!empty($property_definitions)) {
            $writable_properties = array_keys(array_filter($property_definitions, function (DataDefinitionInterface $data_definition) : bool {
                return !$data_definition->isReadOnly();
            }));
            $invalid_property_names = [];
            foreach ($data as $property_name => $property_value) {
                if (!isset($property_definitions[$property_name])) {
                    $alt = static::getAlternatives($property_name, $writable_properties);
                    $invalid_property_names[$property_name] = reset($alt);
                }
            }
            if (!empty($invalid_property_names)) {
                $suggestions = array_values(array_filter($invalid_property_names));
                // Only use the "Did you mean"-style error message if there is a
                // suggestion for every invalid property name.
                if (count($suggestions) === count($invalid_property_names)) {
                    $format = count($invalid_property_names) === 1 ? "The property '%s' does not exist on the '%s' field of type '%s'. Did you mean '%s'?" : "The properties '%s' do not exist on the '%s' field of type '%s'. Did you mean '%s'?";
                    throw new UnexpectedValueException(sprintf($format, implode("', '", array_keys($invalid_property_names)), $item_definition->getFieldDefinition()
                        ->getName(), $item_definition->getFieldDefinition()
                        ->getType(), implode("', '", $suggestions)));
                }
                else {
                    $format = count($invalid_property_names) === 1 ? "The property '%s' does not exist on the '%s' field of type '%s'. Writable properties are: '%s'." : "The properties '%s' do not exist on the '%s' field of type '%s'. Writable properties are: '%s'.";
                    throw new UnexpectedValueException(sprintf($format, implode("', '", array_keys($invalid_property_names)), $item_definition->getFieldDefinition()
                        ->getName(), $item_definition->getFieldDefinition()
                        ->getType(), implode("', '", $writable_properties)));
                }
            }
            foreach ($data as $property_name => $property_value) {
                $property_value_class = $property_definitions[$property_name]->getClass();
                $data_internal[$property_name] = $denormalize_property($property_name, $property_value, $property_value_class, $format, $context);
            }
        }
        else {
            $data_internal = $data;
        }
        return $data_internal;
    }
    
    /**
     * Provides alternatives for a given array and key.
     *
     * @param string $search_key
     *   The search key to get alternatives for.
     * @param array $keys
     *   The search space to search for alternatives in.
     *
     * @return string[]
     *   An array of strings with suitable alternatives.
     *
     * @see \Drupal\Component\DependencyInjection\Container::getAlternatives()
     */
    private static function getAlternatives(string $search_key, array $keys) : array {
        // $search_key is user input and could be longer than the 255 string length
        // limit of levenshtein().
        if (strlen($search_key) > 255) {
            return [];
        }
        $alternatives = [];
        foreach ($keys as $key) {
            $lev = levenshtein($search_key, $key);
            if ($lev <= strlen($search_key) / 3 || str_contains($key, $search_key)) {
                $alternatives[] = $key;
            }
        }
        return $alternatives;
    }
    
    /**
     * Gets a field item instance for use with SerializedColumnNormalizerTrait.
     *
     * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
     *   The JSON:API resource type of the entity being denormalized.
     * @param \Drupal\Core\Field\TypedData\FieldItemDataDefinitionInterface $item_definition
     *   The field item definition of the instance to get.
     *
     * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
     * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
     */
    protected function getFieldItemInstance(ResourceType $resource_type, FieldItemDataDefinitionInterface $item_definition) {
        if ($bundle_key = $this->entityTypeManager
            ->getDefinition($resource_type->getEntityTypeId())
            ->getKey('bundle')) {
            $create_values = [
                $bundle_key => $resource_type->getBundle(),
            ];
        }
        else {
            $create_values = [];
        }
        $entity = $this->entityTypeManager
            ->getStorage($resource_type->getEntityTypeId())
            ->create($create_values);
        $field = $entity->get($item_definition->getFieldDefinition()
            ->getName());
        assert($field instanceof FieldItemListInterface);
        $field_item = $field->appendItem();
        assert($field_item instanceof FieldItemInterface);
        return $field_item;
    }
    
    /**
     * {@inheritdoc}
     */
    public function hasCacheableSupportsMethod() : bool {
        @trigger_error(__METHOD__ . '() is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use getSupportedTypes() instead. See https://www.drupal.org/node/3359695', E_USER_DEPRECATED);
        return TRUE;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getSupportedTypes(?string $format) : array {
        return [
            FieldItemInterface::class => TRUE,
        ];
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY constant Name of key for bubbling cacheability metadata via serialization context.
FieldItemNormalizer::$entityTypeManager protected property The entity type manager.
FieldItemNormalizer::denormalize public function
FieldItemNormalizer::getAlternatives private static function Provides alternatives for a given array and key.
FieldItemNormalizer::getFieldItemInstance protected function Gets a field item instance for use with SerializedColumnNormalizerTrait.
FieldItemNormalizer::getSupportedTypes public function Overrides NormalizerBase::getSupportedTypes
FieldItemNormalizer::hasCacheableSupportsMethod public function Overrides NormalizerBase::hasCacheableSupportsMethod
FieldItemNormalizer::normalize public function This normalizer leaves JSON:API normalizer land and enters the land of
Drupal core&#039;s serialization system. That system was never designed with
cacheability in mind, and hence bubbles cacheability out of band. This must
catch it, and pass it to…
FieldItemNormalizer::__construct public function FieldItemNormalizer constructor.
NormalizerBase::$format protected property List of formats which supports (de-)normalization. Overrides NormalizerBase::$format
NormalizerBase::addCacheableDependency protected function Adds cacheability if applicable.
NormalizerBase::checkFormat protected function Checks if the provided format is supported by this normalizer. Overrides NormalizerBase::checkFormat
NormalizerBase::rasterizeValueRecursive protected static function Rasterizes a value recursively.
NormalizerBase::supportsDenormalization public function Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::supportsDenormalization() 1
NormalizerBase::supportsNormalization public function 1
SerializedColumnNormalizerTrait::checkForSerializedStrings protected function Checks if there is a serialized string for a column.
SerializedColumnNormalizerTrait::dataHasStringForSerializeColumn protected function Checks if the data contains string value for serialize column.
SerializedColumnNormalizerTrait::getCustomSerializedPropertyNames protected function Gets the names of all properties the plugin treats as serialized data.
SerializedColumnNormalizerTrait::getSerializedPropertyNames protected function Gets the names of all serialized properties.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.