"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.9/core/modules/jsonapi/src/JsonApiResource/ResourceObject.php" (18 Nov 2020, 14307 Bytes) of package /linux/www/drupal-8.9.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ResourceObject.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\jsonapi\JsonApiResource;
    4 
    5 use Drupal\Core\Cache\CacheableDependencyInterface;
    6 use Drupal\Core\Cache\CacheableDependencyTrait;
    7 use Drupal\Core\Cache\CacheableMetadata;
    8 use Drupal\Core\Config\Entity\ConfigEntityInterface;
    9 use Drupal\Core\Entity\ContentEntityInterface;
   10 use Drupal\Core\Entity\EntityInterface;
   11 use Drupal\Core\Entity\RevisionableInterface;
   12 use Drupal\Core\TypedData\TypedDataInternalPropertiesHelper;
   13 use Drupal\Core\Url;
   14 use Drupal\jsonapi\JsonApiSpec;
   15 use Drupal\jsonapi\ResourceType\ResourceType;
   16 use Drupal\jsonapi\Revisions\VersionByRel;
   17 use Drupal\jsonapi\Routing\Routes;
   18 use Drupal\user\UserInterface;
   19 
   20 /**
   21  * Represents a JSON:API resource object.
   22  *
   23  * This value object wraps a Drupal entity so that it can carry a JSON:API
   24  * resource type object alongside it. It also helps abstract away differences
   25  * between config and content entities within the JSON:API codebase.
   26  *
   27  * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
   28  *   may change at any time and could break any dependencies on it.
   29  *
   30  * @see https://www.drupal.org/project/drupal/issues/3032787
   31  * @see jsonapi.api.php
   32  */
   33 class ResourceObject implements CacheableDependencyInterface, ResourceIdentifierInterface {
   34 
   35   use CacheableDependencyTrait;
   36   use ResourceIdentifierTrait;
   37 
   38   /**
   39    * The resource object's version identifier.
   40    *
   41    * @var string|null
   42    */
   43   protected $versionIdentifier;
   44 
   45   /**
   46    * The object's fields.
   47    *
   48    * This refers to "fields" in the JSON:API sense of the word. Config entities
   49    * do not have real fields, so in that case, this will be an array of values
   50    * for config entity attributes.
   51    *
   52    * @var \Drupal\Core\Field\FieldItemListInterface[]|mixed[]
   53    */
   54   protected $fields;
   55 
   56   /**
   57    * The resource object's links.
   58    *
   59    * @var \Drupal\jsonapi\JsonApiResource\LinkCollection
   60    */
   61   protected $links;
   62 
   63   /**
   64    * ResourceObject constructor.
   65    *
   66    * @param \Drupal\Core\Cache\CacheableDependencyInterface $cacheability
   67    *   The cacheability for the resource object.
   68    * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
   69    *   The JSON:API resource type of the resource object.
   70    * @param string $id
   71    *   The resource object's ID.
   72    * @param mixed|null $revision_id
   73    *   The resource object's version identifier. NULL, if the resource object is
   74    *   not versionable.
   75    * @param array $fields
   76    *   An array of the resource object's fields, keyed by public field name.
   77    * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links
   78    *   The links for the resource object.
   79    */
   80   public function __construct(CacheableDependencyInterface $cacheability, ResourceType $resource_type, $id, $revision_id, array $fields, LinkCollection $links) {
   81     assert(is_null($revision_id) || $resource_type->isVersionable());
   82     $this->setCacheability($cacheability);
   83     $this->resourceType = $resource_type;
   84     $this->resourceIdentifier = new ResourceIdentifier($resource_type, $id);
   85     $this->versionIdentifier = $revision_id ? 'id:' . $revision_id : NULL;
   86     $this->fields = $fields;
   87     $this->links = $links->withContext($this);
   88   }
   89 
   90   /**
   91    * Creates a new ResourceObject from an entity.
   92    *
   93    * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
   94    *   The JSON:API resource type of the resource object.
   95    * @param \Drupal\Core\Entity\EntityInterface $entity
   96    *   The entity to be represented by this resource object.
   97    * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links
   98    *   (optional) Any links for the resource object, if a `self` link is not
   99    *   provided, one will be automatically added if the resource is locatable
  100    *   and is not an internal entity.
  101    *
  102    * @return static
  103    *   An instantiated resource object.
  104    */
  105   public static function createFromEntity(ResourceType $resource_type, EntityInterface $entity, LinkCollection $links = NULL) {
  106     return new static(
  107       $entity,
  108       $resource_type,
  109       $entity->uuid(),
  110       $resource_type->isVersionable() && $entity instanceof RevisionableInterface ? $entity->getRevisionId() : NULL,
  111       static::extractFieldsFromEntity($resource_type, $entity),
  112       static::buildLinksFromEntity($resource_type, $entity, $links ?: new LinkCollection([]))
  113     );
  114   }
  115 
  116   /**
  117    * Whether the resource object has the given field.
  118    *
  119    * @param string $public_field_name
  120    *   A public field name.
  121    *
  122    * @return bool
  123    *   TRUE if the resource object has the given field, FALSE otherwise.
  124    */
  125   public function hasField($public_field_name) {
  126     return isset($this->fields[$public_field_name]);
  127   }
  128 
  129   /**
  130    * Gets the given field.
  131    *
  132    * @param string $public_field_name
  133    *   A public field name.
  134    *
  135    * @return mixed|\Drupal\Core\Field\FieldItemListInterface|null
  136    *   The field or NULL if the resource object does not have the given field.
  137    *
  138    * @see ::extractFields()
  139    */
  140   public function getField($public_field_name) {
  141     return $this->hasField($public_field_name) ? $this->fields[$public_field_name] : NULL;
  142   }
  143 
  144   /**
  145    * Gets the ResourceObject's fields.
  146    *
  147    * @return array
  148    *   The resource object's fields, keyed by public field name.
  149    *
  150    * @see ::extractFields()
  151    */
  152   public function getFields() {
  153     return $this->fields;
  154   }
  155 
  156   /**
  157    * Gets the ResourceObject's links.
  158    *
  159    * @return \Drupal\jsonapi\JsonApiResource\LinkCollection
  160    *   The resource object's links.
  161    */
  162   public function getLinks() {
  163     return $this->links;
  164   }
  165 
  166   /**
  167    * Gets a version identifier for the ResourceObject.
  168    *
  169    * @return string
  170    *   The version identifier of the resource object, if the resource type is
  171    *   versionable.
  172    */
  173   public function getVersionIdentifier() {
  174     if (!$this->resourceType->isVersionable()) {
  175       throw new \LogicException('Cannot get a version identifier for a non-versionable resource.');
  176     }
  177     return $this->versionIdentifier;
  178   }
  179 
  180   /**
  181    * Gets a Url for the ResourceObject.
  182    *
  183    * @return \Drupal\Core\Url
  184    *   The URL for the identified resource object.
  185    *
  186    * @throws \LogicException
  187    *   Thrown if the resource object is not locatable.
  188    *
  189    * @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::isLocatableResourceType()
  190    */
  191   public function toUrl() {
  192     foreach ($this->links as $key => $link) {
  193       if ($key === 'self') {
  194         $first = reset($link);
  195         return $first->getUri();
  196       }
  197     }
  198     throw new \LogicException('A Url does not exist for this resource object because its resource type is not locatable.');
  199   }
  200 
  201   /**
  202    * Extracts the entity's fields.
  203    *
  204    * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
  205    *   The JSON:API resource type of the given entity.
  206    * @param \Drupal\Core\Entity\EntityInterface $entity
  207    *   The entity from which fields should be extracted.
  208    *
  209    * @return mixed|\Drupal\Core\Field\FieldItemListInterface[]
  210    *   If the resource object represents a content entity, the fields will be
  211    *   objects satisfying FieldItemListInterface. If it represents a config
  212    *   entity, the fields will be scalar values or arrays.
  213    */
  214   protected static function extractFieldsFromEntity(ResourceType $resource_type, EntityInterface $entity) {
  215     assert($entity instanceof ContentEntityInterface || $entity instanceof ConfigEntityInterface);
  216     return $entity instanceof ContentEntityInterface
  217       ? static::extractContentEntityFields($resource_type, $entity)
  218       : static::extractConfigEntityFields($resource_type, $entity);
  219   }
  220 
  221   /**
  222    * Builds a LinkCollection for the given entity.
  223    *
  224    * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
  225    *   The JSON:API resource type of the given entity.
  226    * @param \Drupal\Core\Entity\EntityInterface $entity
  227    *   The entity for which to build links.
  228    * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links
  229    *   (optional) Any extra links for the resource object, if a `self` link is
  230    *   not provided, one will be automatically added if the resource is
  231    *   locatable and is not an internal entity.
  232    *
  233    * @return \Drupal\jsonapi\JsonApiResource\LinkCollection
  234    *   The built links.
  235    */
  236   protected static function buildLinksFromEntity(ResourceType $resource_type, EntityInterface $entity, LinkCollection $links) {
  237     if ($resource_type->isLocatable() && !$resource_type->isInternal()) {
  238       $self_url = Url::fromRoute(Routes::getRouteName($resource_type, 'individual'), ['entity' => $entity->uuid()]);
  239       if ($resource_type->isVersionable()) {
  240         assert($entity instanceof RevisionableInterface);
  241         if (!$links->hasLinkWithKey('self')) {
  242           // If the resource is versionable, the `self` link should be the exact
  243           // link for the represented version. This helps a client track
  244           // revision changes and to disambiguate resource objects with the same
  245           // `type` and `id` in a `version-history` collection.
  246           $self_with_version_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'id:' . $entity->getRevisionId()]);
  247           $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_with_version_url, 'self'));
  248         }
  249         if (!$entity->isDefaultRevision()) {
  250           $latest_version_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'rel:' . VersionByRel::LATEST_VERSION]);
  251           $links = $links->withLink(VersionByRel::LATEST_VERSION, new Link(new CacheableMetadata(), $latest_version_url, VersionByRel::LATEST_VERSION));
  252         }
  253         if (!$entity->isLatestRevision()) {
  254           $working_copy_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'rel:' . VersionByRel::WORKING_COPY]);
  255           $links = $links->withLink(VersionByRel::WORKING_COPY, new Link(new CacheableMetadata(), $working_copy_url, VersionByRel::WORKING_COPY));
  256         }
  257       }
  258       if (!$links->hasLinkWithKey('self')) {
  259         $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_url, 'self'));
  260       }
  261     }
  262     return $links;
  263   }
  264 
  265   /**
  266    * Extracts a content entity's fields.
  267    *
  268    * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
  269    *   The JSON:API resource type of the given entity.
  270    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  271    *   The config entity from which fields should be extracted.
  272    *
  273    * @return \Drupal\Core\Field\FieldItemListInterface[]
  274    *   The fields extracted from a content entity.
  275    */
  276   protected static function extractContentEntityFields(ResourceType $resource_type, ContentEntityInterface $entity) {
  277     $output = [];
  278     $fields = TypedDataInternalPropertiesHelper::getNonInternalProperties($entity->getTypedData());
  279     // Filter the array based on the field names.
  280     $enabled_field_names = array_filter(
  281       array_keys($fields),
  282       [$resource_type, 'isFieldEnabled']
  283     );
  284 
  285     // Special handling for user entities that allows a JSON:API user agent to
  286     // access the display name of a user. For example, this is useful when
  287     // displaying the name of a node's author.
  288     // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254.
  289     $entity_type = $entity->getEntityType();
  290     if ($entity_type->id() == 'user' && $resource_type->isFieldEnabled('display_name')) {
  291       assert($entity instanceof UserInterface);
  292       $display_name = $resource_type->getPublicName('display_name');
  293       $output[$display_name] = $entity->getDisplayName();
  294     }
  295 
  296     // Return a sub-array of $output containing the keys in $enabled_fields.
  297     $input = array_intersect_key($fields, array_flip($enabled_field_names));
  298     foreach ($input as $field_name => $field_value) {
  299       $public_field_name = $resource_type->getPublicName($field_name);
  300       $output[$public_field_name] = $field_value;
  301     }
  302 
  303     return $output;
  304   }
  305 
  306   /**
  307    * Determines the entity type's (internal) label field name.
  308    *
  309    * @param \Drupal\Core\Entity\EntityInterface $entity
  310    *   The entity from which fields should be extracted.
  311    *
  312    * @return string
  313    *   The label field name.
  314    */
  315   protected static function getLabelFieldName(EntityInterface $entity) {
  316     $label_field_name = $entity->getEntityType()->getKey('label');
  317     // Special handling for user entities that allows a JSON:API user agent to
  318     // access the display name of a user. This is useful when displaying the
  319     // name of a node's author.
  320     // @see \Drupal\jsonapi\JsonApiResource\ResourceObject::extractContentEntityFields()
  321     // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254.
  322     if ($entity->getEntityTypeId() === 'user') {
  323       $label_field_name = 'display_name';
  324     }
  325     return $label_field_name;
  326   }
  327 
  328   /**
  329    * Extracts a config entity's fields.
  330    *
  331    * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
  332    *   The JSON:API resource type of the given entity.
  333    * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
  334    *   The config entity from which fields should be extracted.
  335    *
  336    * @return array
  337    *   The fields extracted from a config entity.
  338    */
  339   protected static function extractConfigEntityFields(ResourceType $resource_type, ConfigEntityInterface $entity) {
  340     $enabled_public_fields = [];
  341     $fields = $entity->toArray();
  342     // Filter the array based on the field names.
  343     $enabled_field_names = array_filter(array_keys($fields), function ($internal_field_name) use ($resource_type) {
  344       // Config entities have "fields" which aren't known to the resource type,
  345       // these fields should not be excluded because they cannot be enabled or
  346       // disabled.
  347       return !$resource_type->hasField($internal_field_name) || $resource_type->isFieldEnabled($internal_field_name);
  348     });
  349     // Return a sub-array of $output containing the keys in $enabled_fields.
  350     $input = array_intersect_key($fields, array_flip($enabled_field_names));
  351     /* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
  352     foreach ($input as $field_name => $field_value) {
  353       $public_field_name = $resource_type->getPublicName($field_name);
  354       $enabled_public_fields[$public_field_name] = $field_value;
  355     }
  356     return $enabled_public_fields;
  357   }
  358 
  359 }