FileClient::filename()   A
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 6
eloc 10
c 3
b 0
f 1
nc 6
nop 2
dl 0
loc 14
ccs 11
cts 11
cp 1
crap 6
rs 9.2222
1
<?php
2
/*
3
 * This file is part of the Koded package.
4
 *
5
 * (c) Mihail Binev <[email protected]>
6
 *
7
 * Please view the LICENSE distributed with this source code
8
 * for the full copyright and license information.
9
 */
10
11
namespace Koded\Caching\Client;
12
13
use Koded\Caching\{Cache, CacheException};
14
use Psr\Log\LoggerInterface;
15
use function chmod;
16
use function file_put_contents;
17
use function is_dir;
18
use function is_file;
19
use function Koded\Caching\verify_key;
20
use function Koded\Stdlib\rmdir;
21
use function mkdir;
22
use function rtrim;
23
use function serialize;
24
use function sha1;
25
use function substr;
26
use function sys_get_temp_dir;
27
use function time;
28
use function touch;
29
use function umask;
30
use function unlink;
31
use function unserialize;
32
use function var_export;
33
34
/**
35
 * @property FileClient client
36
 *
37
 */
38
final class FileClient implements Cache
39
{
40
    use ClientTrait, MultiplesTrait;
41
42
    private string $dir = '';
43
    private LoggerInterface $logger;
44
45 75
    public function __construct(LoggerInterface $logger, string $dir, int $ttl = null)
46
    {
47 75
        $this->ttl = $ttl;
48 75
        $this->logger = $logger;
49 75
        $this->setDirectory($dir);
50
    }
51
52 48
    public function get(string $key, mixed $default = null): mixed
53
    {
54
        try {
55 48
            if ($this->has($key, $filename, $cache)) {
56 40
                return unserialize($cache['value']);
57
            }
58 14
            return $default;
59
        } finally {
60 48
            unset($filename, $cache);
61
        }
62
    }
63
64 50
    public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool
65
    {
66 50
        verify_key($key);
67 50
        if (1 > $expiration = $this->timestampWithGlobalTtl($ttl, Cache::DATE_FAR_FAR_AWAY)) {
68
            // The item is considered expired and must be deleted
69 2
            return $this->delete($key);
70
        }
71 49
        $filename = $this->filename($key, true);
72 49
        return (bool)file_put_contents($filename, $this->data($key, $value, $expiration));
73
    }
74
75 10
    public function delete(string $key): bool
76
    {
77 10
        if (false === $this->has($key, $filename)) {
78 8
            return true;
79
        }
80 7
        return unlink($filename);
81
    }
82
83 71
    public function clear(): bool
84
    {
85 71
        return rmdir($this->dir);
86
    }
87
88 53
    public function has(string $key, &$filename = '', &$cache = null): bool
89
    {
90 53
        verify_key($key);
91 53
        $filename = $this->filename($key, false);
92 53
        if (false === is_file($filename)) {
93 17
            return false;
94
        }
95 48
        $cache = include $filename;
96 48
        if ($cache['timestamp'] <= time()) {
97 3
            unlink($filename);
98 3
            return false;
99
        }
100 48
        return true;
101
    }
102
103
    /**
104
     * Normalizes the cache filename.
105
     *
106
     * @param string $key    The cache key
107
     * @param bool   $create Flag to create the file or not
108
     *
109
     * @return string
110
     */
111 53
    private function filename(string $key, bool $create): string
112
    {
113 53
        $filename = sha1($key);
114 53
        $dir = $this->dir . $filename[0];
115 53
        if ($create && false === is_dir($dir)) {
116 49
            mkdir($dir, 0775, true)
117 49
            || $this->logger->error('Failed to create cache directory in: {dir}', ['dir' => $dir]);
118
        }
119 53
        $filename = $dir . '/' . substr($filename, 1) . '.php';
120 53
        if ($create && false === is_file($filename)) {
121 49
            touch($filename);
122 49
            chmod($filename, 0666);
123
        }
124 53
        return $filename;
125
    }
126
127
    /**
128
     * Prepares the cache directory.
129
     *
130
     * @param string $directory
131
     *
132
     * @throws CacheException
133
     */
134 75
    private function setDirectory(string $directory): void
135
    {
136
        // Overrule shell misconfiguration or the web server
137 75
        umask(umask() | 0002);
138 75
        $dir = $directory ?: sys_get_temp_dir() . '/_cache';
139 75
        $dir = rtrim($dir, '/') . '/';
140 75
        if (false === is_dir($dir) && false === mkdir($dir, 0775, true)) {
141 1
            $e = CacheException::forCreatingDirectory($dir);
142 1
            $this->logger->error($e->getMessage());
143 1
            throw $e;
144
        }
145 74
        $this->dir = $dir;
146
    }
147
148
    /**
149
     * Creates a cache content.
150
     *
151
     * @param string   $key   The cache key
152
     * @param mixed    $value The value to be cached
153
     * @param int      $ttl   Time to live
154
     *
155
     * @return string
156
     */
157 49
    private function data(string $key, mixed $value, int $ttl): string
158
    {
159 49
        return '<?php return ' . var_export([
160 49
                'timestamp' => $ttl,
161 49
                'key' => $key,
162 49
                'value' => serialize($value),
163 49
            ], true) . ';';
164
    }
165
}
166