You are here

public function JsonApiDocumentTopLevelNormalizer::denormalize in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/jsonapi/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php \Drupal\jsonapi\Normalizer\JsonApiDocumentTopLevelNormalizer::denormalize()

File

core/modules/jsonapi/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php, line 73

Class

JsonApiDocumentTopLevelNormalizer
Normalizes the top-level document according to the JSON:API specification.

Namespace

Drupal\jsonapi\Normalizer

Code

public function denormalize($data, $class, $format = NULL, array $context = []) {
  $resource_type = $context['resource_type'];

  // Validate a few common errors in document formatting.
  static::validateRequestBody($data, $resource_type);
  $normalized = [];
  if (!empty($data['data']['attributes'])) {
    $normalized = $data['data']['attributes'];
  }
  if (!empty($data['data']['id'])) {
    $uuid_key = $this->entityTypeManager
      ->getDefinition($resource_type
      ->getEntityTypeId())
      ->getKey('uuid');
    $normalized[$uuid_key] = $data['data']['id'];
  }
  if (!empty($data['data']['relationships'])) {

    // Turn all single object relationship data fields into an array of
    // objects.
    $relationships = array_map(function ($relationship) {
      if (isset($relationship['data']['type']) && isset($relationship['data']['id'])) {
        return [
          'data' => [
            $relationship['data'],
          ],
        ];
      }
      else {
        return $relationship;
      }
    }, $data['data']['relationships']);

    // Get an array of ids for every relationship.
    $relationships = array_map(function ($relationship) {
      if (empty($relationship['data'])) {
        return [];
      }
      if (empty($relationship['data'][0]['id'])) {
        throw new BadRequestHttpException("No ID specified for related resource");
      }
      $id_list = array_column($relationship['data'], 'id');
      if (empty($relationship['data'][0]['type'])) {
        throw new BadRequestHttpException("No type specified for related resource");
      }
      if (!($resource_type = $this->resourceTypeRepository
        ->getByTypeName($relationship['data'][0]['type']))) {
        throw new BadRequestHttpException("Invalid type specified for related resource: '" . $relationship['data'][0]['type'] . "'");
      }
      $entity_type_id = $resource_type
        ->getEntityTypeId();
      try {
        $entity_storage = $this->entityTypeManager
          ->getStorage($entity_type_id);
      } catch (PluginNotFoundException $e) {
        throw new BadRequestHttpException("Invalid type specified for related resource: '" . $relationship['data'][0]['type'] . "'");
      }

      // In order to maintain the order ($delta) of the relationships, we need
      // to load the entities and create a mapping between id and uuid.
      $uuid_key = $this->entityTypeManager
        ->getDefinition($entity_type_id)
        ->getKey('uuid');
      $related_entities = array_values($entity_storage
        ->loadByProperties([
        $uuid_key => $id_list,
      ]));
      $map = [];
      foreach ($related_entities as $related_entity) {
        $map[$related_entity
          ->uuid()] = $related_entity
          ->id();
      }

      // $id_list has the correct order of uuids. We stitch this together with
      // $map which contains loaded entities, and then bring in the correct
      // meta values from the relationship, whose deltas match with $id_list.
      $canonical_ids = [];
      foreach ($id_list as $delta => $uuid) {
        if (!isset($map[$uuid])) {

          // @see \Drupal\jsonapi\Normalizer\EntityReferenceFieldNormalizer::normalize()
          if ($uuid === 'virtual') {
            continue;
          }
          throw new NotFoundHttpException(sprintf('The resource identified by `%s:%s` (given as a relationship item) could not be found.', $relationship['data'][$delta]['type'], $uuid));
        }
        $reference_item = [
          'target_id' => $map[$uuid],
        ];
        if (isset($relationship['data'][$delta]['meta'])) {
          $reference_item += $relationship['data'][$delta]['meta'];
        }
        $canonical_ids[] = array_filter($reference_item, function ($key) {
          return substr($key, 0, strlen('drupal_internal__')) !== 'drupal_internal__';
        }, ARRAY_FILTER_USE_KEY);
      }
      return array_filter($canonical_ids);
    }, $relationships);

    // Add the relationship ids.
    $normalized = array_merge($normalized, $relationships);
  }

  // Override deserialization target class with the one in the ResourceType.
  $class = $context['resource_type']
    ->getDeserializationTargetClass();
  return $this->serializer
    ->denormalize($normalized, $class, $format, $context);
}