Completed
Push — master ( b5b8be...5ff268 )
by Peter
13:31
created

PhpCache::set()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 12
ccs 9
cts 9
cp 1
rs 9.4285
cc 1
eloc 9
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * To change this license header, choose License Headers in Project Properties.
5
 * To change this template file, choose Tools | Templates
6
 * and open the template in the editor.
7
 */
8
9
namespace Maslosoft\Addendum\Cache;
10
11
use FilesystemIterator;
12
use Maslosoft\Addendum\Addendum;
13
use Maslosoft\Addendum\Helpers\SoftIncluder;
14
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
15
use Maslosoft\Addendum\Options\MetaOptions;
16
use Maslosoft\Cli\Shared\ConfigDetector;
17
use Maslosoft\Cli\Shared\Helpers\PhpExporter;
18
use RecursiveDirectoryIterator;
19
use RecursiveIteratorIterator;
20
use ReflectionClass;
21
use RuntimeException;
22
use UnexpectedValueException;
23
24
/**
25
 * PhpCache
26
 *
27
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
28
 */
29
abstract class PhpCache
30
{
31
32
	private $metaClass = null;
33
	private $component = null;
34
35
	/**
36
	 * Options
37
	 * @var string
38
	 */
39
	private $instanceId = null;
40
41
	/**
42
	 * Addendum runtime path
43
	 * @var string
44
	 */
45
	private $path = '';
46
47
	/**
48
	 *
49
	 * @var NsCache
50
	 */
51
	private $nsCache = null;
52
53
	/**
54
	 *
55
	 * @var Addendum
56
	 */
57
	private $addendum = null;
58
59
	/**
60
	 * Runtime path
61
	 * @var string
62
	 */
63
	private static $runtimePath = null;
64
65
	/**
66
	 * Local cacheq
67
	 * @var type
68
	 */
69
	private static $cache = [];
70
71
	/**
72
	 * Hash map of prepared directories
73
	 * Key is directory, value is flag indicating if it's prepared.
74
	 * @var bool[]
75
	 */
76
	private static $prepared = [];
77
	private $fileName = null;
78
79
	/**
80
	 *
81
	 * @param string $metaClass
82
	 * @param AnnotatedInterface|string $component
83
	 * @param MetaOptions|Addendum $options
84
	 */
85 49
	public function __construct($metaClass = null, $component = null, $options = null)
86
	{
87 49
		if (null === self::$runtimePath)
88 49
		{
89 1
			self::$runtimePath = (new ConfigDetector)->getRuntimePath();
90 1
		}
91 49
		$this->path = self::$runtimePath . '/addendum';
92 49
		$this->metaClass = $metaClass;
93 49
		$this->component = $component;
94 49
		if (empty($options))
95 49
		{
96 3
			$this->instanceId = Addendum::DefaultInstanceId;
97 3
		}
98 47
		elseif ($options instanceof Addendum)
99
		{
100 47
			$this->instanceId = $options->getInstanceId();
101 47
		}
102
		elseif ($options instanceof MetaOptions)
103
		{
104
			$this->instanceId = $options->instanceId;
105
		}
106
		else
107
		{
108
			throw new UnexpectedValueException('Unknown options');
109
		}
110 49
		$this->prepare();
111 49
		$this->addendum = Addendum::fly($this->instanceId);
112 49
		$this->nsCache = new NsCache(dirname($this->getFilename()), $this->addendum);
113 49
	}
114
115
	/**
116
	 * Set working component
117
	 * @param AnnotatedInterface|string $component
118
	 */
119 51
	public function setComponent($component = null)
120
	{
121
		// Reset filename as it depends on component
122 51
		$this->fileName = null;
123 51
		$this->component = $component;
124 51
	}
125
126 33
	public function setOptions(MetaOptions $options = null)
127
	{
128 33
		$this->fileName = null;
129 33
		$this->nsCache->setOptions($options);
130 33
	}
131
132 53
	private function prepare()
133
	{
134 53
		$fileDir = dirname($this->getFilename());
135 53
		if (isset(self::$prepared[$fileDir]) && self::$prepared[$fileDir])
136 53
		{
137 51
			return true;
138
		}
139 5
		if (!file_exists($this->path))
140 5
		{
141 1
			if (!file_exists(self::$runtimePath))
142 1
			{
143
144 1
				if (is_writable(dirname(self::$runtimePath)))
145 1
				{
146 1
					$this->mkdir(self::$runtimePath);
147 1
				}
148 1
				if (!is_writable(self::$runtimePath))
149 1
				{
150
					throw new RuntimeException(sprintf("Runtime path `%s` must exists and be writable", self::$runtimePath));
151
				}
152 1
			}
153 1
			if (is_writable(self::$runtimePath))
154 1
			{
155 1
				$this->mkdir($this->path);
156 1
			}
157 1
			if (!is_writable($this->path))
158 1
			{
159
				throw new RuntimeException(sprintf("Addendum runtime path `%s` must exists and be writable", $this->path));
160
			}
161 1
		}
162 5
		if (!file_exists($fileDir))
163 5
		{
164 2
			$this->mkdir($fileDir);
165 2
		}
166 5
		self::$prepared[$fileDir] = true;
167 5
	}
168
169 51
	public function get()
170
	{
171 51
		$this->prepare();
172 51
		$fileName = $this->getFilename();
173
174 51
		if (!NsCache::$addeNs)
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
175 51
		{
176
//			codecept_debug('Skip cache check');
177
//			var_dump('Will skip cache check');
178
//			exit;
179 51
		}
180 51
		if (NsCache::$addeNs && !$this->nsCache->valid())
181 51
		{
182 1
			$this->clearCurrentPath();
183 1
			return false;
184
		}
185 51
		$key = $this->getCacheKey();
186 51
		if (isset(self::$cache[$key]))
187 51
		{
188 51
			return self::$cache[$key];
189
		}
190
191 46
		$data = SoftIncluder::includeFile($fileName);
192
193
		// Only false means not existing cache.
194
		// NOTE: Cache might have valid `empty` value, ie. empty array.
195 46
		if (false === $data)
196 46
		{
197 46
			return false;
198
		}
199
200
		// Purge file cache if checkMTime is enabled and file obsolete
201
		if ($this->addendum->checkMTime && file_exists($fileName))
202
		{
203
			$cacheTime = filemtime($fileName);
204
205
			// Partial component name, split by @ and take first argument
206
			if (is_string($this->component) && strstr($this->component, '@'))
207
			{
208
				$parts = explode('@', $this->component);
209
				$componentClass = array_shift($parts);
210
			}
211
			else
212
			{
213
				$componentClass = $this->component;
214
			}
215
			$componentTime = filemtime((new ReflectionClass($componentClass))->getFileName());
216
			if ($componentTime > $cacheTime)
217
			{
218
				$this->remove();
219
				return false;
220
			}
221
		}
222
		self::$cache[$key] = $data;
223
		return $data;
224
	}
225
226 46
	public function set($data)
227
	{
228 46
		$fileName = $this->getFilename();
229 46
		$this->prepare();
230 46
		$key = $this->getCacheKey();
231 46
		self::$cache[$key] = $data;
232
233 46
		file_put_contents($fileName, PhpExporter::export($data));
234 46
		@chmod($fileName, 0666);
235 46
		$this->nsCache->set();
236 46
		return $data;
237
	}
238
239
	public function remove()
240
	{
241
		$fileName = $this->getFilename();
242
		$key = $this->getCacheKey();
243
		unset(self::$cache[$key]);
244
		if (file_exists($fileName))
245
		{
246
			return unlink($fileName);
247
		}
248
		return false;
249
	}
250
251
	/**
252
	 * Clear entire cache
253
	 * @return boolean
254
	 */
255 3
	public function clear()
256
	{
257 3
		self::$prepared = [];
258 3
		return $this->clearPath($this->path);
259
	}
260
261 1
	private function clearCurrentPath()
262
	{
263 1
		$path = dirname($this->getFilename());
264 1
		return $this->clearPath($path);
265
	}
266
267 4
	private function clearPath($path)
268
	{
269 4
		if (!file_exists($path))
270 4
		{
271
			return false;
272
		}
273 4
		foreach (new \DirectoryIterator($path) as $dir)
274
		{
275 4
			if ($dir->isDot() || !$dir->isDir())
276 4
			{
277 4
				continue;
278
			}
279 3
			foreach (new \DirectoryIterator($dir->getPathname()) as $file)
280
			{
281 3
				if (!$file->isFile())
282 3
				{
283 3
					continue;
284
				}
285
286
				// Skip ns cache file, or it might regenerate over and over
287
				// ns file cache is replaced when needed by NsCache
288 2
				if ($file->getBasename() === NsCache::FileName)
289 2
				{
290 2
					continue;
291
				}
292 1
				unlink($file->getPathname());
293 3
			}
294 4
		}
295 4
		self::$prepared[$path] = false;
296 4
	}
297
298 53
	private function getFilename()
299
	{
300 53
		if (!empty($this->fileName))
301 53
		{
302 53
			return $this->fileName;
303
		}
304 53
		if (is_object($this->component))
305 53
		{
306 33
			$className = get_class($this->component);
307 33
		}
308
		else
309
		{
310 49
			$className = $this->component;
311
		}
312 53
		$this->fileName = sprintf('%s/%s@%s/%s.php', $this->path, $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className)));
313 53
		return $this->fileName;
314
	}
315
316 51
	private function getCacheKey()
317
	{
318 51
		if (is_object($this->component))
319 51
		{
320 33
			$className = get_class($this->component);
321 33
		}
322
		else
323
		{
324 47
			$className = $this->component;
325
		}
326 51
		return sprintf('%s@%s@%s@%s.php', $this->classToFile(static::class), $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className)));
327
	}
328
329
	/**
330
	 * Convert slash separated class name to dot separated name.
331
	 * @param string $className
332
	 * @return string
333
	 */
334 53
	private function classToFile($className)
335
	{
336 53
		return str_replace('\\', '.', $className);
337
	}
338
339
	/**
340
	 * Recursively create dir with proper permissions.
341
	 *
342
	 * @param string $path
343
	 */
344 2
	private function mkdir($path)
345
	{
346 2
		$mask = umask(0000);
347 2
		mkdir($path, 0777, true);
348 2
		umask($mask);
349 2
	}
350
351
}
352