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 2.0.0-rc1
1 merge request!3Issue #3319250: V2: Drupal 10, locking down API, etc
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
{
"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
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'],
],
];
}
}
<?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;
}
}
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
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
<?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],
];
}
......
<?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');
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment