Skip to content
Snippets Groups Projects
Commit f25f53ee authored by dpi's avatar dpi
Browse files

Issue #3319250 by dpi: V2: Drupal 10, locking down API, etc

parent b3be6252
Branches
Tags
1 merge request!3Issue #3319250: V2: Drupal 10, locking down API, etc
name: Cache Control Override name: Cache Control Override
type: module type: module
description: Override page Cache-Control header based on bubbled cacheability metadata. description: Override page Cache-Control header based on bubbled cacheability metadata.
core: 8.x core_version_requirement: '>=10'
core_version_requirement: ^8 || ^9 php: 8.1
package: Performance and scalability package: Performance and scalability
{
"name": "drupal/cache_control_override",
"description": "Override page Cache-Control header based on bubbled cacheability metadata.",
"type": "drupal-module",
"require": {
"php": ">=8.1",
"drupal/core": "^10"
}
}
<?php <?php
declare(strict_types=1);
namespace Drupal\cache_control_override\EventSubscriber; namespace Drupal\cache_control_override\EventSubscriber;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableResponseInterface; use Drupal\Core\Cache\CacheableResponseInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Drupal\Core\Cache\CacheBackendInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/** /**
* Cache Control Override. * Cache Control Override.
*
* @internal
* There is no extensibility promise for this class. To override this
* functionality, you may subscribe to events at a higher priority, then
* set $event->stopPropagation(). The service may be decorated. Or you may
* remove, or replace this class entirely in service registration by
* implementing a ServiceProvider.
*/ */
class CacheControlOverrideSubscriber implements EventSubscriberInterface { final class CacheControlOverrideSubscriber implements EventSubscriberInterface {
/** /**
* Overrides cache control header if any of override methods are enabled. * Overrides cache control header if any of override methods are enabled.
* *
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* The event to process. * The event to process.
*/ */
public function onRespond(FilterResponseEvent $event) { public function onRespond(ResponseEvent $event): void {
if (!$event->isMasterRequest()) { if (FALSE === $event->isMainRequest()) {
return; return;
} }
...@@ -34,25 +43,32 @@ class CacheControlOverrideSubscriber implements EventSubscriberInterface { ...@@ -34,25 +43,32 @@ class CacheControlOverrideSubscriber implements EventSubscriberInterface {
// If FinishResponseSubscriber didn't set the response as cacheable, then // If FinishResponseSubscriber didn't set the response as cacheable, then
// don't override anything. // don't override anything.
if (!$response->headers->hasCacheControlDirective('max-age') || !$response->headers->hasCacheControlDirective('public')) { if (FALSE === $response->headers->hasCacheControlDirective('max-age')) {
return;
}
if (FALSE === $response->headers->hasCacheControlDirective('public')) {
return; return;
} }
$max_age = $response->getCacheableMetadata()->getCacheMaxAge(); $maxAge = $response->getCacheableMetadata()->getCacheMaxAge();
// We treat permanent cache max-age as default therefore we don't override // We treat permanent cache max-age as default therefore we don't override
// the max-age. // the max-age.
if ($max_age != Cache::PERMANENT) { if ($maxAge !== CacheBackendInterface::CACHE_PERMANENT) {
$response->headers->set('Cache-Control', 'public, max-age=' . $max_age); $response->headers->set('Cache-Control', 'public, max-age=' . $maxAge);
} }
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public static function getSubscribedEvents() { public static function getSubscribedEvents(): array {
$events[KernelEvents::RESPONSE][] = ['onRespond']; return [
return $events; KernelEvents::RESPONSE => [
['onRespond'],
],
];
} }
} }
<?php <?php
declare(strict_types=1);
namespace Drupal\cache_control_override\PageCache; namespace Drupal\cache_control_override\PageCache;
use Drupal\Core\Cache\CacheableResponseInterface; use Drupal\Core\Cache\CacheableResponseInterface;
...@@ -9,13 +11,19 @@ use Symfony\Component\HttpFoundation\Response; ...@@ -9,13 +11,19 @@ use Symfony\Component\HttpFoundation\Response;
/** /**
* Cache policy for responses that have a bubbled max-age=0. * Cache policy for responses that have a bubbled max-age=0.
*
* @internal
* There is no extensibility promise for this class. To override this
* functionality, the service may be decorated. Or you may
* remove, or replace this class entirely in service registration by
* implementing a ServiceProvider.
*/ */
class DenyOnCacheControlOverride implements ResponsePolicyInterface { final class DenyOnCacheControlOverride implements ResponsePolicyInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function check(Response $response, Request $request) { public function check(Response $response, Request $request): ?string {
if (!$response instanceof CacheableResponseInterface) { if (!$response instanceof CacheableResponseInterface) {
return NULL; return NULL;
} }
...@@ -24,6 +32,8 @@ class DenyOnCacheControlOverride implements ResponsePolicyInterface { ...@@ -24,6 +32,8 @@ class DenyOnCacheControlOverride implements ResponsePolicyInterface {
// @TODO: This will affect users using Internal Page Cache as well, find a way to document that. // @TODO: This will affect users using Internal Page Cache as well, find a way to document that.
return static::DENY; return static::DENY;
} }
return NULL;
} }
} }
name: Cache Control Override test name: Cache Control Override test
type: module type: module
description: 'Provides functionality for testing Cache Control Override.' description: 'Provides functionality for testing Cache Control Override.'
hidden: true package: Testing
core: 8.x
dependencies: dependencies:
- cache_control_override - cache_control_override:cache_control_override
cache_control_override_test.permanent:
path: '/cco'
defaults:
_controller: '\Drupal\cache_control_override_test\Controller\CacheControl::maxAge'
requirements:
_access: 'TRUE'
cache_control_override_test.max_age: cache_control_override_test.max_age:
path: '/cco/{max_age}' path: '/cco/{max_age}'
defaults: defaults:
_controller: '\Drupal\cache_control_override_test\Controller\CacheControl::maxAge' _controller: '\Drupal\cache_control_override_test\Controller\CacheControl'
max_age: -1
requirements: requirements:
_access: 'TRUE' _access: 'TRUE'
max_age: \d+
options:
parameters:
max_age:
type: int
<?php <?php
declare(strict_types=1);
namespace Drupal\cache_control_override_test\Controller; namespace Drupal\cache_control_override_test\Controller;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\HttpFoundation\Response;
/** /**
* Controllers for testing the cache control override. * Test caching with a max age provided from the URL.
*/ */
class CacheControl { final class CacheControl {
/** /**
* Controller callback: Test content with a specified max age. * Test content with a specified max age.
* *
* @param int $max_age * @param string $max_age
* Max age value to be used in the response. * Max age value to be used in the response.
* *
* @return array * @return array|\Symfony\Component\HttpFoundation\Response
* Render array of page output. * The response.
*/ */
public function maxAge($max_age = Cache::PERMANENT) { public function __invoke(mixed $max_age = CacheBackendInterface::CACHE_PERMANENT): array|Response {
return [ return [
'#markup' => 'Max age test content', '#markup' => new TranslatableMarkup('Max age test content: @max_age', [
'@max_age' => $max_age,
]),
'#cache' => ['max-age' => $max_age], '#cache' => ['max-age' => $max_age],
]; ];
} }
......
<?php <?php
declare(strict_types=1);
namespace Drupal\Tests\cache_control_override\Functional; namespace Drupal\Tests\cache_control_override\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase; use Drupal\Tests\BrowserTestBase;
/** /**
...@@ -9,7 +12,12 @@ use Drupal\Tests\BrowserTestBase; ...@@ -9,7 +12,12 @@ use Drupal\Tests\BrowserTestBase;
* *
* @group cache_control_override * @group cache_control_override
*/ */
class CacheControlOverrideMaxAgeTest extends BrowserTestBase { final class CacheControlOverrideMaxAgeTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -22,27 +30,29 @@ class CacheControlOverrideMaxAgeTest extends BrowserTestBase { ...@@ -22,27 +30,29 @@ class CacheControlOverrideMaxAgeTest extends BrowserTestBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function setUp() { public function setUp(): void {
parent::setUp(); parent::setUp();
$this->config('system.performance')
$config = $this->config('system.performance'); ->set('cache.page.max_age', 3600)
$config->set('cache.page.max_age', 3600); ->save();
$config->save();
} }
/** /**
* Test the cache properties in response header data. * Test the cache properties in response header data.
*/ */
public function testMaxAge() { public function testMaxAge(): void {
$this->drupalGet(Url::fromRoute('cache_control_override_test.max_age'));
$this->drupalGet('cco');
$this->assertSession()->responseContains('Max age test content'); $this->assertSession()->responseContains('Max age test content');
$this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=3600, public'); $this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=3600, public');
$this->drupalGet('cco/333'); $this->drupalGet(Url::fromRoute('cache_control_override_test.max_age', route_parameters: [
'max_age' => '333',
]));
$this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=333, public'); $this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=333, public');
$this->drupalGet('cco/0'); $this->drupalGet(Url::fromRoute('cache_control_override_test.max_age', route_parameters: [
'max_age' => '0',
]));
$this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=0, public'); $this->assertSession()->responseHeaderContains('Cache-Control', 'max-age=0, public');
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment