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 OpCacheAdapterimplements 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 (!$ fallbackPoolinstanceof 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 ($ valuesas $ 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 ($ keysas $ 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 ($ keysas $ 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+ }