Chris@18: isVersionable()); Chris@18: $this->setCacheability($cacheability); Chris@18: $this->resourceType = $resource_type; Chris@18: $this->resourceIdentifier = new ResourceIdentifier($resource_type, $id); Chris@18: $this->versionIdentifier = $revision_id ? 'id:' . $revision_id : NULL; Chris@18: $this->fields = $fields; Chris@18: $this->links = $links->withContext($this); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Creates a new ResourceObject from an entity. Chris@18: * Chris@18: * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type Chris@18: * The JSON:API resource type of the resource object. Chris@18: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@18: * The entity to be represented by this resource object. Chris@18: * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links Chris@18: * (optional) Any links for the resource object, if a `self` link is not Chris@18: * provided, one will be automatically added if the resource is locatable Chris@18: * and is not an internal entity. Chris@18: * Chris@18: * @return static Chris@18: * An instantiated resource object. Chris@18: */ Chris@18: public static function createFromEntity(ResourceType $resource_type, EntityInterface $entity, LinkCollection $links = NULL) { Chris@18: return new static( Chris@18: $entity, Chris@18: $resource_type, Chris@18: $entity->uuid(), Chris@18: $resource_type->isVersionable() && $entity instanceof RevisionableInterface ? $entity->getRevisionId() : NULL, Chris@18: static::extractFieldsFromEntity($resource_type, $entity), Chris@18: static::buildLinksFromEntity($resource_type, $entity, $links ?: new LinkCollection([])) Chris@18: ); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Whether the resource object has the given field. Chris@18: * Chris@18: * @param string $public_field_name Chris@18: * A public field name. Chris@18: * Chris@18: * @return bool Chris@18: * TRUE if the resource object has the given field, FALSE otherwise. Chris@18: */ Chris@18: public function hasField($public_field_name) { Chris@18: return isset($this->fields[$public_field_name]); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets the given field. Chris@18: * Chris@18: * @param string $public_field_name Chris@18: * A public field name. Chris@18: * Chris@18: * @return mixed|\Drupal\Core\Field\FieldItemListInterface|null Chris@18: * The field or NULL if the resource object does not have the given field. Chris@18: * Chris@18: * @see ::extractFields() Chris@18: */ Chris@18: public function getField($public_field_name) { Chris@18: return $this->hasField($public_field_name) ? $this->fields[$public_field_name] : NULL; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets the ResourceObject's fields. Chris@18: * Chris@18: * @return array Chris@18: * The resource object's fields, keyed by public field name. Chris@18: * Chris@18: * @see ::extractFields() Chris@18: */ Chris@18: public function getFields() { Chris@18: return $this->fields; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets the ResourceObject's links. Chris@18: * Chris@18: * @return \Drupal\jsonapi\JsonApiResource\LinkCollection Chris@18: * The resource object's links. Chris@18: */ Chris@18: public function getLinks() { Chris@18: return $this->links; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets a version identifier for the ResourceObject. Chris@18: * Chris@18: * @return string Chris@18: * The version identifier of the resource object, if the resource type is Chris@18: * versionable. Chris@18: */ Chris@18: public function getVersionIdentifier() { Chris@18: if (!$this->resourceType->isVersionable()) { Chris@18: throw new \LogicException('Cannot get a version identifier for a non-versionable resource.'); Chris@18: } Chris@18: return $this->versionIdentifier; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets a Url for the ResourceObject. Chris@18: * Chris@18: * @return \Drupal\Core\Url Chris@18: * The URL for the identified resource object. Chris@18: * Chris@18: * @throws \LogicException Chris@18: * Thrown if the resource object is not locatable. Chris@18: * Chris@18: * @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::isLocatableResourceType() Chris@18: */ Chris@18: public function toUrl() { Chris@18: foreach ($this->links as $key => $link) { Chris@18: if ($key === 'self') { Chris@18: $first = reset($link); Chris@18: return $first->getUri(); Chris@18: } Chris@18: } Chris@18: throw new \LogicException('A Url does not exist for this resource object because its resource type is not locatable.'); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Extracts the entity's fields. Chris@18: * Chris@18: * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type Chris@18: * The JSON:API resource type of the given entity. Chris@18: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@18: * The entity from which fields should be extracted. Chris@18: * Chris@18: * @return mixed|\Drupal\Core\Field\FieldItemListInterface[] Chris@18: * If the resource object represents a content entity, the fields will be Chris@18: * objects satisfying FieldItemListInterface. If it represents a config Chris@18: * entity, the fields will be scalar values or arrays. Chris@18: */ Chris@18: protected static function extractFieldsFromEntity(ResourceType $resource_type, EntityInterface $entity) { Chris@18: assert($entity instanceof ContentEntityInterface || $entity instanceof ConfigEntityInterface); Chris@18: return $entity instanceof ContentEntityInterface Chris@18: ? static::extractContentEntityFields($resource_type, $entity) Chris@18: : static::extractConfigEntityFields($resource_type, $entity); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Builds a LinkCollection for the given entity. Chris@18: * Chris@18: * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type Chris@18: * The JSON:API resource type of the given entity. Chris@18: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@18: * The entity for which to build links. Chris@18: * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links Chris@18: * (optional) Any extra links for the resource object, if a `self` link is Chris@18: * not provided, one will be automatically added if the resource is Chris@18: * locatable and is not an internal entity. Chris@18: * Chris@18: * @return \Drupal\jsonapi\JsonApiResource\LinkCollection Chris@18: * The built links. Chris@18: */ Chris@18: protected static function buildLinksFromEntity(ResourceType $resource_type, EntityInterface $entity, LinkCollection $links) { Chris@18: if ($resource_type->isLocatable() && !$resource_type->isInternal()) { Chris@18: $self_url = Url::fromRoute(Routes::getRouteName($resource_type, 'individual'), ['entity' => $entity->uuid()]); Chris@18: if ($resource_type->isVersionable()) { Chris@18: assert($entity instanceof RevisionableInterface); Chris@18: if (!$links->hasLinkWithKey('self')) { Chris@18: // If the resource is versionable, the `self` link should be the exact Chris@18: // link for the represented version. This helps a client track Chris@18: // revision changes and to disambiguate resource objects with the same Chris@18: // `type` and `id` in a `version-history` collection. Chris@18: $self_with_version_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'id:' . $entity->getRevisionId()]); Chris@18: $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_with_version_url, ['self'])); Chris@18: } Chris@18: if (!$entity->isDefaultRevision()) { Chris@18: $latest_version_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'rel:' . VersionByRel::LATEST_VERSION]); Chris@18: $links = $links->withLink(VersionByRel::LATEST_VERSION, new Link(new CacheableMetadata(), $latest_version_url, [VersionByRel::LATEST_VERSION])); Chris@18: } Chris@18: if (!$entity->isLatestRevision()) { Chris@18: $working_copy_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'rel:' . VersionByRel::WORKING_COPY]); Chris@18: $links = $links->withLink(VersionByRel::WORKING_COPY, new Link(new CacheableMetadata(), $working_copy_url, [VersionByRel::WORKING_COPY])); Chris@18: } Chris@18: } Chris@18: if (!$links->hasLinkWithKey('self')) { Chris@18: $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_url, ['self'])); Chris@18: } Chris@18: } Chris@18: return $links; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Extracts a content entity's fields. Chris@18: * Chris@18: * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type Chris@18: * The JSON:API resource type of the given entity. Chris@18: * @param \Drupal\Core\Entity\ContentEntityInterface $entity Chris@18: * The config entity from which fields should be extracted. Chris@18: * Chris@18: * @return \Drupal\Core\Field\FieldItemListInterface[] Chris@18: * The fields extracted from a content entity. Chris@18: */ Chris@18: protected static function extractContentEntityFields(ResourceType $resource_type, ContentEntityInterface $entity) { Chris@18: $output = []; Chris@18: $fields = TypedDataInternalPropertiesHelper::getNonInternalProperties($entity->getTypedData()); Chris@18: // Filter the array based on the field names. Chris@18: $enabled_field_names = array_filter( Chris@18: array_keys($fields), Chris@18: [$resource_type, 'isFieldEnabled'] Chris@18: ); Chris@18: Chris@18: // The "label" field needs special treatment: some entity types have a label Chris@18: // field that is actually backed by a label callback. Chris@18: $entity_type = $entity->getEntityType(); Chris@18: if ($entity_type->hasLabelCallback()) { Chris@18: $fields[static::getLabelFieldName($entity)]->value = $entity->label(); Chris@18: } Chris@18: Chris@18: // Return a sub-array of $output containing the keys in $enabled_fields. Chris@18: $input = array_intersect_key($fields, array_flip($enabled_field_names)); Chris@18: foreach ($input as $field_name => $field_value) { Chris@18: $public_field_name = $resource_type->getPublicName($field_name); Chris@18: $output[$public_field_name] = $field_value; Chris@18: } Chris@18: return $output; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Determines the entity type's (internal) label field name. Chris@18: * Chris@18: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@18: * The entity from which fields should be extracted. Chris@18: * Chris@18: * @return string Chris@18: * The label field name. Chris@18: */ Chris@18: protected static function getLabelFieldName(EntityInterface $entity) { Chris@18: $label_field_name = $entity->getEntityType()->getKey('label'); Chris@18: // @todo Remove this work-around after https://www.drupal.org/project/drupal/issues/2450793 lands. Chris@18: if ($entity->getEntityTypeId() === 'user') { Chris@18: $label_field_name = 'name'; Chris@18: } Chris@18: return $label_field_name; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Extracts a config entity's fields. Chris@18: * Chris@18: * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type Chris@18: * The JSON:API resource type of the given entity. Chris@18: * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity Chris@18: * The config entity from which fields should be extracted. Chris@18: * Chris@18: * @return array Chris@18: * The fields extracted from a config entity. Chris@18: */ Chris@18: protected static function extractConfigEntityFields(ResourceType $resource_type, ConfigEntityInterface $entity) { Chris@18: $enabled_public_fields = []; Chris@18: $fields = $entity->toArray(); Chris@18: // Filter the array based on the field names. Chris@18: $enabled_field_names = array_filter(array_keys($fields), function ($internal_field_name) use ($resource_type) { Chris@18: // Config entities have "fields" which aren't known to the resource type, Chris@18: // these fields should not be excluded because they cannot be enabled or Chris@18: // disabled. Chris@18: return !$resource_type->hasField($internal_field_name) || $resource_type->isFieldEnabled($internal_field_name); Chris@18: }); Chris@18: // Return a sub-array of $output containing the keys in $enabled_fields. Chris@18: $input = array_intersect_key($fields, array_flip($enabled_field_names)); Chris@18: /* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ Chris@18: foreach ($input as $field_name => $field_value) { Chris@18: $public_field_name = $resource_type->getPublicName($field_name); Chris@18: $enabled_public_fields[$public_field_name] = $field_value; Chris@18: } Chris@18: return $enabled_public_fields; Chris@18: } Chris@18: Chris@18: }