diff --git a/cache_control_override.info.yml b/cache_control_override.info.yml index ed1f622a10fce365caa5a758cf2291ec21bc8d08..5014530f9791e2c1178f0fdc4aac473e89db4611 100644 --- a/cache_control_override.info.yml +++ b/cache_control_override.info.yml @@ -1,6 +1,6 @@ name: Cache Control Override type: module description: Override page Cache-Control header based on bubbled cacheability metadata. -core: 8.x -core_version_requirement: ^8 || ^9 +core_version_requirement: '>=10' +php: 8.1 package: Performance and scalability diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..3b2a7881e399cb1005e80b032143ee3864aead25 --- /dev/null +++ b/composer.json @@ -0,0 +1,9 @@ +{ + "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" + } +} diff --git a/src/EventSubscriber/CacheControlOverrideSubscriber.php b/src/EventSubscriber/CacheControlOverrideSubscriber.php index 6ce37d8f06d83dea87b0dee4ddc01d741d2b108f..3bd61fd98389c10cff3456a7f1b99ab9655ce4b5 100644 --- a/src/EventSubscriber/CacheControlOverrideSubscriber.php +++ b/src/EventSubscriber/CacheControlOverrideSubscriber.php @@ -1,26 +1,35 @@ <?php +declare(strict_types=1); + namespace Drupal\cache_control_override\EventSubscriber; -use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableResponseInterface; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; +use Drupal\Core\Cache\CacheBackendInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; /** * 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. * - * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event + * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event * The event to process. */ - public function onRespond(FilterResponseEvent $event) { - if (!$event->isMasterRequest()) { + public function onRespond(ResponseEvent $event): void { + if (FALSE === $event->isMainRequest()) { return; } @@ -34,25 +43,32 @@ class CacheControlOverrideSubscriber implements EventSubscriberInterface { // If FinishResponseSubscriber didn't set the response as cacheable, then // 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; } - $max_age = $response->getCacheableMetadata()->getCacheMaxAge(); + $maxAge = $response->getCacheableMetadata()->getCacheMaxAge(); // We treat permanent cache max-age as default therefore we don't override // the max-age. - if ($max_age != Cache::PERMANENT) { - $response->headers->set('Cache-Control', 'public, max-age=' . $max_age); + if ($maxAge !== CacheBackendInterface::CACHE_PERMANENT) { + $response->headers->set('Cache-Control', 'public, max-age=' . $maxAge); } } /** * {@inheritdoc} */ - public static function getSubscribedEvents() { - $events[KernelEvents::RESPONSE][] = ['onRespond']; - return $events; + public static function getSubscribedEvents(): array { + return [ + KernelEvents::RESPONSE => [ + ['onRespond'], + ], + ]; } } diff --git a/src/PageCache/DenyOnCacheControlOverride.php b/src/PageCache/DenyOnCacheControlOverride.php index 9f8f21291824f53dd0c1af159bd4cf3f5ae9accc..7f68fdfe948155180d85eeb68fe26fd37623c955 100644 --- a/src/PageCache/DenyOnCacheControlOverride.php +++ b/src/PageCache/DenyOnCacheControlOverride.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\cache_control_override\PageCache; use Drupal\Core\Cache\CacheableResponseInterface; @@ -9,13 +11,19 @@ use Symfony\Component\HttpFoundation\Response; /** * 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} */ - public function check(Response $response, Request $request) { + public function check(Response $response, Request $request): ?string { if (!$response instanceof CacheableResponseInterface) { return NULL; } @@ -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. return static::DENY; } + + return NULL; } } diff --git a/tests/modules/cache_control_override_test/cache_control_override_test.info.yml b/tests/modules/cache_control_override_test/cache_control_override_test.info.yml index ab13f5d738e84b698fede65543beaeea301e8aab..4fafa99adfa3d10f8205d8889db65e28d1280ddb 100644 --- a/tests/modules/cache_control_override_test/cache_control_override_test.info.yml +++ b/tests/modules/cache_control_override_test/cache_control_override_test.info.yml @@ -1,7 +1,6 @@ name: Cache Control Override test type: module description: 'Provides functionality for testing Cache Control Override.' -hidden: true -core: 8.x +package: Testing dependencies: - - cache_control_override + - cache_control_override:cache_control_override diff --git a/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml b/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml index 84596937af315632056384d467a14ec5c6811cde..e557c9442a147509c3c11b82bf3a26bdd133b3de 100644 --- a/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml +++ b/tests/modules/cache_control_override_test/cache_control_override_test.routing.yml @@ -1,13 +1,12 @@ -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: path: '/cco/{max_age}' defaults: - _controller: '\Drupal\cache_control_override_test\Controller\CacheControl::maxAge' + _controller: '\Drupal\cache_control_override_test\Controller\CacheControl' + max_age: -1 requirements: _access: 'TRUE' + max_age: \d+ + options: + parameters: + max_age: + type: int diff --git a/tests/modules/cache_control_override_test/src/Controller/CacheControl.php b/tests/modules/cache_control_override_test/src/Controller/CacheControl.php index 28525d86c6c4c71ebb53bf8f947cd496c1b3b1e8..f88307cddfa811e8cf564f16cb5de468ca9eb338 100644 --- a/tests/modules/cache_control_override_test/src/Controller/CacheControl.php +++ b/tests/modules/cache_control_override_test/src/Controller/CacheControl.php @@ -1,27 +1,32 @@ <?php +declare(strict_types=1); + 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. * - * @return array - * Render array of page output. + * @return array|\Symfony\Component\HttpFoundation\Response + * The response. */ - public function maxAge($max_age = Cache::PERMANENT) { - + public function __invoke(mixed $max_age = CacheBackendInterface::CACHE_PERMANENT): array|Response { return [ - '#markup' => 'Max age test content', + '#markup' => new TranslatableMarkup('Max age test content: @max_age', [ + '@max_age' => $max_age, + ]), '#cache' => ['max-age' => $max_age], ]; } diff --git a/tests/src/Functional/CacheControlOverrideMaxAgeTest.php b/tests/src/Functional/CacheControlOverrideMaxAgeTest.php index 30893660f9226177021cee71a34ee0cb03d0bd27..7263454cd59562c5f2b805327471a4b036945750 100644 --- a/tests/src/Functional/CacheControlOverrideMaxAgeTest.php +++ b/tests/src/Functional/CacheControlOverrideMaxAgeTest.php @@ -1,7 +1,10 @@ <?php +declare(strict_types=1); + namespace Drupal\Tests\cache_control_override\Functional; +use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; /** @@ -9,7 +12,12 @@ use Drupal\Tests\BrowserTestBase; * * @group cache_control_override */ -class CacheControlOverrideMaxAgeTest extends BrowserTestBase { +final class CacheControlOverrideMaxAgeTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; /** * {@inheritdoc} @@ -22,27 +30,29 @@ class CacheControlOverrideMaxAgeTest extends BrowserTestBase { /** * {@inheritdoc} */ - public function setUp() { + public function setUp(): void { parent::setUp(); - - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 3600); - $config->save(); + $this->config('system.performance') + ->set('cache.page.max_age', 3600) + ->save(); } /** * Test the cache properties in response header data. */ - public function testMaxAge() { - - $this->drupalGet('cco'); + public function testMaxAge(): void { + $this->drupalGet(Url::fromRoute('cache_control_override_test.max_age')); $this->assertSession()->responseContains('Max age test content'); $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->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'); }