ApcuCache   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 93
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 19
eloc 35
c 2
b 1
f 0
dl 0
loc 93
ccs 39
cts 39
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setAll() 0 9 1
A reloadOnMiss() 0 17 5
A set() 0 3 1
A get() 0 12 4
A ensureTranslationsLoaded() 0 14 3
A getKey() 0 3 1
A __construct() 0 13 3
A has() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\MoTranslator\Cache;
6
7
use PhpMyAdmin\MoTranslator\CacheException;
8
use PhpMyAdmin\MoTranslator\MoParser;
9
10
use function apcu_enabled;
11
use function apcu_entry;
12
use function apcu_exists;
13
use function apcu_fetch;
14
use function apcu_store;
15
use function array_combine;
16
use function array_keys;
17
use function array_map;
18
use function assert;
19
use function function_exists;
20
use function is_array;
21
use function is_string;
22
23
final class ApcuCache implements CacheInterface
24
{
25
    public const LOADED_KEY = '__TRANSLATIONS_LOADED__';
26
27
    public function __construct(
28
        private MoParser $parser,
29
        private string $locale,
30
        private string $domain,
31
        private int $ttl = 0,
32
        private bool $reloadOnMiss = true,
33
        private string $prefix = 'mo_',
34
    ) {
35
        if (! (function_exists('apcu_enabled') && apcu_enabled())) {
36
            throw new CacheException('ACPu extension must be installed and enabled');
37
        }
38
39
        $this->ensureTranslationsLoaded();
40 119
    }
41
42
    public function get(string $msgid): string
43
    {
44
        $msgstr = apcu_fetch($this->getKey($msgid), $success);
45
        if ($success && is_string($msgstr)) {
46
            return $msgstr;
47
        }
48 119
49 7
        if (! $this->reloadOnMiss) {
50
            return $msgid;
51
        }
52 112
53 112
        return $this->reloadOnMiss($msgid);
54 112
    }
55 112
56 112
    private function reloadOnMiss(string $msgid): string
57 112
    {
58
        // store original if translation is not present
59 112
        $cached = apcu_entry($this->getKey($msgid), static function () use ($msgid) {
60 16
            return $msgid;
61
        }, $this->ttl);
62 56
        // if another process has updated cache, return early
63
        if ($cached !== $msgid && is_string($cached)) {
64 56
            return $cached;
65 56
        }
66 35
67
        // reload .mo file, in case entry has been evicted
68
        $this->parser->parseIntoCache($this);
69 21
70 7
        $msgstr = apcu_fetch($this->getKey($msgid), $success);
71
72
        return $success && is_string($msgstr) ? $msgstr : $msgid;
73 14
    }
74
75
    public function set(string $msgid, string $msgstr): void
76 21
    {
77
        apcu_store($this->getKey($msgid), $msgstr, $this->ttl);
78
    }
79 18
80 14
    public function has(string $msgid): bool
81 21
    {
82
        return apcu_exists($this->getKey($msgid));
0 ignored issues
show
Bug Best Practice introduced by
The expression return apcu_exists($this->getKey($msgid)) could return the type string[] which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
83 21
    }
84 7
85
    /** @inheritDoc */
86
    public function setAll(array $translations): void
87
    {
88 14
        $keys = array_map(function (string $msgid): string {
89
            return $this->getKey($msgid);
90 14
        }, array_keys($translations));
91
        $translations = array_combine($keys, $translations);
92 14
        assert(is_array($translations));
93
94
        apcu_store($translations, null, $this->ttl);
95 70
    }
96
97 70
    private function getKey(string $msgid): string
98 10
    {
99
        return $this->prefix . $this->locale . '.' . $this->domain . '.' . $msgid;
100 14
    }
101
102 14
    private function ensureTranslationsLoaded(): void
103
    {
104
        // Try to prevent cache slam if multiple processes are trying to load translations. There is still a race
105 7
        // between the exists check and creating the entry, but at least it's small
106
        $key = $this->getKey(self::LOADED_KEY);
107 6
        $loaded = apcu_exists($key) || apcu_entry($key, static function (): int {
108 7
            return 0;
109 7
        }, $this->ttl);
110 7
        if ($loaded) {
111
            return;
112
        }
113 7
114 1
        $this->parser->parseIntoCache($this);
115
        apcu_store($this->getKey(self::LOADED_KEY), 1, $this->ttl);
116 112
    }
117
}
118