Completed
Push — master ( 1b7924...92759d )
by Chauncey
08:17
created

ClearCacheTemplate::globalCacheItems()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Charcoal\Admin\Template\System;
4
5
use APCUIterator;
6
use APCIterator;
7
use RuntimeException;
8
9
use Stash\Driver\Apc;
10
use Stash\Driver\Memcache;
11
12
use Pimple\Container;
13
14
// From 'charcoal-admin'
15
use Charcoal\Admin\AdminTemplate;
16
17
/**
18
 * Cache information.
19
 */
20
class ClearCacheTemplate extends AdminTemplate
21
{
22
    /**
23
     * Cache service.
24
     *
25
     * @var \Stash\Pool
26
     */
27
    private $cache;
28
29
    /**
30
     * Summary of cache.
31
     *
32
     * @var array
33
     */
34
    private $cacheInfo;
35
36
    /**
37
     * Cache service config.
38
     *
39
     * @var \Charcoal\App\Config\CacheConfig
40
     */
41
    private $cacheConfig;
42
43
    /**
44
     * Driver Name => Class Name.
45
     *
46
     * @var \Stash\Interfaces\DriverInterface
47
     */
48
    private $cacheDriver;
49
50
    /**
51
     * Driver Name => Class Name.
52
     *
53
     * @var array
54
     */
55
    private $availableCacheDrivers;
56
57
    /**
58
     * Retrieve the title of the page.
59
     *
60
     * @return \Charcoal\Translator\Translation|string|null
61
     */
62
    public function title()
63
    {
64
        if ($this->title === null) {
65
            $this->setTitle($this->translator()->translation('Cache information'));
66
        }
67
68
        return $this->title;
69
    }
70
71
    /**
72
     * @return \Charcoal\Admin\Widget\SidemenuWidgetInterface|null
73
     */
74
    public function sidemenu()
75
    {
76
        if ($this->sidemenu === null) {
77
            $this->sidemenu = $this->createSidemenu('system');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createSidemenu('system') can also be of type Charcoal\Admin\Widget\SidemenuWidgetInterface. However, the property $sidemenu is declared as type Charcoal\Admin\SideMenuWidgetInterface. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
78
        }
79
80
        return $this->sidemenu;
81
    }
82
83
    /**
84
     * @param  boolean $force Whether to reload cache information.
85
     * @return array
86
     */
87
    public function cacheInfo($force = false)
88
    {
89
        if ($this->cacheInfo === null || $force === true) {
90
            $flip      = array_flip($this->availableCacheDrivers);
91
            $driver    = get_class($this->cache->getDriver());
92
            $cacheType = isset($flip['\\'.$driver]) ? $flip['\\'.$driver] : $driver;
93
94
            # $globalItems = $this->globalCacheItems();
95
            $pageItems   = $this->pagesCacheItems();
96
            $objectItems = $this->objectsCacheItems();
97
            $this->cacheInfo = [
98
                'type'              => $cacheType,
99
                'active'            => $this->cacheConfig['active'],
100
                'global'            => $this->globalCacheInfo(),
101
                'pages'             => $this->pagesCacheInfo(),
102
                'objects'           => $this->objectsCacheInfo(),
103
                # 'global_items'      => $globalItems,
104
                'pages_items'       => $pageItems,
105
                'objects_items'     => $objectItems,
106
                # 'num_global_items'  => count($globalItems),
107
                'num_pages_items'   => count($pageItems),
108
                'num_objects_items' => count($objectItems),
109
            ];
110
        }
111
112
        return $this->cacheInfo;
113
    }
114
115
    /**
116
     * @return string
117
     */
118
    private function getGlobalCacheKey()
119
    {
120
        return '/::'.$this->cache->getNamespace().'::/';
0 ignored issues
show
Bug introduced by
Are you sure $this->cache->getNamespace() of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

120
        return '/::'./** @scrutinizer ignore-type */ $this->cache->getNamespace().'::/';
Loading history...
121
    }
122
123
    /**
124
     * @return array
125
     */
126
    private function globalCacheInfo()
127
    {
128
        if ($this->isApc()) {
129
            $cacheKey = $this->getGlobalCacheKey();
130
            return $this->apcCacheInfo($cacheKey);
131
        } else {
132
            return [
133
                'num_entries'  => 0,
134
                'total_size'   => 0,
135
                'average_size' => 0,
136
                'total_hits'   => 0,
137
                'average_hits' => 0,
138
            ];
139
        }
140
    }
141
142
    /**
143
     * @return array
144
     */
145
    private function globalCacheItems()
0 ignored issues
show
Unused Code introduced by
The method globalCacheItems() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
146
    {
147
        if ($this->isApc()) {
148
            $cacheKey = $this->getGlobalCacheKey();
149
            return $this->apcCacheItems($cacheKey);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->apcCacheItems($cacheKey) returns the type Generator which is incompatible with the documented return type array.
Loading history...
150
        } else {
151
            return [];
152
        }
153
    }
154
155
    /**
156
     * @return string
157
     */
158
    private function getPagesCacheKey()
159
    {
160
        return '/::'.$this->cache->getNamespace().'::request::|::'.$this->cache->getNamespace().'::template::/';
0 ignored issues
show
Bug introduced by
Are you sure $this->cache->getNamespace() of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

160
        return '/::'./** @scrutinizer ignore-type */ $this->cache->getNamespace().'::request::|::'.$this->cache->getNamespace().'::template::/';
Loading history...
161
    }
162
163
    /**
164
     * @return array
165
     */
166
    private function pagesCacheInfo()
167
    {
168
        if ($this->isApc()) {
169
            $cacheKey = $this->getPagesCacheKey();
170
            return $this->apcCacheInfo($cacheKey);
171
        } else {
172
            return [
173
                'num_entries'  => 0,
174
                'total_size'   => 0,
175
                'average_size' => 0,
176
                'total_hits'   => 0,
177
                'average_hits' => 0,
178
            ];
179
        }
180
    }
181
182
    /**
183
     * @return array
184
     */
185
    private function pagesCacheItems()
186
    {
187
        if ($this->isApc()) {
188
            $cacheKey = $this->getPagesCacheKey();
189
            return $this->apcCacheItems($cacheKey);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->apcCacheItems($cacheKey) returns the type Generator which is incompatible with the documented return type array.
Loading history...
190
        } else {
191
            return [];
192
        }
193
    }
194
195
    /**
196
     * @return string
197
     */
198
    private function getObjectsCacheKey()
199
    {
200
        return '/::'.$this->cache->getNamespace().'::object::|::'.$this->cache->getNamespace().'::metadata::/';
0 ignored issues
show
Bug introduced by
Are you sure $this->cache->getNamespace() of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

200
        return '/::'./** @scrutinizer ignore-type */ $this->cache->getNamespace().'::object::|::'.$this->cache->getNamespace().'::metadata::/';
Loading history...
201
    }
202
203
    /**
204
     * @return array
205
     */
206
    private function objectsCacheInfo()
207
    {
208
        if ($this->isApc()) {
209
            $cacheKey = $this->getObjectsCacheKey();
210
            return $this->apcCacheInfo($cacheKey);
211
        } else {
212
            return [
213
                'num_entries'  => 0,
214
                'total_size'   => 0,
215
                'average_size' => 0,
216
                'total_hits'   => 0,
217
                'average_hits' => 0,
218
            ];
219
        }
220
    }
221
222
    /**
223
     * @return array
224
     */
225
    private function objectsCacheItems()
226
    {
227
        if ($this->isApc()) {
228
            $cacheKey = $this->getObjectsCacheKey();
229
            return $this->apcCacheItems($cacheKey);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->apcCacheItems($cacheKey) returns the type Generator which is incompatible with the documented return type array.
Loading history...
230
        } else {
231
            return [];
232
        }
233
    }
234
235
    /**
236
     * @param  string $key The cache key to look at.
237
     * @return array
238
     */
239
    private function apcCacheInfo($key)
240
    {
241
        $iter = $this->createApcIterator($key);
242
243
        $numEntries = 0;
244
        $sizeTotal  = 0;
245
        $hitsTotal  = 0;
246
        $ttlTotal   = 0;
247
        foreach ($iter as $item) {
248
            $numEntries++;
249
            $sizeTotal += $item['mem_size'];
250
            $hitsTotal += $item['num_hits'];
251
            $ttlTotal  += $item['ttl'];
252
        }
253
        $sizeAvg = $numEntries ? ($sizeTotal / $numEntries) : 0;
254
        $hitsAvg = $numEntries ? ($hitsTotal / $numEntries) : 0;
255
        return [
256
            'num_entries'  => $numEntries,
257
            'total_size'   => $this->formatBytes($sizeTotal),
258
            'average_size' => $this->formatBytes($sizeAvg),
259
            'total_hits'   => $hitsTotal,
260
            'average_hits' => $hitsAvg,
261
        ];
262
    }
263
264
    /**
265
     * @param  string $key The cache key to look at.
266
     * @return array|\Generator
267
     */
268
    private function apcCacheItems($key)
269
    {
270
        $iter = $this->createApcIterator($key);
271
272
        foreach ($iter as $item) {
273
            $item['ident']   = $this->formatApcCacheKey($item['key']);
274
            $item['size']    = $this->formatBytes($item['mem_size']);
275
            $item['created'] = date('Y-m-d H:i:s', $item['creation_time']);
276
            $item['expiry']  = date('Y-m-d H:i:s', ($item['creation_time']+$item['ttl']));
277
            yield $item;
278
        }
279
    }
280
281
    /**
282
     * @param  string $key The cache item key to load.
283
     * @throws RuntimeException If the APC Iterator class is missing.
284
     * @return \APCIterator|\APCUIterator|null
285
     */
286
    private function createApcIterator($key)
287
    {
288
        if (class_exists('\\APCUIterator', false)) {
289
            return new \APCUIterator($key);
290
        } elseif (class_exists('\\APCIterator', false)) {
291
            return new \APCIterator('user', $key);
292
        } else {
293
            throw new RuntimeException('Cache uses APC but no iterator could be found.');
294
        }
295
    }
296
297
    /**
298
     * @return boolean
299
     */
300
    private function isApc()
301
    {
302
        return is_a($this->cache->getDriver(), Apc::class);
303
    }
304
305
    /**
306
     * @return boolean
307
     */
308
    private function isMemcache()
0 ignored issues
show
Unused Code introduced by
The method isMemcache() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
309
    {
310
        return is_a($this->cache->getDriver(), Memcache::class);
311
    }
312
313
    /**
314
     * Human-readable identifier format.
315
     *
316
     * @param  string $key The cache item key to format.
317
     * @return string
318
     */
319
    private function formatApcCacheKey($key)
320
    {
321
        $nss = $this->cache->getNamespace();
322
        $key = str_replace($nss, '', strstr($key, $nss.'::'));
0 ignored issues
show
Bug introduced by
It seems like $nss can also be of type false; however, parameter $search of str_replace() does only seem to accept string|string[], maybe add an additional type check? ( Ignorable by Annotation )

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

322
        $key = str_replace(/** @scrutinizer ignore-type */ $nss, '', strstr($key, $nss.'::'));
Loading history...
Bug introduced by
Are you sure $nss of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

322
        $key = str_replace($nss, '', strstr($key, /** @scrutinizer ignore-type */ $nss.'::'));
Loading history...
323
        $key = preg_replace([ '/:+/', '/\.+/' ], [ '⇒', '/' ], trim($key, ':'));
324
        return $key;
325
    }
326
327
    /**
328
     * Human-readable bytes format.
329
     *
330
     * @param  integer $bytes The number of bytes to format.
331
     * @return string
332
     */
333
    private function formatBytes($bytes)
334
    {
335
        if ($bytes === 0) {
336
            return 0;
337
        }
338
339
        $units = [ 'B', 'KB', 'MB', 'GB', 'TB' ];
340
        $base  = log($bytes, 1024);
341
        $floor = floor($base);
342
        $unit  = $units[$floor];
343
        $size  = round(pow(1024, ($base - $floor)), 2);
344
345
        $locale = localeconv();
346
        $size   = number_format($size, 2, $locale['decimal_point'], $locale['thousands_sep']);
347
348
        return rtrim($size, '.0').' '.$unit;
349
    }
350
351
    /**
352
     * @param Container $container Pimple DI Container.
353
     * @return void
354
     */
355
    protected function setDependencies(Container $container)
356
    {
357
        parent::setDependencies($container);
358
359
        $this->availableCacheDrivers = $container['cache/available-drivers'];
360
        $this->cacheDriver           = $container['cache/driver'];
361
        $this->cache                 = $container['cache'];
362
        $this->cacheConfig           = $container['cache/config'];
363
    }
364
}
365