1111
1212namespace Symfony \Component \Cache \Adapter ;
1313
14+ use Symfony \Component \Cache \CacheItem ;
15+
1416/**
1517 * @author Nicolas Grekas <p@tchwork.com>
1618 */
17- class FilesystemAdapter extends AbstractAdapter
19+ class FilesystemAdapter extends AbstractTagsInvalidatingAdapter
1820{
1921 use FilesystemAdapterTrait;
2022
@@ -24,6 +26,20 @@ public function __construct($namespace = '', $defaultLifetime = 0, $directory =
2426 $ this ->init ($ namespace , $ directory );
2527 }
2628
29+ /**
30+ * {@inheritdoc}
31+ */
32+ public function invalidateTags ($ tags )
33+ {
34+ $ ok = true ;
35+
36+ foreach (CacheItem::normalizeTags ($ tags ) as $ tag ) {
37+ $ ok = $ this ->doInvalidateTag ($ tag ) && $ ok ;
38+ }
39+
40+ return $ ok ;
41+ }
42+
2743 /**
2844 * {@inheritdoc}
2945 */
@@ -68,15 +84,125 @@ protected function doHave($id)
6884 /**
6985 * {@inheritdoc}
7086 */
71- protected function doSave (array $ values , $ lifetime )
87+ protected function doSaveWithTags (array $ values , $ lifetime, array $ tags )
7288 {
7389 $ ok = true ;
7490 $ expiresAt = $ lifetime ? time () + $ lifetime : PHP_INT_MAX ;
91+ $ newTags = $ oldTags = array ();
7592
7693 foreach ($ values as $ id => $ value ) {
94+ $ newIdTags = $ tags [$ id ];
95+ $ file = $ this ->getFile ($ id , true );
96+ $ tagFile = $ this ->getFile ($ id .':tag ' , $ newIdTags );
97+ $ hasFile = file_exists ($ file );
98+
99+ if ($ hadTags = file_exists ($ tagFile )) {
100+ foreach (file ($ tagFile , FILE_IGNORE_NEW_LINES ) as $ tag ) {
101+ if (isset ($ newIdTags [$ tag = rawurldecode ($ tag )])) {
102+ if ($ hasFile ) {
103+ unset($ newIdTags [$ tag ]);
104+ }
105+ } else {
106+ $ oldTags [] = $ tag ;
107+ }
108+ }
109+ if ($ oldTags ) {
110+ $ this ->removeTags ($ id , $ oldTags );
111+ $ oldTags = array ();
112+ }
113+ }
114+ foreach ($ newIdTags as $ tag ) {
115+ $ newTags [$ tag ][] = $ id ;
116+ }
117+
77118 $ ok = $ this ->write ($ this ->getFile ($ id , true ), $ expiresAt ."\n" .rawurlencode ($ id )."\n" .serialize ($ value ), $ expiresAt ) && $ ok ;
119+
120+ if ($ tags [$ id ]) {
121+ $ ok = $ this ->write ($ tagFile , implode ("\n" , array_map ('rawurlencode ' , $ tags [$ id ]))."\n" ) && $ ok ;
122+ } elseif ($ hadTags ) {
123+ @unlink ($ tagFile );
124+ }
125+ }
126+ if ($ newTags ) {
127+ $ ok = $ this ->doTag ($ newTags ) && $ ok ;
128+ }
129+
130+ return $ ok ;
131+ }
132+
133+ private function doTag (array $ tags )
134+ {
135+ $ ok = true ;
136+ $ linkedTags = array ();
137+
138+ foreach ($ tags as $ tag => $ ids ) {
139+ $ file = $ this ->getFile ($ tag , true );
140+ $ linkedTags [$ tag ] = file_exists ($ file ) ?: null ;
141+ $ h = fopen ($ file , 'ab ' );
142+
143+ foreach ($ ids as $ id ) {
144+ $ ok = fwrite ($ h , rawurlencode ($ id )."\n" ) && $ ok ;
145+ }
146+ fclose ($ h );
147+
148+ while (!isset ($ linkedTags [$ tag ]) && 0 < $ r = strrpos ($ tag , '/ ' )) {
149+ $ linkedTags [$ tag ] = true ;
150+ $ parent = substr ($ tag , 0 , $ r );
151+ $ file = $ this ->getFile ($ parent , true );
152+ $ linkedTags [$ parent ] = file_exists ($ file ) ?: null ;
153+ $ ok = file_put_contents ($ file , rawurlencode ($ tag )."\n" , FILE_APPEND ) && $ ok ;
154+ $ tag = $ parent ;
155+ }
156+ }
157+
158+ return $ ok ;
159+ }
160+
161+ private function doInvalidateTag ($ tag )
162+ {
163+ if (!$ h = @fopen ($ this ->getFile ($ tag ), 'r+b ' )) {
164+ return true ;
165+ }
166+ $ ok = true ;
167+ $ count = 0 ;
168+
169+ while (false !== $ id = fgets ($ h )) {
170+ if ('! ' === $ id [0 ]) {
171+ continue ;
172+ }
173+ $ id = rawurldecode (substr ($ id , 0 , -1 ));
174+
175+ if ('/ ' === $ id [0 ]) {
176+ $ ok = $ this ->doInvalidateTag ($ id ) && $ ok ;
177+ } elseif (file_exists ($ file = $ this ->getFile ($ id ))) {
178+ $ count += $ unlink = @unlink ($ file );
179+ $ ok = ($ unlink || !file_exists ($ file )) && $ ok ;
180+ }
78181 }
79182
183+ ftruncate ($ h , 0 );
184+ fclose ($ h );
185+ CacheItem:log ($ this ->logger , 'Invalidating {count} items tagged as "{tag}" ' , array ('tag ' => $ tag , 'count ' => $ count ));
186+
80187 return $ ok ;
81188 }
189+
190+ private function removeTags ($ id , $ tags )
191+ {
192+ $ idLine = rawurlencode ($ id )."\n" ;
193+ $ idSeek = -strlen ($ idLine );
194+
195+ foreach ($ tags as $ tag ) {
196+ if (!$ h = @fopen ($ this ->getFile ($ tag ), 'r+b ' )) {
197+ continue ;
198+ }
199+ while (false !== $ line = fgets ($ h )) {
200+ if ($ line === $ idLine ) {
201+ fseek ($ h , $ idSeek , SEEK_CUR );
202+ fwrite ($ h , '! ' );
203+ }
204+ }
205+ fclose ($ h );
206+ }
207+ }
82208}
0 commit comments