Test Failed
Push — master ( e503e7...ca58d9 )
by Sebastian
13:27
created

DiskCache::handleTtl()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 12
ccs 3
cts 3
cp 1
rs 10
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
/**
4
 * Linna Framework.
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2018, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types=1);
11
12
13
namespace Linna\Cache;
14
15
use DateInterval;
16
use Psr\SimpleCache\CacheInterface;
17
18
/**
19
 * PSR-16 Disk Cache.
20
 *
21
 * Before use it, is possible configure ramdisk, work only on linux:
22
 * - mkdir /tmp/linna-cache
23
 * - sudo mount -t tmpfs -o size=128m tmpfs /tmp/linna-cache
24
 *
25
 * For check Ram Disk status
26
 * - df -h /tmp/linna-cache
27
 *
28
 * Serialize option is required when is needed store a class instance.
29
 * If you not utilize serialize, must declare __set_state() method inside
30
 * class or get from cache fail.
31
 */
32
class DiskCache implements CacheInterface
33
{
34
    use ActionMultipleTrait;
35
36
    /**
37
     * @var string Directory for cache storage.
38
     */
39
    protected string $dir = '/tmp';
40
41
    /**
42
     * Constructor.
43
     *
44 1
     * @param array<mixed> $options
45
     */
46 1
    public function __construct(array $options = [])
47 1
    {
48
        ['dir' => $this->dir] = \array_replace_recursive(['dir' => '/tmp'], $options);
49
    }
50
51
    /**
52
     * Fetches a value from the cache.
53
     *
54
     * @param string $key     The unique key of this item in the cache.
55
     * @param mixed  $default Default value to return if the key does not exist.
56
     *
57 7
     * @return mixed The value of the item from the cache, or $default in case of cache miss.
58
     *
59
     * @throws \Psr\SimpleCache\InvalidArgumentException
60 7
     *   MUST be thrown if the $key string is not a legal value.
61
     */
62 7
    public function get(string $key, mixed $default = null): mixed
63 4
    {
64
        //create file name
65
        $file = $this->dir.'/'.\sha1($key).'.php';
66 3
67
        if ($this->doesFileChecksFailed($file)) {
68 3
            return $default;
69
        }
70
71
        $cacheValue = include $file;
72
73
        return \unserialize($cacheValue['value']);
74
    }
75
76
    /**
77
     * Checks for cache file.
78 14
     *
79
     * @param string $file
80
     *
81 14
     * @return bool
82 4
     */
83
    private function doesFileChecksFailed(string $file): bool
84
    {
85
        //check if file exist
86 12
        if (!\file_exists($file)) {
87
            return true;
88
        }
89 12
90 4
        //take cache from file
91
        $cacheValue = include $file;
92 4
93
        //check if cache is expired and delete file from storage
94
        if ($cacheValue['expires'] <= \time() && $cacheValue['expires'] !== 0) {
95 9
            \unlink($file);
96
97
            return true;
98
        }
99
100
        return false;
101
    }
102
103
    /**
104
     * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
105
     *
106
     * @param string                 $key   The key of the item to store.
107
     * @param mixed                  $value The value of the item to store, must be serializable.
108
     * @param null|int|\DateInterval $ttl   Optional. The TTL value of this item. If no value is sent and
109 13
     *                                      the driver supports TTL then the library may set a default value
110
     *                                      for it or let the driver take care of that.
111
     *
112
     * @return bool True on success and false on failure.
113 13
     *
114 13
     * @throws \Psr\SimpleCache\InvalidArgumentException
115 13
     *   MUST be thrown if the $key string is not a legal value.
116
     */
117
    public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool
118
    {
119
        $vTtl = $this->handleTtl($ttl);
120 13
        
121 13
        //create cache array
122
        $cache = [
123
            'key'     => $key,
124 13
            'value'   => \serialize($value),
125
            'expires' => $this->calculateTtl($vTtl),
126 13
        ];
127
128
        //export
129
        // HHVM fails at __set_state, so just use object cast for now
130
        $content = \str_replace('stdClass::__set_state', '(object)', \var_export($cache, true));
131
        $content = "<?php return {$content};";
132
133
        //write file
134
        \file_put_contents($this->dir.'/'.\sha1($key).'.php', $content);
135
136 13
        return true;
137
    }
138
139 13
    /**
140 4
     * Handle ttl parameter.
141
     * 
142
     * @param null|int|\DateInterval $ttl   Optional. The TTL value of this item. If no value is sent and
143 9
     *                                      the driver supports TTL then the library may set a default value
144
     *                                      for it or let the driver take care of that.
145
     * 
146
     * @return int Ttl in seconds.
147
     */
148
    private function handleTtl(DateInterval|int|null $ttl): int
149
    {
150
        if ($ttl == null){
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $ttl of type DateInterval|integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
151
            return 0;
152
        }
153 3
        if (is_int($ttl)) {
154
            return $ttl;
155
        }
156 3
        if ($ttl instanceof DateInterval) {
0 ignored issues
show
introduced by
$ttl is always a sub-type of DateInterval.
Loading history...
157
            $now = new \DateTime();
158
            $now->add($ttl);
159 3
            return (int) $now->format('U');
160 2
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 156 is false. This is incompatible with the type-hinted return integer. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
161
    }
162 2
    
163
    /**
164
     * Calculate ttl for cache file.
165 1
     *
166
     * @param int $ttl
167
     *
168
     * @return int
169
     */
170
    private function calculateTtl(int $ttl): int
171
    {
172
        //check for usage of ttl default class option value
173 51
        if ($ttl) {
174
            return \time() + $ttl;
175 51
        }
176
177 51
        return $ttl;
178
    }
179
180
    /**
181
     * Delete an item from the cache by its unique key.
182
     *
183
     * @param string $key The unique cache key of the item to delete.
184
     *
185
     * @return bool True if the item was successfully removed. False if there was an error.
186
     *
187
     * @throws \Psr\SimpleCache\InvalidArgumentException
188
     *   MUST be thrown if the $key string is not a legal value.
189
     */
190
    public function delete(string $key): bool
191
    {
192 10
        //create file name
193
        $file = $this->dir.'/'.\sha1($key).'.php';
194 10
195
        //chek if file exist and delete
196
        if (\file_exists($file)) {
197
            \unlink($file);
198
199
            return true;
200
        }
201
202
        return false;
203
    }
204
205
    /**
206
     * Wipes clean the entire cache's keys.
207
     *
208
     * @return bool True on success and false on failure.
209
     */
210
    public function clear(): bool
211
    {
212
        \array_map('unlink', (array) \glob($this->dir.'/*.php'));
213
214
        return true;
215
    }
216
217
    /**
218
     * Determines whether an item is present in the cache.
219
     *
220
     * NOTE: It is recommended that has() is only to be used for cache warming type purposes
221
     * and not to be used within your live applications operations for get/set, as this method
222
     * is subject to a race condition where your has() will return true and immediately after,
223
     * another script can remove it making the state of your app out of date.
224
     *
225
     * @param string $key The cache item key.
226
     *
227
     * @return bool
228
     *
229
     * @throws \Psr\SimpleCache\InvalidArgumentException
230
     *   MUST be thrown if the $key string is not a legal value.
231
     */
232
    public function has(string $key): bool
233
    {
234
        return !$this->doesFileChecksFailed($this->dir.'/'.\sha1($key).'.php');
235
    }
236
}
237