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