8000 [Cache] Add PhpArrayAdapter to use shared memory on PHP 7.0+ · symfony/symfony@28a40d2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 28a40d2

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

File tree

3 files changed

+538
-0
lines changed

3 files changed

+538
-0
lines changed
Lines 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-on 8000 ly 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