final class ResourceVersionRouteEnhancer in Drupal 9
Same name and namespace in other branches
- 8 core/modules/jsonapi/src/Revisions/ResourceVersionRouteEnhancer.php \Drupal\jsonapi\Revisions\ResourceVersionRouteEnhancer
- 10 core/modules/jsonapi/src/Revisions/ResourceVersionRouteEnhancer.php \Drupal\jsonapi\Revisions\ResourceVersionRouteEnhancer
Loads an appropriate revision for the requested resource version.
@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.
Hierarchy
- class \Drupal\jsonapi\Revisions\ResourceVersionRouteEnhancer implements EnhancerInterface
Expanded class hierarchy of ResourceVersionRouteEnhancer
See also
https://www.drupal.org/project/drupal/issues/3032787
1 file declares its use of ResourceVersionRouteEnhancer
- EntityResource.php in core/
modules/ jsonapi/ src/ Controller/ EntityResource.php
1 string reference to 'ResourceVersionRouteEnhancer'
- jsonapi.services.yml in core/
modules/ jsonapi/ jsonapi.services.yml - core/modules/jsonapi/jsonapi.services.yml
1 service uses ResourceVersionRouteEnhancer
File
- core/
modules/ jsonapi/ src/ Revisions/ ResourceVersionRouteEnhancer.php, line 24
Namespace
Drupal\jsonapi\RevisionsView source
final class ResourceVersionRouteEnhancer implements EnhancerInterface {
/**
* The route default parameter name.
*
* @var string
*/
const REVISION_ID_KEY = 'revision_id';
/**
* The query parameter for providing a version (revision) value.
*
* @var string
*/
const RESOURCE_VERSION_QUERY_PARAMETER = 'resourceVersion';
/**
* A route parameter key which indicates that working copies were requested.
*
* @var string
*/
const WORKING_COPIES_REQUESTED = 'working_copies_requested';
/**
* The cache context by which vary the loaded entity revision.
*
* @var string
*
* @todo When D8 requires PHP >=5.6, convert to expression using the RESOURCE_VERSION_QUERY_PARAMETER constant.
*/
const CACHE_CONTEXT = 'url.query_args:resourceVersion';
/**
* Resource version validation regex.
*
* @var string
*
* @todo When D8 requires PHP >=5.6, convert to expression using the VersionNegotiator::SEPARATOR constant.
*/
const VERSION_IDENTIFIER_VALIDATOR = '/^[a-z]+[a-z_]*[a-z]+:[a-zA-Z0-9\\-]+(:[a-zA-Z0-9\\-]+)*$/';
/**
* The revision ID negotiator.
*
* @var \Drupal\jsonapi\Revisions\VersionNegotiator
*/
protected $versionNegotiator;
/**
* ResourceVersionRouteEnhancer constructor.
*
* @param \Drupal\jsonapi\Revisions\VersionNegotiator $version_negotiator_manager
* The version negotiator.
*/
public function __construct(VersionNegotiator $version_negotiator_manager) {
$this->versionNegotiator = $version_negotiator_manager;
}
/**
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request) {
if (!Routes::isJsonApiRequest($defaults) || !($resource_type = Routes::getResourceTypeNameFromParameters($defaults))) {
return $defaults;
}
$has_version_param = $request->query
->has(static::RESOURCE_VERSION_QUERY_PARAMETER);
// If the resource type is not versionable, then nothing needs to be
// enhanced.
if (!$resource_type
->isVersionable()) {
// If the query parameter was provided but the resource type is not
// versionable, provide a helpful error.
if ($has_version_param) {
// Until Drupal core has a generic revision access API, it is only safe
// to support the `node` and `media` entity types because they are the
// only // entity types that have revision access checks for forward
// revisions that are not the default and not the latest revision.
$cacheability = (new CacheableMetadata())
->addCacheContexts([
'url.path',
static::CACHE_CONTEXT,
]);
/* Uncomment the next line and remove the following one when https://www.drupal.org/project/drupal/issues/3002352 lands in core. */
/* throw new CacheableHttpException($cacheability, 501, 'Resource versioning is not yet supported for this resource type.'); */
$message = 'JSON:API does not yet support resource versioning for this resource type.';
$message .= ' For context, see https://www.drupal.org/project/drupal/issues/2992833#comment-12818258.';
$message .= ' To contribute, see https://www.drupal.org/project/drupal/issues/2350939 and https://www.drupal.org/project/drupal/issues/2809177.';
throw new CacheableHttpException($cacheability, 501, $message);
}
return $defaults;
}
// Since the resource type is versionable, responses must always vary by the
// requested version, without regard for whether a version query parameter
// was provided or not.
if (isset($defaults['entity'])) {
assert($defaults['entity'] instanceof EntityInterface);
$defaults['entity']
->addCacheContexts([
static::CACHE_CONTEXT,
]);
}
// If no version was specified, nothing is left to enhance.
if (!$has_version_param) {
return $defaults;
}
// Provide a helpful error when a version is specified with an unsafe
// method.
if (!$request
->isMethodCacheable()) {
throw new BadRequestHttpException(sprintf('%s requests with a `%s` query parameter are not supported.', $request
->getMethod(), static::RESOURCE_VERSION_QUERY_PARAMETER));
}
$resource_version_identifier = $request->query
->get(static::RESOURCE_VERSION_QUERY_PARAMETER);
if (!static::isValidVersionIdentifier($resource_version_identifier)) {
$cacheability = (new CacheableMetadata())
->addCacheContexts([
static::CACHE_CONTEXT,
]);
$message = sprintf('A resource version identifier was provided in an invalid format: `%s`', $resource_version_identifier);
throw new CacheableBadRequestHttpException($cacheability, $message);
}
// Determine if the request is for a collection resource.
if ($defaults[RouteObjectInterface::CONTROLLER_NAME] === Routes::CONTROLLER_SERVICE_NAME . ':getCollection') {
$latest_version_identifier = 'rel' . VersionNegotiator::SEPARATOR . 'latest-version';
$working_copy_identifier = 'rel' . VersionNegotiator::SEPARATOR . 'working-copy';
// Until Drupal core has a revision access API that works on entity
// queries, filtering is not permitted on non-default revisions.
if ($request->query
->has('filter') && $resource_version_identifier !== $latest_version_identifier) {
$cache_contexts = [
'url.path',
static::CACHE_CONTEXT,
'url.query_args:filter',
];
$cacheability = (new CacheableMetadata())
->addCacheContexts($cache_contexts);
$message = 'JSON:API does not support filtering on revisions other than the latest version because a secure Drupal core API does not yet exist to do so.';
throw new CacheableHttpException($cacheability, 501, $message);
}
// 'latest-version' and 'working-copy' are the only acceptable version
// identifiers for a collection resource.
if (!in_array($resource_version_identifier, [
$latest_version_identifier,
$working_copy_identifier,
])) {
$cacheability = (new CacheableMetadata())
->addCacheContexts([
'url.path',
static::CACHE_CONTEXT,
]);
$message = sprintf('Collection resources only support the following resource version identifiers: %s', implode(', ', [
$latest_version_identifier,
$working_copy_identifier,
]));
throw new CacheableBadRequestHttpException($cacheability, $message);
}
// Whether the collection to be loaded should include only working copies.
$defaults[static::WORKING_COPIES_REQUESTED] = $resource_version_identifier === $working_copy_identifier;
return $defaults;
}
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $defaults['entity'];
/** @var \Drupal\jsonapi\Revisions\VersionNegotiatorInterface $negotiator */
$resolved_revision = $this->versionNegotiator
->getRevision($entity, $resource_version_identifier);
// Ensure none of the original entity cacheability is lost, especially the
// query argument's cache context.
$resolved_revision
->addCacheableDependency($entity);
return [
'entity' => $resolved_revision,
] + $defaults;
}
/**
* Validates the user input.
*
* @param string $resource_version
* The requested resource version identifier.
*
* @return bool
* TRUE if the received resource version value is valid, FALSE otherwise.
*/
protected static function isValidVersionIdentifier($resource_version) {
return preg_match(static::VERSION_IDENTIFIER_VALIDATOR, $resource_version) === 1;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ResourceVersionRouteEnhancer:: |
protected | property | The revision ID negotiator. | |
ResourceVersionRouteEnhancer:: |
constant | The cache context by which vary the loaded entity revision. | ||
ResourceVersionRouteEnhancer:: |
public | function |
Updates the defaults for a route definition based on the request. Overrides EnhancerInterface:: |
|
ResourceVersionRouteEnhancer:: |
protected static | function | Validates the user input. | |
ResourceVersionRouteEnhancer:: |
constant | The query parameter for providing a version (revision) value. | ||
ResourceVersionRouteEnhancer:: |
constant | The route default parameter name. | ||
ResourceVersionRouteEnhancer:: |
constant | Resource version validation regex. | ||
ResourceVersionRouteEnhancer:: |
constant | A route parameter key which indicates that working copies were requested. | ||
ResourceVersionRouteEnhancer:: |
public | function | ResourceVersionRouteEnhancer constructor. |