LogDecorator::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 10
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 3
crap 2
1
<?php
2
3
namespace Vectorface\Cache;
4
5
use DateInterval;
6
use InvalidArgumentException;
7
use Psr\Log\LoggerInterface;
8
use Vectorface\Cache\Exception\CacheException;
9
10
/**
11
 * Decorates (Wraps) a Cache implementation with logging
12
 *
13
 * Note:
14
 *   This logs an estimated serialized object size. Cache serialization may
15
 *   use a different serialization mechanism, so the size should be used to
16
 *   give an idea of actual cached size rather than an exact value.
17
 */
18
class LogDecorator implements Cache, AtomicCounter
19
{
20
    /**
21
     * The wrapped cache class
22
     */
23
    private Cache|AtomicCounter $cache;
24
25
    /**
26
     * The logger instance to which operations will be logged
27
     */
28
    private LoggerInterface|null $log;
29
30
    /**
31
     * The log level, which corresponds to a PSR-3 log level function call
32
     */
33
    private string $level;
34
35
    /**
36
     * @param Cache|AtomicCounter $cache
37
     * @param LoggerInterface|null $log
38
     * @param string $level
39
     */
40
    public function __construct(Cache|AtomicCounter $cache, LoggerInterface|null $log = null, string $level = 'debug')
41
    {
42
        $levels = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug'];
43
        if (!in_array($level, $levels)) {
44 4
            throw new InvalidArgumentException("Incompatible log level: {$level}");
45
        }
46 4
47 4
        $this->cache = $cache;
48 1
        $this->log = $log;
49
        $this->level = $level;
50
    }
51 3
52 3
    /**
53 3
     * @inheritDoc
54 3
     * @throws CacheException
55
     */
56
    public function get(string $key, mixed $default = null) : mixed
57
    {
58
        $this->throwIfNotInstanceof(Cache::class);
59
60 2
        /** @scrutinizer ignore-call */
61
        $result = $this->cache->get($key);
62 2
        if ($result === null) {
63
            $this->log(sprintf("get %s MISS", $key));
64 2
            return $default;
65 2
        }
66 2
67 2
        $this->log(sprintf(
68
            "get %s HIT size=%d",
69
            $key,
70 1
            $this->getSize($result)
71 1
        ));
72 1
        return $result;
73 1
    }
74
75 1
    /**
76
     * @inheritDoc
77
     */
78
    public function set(string $key, mixed $value, DateInterval|int|null $ttl = null) : bool
79
    {
80
        $this->throwIfNotInstanceof(Cache::class);
81 3
82
        /** @scrutinizer ignore-call */
83 3
        $result = $this->cache->set($key, $value, $ttl);
84
        $this->log(sprintf(
85 3
            "set %s %s ttl=%s, type=%s, size=%d",
86 3
            $key,
87 3
            $result ? 'SUCCESS' : 'FAILURE',
88 3
            is_numeric($ttl) ? $ttl : "false",
89 3
            gettype($value),
90 3
            $this->getSize($value)
91 3
        ));
92 3
        return $result;
93
    }
94 3
95
    /**
96
     * @inheritDoc
97
     * @throws CacheException
98
     */
99
    public function delete(string $key) : bool
100
    {
101 2
        $this->throwIfNotInstanceof(Cache::class);
102
103 2
        /** @scrutinizer ignore-call */
104
        $result = $this->cache->delete($key);
105 2
        $this->log(sprintf(
106 2
            "delete %s %s",
107 2
            $key,
108 2
            $result ? 'SUCCESS' : 'FAILURE'
109 2
        ));
110
        return $result;
111 2
    }
112
113
    /**
114
     * @inheritDoc
115
     * @throws CacheException
116
     */
117
    public function flush() : bool
118 2
    {
119
        $this->throwIfNotInstanceof(Cache::class);
120 2
121
        /** @scrutinizer ignore-call */
122 2
        $result = $this->cache->flush();
123 2
        $this->log(sprintf("flush %s", $result ? 'SUCCESS' : 'FAILURE'));
124 2
        return $result;
125
    }
126
127
    /**
128
     * @inheritDoc
129
     * @throws CacheException
130
     */
131 2
    public function clean() : bool
132
    {
133 2
        $this->throwIfNotInstanceof(Cache::class);
134
135 2
        /** @scrutinizer ignore-call */
136 2
        $result = $this->cache->clean();
137 2
        $this->log(sprintf("clean %s", $result ? 'SUCCESS' : 'FAILURE'));
138
        return $result;
139
    }
140
141
    /**
142
     * @inheritDoc
143
     * @throws CacheException
144 1
     */
145
    public function clear() : bool
146 1
    {
147
        $this->throwIfNotInstanceof(Cache::class);
148 1
149
        return $this->flush();
150
    }
151
152
    /**
153
     * @inheritDoc
154
     * @throws CacheException
155 1
     */
156
    public function getMultiple(iterable $keys, mixed $default = null) : iterable
157 1
    {
158
        $this->throwIfNotInstanceof(Cache::class);
159 1
160 1
        /** @scrutinizer ignore-call */
161 1
        $values = $this->cache->getMultiple($keys, $default);
162 1
        $this->log(sprintf(
163 1
            "getMultiple [%s] count=%s",
164
            implode(', ', $keys),
0 ignored issues
show
Bug introduced by
$keys of type iterable is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

164
            implode(', ', /** @scrutinizer ignore-type */ $keys),
Loading history...
165 1
            is_array($values) ? count($values) : ('[' . gettype($values) . ']')
166
        ));
167
        return $values;
168
    }
169
170
    /**
171 1
     * @inheritDoc
172
     */
173 1
    public function setMultiple(iterable $values, DateInterval|int|null $ttl = null) : bool
174
    {
175 1
        $this->throwIfNotInstanceof(Cache::class);
176 1
177 1
        /** @scrutinizer ignore-call */
178 1
        $result = $this->cache->setMultiple($values, $ttl);
179 1
        $this->log(sprintf(
180 1
            "setMultiple [%s] %s ttl=%s",
181
            implode(', ', array_keys($values)),
0 ignored issues
show
Bug introduced by
$values of type iterable is incompatible with the type array expected by parameter $array of array_keys(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

181
            implode(', ', array_keys(/** @scrutinizer ignore-type */ $values)),
Loading history...
182 1
            $result ? 'SUCCESS' : 'FAILURE',
183
            is_numeric($ttl) ? $ttl : "null"
184
        ));
185
        return $result;
186
    }
187
188
    /**
189 1
     * @inheritDoc
190
     * @throws CacheException
191 1
     */
192
    public function deleteMultiple(iterable $keys) : bool
193 1
    {
194 1
        $this->throwIfNotInstanceof(Cache::class);
195 1
196 1
        /** @scrutinizer ignore-call */
197 1
        $result = $this->cache->deleteMultiple($keys);
198
        $this->log(sprintf(
199 1
            "deleteMultiple [%s] %s",
200
            implode(', ', $keys),
0 ignored issues
show
Bug introduced by
$keys of type iterable is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

200
            implode(', ', /** @scrutinizer ignore-type */ $keys),
Loading history...
201
            $result ? 'SUCCESS' : 'FAILURE'
202
        ));
203
        return $result;
204
    }
205
206 1
    /**
207
     * @inheritDoc
208 1
     * @throws CacheException
209
     */
210 1
    public function has(string $key) : bool
211 1
    {
212 1
        $this->throwIfNotInstanceof(Cache::class);
213 1
214 1
        /** @scrutinizer ignore-call */
215
        $result = $this->cache->has($key);
216 1
        $this->log(sprintf(
217
            "has %s %s",
218
            $key,
219
            $result ? 'true' : 'false'
220
        ));
221
        return $result;
222
    }
223 2
224
    /**
225 2
     * @inheritDoc
226
     * @throws CacheException
227 2
     */
228 2
    public function increment(string $key, int $step = 1, DateInterval|int|null $ttl = null) : int|false
229 2
    {
230 2
        $this->throwIfNotInstanceof(AtomicCounter::class);
231 2
232 2
        /** @scrutinizer ignore-call */
233 2
        $result = $this->cache->increment($key, $step, $ttl);
234
        $this->log(sprintf(
235 2
            "increment %s by %d %s, value=%d",
236
            $key,
237
            $step,
238
            ($result !== false ? 'SUCCESS' : 'FAILURE'),
239
            ($result !== false ? $result : 0)
240
        ));
241
        return $result;
242 2
    }
243
244 2
    /**
245
     * @inheritDoc
246 2
     * @throws CacheException
247 2
     */
248 2
    public function decrement(string $key, int $step = 1, DateInterval|int|null $ttl = null) : int|false
249 2
    {
250 2
        $this->throwIfNotInstanceof(AtomicCounter::class);
251 2
252 2
        /** @scrutinizer ignore-call */
253
        $result = $this->cache->decrement($key, $step, $ttl);
254 2
        $this->log(sprintf(
255
            "decrement %s by %d %s, value=%d",
256
            $key,
257
            $step,
258
            ($result !== false ? 'SUCCESS' : 'FAILURE'),
259
            ($result !== false ? $result : 0)
260
        ));
261
        return $result;
262 3
    }
263
264 3
    /**
265 1
     * Log a message to the configured logger
266
     */
267
    private function log(string $message) : void
268 2
    {
269 2
        if (!$this->log) {
270
            return;
271
        }
272
273
        ([$this->log, $this->level])($message);
274
    }
275
276
    /**
277 3
     * Guards against calls on a decorated instance that does not support the underlying method
278
     *
279 3
     * @param string $class
280
     * @throws CacheException
281
     */
282 3
    private function throwIfNotInstanceof(string $class) : void
283
    {
284
        if (! $this->cache instanceof $class) {
285
            throw new CacheException("This decorated instance does not implement {$class}");
286
        }
287
    }
288
289
    /**
290 3
     * Get a reasonable estimation for the serialized size of a cacheable value
291
     *
292 3
     * @param mixed $val The cacheable value
293
     * @return int An estimate of the cached size of the value
294
     */
295
    private function getSize(mixed $val) : int
296
    {
297
        return strlen(is_scalar($val) ? (string)$val : serialize($val));
298
    }
299
}
300