Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 28a40d2

Browse filesBrowse files
committed
[Cache] Add PhpArrayAdapter to use shared memory on PHP 7.0+
1 parent 06f5c86 commit 28a40d2
Copy full SHA for 28a40d2

File tree

3 files changed

+538
-0
lines changed
Filter options

3 files changed

+538
-0
lines changed
+358Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Adapter;
13+
14+
use Psr\Cache\CacheItemInterface;
15+
use Psr\Cache\CacheItemPoolInterface;
16+
use Symfony\Component\Cache\CacheItem;
17+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
18+
19+
/**
20+
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
21+
* Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
22+
*
23+
* @author Titouan Galopin <galopintitouan@gmail.com>
24+
* @author Nicolas Grekas <p@tchwork.com>
25+
*/
26+
class PhpArrayAdapter implements AdapterInterface
27+
{
28+
private $file;
29+
private $values;
30+
private $createCacheItem;
31+
private $fallbackPool;
32+
33+
/**
34+
* @param string $file The PHP file were values are cached
35+
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
36+
*/
37+
public function __construct($file, AdapterInterface $fallbackPool)
38+
{
39+
$this->file = $file;
40+
$this->fallbackPool = $fallbackPool;
41+
$this->createCacheItem = \Closure::bind(
42+
function ($key, $value) {
43+
$item = new CacheItem();
44+
$item->key = $key;
45+
$item->value = $value;
46+
$item->isHit = true;
47+
48+
return $item;
49+
},
50+
null,
51+
CacheItem::class
52+
);
53+
}
54+
55+
/**
56+
* This adapter should only be used on PHP 7.0+ to take advantage of how PHP
57+
* stores arrays in its latest versions. This factory method decorates the given
58+
* fallback pool with this adapter only if the current PHP version is supported.
59+
*
60+
* @param string $file The PHP file were values are cached
61+
*
62+
* @return CacheItemPoolInterface
63+
*/
64+
public static function create($file, CacheItemPoolInterface $fallbackPool)
65+
{
66+
// Shared memory is available in PHP 7.0+ with OPCache enabled and in HHVM
67+
if ((PHP_VERSION_ID >= 70000 && ini_get('opcache.enable')) || defined('HHVM_VERSION')) {
68+
if (!$fallbackPool instanceof AdapterInterface) {
69+
$fallbackPool = new ProxyAdapter($fallbackPool);
70+
}
71+
72+
return new static($file, $fallbackPool);
73+
}
74+
75+
return $fallbackPool;
76+
}
77+
78+
/**
79+
* Store an array of cached values.
80+
*
81+
* @param array $values The cached values
82+
*/
83+
public function warmUp(array $values)
84+
{
85+
if (file_exists($this->file)) {
86+
if (!is_file($this->file)) {
87+
throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: %s.', $this->file));
88+
}
89+
90+
if (!is_writable($this->file)) {
91+
throw new InvalidArgumentException(sprintf('Cache file is not writable: %s.', $this->file));
92+
}
93+
} else {
94+
$directory = dirname($this->file);
95+
96+
if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
97+
throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: %s.', $directory));
98+
}
99+
100+
if (!is_writable($directory)) {
101+
throw new InvalidArgumentException(sprintf('Cache directory is not writable: %s.', $directory));
102+
}
103+
}
104+
105+
$dump = <<<'EOF'
106+
<?php
107+
108+
// This file has been auto-generated by the Symfony Cache Component.
109+
110+
return array(
111+
112+
113+
EOF;
114+
115+
foreach ($values as $key => $value) {
116+
CacheItem::validateKey(is_int($key) ? (string) $key : $key);
117+
118+
if (null === $value || is_object($value)) {
119+
try {
120+
$value = serialize($value);
121+
} catch (\Exception $e) {
122+
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, get_class($value)), 0, $e);
123+
}
124+
} elseif (is_array($value)) {
125+
try {
126+
$serialized = serialize($value);
127+
$unserialized = unserialize($serialized);
128+
} catch (\Exception $e) {
129+
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 0, $e);
130+
}
131+
// Store arrays serialized if they contain any objects or references
132+
if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
133+
$value = $serialized;
134+
}
135+
} elseif (is_string($value)) {
136+
// Serialize strings if they could be confused with serialized objects or arrays
137+
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
138+
$value = serialize($value);
139+
}
140+
} elseif (!is_scalar($value)) {
141+
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, gettype($value)));
142+
}
143+
144+
$dump .= var_export($key, true).' => '.var_export($value, true).",\n";
145+
}
146+
147+
$dump .= "\n);\n";
148+
$dump = str_replace("' . \"\\0\" . '", "\0", $dump);
149+
150+
$tmpFile = uniqid($this->file);
151+
152+
file_put_contents($tmpFile, $dump);
153+
@chmod($tmpFile, 0666);
154+
unset($serialized, $unserialized, $value, $dump);
155+
156+
@rename($tmpFile, $this->file);
157+
158+
$this->values = (include $this->file) ?: array();
159+
}
160+
161+
/**
162+
* {@inheritdoc}
163+
*/
164+
public function getItem($key)
165+
{
166+
if (null === $this->values) {
167+
$this->initialize();
168+
}
169+
170+
if (!is_string($key)) {
171+
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
172+
}
173+
174+
if (!isset($this->values[$key])) {
175+
return $this->fallbackPool->getItem($key);
176+
}
177+
178+
$value = $this->values[$key];
179+
180+
if ('N;' === $value) {
181+
$value = null;
182+
} elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) {
183+
$value = unserialize($value);
184+
}
185+
186+
$f = $this->createCacheItem;
187+
188+
return $f($key, $value);
189+
}
190+
191+
/**
192+
* {@inheritdoc}
193+
*/
194+
public function getItems(array $keys = array())
195+
{
196+
if (null === $this->values) {
197+
$this->initialize();
198+
}
199+
200+
foreach ($keys as $key) {
201+
if (!is_string($key)) {
202+
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
203+
}
204+
}
205+
206+
return $this->generateItems($keys);
207+
}
208+
209+
/**
210+
* {@inheritdoc}
211+
*/
212+
public function hasItem($key)
213+
{
214+
if (null === $this->values) {
215+
$this->initialize();
216+
}
217+
218+
if (!is_string($key)) {
219+
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
220+
}
221+
222+
return isset($this->values[$key]) || $this->fallbackPool->hasItem($key);
223+
}
224+
225+
/**
226+
* {@inheritdoc}
227+
*/
228+
public function clear()
229+
{
230+
$this->values = array();
231+
232+
$cleared = @unlink($this->file) || !file_exists($this->file);
233+
234+
return $this->fallbackPool->clear() && $cleared;
235+
}
236+
237+
/**
238+
* {@inheritdoc}
239+
*/
240+
public function deleteItem($key)
241+
{
242+
if (null === $this->values) {
243+
$this->initialize();
244+
}
245+
246+
if (!is_string($key)) {
247+
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
248+
}
249+
250+
return !isset($this->values[$key]) && $this->fallbackPool->deleteItem($key);
251+
}
252+
253+
/**
254+
* {@inheritdoc}
255+
*/
256+
public function deleteItems(array $keys)
257+
{
258+
if (null === $this->values) {
259+
$this->initialize();
260+
}
261+
262+
$deleted = true;
263+
$fallbackKeys = array();
264+
265+
foreach ($keys as $key) {
266+
if (!is_string($key)) {
267+
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
268+
}
269+
270+
if (isset($this->values[$key])) {
271+
$deleted = false;
272+
} else {
273+
$fallbackKeys[] = $key;
274+
}
275+
}
276+
277+
if ($fallbackKeys) {
278+
$deleted = $this->fallbackPool->deleteItems($fallbackKeys) && $deleted;
279+
}
280+
281+
return $deleted;
282+
}
283+
284+
/**
285+
* {@inheritdoc}
286+
*/
287+
public function save(CacheItemInterface $item)
288+
{
289+
if (null === $this->values) {
290+
$this->initialize();
291+
}
292+
293+
return !isset($this->values[$item->getKey()]) && $this->fallbackPool->save($item);
294+
}
295+
296+
/**
297+
* {@inheritdoc}
298+
*/
299+
public function saveDeferred(CacheItemInterface $item)
300+
{
301+
if (null === $this->values) {
302+
$this->initialize();
303+
}
304+
305+
return !isset($this->values[$item->getKey()]) && $this->fallbackPool->saveDeferred($item);
306+
}
307+
308+
/**
309+
* {@inheritdoc}
310+
*/
311+
public function commit()
312+
{
313+
return $this->fallbackPool->commit();
314+
}
315+
316+
/**
317+
* Load the cache file.
318+
*/
319+
private function initialize()
320+
{
321+
$this->values = @(include $this->file) ?: array();
322+
}
323+
324+
/**
325+
* Generator for items.
326+
*
327+
* @param array $keys
328+
*
329+
* @return \Generator
330+
*/
331+
private function generateItems(array $keys)
332+
{
333+
$f = $this->createCacheItem;
334+
$fallbackKeys = array();
335+
336+
foreach ($keys as $key) {
337+
if (isset($this->values[$key])) {
338+
$value = $this->values[$key];
339+
340+
if ('N;' === $value) {
341+
$value = null;
342+
} elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) {
343+
$value = unserialize($value);
344+
}
345+
346+
yield $key => $f($key, $value);
347+
} else {
348+
$fallbackKeys[] = $key;
349+
}
350+
}
351+
352+
if ($fallbackKeys) {
353+
foreach ($this->fallbackPool->getItems($fallbackKeys) as $key => $item) {
354+
yield $key => $item;
355+
}
356+
}
357+
}
358+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.