xref: /web-bugs/src/Utils/Cache.php (revision ae57162e)
1<?php
2
3namespace App\Utils;
4
5/**
6 * A simple cache service for storing data in file(s).
7 */
8class Cache
9{
10    /**
11     * Pool of items.
12     *
13     * @var array
14     */
15    private $pool = [];
16
17    /**
18     * Temporary cache directory where data is stored.
19     *
20     * @var string
21     */
22    private $dir;
23
24    /**
25     * Default time after cache file is considered expired in seconds.
26     */
27    public const TTL = 3600;
28
29    /**
30     * Class constructor.
31     */
32    public function __construct(string $dir)
33    {
34        $this->dir = $dir;
35
36        // Create cache directory if it doesn't exist.
37        if (!file_exists($this->dir)) {
38            mkdir($this->dir, 0777, true);
39            chmod($this->dir, 0777);
40        }
41
42        // Validate cache directory
43        if (!is_dir($this->dir)) {
44            throw new \Exception($this->dir.' is not a valid directory.');
45        }
46    }
47
48    /**
49     * Write data to cache file.
50     */
51    public function set(string $key, $data, int $ttl = self::TTL): void
52    {
53        if (!$this->validateKey($key)) {
54            throw new Exception('Key name '.$key.' is invalid.');
55        }
56
57        $item = [time() + $ttl, serialize($data)];
58        $this->pool[$key] = $data;
59
60        $string = '<?php return '.var_export($item, true).";\n";
61
62        file_put_contents($this->dir.'/'.$key.'.php', $string);
63    }
64
65    /**
66     * Check if item has been cached and is available.
67     */
68    public function has(string $key): bool
69    {
70        if (isset($this->pool[$key])) {
71            return true;
72        }
73
74        $file = $this->dir.'/'.$key.'.php';
75
76        if (!is_file($file)) {
77            return false;
78        }
79
80        $data = require $file;
81
82        if (is_array($data) && isset($data[0]) && is_int($data[0]) && time() < $data[0]) {
83            return true;
84        }
85
86        return false;
87    }
88
89    /**
90     * Get data from the cache pool.
91     */
92    public function get(string $key): ?array
93    {
94        if (isset($this->pool[$key])) {
95            return $this->pool[$key];
96        }
97
98        $file = $this->dir.'/'.$key.'.php';
99
100        if (is_file($file)) {
101            $data = require $file;
102            $this->pool[$key] = unserialize($data[1]);
103
104            return $this->pool[$key];
105        }
106
107        return null;
108    }
109
110    /**
111     * Wipes entire cache.
112     */
113    public function clear(): bool
114    {
115        $success = true;
116
117        $this->pool = [];
118
119        foreach (new \DirectoryIterator($this->dir) as $fileInfo) {
120            if ($fileInfo->isDot()) {
121                continue;
122            }
123
124            if (!unlink($fileInfo->getRealPath())) {
125                $success = false;
126            }
127        }
128
129        return $success;
130    }
131
132    /**
133     * Delete item from the cache.
134     */
135    public function delete(string $key): bool
136    {
137        $success = true;
138
139        unset($this->pool[$key]);
140
141        $file = $this->dir.'/'.$key.'.php';
142
143        if (is_file($file) && !unlink($file)) {
144            $success = false;
145        }
146
147        return $success;
148    }
149
150    /**
151     * Validate key.
152     */
153    private function validateKey(string $key): bool
154    {
155        return (bool) preg_match('/[a-z\_\-0-9]/i', $key);
156    }
157}
158