Skip to content

Commit fa2e7e0

Browse files
committed
[Cache] Add OpCacheAdapter to use shared memory on PHP 7.0+
1 parent 8cc6583 commit fa2e7e0

File tree

3 files changed

+528
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)