Test Failed
Push — 1.0.0-dev ( 52e7b8...740ad8 )
by nguereza
02:33
created

FileCache::getFilePath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 1
b 0
f 0
1
<?php
2
    defined('ROOT_PATH') or exit('Access denied');
3
    /**
4
     * TNH Framework
5
     *
6
     * A simple PHP framework using HMVC architecture
7
     *
8
     * This content is released under the MIT License (MIT)
9
     *
10
     * Copyright (c) 2017 TNH Framework
11
     *
12
     * Permission is hereby granted, free of charge, to any person obtaining a copy
13
     * of this software and associated documentation files (the "Software"), to deal
14
     * in the Software without restriction, including without limitation the rights
15
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
     * copies of the Software, and to permit persons to whom the Software is
17
     * furnished to do so, subject to the following conditions:
18
     *
19
     * The above copyright notice and this permission notice shall be included in all
20
     * copies or substantial portions of the Software.
21
     *
22
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
     * SOFTWARE.
29
     */
30
31
    class FileCache extends BaseClass implements CacheInterface {
32
		
33
        /**
34
         * Whether to enable compression of the cache data file.
35
         * Note: extension zlib must be availabe for this to work
36
         * @var boolean
37
         */
38
        private $compressCacheData = true;
39
40
        /**
41
         * The path of saved file cache
42
         * @var string
43
         */
44
        private $cacheFilePath = null;
45
46
        /**
47
         * Class constructor
48
         * @param string|null $cacheFilePath the path to save cache data
49
         */
50
        public function __construct($cacheFilePath = null) {
51
            parent::__construct();
52
            $this->setCacheFilePath($cacheFilePath);
53
            if (!$this->isSupported()) {
54
                show_error('The cache for file system is not available. Check the cache directory if is exist or is writable.');
55
            }
56
			
57
            //if Zlib extension is not loaded set compressCacheData to false
58
            if (!extension_loaded('zlib')) {
59
                $this->logger->warning('The zlib extension is not loaded set cache compress data to false');
60
                $this->compressCacheData = false;
61
            }
62
        }
63
64
        /**
65
         * This is used to get the cache data using the key
66
         * 
67
         * @param  string $key the key to identify the cache data
68
         * 
69
         * @return mixed      the cache data if exists else return false
70
         */
71
        public function get($key) {
72
            $this->logger->debug('Getting cache data for key [' . $key . ']');
73
            $filePath = $this->getFilePath($key);
74
            if (!file_exists($filePath)) {
75
                $this->logger->info('No cache file found for the key [' . $key . '], return false');
76
                return false;
77
            }
78
            $this->logger->info('The cache file [' . $filePath . '] for the key [' . $key . '] exists, check if the cache data is valid');
79
            $data = $this->getCacheFileContent($filePath);
80
            if ($data === false) {
81
                $this->logger->error('The cache data for the key [' . $key . '] is not valid, return false');
82
                // If unserializing somehow didn't work out, we'll delete the file
83
                unlink($filePath);
84
                return false;
85
            }
86
            if (time() > $data['expire']) {
87
                $this->logger->info('The cache data for the key [' . $key . '] already expired delete the cache file [' . $filePath . ']');
88
                // Unlinking when the file was expired
89
                unlink($filePath);
90
                return false;
91
            } 
92
            $this->logger->info('The cache not yet expire, now return the cache data '
93
                                . 'for key [' . $key . '], the cache will expire ' 
94
                                . 'at [' . date('Y-m-d H:i:s', $data['expire']) . ']');
95
            return $data['data'];
96
        }
97
98
99
        /**
100
         * Save data to the cache
101
         * 
102
         * @param string  $key  the key to identify this cache data
103
         * @param mixed  $data the cache data
104
         * @param integer $ttl  the cache life time
105
         * 
106
         * @return boolean true if success otherwise will return false
107
         */
108
        public function set($key, $data, $ttl = 0) {
109
            $expire = time() + $ttl;
110
            $this->logger->debug('Setting cache data for key [' . $key . '], '
111
                           . 'time to live [' . $ttl . '], '
112
                           . 'expire at [' . date('Y-m-d H:i:s', $expire) . ']');
113
            $filePath = $this->getFilePath($key);
114
            $handle = fopen($filePath, 'w');
115
            if (!is_resource($handle)) {
116
                $this->logger->error('Can not open the file cache '
117
                                     . '[' . $filePath . '] for the key [' . $key . '], return false');
118
                return false;
119
            }
120
            flock($handle, LOCK_EX); // exclusive lock, will get released when the file is closed
121
            //Serializing along with the TTL
122
            $cacheData = serialize(array(
123
                                    'mtime' => time(),
124
                                    'expire' => $expire,
125
                                    'data' => $data,
126
                                    'ttl' => $ttl
127
                                    ));	
128
            if ($this->compressCacheData) {
129
                $cacheData = gzdeflate($cacheData, 9);
130
            }	   
131
            $result = fwrite($handle, $cacheData);
132
            if (!$result) {
133
                $this->logger->error('Can not write cache data into file [' . $filePath . '] '
134
                                    . 'for the key [' . $key . '], return false');
135
                fclose($handle);
136
                return false;
137
            } 
138
            $this->logger->info('Cache data saved into file [' . $filePath . '] for the key [' . $key . ']');
139
            fclose($handle);
140
            chmod($filePath, 0640);
141
            return true;
142
        }	
143
144
145
        /**
146
         * Delete the cache data for given key
147
         * 
148
         * @param  string $key the key for cache to be deleted
149
         * 
150
         * @return boolean      true if the cache is delete, false if can't delete 
151
         * the cache or the cache with the given key not exist
152
         */
153
        public function delete($key) {
154
            $this->logger->debug('Deleting of cache data for key [' . $key . ']');
155
            $filePath = $this->getFilePath($key);
156
            $this->logger->info('The file path for the key [' . $key . '] is [' . $filePath . ']');
157
            if (!file_exists($filePath)) {
158
                $this->logger->info('The cache file does not exists skipping');
159
                return false;
160
            } 
161
            $this->logger->info('Found cache file [' . $filePath . '] remove it');
162
            return unlink($filePath);
163
        }
164
		
165
        /**
166
         * Get the cache information for given key
167
         * 
168
         * @param  string $key the key for cache to get the information for
169
         * 
170
         * @return boolean|array    the cache information. The associative 
171
         * array and must contains the following information:
172
         * 'mtime' => creation time of the cache (Unix timestamp),
173
         * 'expire' => expiration time of the cache (Unix timestamp),
174
         * 'ttl' => the time to live of the cache in second
175
         */
176
        public function getInfo($key) {
177
            $this->logger->debug('Getting of cache info for key [' . $key . ']');
178
            $filePath = $this->getFilePath($key);
179
            $this->logger->info('The file path for the key [' . $key . '] is [' . $filePath . ']');
180
            if (!file_exists($filePath)) {
181
                $this->logger->info('This cache file does not exists skipping');
182
                return false;
183
            }
184
            $this->logger->info('Found cache file [' . $filePath . '] check the validity');
185
            $data = $this->getCacheFileContent($filePath);
186
            if ($data === false) {
187
                $this->logger->warning('Can not get the cache data for file [' . $filePath . ']');
188
                return false;
189
            }
190
            $this->logger->info('This cache data is OK check for expire');
191
            if ($data['expire'] > time()) {
192
                $this->logger->info('This cache not yet expired return cache informations');
193
                $info = array(
194
                    'mtime' => $data['mtime'],
195
                    'expire' => $data['expire'],
196
                    'ttl' => $data['ttl']
197
                    );
198
                return $info;
199
            }
200
            $this->logger->info('This cache already expired return false');
201
            return false;
202
        }
203
204
        /**
205
         * Used to delete expired cache data
206
         * @see  CacheInterface::deleteExpiredCache
207
         */
208
        public function deleteExpiredCache() {
209
            $this->logger->debug('Deleting of expired cache files');
210
            $list = glob($this->cacheFilePath . '*.cache');
211
            if (!$list) {
212
                $this->logger->info('No cache files were found skipping');
213
                return;
214
            }
215
            $this->logger->info('Found [' . count($list) . '] cache files to remove if expired');
216
            foreach ($list as $file) {
217
                $this->logger->debug('Processing the cache file [' . $file . ']');
218
                $data = $this->getCacheFileContent($file);
219
                if ($data === false) {
220
                    $this->logger->warning('Can not get cache data for file [' . $file . ']');
221
                } else if (time() > $data['expire']) {
222
                    $this->logger->info('The cache data for file [' . $file . '] already expired remove it');
223
                    unlink($file);
224
                } else {
225
                    $this->logger->info('The cache data for file [' . $file . '] not yet expired skip it');
226
                }
227
            }
228
        }	
229
230
        /**
231
         * Remove all file from cache folder
232
         * @see  CacheInterface::clean
233
         */
234
        public function clean() {
235
            $this->logger->debug('Deleting of all cache files');
236
            $list = glob($this->cacheFilePath . '*.cache');
237
            if (!$list) {
238
                $this->logger->info('No cache files were found skipping');
239
                return;
240
            }
241
            $this->logger->info('Found [' . count($list) . '] cache files to remove');
242
            foreach ($list as $file) {
243
                $this->logger->debug('Processing the cache file [' . $file . ']');
244
                unlink($file);
245
            }
246
        }
247
	
248
        /**
249
         * Whether the compression is enabled or not
250
         * 
251
         * @return boolean
252
         */
253
        public function isCompressCacheData() {
254
            return $this->compressCacheData;
255
        }
256
257
        /**
258
         * Set the compression data status
259
         * Note: this is set to "true" only if zlib extension is loaded and enabled
260
         * 
261
         * @param boolean $compressCacheData
262
         *
263
         * @return object
264
         */
265
        public function setCompressCacheData($status = true) {
266
            //if zlib extension is not loaded set compressCacheData to false
267
            if ($status === true && !extension_loaded('zlib')) {
268
                $this->logger->warning('The zlib extension is not loaded set cache compress data to false');
269
                $this->compressCacheData = false;
270
            } else {
271
                $this->compressCacheData = $status;
272
            }
273
            return $this;
274
        }
275
		
276
        /**
277
         * Check whether the cache feature for the handle is supported
278
         *
279
         * @return bool
280
         */
281
        public function isSupported() {
282
            return $this->cacheFilePath 
283
                    && is_dir($this->cacheFilePath) 
284
                    && is_writable($this->cacheFilePath);
285
        }
286
287
        /**
288
         * Set the cache file path used to save the cache data
289
         * 
290
         * @param string|null $path the file path if null will use the constant CACHE_PATH
291
         *
292
         * @return object the current instance
293
         */
294
        public function setCacheFilePath($path = null) {
295
            if ($path !== null) {
296
                if (substr($path, -1) != DS) {
297
                    $path .= DS;
298
                }
299
                $this->cacheFilePath = $path;
300
                return $this;
301
            }
302
            $this->cacheFilePath = CACHE_PATH;
303
            return $this;
304
        }
305
306
        /**
307
         * Get the cache file content for the given path
308
         * @param  string $filePath the file path
309
         * @return boolean|array           the file cache content
310
         */
311
        protected function getCacheFileContent($filePath) {
312
            $data = file_get_contents($filePath);
313
            if ($this->compressCacheData) {
314
                $data = gzinflate($data);
315
            }
316
            $data = @unserialize($data);
317
            if (!$data) {
318
                $this->logger->warning('Can not unserialize the cache data for file [' . $filePath . ']');
319
                return false;
320
            }
321
            return $data;
322
        }
323
324
	
325
        /**
326
         * Get the cache file full path for the given key
327
         *
328
         * @param string $key the cache item key
329
         * 
330
         * @return string the full cache file path for this key
331
         */
332
        private function getFilePath($key) {
333
            return $this->cacheFilePath . md5($key) . '.cache';
334
        }
335
    }
336