Passed
Push — master ( fce2ec...34bcb7 )
by William
09:40 queued 07:12
created

ApcuCache::getKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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
    /** @var MoParser */
28
    private $parser;
29
    /** @var string */
30
    private $locale;
31
    /** @var string */
32
    private $domain;
33
    /** @var int */
34
    private $ttl;
35
    /** @var bool */
36
    private $reloadOnMiss;
37
    /** @var string */
38
    private $prefix;
39
40 119
    public function __construct(
41
        MoParser $parser,
42
        string $locale,
43
        string $domain,
44
        int $ttl = 0,
45
        bool $reloadOnMiss = true,
46
        string $prefix = 'mo_'
47
    ) {
48 119
        if (! (function_exists('apcu_enabled') && apcu_enabled())) {
49 7
            throw new CacheException('ACPu extension must be installed and enabled');
50
        }
51
52 112
        $this->parser = $parser;
53 112
        $this->locale = $locale;
54 112
        $this->domain = $domain;
55 112
        $this->ttl = $ttl;
56 112
        $this->reloadOnMiss = $reloadOnMiss;
57 112
        $this->prefix = $prefix;
58
59 112
        $this->ensureTranslationsLoaded();
60 32
    }
61
62 56
    public function get(string $msgid): string
63
    {
64 56
        $msgstr = apcu_fetch($this->getKey($msgid), $success);
65 56
        if ($success && is_string($msgstr)) {
66 35
            return $msgstr;
67
        }
68
69 21
        if (! $this->reloadOnMiss) {
70 7
            return $msgid;
71
        }
72
73 14
        return $this->reloadOnMiss($msgid);
74
    }
75
76 21
    private function reloadOnMiss(string $msgid): string
77
    {
78
        // store original if translation is not present
79 15
        $cached = apcu_entry($this->getKey($msgid), static function () use ($msgid) {
80 14
            return $msgid;
81 21
        }, $this->ttl);
82
        // if another process has updated cache, return early
83 21
        if ($cached !== $msgid && is_string($cached)) {
84 7
            return $cached;
85
        }
86
87
        // reload .mo file, in case entry has been evicted
88 14
        $this->parser->parseIntoCache($this);
89
90 14
        $msgstr = apcu_fetch($this->getKey($msgid), $success);
91
92 14
        return $success && is_string($msgstr) ? $msgstr : $msgid;
93
    }
94
95 70
    public function set(string $msgid, string $msgstr): void
96
    {
97 70
        apcu_store($this->getKey($msgid), $msgstr, $this->ttl);
98 20
    }
99
100 14
    public function has(string $msgid): bool
101
    {
102 14
        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...
103
    }
104
105 7
    public function setAll(array $translations): void
106
    {
107 5
        $keys = array_map(function (string $msgid): string {
108 7
            return $this->getKey($msgid);
109 7
        }, array_keys($translations));
110 7
        $translations = array_combine($keys, $translations);
111
        assert(is_array($translations));
112
113 7
        apcu_store($translations, null, $this->ttl);
114 2
    }
115
116 112
    private function getKey(string $msgid): string
117
    {
118 112
        return $this->prefix . $this->locale . '.' . $this->domain . '.' . $msgid;
119
    }
120
121 112
    private function ensureTranslationsLoaded(): void
122
    {
123
        // Try to prevent cache slam if multiple processes are trying to load translations. There is still a race
124
        // between the exists check and creating the entry, but at least it's small
125 112
        $key = $this->getKey(self::LOADED_KEY);
126 80
        $loaded = apcu_exists($key) || apcu_entry($key, static function (): int {
127 105
            return 0;
128 109
        }, $this->ttl);
129 112
        if ($loaded) {
130 7
            return;
131
        }
132
133 105
        $this->parser->parseIntoCache($this);
134 105
        apcu_store($this->getKey(self::LOADED_KEY), 1, $this->ttl);
135 30
    }
136
}
137