Passed
Push — 1.0.0-dev ( 632010...3e5cd9 )
by nguereza
02:59
created

FileCache::getInfo()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 27
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 6
eloc 22
nc 4
nop 1
dl 0
loc 27
rs 8.9457
c 4
b 1
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 GNU GPL License (GPL)
9
     *
10
     * Copyright (C) 2017 Tony NGUEREZA
11
     *
12
     * This program is free software; you can redistribute it and/or
13
     * modify it under the terms of the GNU General Public License
14
     * as published by the Free Software Foundation; either version 3
15
     * of the License, or (at your option) any later version.
16
     *
17
     * This program is distributed in the hope that it will be useful,
18
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
     * GNU General Public License for more details.
21
     *
22
     * You should have received a copy of the GNU General Public License
23
     * along with this program; if not, write to the Free Software
24
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25
     */
26
27
    class FileCache extends BaseClass implements CacheInterface {
28
		
29
        /**
30
         * Whether to enable compression of the cache data file.
31
         * @var boolean
32
         */
33
        private $compressCacheData = true;
34
35
        /**
36
         * The path to file cache data
37
         * @var string
38
         */
39
        private $cacheFilePath = null;
40
41
        /**
42
         * Class constructor
43
         * @param string|null $cacheFilePath the path to save cache data
44
         */
45
        public function __construct($cacheFilePath = null) {
46
            parent::__construct();
47
            $this->setCacheFilePath($cacheFilePath);
48
            if (!$this->isSupported()) {
49
                show_error('The cache for file system is not available. Check the cache directory if is exists or is writable.');
50
            }
51
			
52
            //if Zlib extension is not loaded set compressCacheData to false
53
            if (!extension_loaded('zlib')) {
54
                $this->logger->warning('The zlib extension is not loaded set cache compress data to FALSE');
55
                $this->compressCacheData = false;
56
            }
57
        }
58
59
        /**
60
         * This is used to get the cache data using the key
61
         * @param  string $key the key to identify the cache data
62
         * @return mixed      the cache data if exists else return false
63
         */
64
        public function get($key) {
65
            $this->logger->debug('Getting cache data for key [' . $key . ']');
66
            $filePath = $this->getFilePath($key);
67
            if (!file_exists($filePath)) {
68
                $this->logger->info('No cache file found for the key [' . $key . '], return false');
69
                return false;
70
            }
71
            $this->logger->info('The cache file [' . $filePath . '] for the key [' . $key . '] exists, check if the cache data is valid');
72
            $handle = fopen($filePath, 'r');
73
            if (!is_resource($handle)) {
74
                $this->logger->error('Can not open the file cache [' . $filePath . '] for the key [' . $key . '], return false');
75
                return false;
76
            }
77
            // Getting a shared lock 
78
            flock($handle, LOCK_SH);
79
            $data = file_get_contents($filePath);
80
            fclose($handle);
81
            if ($this->compressCacheData) {
82
                $data = gzinflate($data);
83
            }   
84
            $data = @unserialize($data);
85
            if (!$data) {
86
                $this->logger->error('Can not unserialize the cache data for the key [' . $key . '], return false');
87
                // If unserializing somehow didn't work out, we'll delete the file
88
                unlink($filePath);
89
                return false;
90
            }
91
            if (time() > $data['expire']) {
92
                $this->logger->info('The cache data for the key [' . $key . '] already expired delete the cache file [' . $filePath . ']');
93
            // Unlinking when the file was expired
94
            unlink($filePath);
95
            return false;
96
            } 
97
            $this->logger->info('The cache not yet expire, now return the cache data for key [' . $key . '], the cache will expire at [' . date('Y-m-d H:i:s', $data['expire']) . ']');
98
            return $data['data'];
99
        }
100
101
102
        /**
103
         * Save data to the cache
104
         * @param string  $key  the key to identify this cache data
105
         * @param mixed  $data the cache data
106
         * @param integer $ttl  the cache life time
107
         * @return boolean true if success otherwise will return false
108
         */
109
        public function set($key, $data, $ttl = 0) {
110
            $expire = time() + $ttl;
111
            $this->logger->debug('Setting cache data for key [' . $key . '], time to live [' . $ttl . '], expire at [' . date('Y-m-d H:i:s', $expire) . ']');
112
            $filePath = $this->getFilePath($key);
113
            $handle = fopen($filePath, 'w');
114
            if (!is_resource($handle)) {
115
                $this->logger->error('Can not open the file cache [' . $filePath . '] for the key [' . $key . '], return false');
116
                return false;
117
            }
118
            flock($handle, LOCK_EX); // exclusive lock, will get released when the file is closed
119
            //Serializing along with the TTL
120
            $cacheData = serialize(array(
121
                                    'mtime' => time(),
122
                                    'expire' => $expire,
123
                                    'data' => $data,
124
                                    'ttl' => $ttl
125
                                    )
126
                                );	
127
            if ($this->compressCacheData) {
128
                $cacheData = gzdeflate($cacheData, 9);
129
            }	   
130
            $result = fwrite($handle, $cacheData);
131
            if (!$result) {
132
                $this->logger->error('Can not write cache data into file [' . $filePath . '] for the key [' . $key . '], return false');
133
                fclose($handle);
134
                return false;
135
            } 
136
            $this->logger->info('Cache data saved into file [' . $filePath . '] for the key [' . $key . ']');
137
            fclose($handle);
138
            chmod($filePath, 0640);
139
            return true;
140
        }	
141
142
143
        /**
144
         * Delete the cache data for given key
145
         * @param  string $key the key for cache to be deleted
146
         * @return boolean      true if the cache is delete, false if can't delete 
147
         * the cache or the cache with the given key not exist
148
         */
149
        public function delete($key) {
150
            $this->logger->debug('Deleting of cache data for key [' . $key . ']');
151
            $filePath = $this->getFilePath($key);
152
            $this->logger->info('The file path for the key [' . $key . '] is [' . $filePath . ']');
153
            if (!file_exists($filePath)) {
154
                $this->logger->info('This cache file does not exists skipping');
155
                return false;
156
            } 
157
            $this->logger->info('Found cache file [' . $filePath . '] remove it');
158
            return unlink($filePath);
159
        }
160
		
161
        /**
162
         * Get the cache information for given key
163
         * @param  string $key the key for cache to get the information for
164
         * @return boolean|array    the cache information. The associative array and must contains the following information:
165
         * 'mtime' => creation time of the cache (Unix timestamp),
166
         * 'expire' => expiration time of the cache (Unix timestamp),
167
         * 'ttl' => the time to live of the cache in second
168
         */
169
        public function getInfo($key) {
170
            $this->logger->debug('Getting of cache info for key [' . $key . ']');
171
            $filePath = $this->getFilePath($key);
172
            $this->logger->info('The file path for the key [' . $key . '] is [' . $filePath . ']');
173
            if (!file_exists($filePath)) {
174
                $this->logger->info('This cache file does not exists skipping');
175
                return false;
176
            }
177
            $this->logger->info('Found cache file [' . $filePath . '] check the validity');
178
                $data = file_get_contents($filePath);
179
            $data = @unserialize($this->compressCacheData ? gzinflate($data) : $data);
180
            if (!$data) {
181
                $this->logger->warning('Can not unserialize the cache data for file [' . $filePath . ']');
182
                return false;
183
            }
184
            $this->logger->info('This cache data is OK check for expire');
185
            if (isset($data['expire']) && $data['expire'] > time()) {
186
                $this->logger->info('This cache not yet expired return cache informations');
187
                $info = array(
188
                    'mtime' => $data['mtime'],
189
                    'expire' => $data['expire'],
190
                    'ttl' => $data['ttl']
191
                    );
192
                return $info;
193
            }
194
            $this->logger->info('This cache already expired return false');
195
            return false;
196
        }
197
198
199
        /**
200
         * Used to delete expired cache data
201
         */
202
        public function deleteExpiredCache() {
203
            $this->logger->debug('Deleting of expired cache files');
204
            $list = glob($this->cacheFilePath . '*.cache');
205
            if (!$list) {
206
                $this->logger->info('No cache files were found skipping');
207
            } else {
208
                $this->logger->info('Found [' . count($list) . '] cache files to remove if expired');
209
                foreach ($list as $file) {
210
                    $this->logger->debug('Processing the cache file [' . $file . ']');
211
                    $data = file_get_contents($file);
212
                        $data = @unserialize($this->compressCacheData ? gzinflate($data) : $data);
213
                        if (!$data) {
214
                            $this->logger->warning('Can not unserialize the cache data for file [' . $file . ']');
215
                        } else if (time() > $data['expire']) {
216
                            $this->logger->info('The cache data for file [' . $file . '] already expired remove it');
217
                            unlink($file);
218
                        } else {
219
                            $this->logger->info('The cache data for file [' . $file . '] not yet expired skip it');
220
                        }
221
                }
222
            }
223
        }	
224
225
        /**
226
         * Remove all file from cache folder
227
         */
228
        public function clean() {
229
            $this->logger->debug('Deleting of all cache files');
230
            $list = glob($this->cacheFilePath . '*.cache');
231
            if (!$list) {
232
                $this->logger->info('No cache files were found skipping');
233
            } else {
234
                $this->logger->info('Found [' . count($list) . '] cache files to remove');
235
                foreach ($list as $file) {
236
                    $this->logger->debug('Processing the cache file [' . $file . ']');
237
                    unlink($file);
238
                }
239
            }
240
        }
241
	
242
        /**
243
         * @return boolean
244
         */
245
        public function isCompressCacheData() {
246
            return $this->compressCacheData;
247
        }
248
249
        /**
250
         * @param boolean $compressCacheData
251
         *
252
         * @return object
253
         */
254
        public function setCompressCacheData($status = true) {
255
            //if Zlib extension is not loaded set compressCacheData to false
256
            if ($status === true && !extension_loaded('zlib')) {
257
				
258
                $this->logger->warning('The zlib extension is not loaded set cache compress data to FALSE');
259
                $this->compressCacheData = false;
260
            } else {
261
                $this->compressCacheData = $status;
262
            }
263
            return $this;
264
        }
265
		
266
        /**
267
         * Check whether the cache feature for the handle is supported
268
         *
269
         * @return bool
270
         */
271
        public function isSupported() {
272
            return $this->cacheFilePath 
273
                    && is_dir($this->cacheFilePath) 
274
                    && is_writable($this->cacheFilePath);
275
        }
276
277
        /**
278
         * Set the cache file path used to save the cache data
279
         * @param string|null $path the file path if null will use the constant CACHE_PATH
280
         *
281
         * @return object the current instance
282
         */
283
        public function setCacheFilePath($path = null) {
284
            if ($path !== null) {
285
                if (substr($path, -1) != DS) {
286
                    $path .= DS;
287
                }
288
                $this->cacheFilePath = $path;
289
                return $this;
290
            }
291
            $this->cacheFilePath = CACHE_PATH;
292
            return $this;
293
        }
294
295
	
296
        /**
297
         * Get the cache file full path for the given key
298
         *
299
         * @param string $key the cache item key
300
         * @return string the full cache file path for this key
301
         */
302
        private function getFilePath($key) {
303
            return $this->cacheFilePath . md5($key) . '.cache';
304
        }
305
    }
306