Passed
Pull Request — master (#42)
by
unknown
09:29
created

ApcuCache::setAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 9
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
    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
        if (! (function_exists('apcu_enabled') && apcu_enabled())) {
49
            throw new CacheException('ACPu extension must be installed and enabled');
50
        }
51
52
        $this->parser = $parser;
53
        $this->locale = $locale;
54
        $this->domain = $domain;
55
        $this->ttl = $ttl;
56
        $this->reloadOnMiss = $reloadOnMiss;
57
        $this->prefix = $prefix;
58
59
        $this->ensureTranslationsLoaded();
60
    }
61
62
    public function get(string $msgid): string
63
    {
64
        $msgstr = apcu_fetch($this->getKey($msgid), $success);
65
        if ($success && is_string($msgstr)) {
66
            return $msgstr;
67
        }
68
69
        if (! $this->reloadOnMiss) {
70
            return $msgid;
71
        }
72
73
        // store original in case translation is not present
74
        apcu_store($msgid, $msgid);
75
        // reload .mo file, in case entry has been evicted
76
        $this->parser->parseIntoCache($this);
77
78
        $msgstr = apcu_fetch($this->getKey($msgid), $success);
79
80
        return $success && is_string($msgstr) ? $msgstr : $msgid;
81
    }
82
83
    public function set(string $msgid, string $msgstr): void
84
    {
85
        apcu_store($this->getKey($msgid), $msgstr, $this->ttl);
86
    }
87
88
    public function has(string $msgid): bool
89
    {
90
        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...
91
    }
92
93
    public function setAll(array $translations): void
94
    {
95
        $keys = array_map(function (string $msgid): string {
96
            return $this->getKey($msgid);
97
        }, array_keys($translations));
98
        $translations = array_combine($keys, $translations);
99
        assert(is_array($translations));
100
101
        apcu_store($translations, null, $this->ttl);
102
    }
103
104
    private function getKey(string $msgid): string
105
    {
106
        return $this->prefix . $this->locale . '.' . $this->domain . '.' . $msgid;
107
    }
108
109
    private function ensureTranslationsLoaded(): void
110
    {
111
        // Try to prevent cache slam if multiple processes are trying to load translations. There is still a race
112
        // between the exists check and creating the entry, but at least it's small
113
        $key = $this->getKey(self::LOADED_KEY);
114
        $loaded = apcu_exists($key) || apcu_entry($key, static function (): int {
115
            return 0;
116
        });
117
        if ($loaded) {
118
            return;
119
        }
120
121
        $this->parser->parseIntoCache($this);
122
        apcu_store($this->getKey(self::LOADED_KEY), 1, $this->ttl);
123
    }
124
}
125