1111
1212namespace Symfony \Component \Cache \Adapter ;
1313
14+ use Symfony \Component \Cache \CacheItem ;
1415use Symfony \Component \Cache \Exception \InvalidArgumentException ;
1516
1617/**
1718 * @author Nicolas Grekas <p@tchwork.com>
1819 */
19- class FilesystemAdapter extends AbstractAdapter
20+ class FilesystemAdapter extends AbstractAdapter implements TagInvalidationInterface
2021{
2122 private $ directory ;
2223
@@ -50,6 +51,22 @@ public function __construct($namespace = '', $defaultLifetime = 0, $directory =
5051 $ this ->directory = $ dir ;
5152 }
5253
54+ /**
55+ * {@inheritdoc}
56+ */
57+ public function invalidate ($ tag )
58+ {
59+ $ tag = '/ ' .CacheItem::normalizeTag ($ tag );
60+ $ ok = true ;
61+
62+ foreach ($ this ->getInvalidatedIds ($ tag ) as $ id ) {
63+ $ file = $ this ->getFile ($ id , false );
64+ $ ok = (!file_exists ($ file ) || @unlink ($ file ) || !file_exists ($ file )) && $ ok ;
65+ }
66+
67+ return $ ok ;
68+ }
69+
5370 /**
5471 * {@inheritdoc}
5572 */
@@ -59,7 +76,7 @@ protected function doFetch(array $ids)
5976 $ now = time ();
6077
6178 foreach ($ ids as $ id ) {
62- $ file = $ this ->getFile ($ id );
79+ $ file = $ this ->getFile ($ id, false );
6380 if (!$ h = @fopen ($ file , 'rb ' )) {
6481 continue ;
6582 }
@@ -89,7 +106,7 @@ protected function doFetch(array $ids)
89106 */
90107 protected function doHave ($ id )
91108 {
92- $ file = $ this ->getFile ($ id );
109+ $ file = $ this ->getFile ($ id, false );
93110
94111 return file_exists ($ file ) && (@filemtime ($ file ) > time () || $ this ->doFetch (array ($ id )));
95112 }
@@ -116,7 +133,8 @@ protected function doDelete(array $ids)
116133 $ ok = true ;
117134
118135 foreach ($ ids as $ id ) {
119- $ file = $ this ->getFile ($ id );
136+ $ this ->removeTags ($ id );
137+ $ file = $ this ->getFile ($ id , false );
120138 $ ok = (!file_exists ($ file ) || @unlink ($ file ) || !file_exists ($ file )) && $ ok ;
121139 }
122140
@@ -132,14 +150,11 @@ protected function doSave(array $values, $lifetime)
132150 $ expiresAt = $ lifetime ? time () + $ lifetime : PHP_INT_MAX ;
133151
134152 foreach ($ values as $ id => $ value ) {
135- $ file = $ this ->getFile ($ id );
136- $ dir = dirname ($ file );
137- if (!file_exists ($ dir )) {
138- @mkdir ($ dir , 0777 , true );
139- }
153+ $ file = $ this ->getFile ($ id , true );
140154 $ value = $ expiresAt ."\n" .rawurlencode ($ id )."\n" .serialize ($ value );
141155 if (false !== @file_put_contents ($ file , $ value , LOCK_EX )) {
142156 @touch ($ file , $ expiresAt );
157+ $ this ->removeTags ($ id );
143158 } else {
144159 $ ok = false ;
145160 }
@@ -148,10 +163,103 @@ protected function doSave(array $values, $lifetime)
148163 return $ ok ;
149164 }
150165
151- private function getFile ($ id )
166+ /**
167+ * {@inheritdoc}
168+ */
169+ protected function doTag (array $ tags )
170+ {
171+ $ ok = true ;
172+ $ byIds = array ();
173+
174+ foreach ($ tags as $ tag => $ ids ) {
175+ $ file = $ this ->getFile ($ tag , true );
176+ $ h = fopen ($ file , 'ab ' );
177+
178+ foreach ($ ids as $ id ) {
179+ $ ok = fwrite ($ h , rawurlencode ($ id )."\n" ) && $ ok ;
180+ $ byIds [$ id ][] = $ tag ;
181+ }
182+ fclose ($ h );
183+ }
184+ foreach ($ byIds as $ id => $ tags ) {
185+ $ file = $ this ->getFile ($ id .':tag ' , true );
186+ $ h = fopen ($ file , 'ab ' );
187+
188+ foreach ($ tags as $ tag ) {
189+ fwrite ($ h , rawurlencode ($ tag )."\n" );
190+ }
191+ fclose ($ h );
192+ }
193+ $ s = strpos ($ tag , '/ ' );
194+ $ r = strrpos ($ tag , '/ ' );
195+ while ($ r > $ s ) {
196+ $ parent = substr ($ tag , 0 , $ r );
197+ $ ok = file_put_contents ($ this ->getFile ($ parent , true ), rawurlencode ($ tag )."\n" , FILE_APPEND ) && $ ok ;
198+ $ r = strrpos ($ tag = $ parent , '/ ' );
199+ }
200+
201+ return $ ok ;
202+ }
203+
204+ private function getInvalidatedIds ($ tag )
205+ {
206+ $ file = $ this ->getFile ($ tag , false );
207+
208+ if ($ h = @fopen ($ file , 'rb ' )) {
209+ while (false !== $ id = fgets ($ h )) {
210+ if ('! ' === $ id [0 ]) {
211+ continue ;
212+ }
213+ $ id = rawurldecode (substr ($ id , 0 , -1 ));
214+
215+ if ('/ ' === $ id [0 ]) {
216+ foreach ($ this ->getInvalidatedIds ($ id ) as $ id ) {
217+ yield $ id ;
218+ }
219+ } else {
220+ yield $ id ;
221+ }
222+ }
223+
224+ fclose ($ h );
225+ @unlink ($ file );
226+ }
227+ }
228+
229+ public function removeTags ($ id )
230+ {
231+ if (!file_exists ($ file = $ this ->getFile ($ id .':tag ' , false ))) {
232+ return ;
233+ }
234+ $ idLine = rawurlencode ($ id )."\n" ;
235+ $ idSeek = -strlen ($ idLine );
236+
237+ foreach (file ($ file , FILE_IGNORE_NEW_LINES ) as $ tag ) {
238+ if (!file_exists ($ tagFile = $ this ->getFile (rawurldecode ($ tag ), false ))) {
239+ continue ;
240+ }
241+ $ h = fopen ($ tagFile , 'r+b ' );
242+
243+ while (false !== $ line = fgets ($ h )) {
244+ if ($ line === $ idLine ) {
245+ fseek ($ h , $ idSeek , SEEK_CUR );
246+ fwrite ($ h , '! ' );
247+ }
248+ }
249+ fclose ($ h );
250+ }
251+ @unlink ($ file );
252+ }
253+
254+ private function getFile ($ id , $ mkdir )
152255 {
153256 $ hash = str_replace ('/ ' , '- ' , base64_encode (md5 ($ id , true )));
257+ $ dir = $ this ->directory .$ hash [0 ].DIRECTORY_SEPARATOR .$ hash [1 ].DIRECTORY_SEPARATOR ;
258+
259+ if ($ mkdir && !file_exists ($ dir )) {
260+ @mkdir ($ dir , 0777 , true );
261+ }
154262
155- return $ this -> directory . $ hash [ 0 ]. DIRECTORY_SEPARATOR . $ hash [ 1 ]. DIRECTORY_SEPARATOR .substr ($ hash , 2 , -2 );
263+ return $ dir .substr ($ hash , 2 , -2 );
156264 }
157265}
0 commit comments