Lines 89.74% 35 / 39
Methods 60.00% 3 / 5
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 5 / 5 100.00% 1 / 1 1
 createIfEnabled 100.00% 5 / 5 100.00% 1 / 1 3
 read 86.66% 13 / 15 0.00% 0 / 1 8.15
 warm 84.61% 11 / 13 0.00% 0 / 1 5.09
 clear 100.00% 1 / 1 100.00% 1 / 1 1
29final class FilesystemConfigurationCache
30{
31    private const string CACHE_KEY = 'configuration-rows';
32
33    private const int DEFAULT_TTL = 300;
34
35    private readonly FilesystemAdapter $adapter;
36
37    /**
38     * @param string $identity Distinguishes tenants sharing one cache directory
39     *                         (multisite config dir, table prefix, table name).
40     */
41    public function __construct(string $cacheDir, string $identity, int $ttl = self::DEFAULT_TTL)
42    {
43        $this->adapter = new FilesystemAdapter(
44            namespace: 'pmf-config-' . substr(md5($identity), offset: 0, length: 12),
45            defaultLifetime: $ttl,
46            directory: $cacheDir,
47        );
48    }
49
50    /**
51     * Returns null (disabled) in debug mode or when CONFIG_CACHE_ENABLED is falsy.
52     */
53    public static function createIfEnabled(bool $debug, mixed $enabled, string $cacheDir, string $identity): ?self
54    {
55        if ($debug) {
56            return null;
57        }
58
59        if (!filter_var($enabled ?? 'true', FILTER_VALIDATE_BOOLEAN)) {
60            return null;
61        }
62
63        return new self($cacheDir, $identity);
64    }
65
66    /**
67     * Returns the cached configuration rows, or null on a cache miss.
68     *
69     * @return array<int, \stdClass>|null
70     */
71    public function read(): ?array
72    {
73        $item = $this->adapter->getItem(self::CACHE_KEY);
74        if (!$item->isHit()) {
75            return null;
76        }
77
78        /** @var mixed $payload */
79        $payload = $item->get();
80        if (!is_array($payload) || $payload === []) {
81            return null;
82        }
83
84        $rows = [];
85        /** @var mixed $entry */
86        foreach ($payload as $entry) {
87            if (!is_array($entry) || !array_key_exists('config_name', $entry) || $entry['config_name'] === null) {
88                return null;
89            }
90
91            $rows[] = (object) [
92                'config_name' => (string) $entry['config_name'],
93                'config_value' => (string) ($entry['config_value'] ?? ''),
94            ];
95        }
96
97        return $rows;
98    }
99
100    /**
101     * @param array<int, \stdClass> $rows
102     */
103    public function warm(array $rows): void
104    {
105        $payload = [];
106        foreach ($rows as $row) {
107            if (!property_exists($row, 'config_name') || $row->config_name === null) {
108                continue;
109            }
110
111            $payload[] = [
112                'config_name' => (string) $row->config_name,
113                'config_value' => (string) ($row->config_value ?? ''),
114            ];
115        }
116
117        if ($payload === []) {
118            return;
119        }
120
121        $item = $this->adapter->getItem(self::CACHE_KEY);
122        $item->set($payload);
123        $this->adapter->save($item);
124    }
125
126    public function clear(): void
127    {
128        $this->adapter->deleteItem(self::CACHE_KEY);
129    }
130}