You are here

protected function IncludeResolver::resolveIncludeTree in JSON:API 8.2

Receives a tree of include field names and resolves resources for it.

This method takes a tree of relationship field names and JSON:API Data object. For the top-level of the tree and for each entity in the collection, it gets the target entity type and IDs for each relationship field. The method then loads all of those targets and calls itself recursively with the next level of the tree and those loaded resources.

Parameters

array $include_tree: The include paths, represented as a tree.

\Drupal\jsonapi\JsonApiResource\Data $data: The entity collection from which includes should be resolved.

\Drupal\jsonapi\JsonApiResource\Data|null $includes: (Internal use only) Any prior resolved includes.

Return value

\Drupal\jsonapi\JsonApiResource\Data A JSON:API Data of included items.

Throws

\Drupal\Component\Plugin\Exception\PluginNotFoundException Thrown if an included entity type doesn't exist.

\Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException Thrown if a storage handler couldn't be loaded.

1 call to IncludeResolver::resolveIncludeTree()
IncludeResolver::resolve in src/IncludeResolver.php
Resolves included resources.

File

src/IncludeResolver.php, line 101

Class

IncludeResolver
Resolves included resources for an entity or collection of entities.

Namespace

Drupal\jsonapi

Code

protected function resolveIncludeTree(array $include_tree, Data $data, Data $includes = NULL) {
  $includes = is_null($includes) ? new IncludedData([]) : $includes;
  foreach ($include_tree as $field_name => $children) {
    $references = [];
    foreach ($data as $resource_object) {

      // Some objects in the collection may be LabelOnlyResourceObjects or
      // EntityAccessDeniedHttpException objects.
      assert($resource_object instanceof ResourceIdentifierInterface);
      if ($resource_object instanceof LabelOnlyResourceObject) {
        $message = "The current user is not allowed to view this relationship.";
        $exception = new EntityAccessDeniedHttpException($resource_object
          ->getEntity(), AccessResult::forbidden("The user only has authorization for the 'view label' operation."), '', $message, $field_name);
        $includes = IncludedData::merge($includes, new IncludedData([
          $exception,
        ]));
        continue;
      }
      elseif (!$resource_object instanceof ResourceObject) {
        continue;
      }
      $public_field_name = $resource_object
        ->getResourceType()
        ->getPublicName($field_name);

      // Not all entities in $entity_collection will be of the same bundle and
      // may not have all of the same fields. Therefore, calling
      // $resource_object->get($a_missing_field_name) will result in an
      // exception.
      if (!$resource_object
        ->hasField($public_field_name)) {
        continue;
      }
      $field_list = $resource_object
        ->getField($public_field_name);

      // Config entities don't have real fields and can't have relationships.
      if (!$field_list instanceof FieldItemListInterface) {
        continue;
      }
      $field_access = $field_list
        ->access('view', NULL, TRUE);
      if (!$field_access
        ->isAllowed()) {
        $message = 'The current user is not allowed to view this relationship.';
        $exception = new EntityAccessDeniedHttpException($field_list
          ->getEntity(), $field_access, '', $message, $public_field_name);
        $includes = IncludedData::merge($includes, new IncludedData([
          $exception,
        ]));
        continue;
      }
      $target_type = $field_list
        ->getFieldDefinition()
        ->getFieldStorageDefinition()
        ->getSetting('target_type');
      assert(!empty($target_type));
      foreach ($field_list as $field_item) {
        assert($field_item instanceof EntityReferenceItem);
        $references[$target_type][] = $field_item
          ->get($field_item::mainPropertyName())
          ->getValue();
      }
    }
    foreach ($references as $target_type => $ids) {
      $entity_storage = $this->entityTypeManager
        ->getStorage($target_type);
      $targeted_entities = $entity_storage
        ->loadMultiple(array_unique($ids));
      $access_checked_entities = array_map(function (EntityInterface $entity) {
        return $this->entityAccessChecker
          ->getAccessCheckedResourceObject($entity);
      }, $targeted_entities);
      $targeted_collection = new IncludedData(array_filter($access_checked_entities, function (ResourceIdentifierInterface $resource_object) {
        return !$resource_object
          ->getResourceType()
          ->isInternal();
      }));
      $includes = static::resolveIncludeTree($children, $targeted_collection, IncludedData::merge($includes, $targeted_collection));
    }
  }
  return $includes;
}