Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
use Frosh\Tools\Components\Health\HealthCollection;
use Frosh\Tools\Components\Health\SettingsResult;

/**
* Recommends RedisTagAware for the HTTP cache pool only.
*
* Shopware's HTTP cache relies on tag-based invalidation. The plain Redis adapter
* stores tags inefficiently; RedisTagAwareAdapter is the recommended backend.
*
* Other pools (e.g. cache.object) may intentionally use plain Redis — this checker
* must not warn about those. See https://github.com/FriendsOfShopware/FroshTools/issues/245
*/
class RedisTagAwareChecker implements PerformanceCheckerInterface, CheckerInterface
{
public function __construct(
Expand All @@ -19,8 +28,14 @@ public function __construct(

public function collect(HealthCollection $collection): void
{
// HTTP cache pool only — not cache.object / cache.app / system.
if (!$this->cacheRegistry->has('cache.http')) {
return;
}

$httpCacheType = $this->cacheRegistry->get('cache.http')->getType();

// Already TagAware, or not Redis at all (filesystem, array, …).
if (!\str_starts_with($httpCacheType, CacheAdapter::TYPE_REDIS)
|| \str_starts_with($httpCacheType, CacheAdapter::TYPE_REDIS_TAG_AWARE)) {
return;
Expand All @@ -29,8 +44,8 @@ public function collect(HealthCollection $collection): void
$collection->add(
SettingsResult::warning(
'redis-tag-aware',
'Redis adapter should be TagAware',
CacheAdapter::TYPE_REDIS,
'HTTP cache (cache.http) Redis adapter should be TagAware',
$httpCacheType,
CacheAdapter::TYPE_REDIS_TAG_AWARE,
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,9 @@ export default {
},
'redis-tag-aware': {
description:
'The HTTP cache uses the plain Redis adapter, which stores cache tags inefficiently. The TagAware Redis adapter is optimised for tag-based invalidation as used by Shopware.',
'This check looks at the HTTP cache pool (cache.http) only — not cache.object or other pools. The plain Redis adapter stores cache tags inefficiently; RedisTagAware is optimised for Shopware’s tag-based HTTP cache invalidation. Using plain Redis for cache.object on purpose is fine and will not trigger this warning.',
solution:
'Switch the cache adapter to the TagAware Redis adapter ("redis++").',
'Configure the HTTP cache pool (or the default app adapter that cache.http inherits) to use the TagAware Redis adapter.',
code: "# config/packages/cache.yaml\nframework:\n cache:\n app: cache.adapter.redis_tag_aware\n default_redis_provider: 'redis://localhost'\n # optional: keep object cache on plain Redis if you prefer\n pools:\n cache.object:\n adapter: cache.adapter.redis",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Frosh\Tools\Tests\Components\Health\Checker\PerformanceChecker;

use Frosh\Tools\Components\CacheAdapter;
use Frosh\Tools\Components\CacheRegistry;
use Frosh\Tools\Components\Health\Checker\PerformanceChecker\RedisTagAwareChecker;
use Frosh\Tools\Components\Health\HealthCollection;
use Frosh\Tools\Components\Health\SettingsResult;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(RedisTagAwareChecker::class)]
class RedisTagAwareCheckerTest extends TestCase
{
public function testDoesNothingWhenHttpPoolMissing(): void
{
$registry = $this->createMock(CacheRegistry::class);
$registry->method('has')->with('cache.http')->willReturn(false);
$registry->expects(static::never())->method('get');

$collection = new HealthCollection();
(new RedisTagAwareChecker($registry))->collect($collection);

static::assertCount(0, $collection);
}

public function testDoesNothingWhenHttpUsesTagAwareRedis(): void
{
$collection = $this->collectWithHttpType(CacheAdapter::TYPE_REDIS_TAG_AWARE . ' 7.2.0');

static::assertCount(0, $collection);
}

public function testDoesNothingWhenHttpIsNotRedis(): void
{
$collection = $this->collectWithHttpType(CacheAdapter::TYPE_FILESYSTEM);

static::assertCount(0, $collection);
}

public function testDoesNothingWhenObjectWouldBePlainRedisButHttpIsTagAware(): void
{
// Regression for #245: plain Redis on cache.object must not trigger this check.
// The checker only reads cache.http — TagAware HTTP + plain object is valid.
$http = $this->createMock(CacheAdapter::class);
$http->method('getType')->willReturn(CacheAdapter::TYPE_REDIS_TAG_AWARE . ' 7.2.0');

$registry = $this->createMock(CacheRegistry::class);
$registry->method('has')->with('cache.http')->willReturn(true);
$registry->method('get')->with('cache.http')->willReturn($http);

$collection = new HealthCollection();
(new RedisTagAwareChecker($registry))->collect($collection);

static::assertCount(0, $collection);
}

public function testWarnsWhenHttpUsesPlainRedis(): void
{
$collection = $this->collectWithHttpType(CacheAdapter::TYPE_REDIS . ' 7.2.0');

static::assertCount(1, $collection);
/** @var SettingsResult $result */
$result = $collection->first();
static::assertSame(SettingsResult::WARNING, $result->state);
static::assertSame('redis-tag-aware', $result->id);
static::assertStringContainsString('cache.http', $result->getVars()['snippet']);
static::assertStringContainsString('HTTP cache', $result->getVars()['snippet']);
static::assertStringStartsWith(CacheAdapter::TYPE_REDIS, $result->current);
static::assertSame(CacheAdapter::TYPE_REDIS_TAG_AWARE, $result->recommended);
}

private function collectWithHttpType(string $type): HealthCollection
{
$http = $this->createMock(CacheAdapter::class);
$http->method('getType')->willReturn($type);

$registry = $this->createMock(CacheRegistry::class);
$registry->method('has')->with('cache.http')->willReturn(true);
$registry->method('get')->with('cache.http')->willReturn($http);

$collection = new HealthCollection();
(new RedisTagAwareChecker($registry))->collect($collection);

return $collection;
}
}
Loading