Completed
Push — master ( eb5224...04979f )
by Peter
11:53
created

PhpCache::setOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
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
	 *
73
	 * @param string $metaClass
74
	 * @param AnnotatedInterface|string $component
75
	 * @param MetaOptions|Addendum $options
76
	 */
77 48
	public function __construct($metaClass = null, $component = null, $options = null)
78
	{
79 48
		if (null === self::$runtimePath)
80
		{
81 1
			self::$runtimePath = (new ConfigDetector)->getRuntimePath();
82
		}
83 48
		$this->path = self::$runtimePath . '/addendum';
84 48
		$this->metaClass = $metaClass;
85 48
		$this->component = $component;
86 48
		if (empty($options))
87
		{
88 3
			$this->instanceId = Addendum::DefaultInstanceId;
89
		}
90
		elseif ($options instanceof Addendum)
91
		{
92 46
			$this->instanceId = $options->getInstanceId();
93
		}
94
		elseif ($options instanceof MetaOptions)
95
		{
96
			$this->instanceId = $options->instanceId;
97
		}
98
		else
99
		{
100
			throw new UnexpectedValueException('Unknown options');
101
		}
102 48
		$this->addendum = Addendum::fly($this->instanceId);
103 48
		$this->nsCache = new NsCache(dirname($this->getFilename()), $this->addendum);
104 48
	}
105
106
	/**
107
	 * Set working component
108
	 * @param AnnotatedInterface|string $component
109
	 */
110 50
	public function setComponent($component = null)
111
	{
112 50
		$this->component = $component;
113 50
	}
114
115 32
	public function setOptions(MetaOptions $options = null)
116
	{
117 32
		$this->nsCache->setOptions($options);
118 32
	}
119
120 50
	private function prepare()
121
	{
122 50
		if (!file_exists($this->path))
123
		{
124 2
			if (!file_exists(self::$runtimePath))
125
			{
126
127 1
				if (is_writable(dirname(self::$runtimePath)))
128
				{
129 1
					$this->mkdir(self::$runtimePath);
130
				}
131 1
				if (!is_writable(self::$runtimePath))
132
				{
133
					throw new RuntimeException(sprintf("Runtime path `%s` must exists and be writable", self::$runtimePath));
134
				}
135
			}
136 2
			if (is_writable(self::$runtimePath))
137
			{
138 2
				$this->mkdir($this->path);
139
			}
140 2
			if (!is_writable($this->path))
141
			{
142
				throw new RuntimeException(sprintf("Addendum runtime path `%s` must exists and be writable", $this->path));
143
			}
144
		}
145 50
		if (!file_exists(dirname($this->getFilename())))
146
		{
147 4
			$this->mkdir(dirname($this->getFilename()));
148
		}
149 50
	}
150
151 50
	public function get()
152
	{
153 50
		$this->prepare();
154 50
		$fileName = $this->getFilename();
155
156 50
		if (!$this->nsCache->valid())
157
		{
158 2
			$this->clearCurrentPath();
159 2
			return false;
160
		}
161 50
		$key = $this->getCacheKey();
162 50
		if (isset(self::$cache[$key]))
163
		{
164 50
			return self::$cache[$key];
165
		}
166
167 45
		$data = SoftIncluder::includeFile($fileName);
168
169 45
		if (empty($data))
170
		{
171 45
			return false;
172
		}
173
174
		// Purge file cache if checkMTime is enabled and file obsolete
175
		if ($this->addendum->checkMTime && file_exists($fileName))
176
		{
177
			$cacheTime = filemtime($fileName);
178
179
			// Partial component name, split by @ and take first argument
180
			if (is_string($this->component) && strstr($this->component, '@'))
181
			{
182
				$parts = explode('@', $this->component);
183
				$componentClass = array_shift($parts);
184
			}
185
			else
186
			{
187
				$componentClass = $this->component;
188
			}
189
			$componentTime = filemtime((new ReflectionClass($componentClass))->getFileName());
190
			if ($componentTime > $cacheTime)
191
			{
192
				$this->remove();
193
				return false;
194
			}
195
		}
196
		self::$cache[$key] = $data;
197
		return $data;
198
	}
199
200 45
	public function set($data)
201
	{
202 45
		$fileName = $this->getFilename();
203 45
		$this->prepare();
204 45
		$key = $this->getCacheKey();
205 45
		self::$cache[$key] = $data;
206
207 45
		file_put_contents($fileName, PhpExporter::export($data));
208 45
		@chmod($fileName, 0666);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
209 45
		$this->nsCache->set();
210 45
		return $data;
211
	}
212
213
	public function remove()
214
	{
215
		$fileName = $this->getFilename();
216
		$key = $this->getCacheKey();
217
		unset(self::$cache[$key]);
218
		if (file_exists($fileName))
219
		{
220
			return unlink($fileName);
221
		}
222
		return false;
223
	}
224
225
	/**
226
	 * Clear entire cache
227
	 * @return boolean
228
	 */
229 3
	public function clear()
230
	{
231 3
		return $this->clearPath($this->path);
232
	}
233
234 2
	private function clearCurrentPath()
235
	{
236 2
		return $this->clearPath(dirname($this->getFilename()));
237
	}
238
239 5
	private function clearPath($path)
240
	{
241 5
		if (!file_exists($path))
242
		{
243 3
			return false;
244
		}
245 3
		foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $dir)
246
		{
247 3
			$dir->isDir() && !$dir->isLink() ? rmdir($dir->getPathname()) : unlink($dir->getPathname());
248
		}
249 3
		return rmdir($path);
250
	}
251
252 52
	private function getFilename()
253
	{
254 52
		if (is_object($this->component))
255
		{
256 32
			$className = get_class($this->component);
257
		}
258
		else
259
		{
260 48
			$className = $this->component;
261
		}
262 52
		return sprintf('%s/%s@%s/%s.php', $this->path, $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className)));
263
	}
264
265 50
	private function getCacheKey()
266
	{
267 50
		if (is_object($this->component))
268
		{
269 32
			$className = get_class($this->component);
270
		}
271
		else
272
		{
273 46
			$className = $this->component;
274
		}
275 50
		return sprintf('%s@%s@%s@%s.php', $this->classToFile(static::class), $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className)));
276
	}
277
278
	/**
279
	 * Convert slash separated class name to dot separated name.
280
	 * @param string $className
281
	 * @return string
282
	 */
283 52
	private function classToFile($className)
284
	{
285 52
		return str_replace('\\', '.', $className);
286
	}
287
288
	/**
289
	 * Recursively create dir with proper permissions.
290
	 *
291
	 * @param string $path
292
	 */
293 4
	private function mkdir($path)
294
	{
295 4
		$mask = umask(0000);
296 4
		mkdir($path, 0777, true);
297 4
		umask($mask);
298 4
	}
299
300
}
301