Passed
Pull Request — master (#1596)
by Michael
10:19
created

XoopsCache::write()   C

Complexity

Conditions 15
Paths 72

Size

Total Lines 59
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 15
eloc 36
c 3
b 1
f 0
nc 72
nop 3
dl 0
loc 59
rs 5.9166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Cache engine For XOOPS
4
 *
5
 * You may not change or alter any portion of this comment or credits
6
 * of supporting developers from this source code or any supporting source code
7
 * which is considered copyrighted (c) material of the original comment or credit authors.
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * @copyright       (c) 2000-2025 XOOPS Project (https://xoops.org)
13
 * @license             GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14
 * @package             class
15
 * @subpackage          cache
16
 * @since               2.3.0
17
 * @author              Taiwen Jiang <[email protected]>
18
 */
19
20
defined('XOOPS_ROOT_PATH') || exit('Restricted access');
21
22
/**
23
 * Caching for CakePHP.
24
 *
25
 * @package    cake
26
 * @subpackage cake.cake.libs
27
 */
28
class XoopsCache
29
{
30
    /**
31
     * Cache engine instances
32
     *
33
     * @var array<string, XoopsCacheEngine|null>
34
     * @access protected
35
     */
36
    protected $engine = [];
37
38
39
    /**
40
     * Cache configuration stack
41
     *
42
     * @var array
43
     * @access private
44
     */
45
    private $configs = [];
46
47
    /**
48
     * Holds name of the current configuration being used
49
     *
50
     * @var string|null
51
     * @access private
52
     */
53
    private $name;
54
55
    /**
56
     * XoopsCache::__construct()
57
     */
58
    public function __construct() {}
59
60
    /**
61
     * Returns a singleton instance
62
     *
63
     * @return object
64
     * @access public
65
     */
66
    public static function getInstance()
67
    {
68
        static $instance;
69
        if (!isset($instance)) {
70
            $class    = self::class;
71
            $instance = new $class();
72
        }
73
74
        return $instance;
75
    }
76
77
    /**
78
     * Resolves and validates engine configuration
79
     *
80
     * @param string|array|null $config Configuration name or array
81
     * @return array{engine: string, settings: array<string, mixed>}|false
82
     */
83
    protected function resolveEngineConfig($config)
84
    {
85
        $config = $this->config($config);
86
87
        if (
88
            !is_array($config) ||
89
            !isset($config['engine'], $config['settings']) ||
90
            !is_string($config['engine']) ||
91
            !is_array($config['settings'])
92
        ) {
93
            return false;
94
        }
95
96
        return $config;
97
    }
98
99
    /**
100
     * Resolve engine name from parameter or current configuration
101
     *
102
     * @param string|null $engine Engine name or null to use current
103
     * @return string|null Engine name or null if cannot be resolved
104
     */
105
    private function resolveEngineName($engine)
106
    {
107
        if (is_string($engine) && $engine !== '') {
108
            return $engine;
109
        }
110
111
        if (
112
            is_string($this->name) &&
113
            $this->name !== '' &&
114
            isset($this->configs[$this->name]['engine'])
115
        ) {
116
            return $this->configs[$this->name]['engine'];
117
        }
118
119
        return null;
120
    }
121
122
    /**
123
     * Tries to find and include a file for a cache engine
124
     *
125
     * @param  string $name Name of the engine
126
     * @return bool True if the engine was successfully loaded, false on failure
127
     * @access private
128
     */
129
    private function loadEngine($name)
130
    {
131
        if (!class_exists('XoopsCache' . ucfirst($name))) {
132
            if (file_exists($file = __DIR__ . '/' . strtolower($name) . '.php')) {
133
                include $file;
134
            } else {
135
                trigger_error('File :' . $file . ' not found in file : ' . __FILE__ . ' at line: ' . __LINE__, E_USER_WARNING);
136
                return false;
137
            }
138
        }
139
140
        return true;
141
    }
142
143
    /**
144
     * Set the cache configuration to use
145
     *
146
     * @param  string|array $name     Name of the configuration
147
     * @param  array  $settings Optional associative array of settings passed to the engine
148
     * @return array|false  (engine, settings) on success, false on failure
149
     * @access public
150
     */
151
    public function config($name = 'default', $settings = [])
152
    {
153
        $_this = XoopsCache::getInstance();
154
155
        if (!is_array($settings)) {
0 ignored issues
show
introduced by
The condition is_array($settings) is always true.
Loading history...
156
            $settings = [];
157
        }
158
159
        if (is_array($name)) {
160
            $config = $name;
161
162
            if (isset($config['name']) && is_string($config['name'])) {
163
                $name = $config['name'];
164
            }
165
166
            if (isset($config['settings']) && is_array($config['settings'])) {
167
                // Preserve legacy behavior: overwrite settings, do NOT merge
168
                $settings = $config['settings'];
169
            }
170
        }
171
172
        if (!is_string($name) || $name === '') {
173
            $name = 'default';
174
        }
175
176
        if (isset($_this->configs[$name])) {
177
            $settings = array_merge($_this->configs[$name], $settings);
178
        } elseif (!empty($settings)) {
179
            $_this->configs[$name] = $settings;
180
        } elseif (
181
            $_this->configs !== null
182
            && is_string($_this->name)
183
            && $_this->name !== ''
184
            && isset($_this->configs[$_this->name])
185
        ) {
186
            $name     = $_this->name;
187
            $settings = $_this->configs[$_this->name];
188
        } else {
189
            $name = 'default';
190
            if (!empty($_this->configs['default'])) {
191
                $settings = $_this->configs['default'];
192
            } else {
193
                $settings = [
194
                    'engine' => 'file',
195
                ];
196
            }
197
        }
198
199
        $engine = !empty($settings['engine']) ? $settings['engine'] : 'file';
200
201
        if ($name !== $_this->name) {
202
            if ($_this->engine($engine, $settings) === false) {
203
                trigger_error("Cache Engine {$engine} is not set", E_USER_WARNING);
204
205
                return false;
206
            }
207
            $_this->name           = $name;
208
            $_this->configs[$name] = $_this->settings($engine);
209
        }
210
211
        $settings = $_this->configs[$name];
212
213
        return compact('engine', 'settings');
214
    }
215
216
    /**
217
     * Set the cache engine to use or modify settings for one instance
218
     *
219
     * @param  string $name     Name of the engine (without 'Engine')
220
     * @param  array  $settings Optional associative array of settings passed to the engine
221
     * @return bool True on success, false on failure
222
     * @access public
223
     */
224
    public function engine($name = 'file', $settings = []): bool
225
    {
226
        if (!is_string($name) || $name === '') {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
227
            return false;
228
        }
229
        if (!is_array($settings)) {
0 ignored issues
show
introduced by
The condition is_array($settings) is always true.
Loading history...
230
            $settings = [];
231
        }
232
233
        $cacheClass = 'XoopsCache' . ucfirst($name);
234
        $_this      = XoopsCache::getInstance();
235
236
        if (!isset($_this->engine[$name])) {
237
            if ($_this->loadEngine($name) === false) {
238
                trigger_error("Cache Engine {$name} is not loaded", E_USER_WARNING);
239
                return false;
240
            }
241
            $_this->engine[$name] = new $cacheClass();
242
        }
243
244
        if ($_this->engine[$name]->init($settings)) {
245
            if (time() % $_this->engine[$name]->settings['probability'] == 0) {
246
                $_this->engine[$name]->gc();
247
            }
248
            return true;
249
        }
250
251
        $_this->engine[$name] = null;
252
        trigger_error("Cache Engine {$name} is not initialized", E_USER_WARNING);
253
254
        return false;
255
    }
256
257
    /**
258
     * Garbage collection
259
     *
260
     * Permanently remove all expired and deleted data
261
     *
262
     * @return bool True on success, false on failure
263
     * @access public
264
     */
265
    public function gc(): bool
266
    {
267
        $_this  = XoopsCache::getInstance();
268
        $config = $_this->resolveEngineConfig(null);
269
270
        if ($config === false) {
271
            return false;
272
        }
273
274
        $engine = $config['engine'];
275
276
        // Check if engine is initialized before calling gc()
277
        if (!$_this->isInitialized($engine)) {
278
            trigger_error("Cache engine {$engine} not initialized for garbage collection", E_USER_WARNING);
279
            return false;
280
        }
281
282
        $_this->engine[$engine]->gc();
283
284
        return true;
285
    }
286
287
    /**
288
     * Write data for key into cache
289
     *
290
     * @param  string $key       Identifier for the data
291
     * @param  mixed  $value     Data to be cached - anything except a resource
292
     * @param  mixed  $duration  Optional - string configuration name OR how long to cache the data, either in seconds or a
293
     *                           string that can be parsed by the strtotime() function OR array('config' => 'default', 'duration' => '3600')
294
     * @return bool True if the data was successfully cached, false on failure
295
     * @access public
296
     */
297
    public static function write($key, $value, $duration = null): bool
298
    {
299
        $key    = substr(md5(XOOPS_URL), 0, 8) . '_' . $key;
300
        $_this  = XoopsCache::getInstance();
301
302
        $config = null;
303
304
        if (is_array($duration)) {
305
            if (isset($duration['config']) && is_string($duration['config'])) {
306
                $config = $duration['config'];
307
            }
308
            if (isset($duration['duration'])) {
309
                $duration = $duration['duration'];
310
            } else {
311
                $duration = null;
312
            }
313
        } elseif (
314
            is_string($duration)
315
            && $duration !== ''
316
            && isset($_this->configs[$duration])
317
        ) {
318
            $config   = $duration;
319
            $duration = null;
320
        }
321
322
        $config = $_this->resolveEngineConfig($config);
323
        if ($config === false) {
324
            return false;
325
        }
326
327
        $engine   = $config['engine'];
328
        $settings = $config['settings'];
329
330
        if (!$_this->isInitialized($engine)) {
331
            trigger_error('Cache write not initialized: ' . $engine);
332
333
            return false;
334
        }
335
336
        if (!$key = $_this->key($key)) {
337
            return false;
338
        }
339
340
        if (is_resource($value)) {
341
            return false;
342
        }
343
344
        if (!$duration) {
345
            $duration = $settings['duration'];
346
        }
347
        $duration = is_numeric($duration) ? (int) $duration : strtotime($duration) - time();
348
349
        if ($duration < 1) {
350
            return false;
351
        }
352
        $_this->engine[$engine]->init($settings);
353
        $success = $_this->engine[$engine]->write($key, $value, $duration);
354
355
        return $success;
356
    }
357
358
    /**
359
     * Read a key from the cache
360
     *
361
     * @param  string $key    Identifier for the data
362
     * @param  string|array|null $config name of the configuration to use
363
     * @return mixed The cached data, or false if:
364
     *               - Configuration is invalid
365
     *               - Data doesn't exist
366
     *               - Data has expired
367
     *               - Error occurred during retrieval
368
     * @access public
369
     */
370
    public static function read($key, $config = null)
371
    {
372
        $key    = substr(md5(XOOPS_URL), 0, 8) . '_' . $key;
373
        $_this  = XoopsCache::getInstance();
374
375
        $config = $_this->resolveEngineConfig($config);
376
        if ($config === false) {
377
            return false;
378
        }
379
380
        $engine   = $config['engine'];
381
        $settings = $config['settings'];
382
383
        if (!$_this->isInitialized($engine)) {
384
            return false;
385
        }
386
        if (!$key = $_this->key($key)) {
387
            return false;
388
        }
389
        $_this->engine[$engine]->init($settings);
390
        $success = $_this->engine[$engine]->read($key);
391
392
        return $success;
393
    }
394
395
    /**
396
     * Delete a key from the cache
397
     *
398
     * @param  string $key    Identifier for the data
399
     * @param string|null $config name of the configuration to use
400
     * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
401
     * @access public
402
     */
403
    public static function delete($key, $config = null): bool
404
    {
405
        $key   = substr(md5(XOOPS_URL), 0, 8) . '_' . $key;
406
        $_this = XoopsCache::getInstance();
407
408
        $config = $_this->resolveEngineConfig($config);
409
        if ($config === false) {
410
            return false;
411
        }
412
413
        $engine   = $config['engine'];
414
        $settings = $config['settings'];
415
416
        if (!$_this->isInitialized($engine)) {
417
            return false;
418
        }
419
420
        if (!$key = $_this->key($key)) {
421
            return false;
422
        }
423
424
        $_this->engine[$engine]->init($settings);
425
        $success = $_this->engine[$engine]->delete($key);
426
427
        return $success;
428
    }
429
430
    /**
431
     * Delete all keys from the cache
432
     *
433
     * @param  boolean $check  if true will check expiration, otherwise delete all
434
     * @param string|null $config name of the configuration to use
435
     * @return boolean True if the cache was successfully cleared, false otherwise
436
     * @access public
437
     */
438
    public function clear($check = false, $config = null): bool
439
    {
440
        $_this = XoopsCache::getInstance();
441
442
        $config = $_this->resolveEngineConfig($config);
443
        if ($config === false) {
444
            return false;
445
        }
446
447
        $engine   = $config['engine'];
448
        $settings = $config['settings'];
449
450
        if (!$_this->isInitialized($engine)) {
451
            return false;
452
        }
453
454
        $success = $_this->engine[$engine]->clear($check);
455
        $_this->engine[$engine]->init($settings);
456
457
        return $success;
458
    }
459
460
461
    /**
462
     * Check if Cache has initialized a working storage engine
463
     *
464
     * @param  string|null $engine Name of the engine
465
     * @return bool
466
     * @access public
467
     */
468
    public function isInitialized($engine = null)
469
    {
470
        $_this = XoopsCache::getInstance();
471
        $engine = $_this->resolveEngineName($engine);
472
473
        return is_string($engine)
474
            && isset($_this->engine[$engine])
475
            && $_this->engine[$engine] !== null;
476
    }
477
478
    /**
479
     * Return the settings for current cache engine
480
     *
481
     * @param  string|null $engine Name of the engine
482
     * @return array list of settings for this engine
483
     * @access public
484
     */
485
    public function settings($engine = null)
486
    {
487
        $_this = XoopsCache::getInstance();
488
        $engine = $_this->resolveEngineName($engine);
489
490
        if (
491
            is_string($engine) &&
492
            isset($_this->engine[$engine]) &&
493
            $_this->engine[$engine] !== null
494
        ) {
495
            return $_this->engine[$engine]->settings();
496
        }
497
498
        return [];
499
    }
500
501
    /**
502
     * generates a safe key
503
     *
504
     * @param  string $key the key passed over
505
     * @return mixed  string $key or false
506
     * @access private
507
     */
508
    public function key($key)
509
    {
510
        if (empty($key)) {
511
            return false;
512
        }
513
        $key = str_replace(['/', '.'], '_', (string) $key);
514
515
        return $key;
516
    }
517
}
518
519
/**
520
 * Abstract class for storage engine for caching
521
 *
522
 * @package    core
523
 * @subpackage cache
524
 */
525
class XoopsCacheEngine
526
{
527
    /**
528
     * settings of current engine instance
529
     *
530
     * @var array
531
     * @access public
532
     */
533
    public $settings;
534
535
    /**
536
     * Initialize the cache engine
537
     *
538
     * Called automatically by the cache frontend. This method may be called
539
     * multiple times to reconfigure the engine with different settings.
540
     * Implementations should handle repeated initialization gracefully.
541
     *
542
     * @param  array $settings Associative array of parameters for the engine
543
     * @return boolean True if the engine has been successfully initialized, false if not
544
     * @access   public
545
     */
546
    public function init($settings = [])
547
    {
548
        $this->settings = array_merge(
549
            [
550
                'duration'    => 31556926,
551
                'probability' => 100,
552
            ],
553
            $settings,
554
        );
555
556
        return true;
557
    }
558
559
    /**
560
     * Garbage collection
561
     *
562
     * Permanently remove all expired and deleted data
563
     *
564
     * @access public
565
     */
566
    public function gc() {}
567
568
    /**
569
     * Write value for a key into cache
570
     *
571
     * @param  string $key      Identifier for the data
572
     * @param  mixed  $value    Data to be cached
573
     * @param  mixed  $duration How long to cache the data, in seconds
574
     * @return boolean True if the data was successfully cached, false on failure
575
     * @access public
576
     */
577
    public function write($key, $value, $duration = null)
578
    {
579
        trigger_error(sprintf(__('Method write() not implemented in %s', true), get_class($this)), E_USER_ERROR);
0 ignored issues
show
Bug introduced by
The function __ was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

579
        trigger_error(sprintf(/** @scrutinizer ignore-call */ __('Method write() not implemented in %s', true), get_class($this)), E_USER_ERROR);
Loading history...
580
    }
581
582
    /**
583
     * Read a key from the cache
584
     *
585
     * @param  string $key Identifier for the data
586
     * @return mixed  The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
587
     * @access public
588
     */
589
    public function read($key)
590
    {
591
        trigger_error(sprintf(__('Method read() not implemented in %s', true), get_class($this)), E_USER_ERROR);
0 ignored issues
show
Bug introduced by
The function __ was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

591
        trigger_error(sprintf(/** @scrutinizer ignore-call */ __('Method read() not implemented in %s', true), get_class($this)), E_USER_ERROR);
Loading history...
592
    }
593
594
    /**
595
     * Delete a key from the cache
596
     *
597
     * @param  string $key Identifier for the data
598
     * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
599
     * @access public
600
     */
601
    public function delete($key) {}
602
603
    /**
604
     * Delete all keys from the cache
605
     *
606
     * @param  boolean $check if true will check expiration, otherwise delete all
607
     * @return boolean True if the cache was successfully cleared, false otherwise
608
     * @access public
609
     */
610
    public function clear($check) {}
611
612
    /**
613
     * Cache Engine settings
614
     *
615
     * @return array settings
616
     * @access public
617
     */
618
    public function settings()
619
    {
620
        return $this->settings;
621
    }
622
}
623