Relationship.php

Same filename in other branches
  1. 8.9.x core/modules/jsonapi/src/JsonApiResource/Relationship.php
  2. 10 core/modules/jsonapi/src/JsonApiResource/Relationship.php
  3. 11.x core/modules/jsonapi/src/JsonApiResource/Relationship.php

Namespace

Drupal\jsonapi\JsonApiResource

File

core/modules/jsonapi/src/JsonApiResource/Relationship.php

View source
<?php

namespace Drupal\jsonapi\JsonApiResource;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Url;
use Drupal\jsonapi\JsonApiSpec;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\Routing\Routes;

/**
 * Represents references from one resource object to other resource object(s).
 *
 * @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.
 *
 * @see https://www.drupal.org/project/drupal/issues/3032787
 * @see jsonapi.api.php
 */
class Relationship implements TopLevelDataInterface {
    
    /**
     * The context resource object of the relationship.
     *
     * A relationship object represents references from a resource object in
     * which it’s defined to other resource objects. Respectively, the "context"
     * of the relationship and the "target(s)" of the relationship.
     *
     * A relationship object's context either comes from the resource object that
     * contains it or, in the case that the relationship object is accessed
     * directly via a relationship URL, from its `self` URL, which should identify
     * the resource to which it belongs.
     *
     * @var \Drupal\jsonapi\JsonApiResource\ResourceObject
     *
     * @see https://jsonapi.org/format/#document-resource-object-relationships
     * @see https://jsonapi.org/recommendations/#urls-relationships
     */
    protected $context;
    
    /**
     * The data of the relationship object.
     *
     * @var \Drupal\jsonapi\JsonApiResource\RelationshipData
     */
    protected $data;
    
    /**
     * The relationship's public field name.
     *
     * @var string
     */
    protected $fieldName;
    
    /**
     * The relationship object's links.
     *
     * @var \Drupal\jsonapi\JsonApiResource\LinkCollection
     */
    protected $links;
    
    /**
     * The relationship object's meta member.
     *
     * @var array
     */
    protected $meta;
    
    /**
     * Relationship constructor.
     *
     * This constructor is protected by design. To create a new relationship, use
     * static::createFromEntityReferenceField().
     *
     * @param string $public_field_name
     *   The public field name of the relationship field.
     * @param \Drupal\jsonapi\JsonApiResource\RelationshipData $data
     *   The relationship data.
     * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links
     *   Any links for the resource object, if a `self` link is not
     *   provided, one will be automatically added if the resource is locatable
     *   and is not internal.
     * @param array $meta
     *   Any relationship metadata.
     * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $context
     *   The relationship's context resource object. Use the
     *   self::withContext() method to establish a context.
     *
     * @see \Drupal\jsonapi\JsonApiResource\Relationship::createFromEntityReferenceField()
     */
    protected function __construct($public_field_name, RelationshipData $data, LinkCollection $links, array $meta, ResourceObject $context) {
        $this->fieldName = $public_field_name;
        $this->data = $data;
        $this->links = $links->withContext($this);
        $this->meta = $meta;
        $this->context = $context;
    }
    
    /**
     * Creates a new Relationship from an entity reference field.
     *
     * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $context
     *   The context resource object of the relationship to be created.
     * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field
     *   The entity reference field from which to create the relationship.
     * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links
     *   (optional) Any extra links for the Relationship, if a `self` link is not
     *   provided, one will be automatically added if the context resource is
     *   locatable and is not internal.
     * @param array $meta
     *   (optional) Any relationship metadata.
     *
     * @return static
     *   An instantiated relationship object.
     */
    public static function createFromEntityReferenceField(ResourceObject $context, EntityReferenceFieldItemListInterface $field, LinkCollection $links = NULL, array $meta = []) {
        $context_resource_type = $context->getResourceType();
        $resource_field = $context_resource_type->getFieldByInternalName($field->getName());
        return new static($resource_field->getPublicName(), new RelationshipData(ResourceIdentifier::toResourceIdentifiers($field), $resource_field->hasOne() ? 1 : -1), static::buildLinkCollectionFromEntityReferenceField($context, $field, $links ?: new LinkCollection([])), $meta, $context);
    }
    
    /**
     * Gets context resource object of the relationship.
     *
     * @return \Drupal\jsonapi\JsonApiResource\ResourceObject
     *   The context ResourceObject.
     *
     * @see \Drupal\jsonapi\JsonApiResource\Relationship::$context
     */
    public function getContext() {
        return $this->context;
    }
    
    /**
     * Gets the relationship object's public field name.
     *
     * @return string
     *   The relationship's field name.
     */
    public function getFieldName() {
        return $this->fieldName;
    }
    
    /**
     * Gets the relationship object's data.
     *
     * @return \Drupal\jsonapi\JsonApiResource\RelationshipData
     *   The relationship's data.
     */
    public function getData() {
        return $this->data;
    }
    
    /**
     * Gets the relationship object's links.
     *
     * @return \Drupal\jsonapi\JsonApiResource\LinkCollection
     *   The relationship object's links.
     */
    public function getLinks() {
        return $this->links;
    }
    
    /**
     * Gets the relationship object's metadata.
     *
     * @return array
     *   The relationship object's metadata.
     */
    public function getMeta() {
        return $this->meta;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getOmissions() {
        return new OmittedData([]);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getMergedLinks(LinkCollection $top_level_links) {
        // When directly fetching a relationship object, the relationship object's
        // links become the top-level object's links unless they've been
        // overridden. Overrides are especially important for the `self` link, which
        // must match the link that generated the response. For example, the
        // top-level `self` link might have an `include` query parameter that would
        // be lost otherwise.
        // See https://jsonapi.org/format/#fetching-relationships-responses-200 and
        // https://jsonapi.org/format/#document-top-level.
        return LinkCollection::merge($top_level_links, $this->getLinks()
            ->filter(function ($key) use ($top_level_links) {
            return !$top_level_links->hasLinkWithKey($key);
        })
            ->withContext($top_level_links->getContext()));
    }
    
    /**
     * {@inheritdoc}
     */
    public function getMergedMeta(array $top_level_meta) {
        return NestedArray::mergeDeep($top_level_meta, $this->getMeta());
    }
    
    /**
     * Builds a LinkCollection for the given entity reference field.
     *
     * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $context
     *   The context resource object of the relationship object.
     * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field
     *   The entity reference field from which to create the links.
     * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links
     *   Any extra links for the Relationship, if a `self` link is not provided,
     *   one will be automatically added if the context resource is locatable and
     *   is not internal.
     *
     * @return \Drupal\jsonapi\JsonApiResource\LinkCollection
     *   The built links.
     */
    protected static function buildLinkCollectionFromEntityReferenceField(ResourceObject $context, EntityReferenceFieldItemListInterface $field, LinkCollection $links) {
        $context_resource_type = $context->getResourceType();
        $public_field_name = $context_resource_type->getPublicName($field->getName());
        if ($context_resource_type->isLocatable() && !$context_resource_type->isInternal()) {
            $context_is_versionable = $context_resource_type->isVersionable();
            if (!$links->hasLinkWithKey('self')) {
                $route_name = Routes::getRouteName($context_resource_type, "{$public_field_name}.relationship.get");
                $self_link = Url::fromRoute($route_name, [
                    'entity' => $context->getId(),
                ]);
                if ($context_is_versionable) {
                    $self_link->setOption('query', [
                        JsonApiSpec::VERSION_QUERY_PARAMETER => $context->getVersionIdentifier(),
                    ]);
                }
                $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_link, 'self'));
            }
            $has_non_internal_resource_type = array_reduce($context_resource_type->getRelatableResourceTypesByField($public_field_name), function ($carry, ResourceType $target) {
                return $carry ?: !$target->isInternal();
            }, FALSE);
            // If a `related` link was not provided, automatically generate one from
            // the relationship object to the collection resource with all of the
            // resources targeted by this relationship. However, that link should
            // *not* be generated if all of the relatable resources are internal.
            // That's because, in that case, a route will not exist for it.
            if (!$links->hasLinkWithKey('related') && $has_non_internal_resource_type) {
                $route_name = Routes::getRouteName($context_resource_type, "{$public_field_name}.related");
                $related_link = Url::fromRoute($route_name, [
                    'entity' => $context->getId(),
                ]);
                if ($context_is_versionable) {
                    $related_link->setOption('query', [
                        JsonApiSpec::VERSION_QUERY_PARAMETER => $context->getVersionIdentifier(),
                    ]);
                }
                $links = $links->withLink('related', new Link(new CacheableMetadata(), $related_link, 'related'));
            }
        }
        return $links;
    }

}

Classes

Title Deprecated Summary
Relationship Represents references from one resource object to other resource object(s).

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