Completed
Push — master ( 5ff268...22b422 )
by Peter
03:58
created

PhpCache::get()   C

Complexity

Conditions 10
Paths 8

Size

Total Lines 50
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 22.5

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 50
ccs 12
cts 24
cp 0.5
rs 5.7647
cc 10
eloc 25
nc 8
nop 0
crap 22.5

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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