11
11
12
12
namespace Symfony \Component \Cache \Adapter ;
13
13
14
+ use Symfony \Component \Cache \CacheItem ;
14
15
use Symfony \Component \Cache \Exception \InvalidArgumentException ;
15
16
16
17
/**
17
18
* @author Nicolas Grekas <p@tchwork.com>
18
19
*/
19
- class FilesystemAdapter extends AbstractAdapter
20
+ class FilesystemAdapter extends AbstractAdapter implements TagInvalidationInterface
20
21
{
21
22
private $ directory ;
22
23
@@ -50,6 +51,22 @@ public function __construct($namespace = '', $defaultLifetime = 0, $directory =
50
51
$ this ->directory = $ dir ;
51
52
}
52
53
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
+
53
70
/**
54
71
* {@inheritdoc}
55
72
*/
@@ -59,7 +76,7 @@ protected function doFetch(array $ids)
59
76
$ now = time ();
60
77
61
78
foreach ($ ids as $ id ) {
62
- $ file = $ this ->getFile ($ id );
79
+ $ file = $ this ->getFile ($ id, false );
63
80
if (!$ h = @fopen ($ file , 'rb ' )) {
64
81
continue ;
65
82
}
@@ -89,7 +106,7 @@ protected function doFetch(array $ids)
89
106
*/
90
107
protected function doHave ($ id )
91
108
{
92
- $ file = $ this ->getFile ($ id );
109
+ $ file = $ this ->getFile ($ id, false );
93
110
94
111
return file_exists ($ file ) && (@filemtime ($ file ) > time () || $ this ->doFetch (array ($ id )));
95
112
}
@@ -116,7 +133,8 @@ protected function doDelete(array $ids)
116
133
$ ok = true ;
117
134
118
135
foreach ($ ids as $ id ) {
119
- $ file = $ this ->getFile ($ id );
136
+ $ this ->removeTags ($ id );
137
+ $ file = $ this ->getFile ($ id , false );
120
138
$ ok = (!file_exists ($ file ) || @unlink ($ file ) || !file_exists ($ file )) && $ ok ;
121
139
}
122
140
@@ -132,14 +150,11 @@ protected function doSave(array $values, $lifetime)
132
150
$ expiresAt = $ lifetime ? time () + $ lifetime : PHP_INT_MAX ;
133
151
134
152
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 );
140
154
$ value = $ expiresAt ."\n" .rawurlencode ($ id )."\n" .serialize ($ value );
141
155
if (false !== @file_put_contents ($ file , $ value , LOCK_EX )) {
142
156
@touch ($ file , $ expiresAt );
157
+ $ this ->removeTags ($ id );
143
158
} else {
144
159
$ ok = false ;
145
160
}
@@ -148,10 +163,103 @@ protected function doSave(array $values, $lifetime)
148
163
return $ ok ;
149
164
}
150
165
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 )
152
255
{
153
256
$ 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
+ }
154
262
155
- return $ this -> directory . $ hash [ 0 ]. DIRECTORY_SEPARATOR . $ hash [ 1 ]. DIRECTORY_SEPARATOR .substr ($ hash , 2 , -2 );
263
+ return $ dir .substr ($ hash , 2 , -2 );
156
264
}
157
265
}
0 commit comments