Completed
Push — master ( 951353...997566 )
by Peter
05:24
created

PhpCache::clearCurrentPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 5
rs 9.4285
ccs 3
cts 3
cp 1
cc 1
eloc 3
nc 1
nop 0
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 48
	public function __construct($metaClass = null, $component = null, $options = null)
86
	{
87 48
		if (null === self::$runtimePath)
88
		{
89 1
			self::$runtimePath = (new ConfigDetector)->getRuntimePath();
90
		}
91 48
		$this->path = self::$runtimePath . '/addendum';
92 48
		$this->metaClass = $metaClass;
93 48
		$this->component = $component;
94 48
		if (empty($options))
95
		{
96 3
			$this->instanceId = Addendum::DefaultInstanceId;
97
		}
98
		elseif ($options instanceof Addendum)
99
		{
100 46
			$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 48
		$this->prepare();
111 48
		$this->addendum = Addendum::fly($this->instanceId);
112 48
		$this->nsCache = new NsCache(dirname($this->getFilename()), $this->addendum);
113 48
	}
114
115
	/**
116
	 * Set working component
117
	 * @param AnnotatedInterface|string $component
118
	 */
119 50
	public function setComponent($component = null)
120
	{
121
		// Reset filename as it depends on component
122 50
		$this->fileName = null;
123 50
		$this->component = $component;
124 50
	}
125
126 32
	public function setOptions(MetaOptions $options = null)
127
	{
128 32
		$this->fileName = null;
129 32
		$this->nsCache->setOptions($options);
130 32
	}
131
132 52
	private function prepare()
133
	{
134 52
		$fileDir = dirname($this->getFilename());
135 52
		if (isset(self::$prepared[$fileDir]) && self::$prepared[$fileDir])
136
		{
137 50
			return true;
138
		}
139 6
		if (!file_exists($this->path))
140
		{
141 4
			if (!file_exists(self::$runtimePath))
142
			{
143
144 1
				if (is_writable(dirname(self::$runtimePath)))
145
				{
146 1
					$this->mkdir(self::$runtimePath);
147
				}
148 1
				if (!is_writable(self::$runtimePath))
149
				{
150
					throw new RuntimeException(sprintf("Runtime path `%s` must exists and be writable", self::$runtimePath));
151
				}
152
			}
153 4
			if (is_writable(self::$runtimePath))
154
			{
155 4
				$this->mkdir($this->path);
156
			}
157 4
			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 6
		if (!file_exists($fileDir))
163
		{
164 6
			$this->mkdir($fileDir);
165
		}
166 6
		self::$prepared[$fileDir] = true;
167 6
	}
168
169 50
	public function get()
170
	{
171 50
		$this->prepare();
172 50
		$fileName = $this->getFilename();
173
174 50
		if (!$this->nsCache->valid())
175
		{
176 2
			$this->clearCurrentPath();
177 2
			return false;
178
		}
179 50
		$key = $this->getCacheKey();
180 50
		if (isset(self::$cache[$key]))
181
		{
182 50
			return self::$cache[$key];
183
		}
184
185 45
		$data = SoftIncluder::includeFile($fileName);
186
187 45
		if (empty($data))
188
		{
189 45
			return false;
190
		}
191
192
		// Purge file cache if checkMTime is enabled and file obsolete
193
		if ($this->addendum->checkMTime && file_exists($fileName))
194
		{
195
			$cacheTime = filemtime($fileName);
196
197
			// Partial component name, split by @ and take first argument
198
			if (is_string($this->component) && strstr($this->component, '@'))
199
			{
200
				$parts = explode('@', $this->component);
201
				$componentClass = array_shift($parts);
202
			}
203
			else
204
			{
205
				$componentClass = $this->component;
206
			}
207
			$componentTime = filemtime((new ReflectionClass($componentClass))->getFileName());
208
			if ($componentTime > $cacheTime)
209
			{
210
				$this->remove();
211
				return false;
212
			}
213
		}
214
		self::$cache[$key] = $data;
215
		return $data;
216
	}
217
218 45
	public function set($data)
219
	{
220 45
		$fileName = $this->getFilename();
221 45
		$this->prepare();
222 45
		$key = $this->getCacheKey();
223 45
		self::$cache[$key] = $data;
224
225 45
		file_put_contents($fileName, PhpExporter::export($data));
226 45
		@chmod($fileName, 0666);
227 45
		$this->nsCache->set();
228 45
		return $data;
229
	}
230
231
	public function remove()
232
	{
233
		$fileName = $this->getFilename();
234
		$key = $this->getCacheKey();
235
		unset(self::$cache[$key]);
236
		if (file_exists($fileName))
237
		{
238
			return unlink($fileName);
239
		}
240
		return false;
241
	}
242
243
	/**
244
	 * Clear entire cache
245
	 * @return boolean
246
	 */
247 3
	public function clear()
248
	{
249 3
		self::$prepared = [];
250 3
		return $this->clearPath($this->path);
251
	}
252
253 2
	private function clearCurrentPath()
254
	{
255 2
		$path = dirname($this->getFilename());
256 2
		return $this->clearPath($path);
257
	}
258
259 5
	private function clearPath($path)
260
	{
261 5
		if (!file_exists($path))
262
		{
263
			return false;
264
		}
265 5
		foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $dir)
266
		{
267 5
			$dir->isDir() && !$dir->isLink() ? rmdir($dir->getPathname()) : unlink($dir->getPathname());
268
		}
269 5
		self::$prepared[$path] = false;
270 5
		return rmdir($path);
271
	}
272
273 52
	private function getFilename()
274
	{
275 52
		if (!empty($this->fileName))
276
		{
277 52
			return $this->fileName;
278
		}
279 52
		if (is_object($this->component))
280
		{
281 32
			$className = get_class($this->component);
282
		}
283
		else
284
		{
285 48
			$className = $this->component;
286
		}
287 52
		$this->fileName = sprintf('%s/%s@%s/%s.php', $this->path, $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className)));
288 52
		return $this->fileName;
289
	}
290
291 50
	private function getCacheKey()
292
	{
293 50
		if (is_object($this->component))
294
		{
295 32
			$className = get_class($this->component);
296
		}
297
		else
298
		{
299 46
			$className = $this->component;
300
		}
301 50
		return sprintf('%s@%s@%s@%s.php', $this->classToFile(static::class), $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className)));
302
	}
303
304
	/**
305
	 * Convert slash separated class name to dot separated name.
306
	 * @param string $className
307
	 * @return string
308
	 */
309 52
	private function classToFile($className)
310
	{
311 52
		return str_replace('\\', '.', $className);
312
	}
313
314
	/**
315
	 * Recursively create dir with proper permissions.
316
	 *
317
	 * @param string $path
318
	 */
319 6
	private function mkdir($path)
320
	{
321 6
		$mask = umask(0000);
322 6
		mkdir($path, 0777, true);
323 6
		umask($mask);
324 6
	}
325
326
}
327