jsonapi.services.yml | 6 ++ .../ResourceTypeChangeSubscriber.php | 85 ++++++++++++++++++++++ tests/src/Functional/NodeTest.php | 1 + tests/src/Functional/ResourceTestBase.php | 1 + 4 files changed, 93 insertions(+) diff --git a/jsonapi.services.yml b/jsonapi.services.yml index f059898..e18fddb 100644 --- a/jsonapi.services.yml +++ b/jsonapi.services.yml @@ -161,6 +161,12 @@ services: class: Drupal\jsonapi\EventSubscriber\JsonApiRequestValidator tags: - { name: event_subscriber } + jsonapi.resource_type_change_subscriber: + class: Drupal\jsonapi\EventSubscriber\ResourceTypeChangeSubscriber + arguments: ['@router.builder'] + tags: + - { name: event_subscriber } + - { name: cache_tags_invalidator} jsonapi.resource_response.subscriber: class: Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber arguments: ['@jsonapi.serializer_do_not_use_removal_imminent'] diff --git a/src/EventSubscriber/ResourceTypeChangeSubscriber.php b/src/EventSubscriber/ResourceTypeChangeSubscriber.php new file mode 100644 index 0000000..1c7877f --- /dev/null +++ b/src/EventSubscriber/ResourceTypeChangeSubscriber.php @@ -0,0 +1,85 @@ +routeBuilder = $route_builder; + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) { + if (in_array('entity_field_info', $tags, TRUE)) { + // Clear static caches in the resource type repository, ensuring that + // anything else for the remainder of the request is operating on + // up-to-date information. + \Drupal::getContainer()->get('jsonapi.resource_type.repository')->clearCachedDefinitions(); + + // Rebuild routes. + // @see \Drupal\jsonapi\Routing\Routes + $this->routeBuilder->setRebuildNeeded(); + } + } + + /** + * Ensures Drupal data model changes invalidate all JSON API responses. + * + * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event + * The response event. + */ + public function onResponse(FilterResponseEvent $event) { + if ($event->getRequest()->getRequestFormat() !== 'api_json') { + return; + } + + $response = $event->getResponse(); + if (!$response instanceof CacheableResponseInterface) { + return; + } + + $response->getCacheableMetadata() + ->addCacheTags([ + // @see \Drupal\Core\Entity\EntityFieldManager::clearCachedFieldDefinitions() + 'entity_field_info', + ]); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + // Run before \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber::onResponse() + // (priority 128), so we can add JSON API's config cache tag. + $events[KernelEvents::RESPONSE][] = ['onResponse', 150]; + return $events; + } + +} diff --git a/tests/src/Functional/NodeTest.php b/tests/src/Functional/NodeTest.php index 5e20312..18fe783 100644 --- a/tests/src/Functional/NodeTest.php +++ b/tests/src/Functional/NodeTest.php @@ -293,6 +293,7 @@ class NodeTest extends ResourceTestBase { $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [static::$entityTypeId => $this->entity->uuid()]); /* $url = $this->entity->toUrl('jsonapi'); */ $request_options = $this->getAuthenticationRequestOptions(); + $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; // 403 when accessing own unpublished node. $response = $this->request('GET', $url, $request_options); diff --git a/tests/src/Functional/ResourceTestBase.php b/tests/src/Functional/ResourceTestBase.php index 715a64d..07541f9 100644 --- a/tests/src/Functional/ResourceTestBase.php +++ b/tests/src/Functional/ResourceTestBase.php @@ -433,6 +433,7 @@ abstract class ResourceTestBase extends BrowserTestBase { protected function getExpectedCacheTags(array $sparse_fieldset = NULL) { $expected_cache_tags = [ 'http_response', + 'entity_field_info', ]; return Cache::mergeTags($expected_cache_tags, $this->entity->getCacheTags()); }