Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Store.php
001 <?php
002
003 /*
004 * This file is part of the Symfony package.
005 *
006 * (c) Fabien Potencier <fabien@symfony.com>
007 *
008 * This code is partially based on the Rack-Cache library by Ryan Tomayko,
009 * which is released under the MIT license.
010 *
011 * For the full copyright and license information, please view the LICENSE
012 * file that was distributed with this source code.
013 */
014
015 namespace Symfony\Component\HttpKernel\HttpCache;
016
017 use Symfony\Component\HttpFoundation\Request;
018 use Symfony\Component\HttpFoundation\Response;
019
020 /**
021 * Store implements all the logic for storing cache metadata (Request and Response headers).
022 *
023 * @author Fabien Potencier <fabien@symfony.com>
024 */
025 class Store implements StoreInterface
026 {
027 protected $root;
028 private $keyCache;
029 private $locks;
030
031 /**
032 * Constructor.
033 *
034 * @param string $root The path to the cache directory
035 *
036 * @throws \RuntimeException
037 */
038 public function __construct($root)
039 {
040 $this->root = $root;
041 if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) {
042 throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root));
043 }
044 $this->keyCache = new \SplObjectStorage();
045 $this->locks = array();
046 }
047
048 /**
049 * Cleanups storage.
050 */
051 public function cleanup()
052 {
053 // unlock everything
054 foreach ($this->locks as $lock) {
055 flock($lock, LOCK_UN);
056 fclose($lock);
057 }
058
059 $this->locks = array();
060 }
061
062 /**
063 * Tries to lock the cache for a given Request, without blocking.
064 *
065 * @param Request $request A Request instance
066 *
067 * @return bool|string true if the lock is acquired, the path to the current lock otherwise
068 */
069 public function lock(Request $request)
070 {
071 $key = $this->getCacheKey($request);
072
073 if (!isset($this->locks[$key])) {
074 $path = $this->getPath($key);
075 if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
076 return $path;
077 }
078 $h = fopen($path, 'cb');
079 if (!flock($h, LOCK_EX | LOCK_NB)) {
080 fclose($h);
081
082 return $path;
083 }
084
085 $this->locks[$key] = $h;
086 }
087
088 return true;
089 }
090
091 /**
092 * Releases the lock for the given Request.
093 *
094 * @param Request $request A Request instance
095 *
096 * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise
097 */
098 public function unlock(Request $request)
099 {
100 $key = $this->getCacheKey($request);
101
102 if (isset($this->locks[$key])) {
103 flock($this->locks[$key], LOCK_UN);
104 fclose($this->locks[$key]);
105 unset($this->locks[$key]);
106
107 return true;
108 }
109
110 return false;
111 }
112
113 public function isLocked(Request $request)
114 {
115 $key = $this->getCacheKey($request);
116
117 if (isset($this->locks[$key])) {
118 return true; // shortcut if lock held by this process
119 }
120
121 if (!file_exists($path = $this->getPath($key))) {
122 return false;
123 }
124
125 $h = fopen($path, 'rb');
126 flock($h, LOCK_EX | LOCK_NB, $wouldBlock);
127 flock($h, LOCK_UN); // release the lock we just acquired
128 fclose($h);
129
130 return (bool) $wouldBlock;
131 }
132
133 /**
134 * Locates a cached Response for the Request provided.
135 *
136 * @param Request $request A Request instance
137 *
138 * @return Response|null A Response instance, or null if no cache entry was found
139 */
140 public function lookup(Request $request)
141 {
142 $key = $this->getCacheKey($request);
143
144 if (!$entries = $this->getMetadata($key)) {
145 return;
146 }
147
148 // find a cached entry that matches the request.
149 $match = null;
150 foreach ($entries as $entry) {
151 if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) {
152 $match = $entry;
153
154 break;
155 }
156 }
157
158 if (null === $match) {
159 return;
160 }
161
162 list($req, $headers) = $match;
163 if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) {
164 return $this->restoreResponse($headers, $body);
165 }
166
167 // TODO the metaStore referenced an entity that doesn't exist in
168 // the entityStore. We definitely want to return nil but we should
169 // also purge the entry from the meta-store when this is detected.
170 }
171
172 /**
173 * Writes a cache entry to the store for the given Request and Response.
174 *
175 * Existing entries are read and any that match the response are removed. This
176 * method calls write with the new list of cache entries.
177 *
178 * @param Request $request A Request instance
179 * @param Response $response A Response instance
180 *
181 * @return string The key under which the response is stored
182 *
183 * @throws \RuntimeException
184 */
185 public function write(Request $request, Response $response)
186 {
187 $key = $this->getCacheKey($request);
188 $storedEnv = $this->persistRequest($request);
189
190 // write the response body to the entity store if this is the original response
191 if (!$response->headers->has('X-Content-Digest')) {
192 $digest = $this->generateContentDigest($response);
193
194 if (false === $this->save($digest, $response->getContent())) {
195 throw new \RuntimeException('Unable to store the entity.');
196 }
197
198 $response->headers->set('X-Content-Digest', $digest);
199
200 if (!$response->headers->has('Transfer-Encoding')) {
201 $response->headers->set('Content-Length', strlen($response->getContent()));
202 }
203 }
204
205 // read existing cache entries, remove non-varying, and add this one to the list
206 $entries = array();
207 $vary = $response->headers->get('vary');
208 foreach ($this->getMetadata($key) as $entry) {
209 if (!isset($entry[1]['vary'][0])) {
210 $entry[1]['vary'] = array('');
211 }
212
213 if ($vary != $entry[1]['vary'][0] || !$this->requestsMatch($vary, $entry[0], $storedEnv)) {
214 $entries[] = $entry;
215 }
216 }
217
218 $headers = $this->persistResponse($response);
219 unset($headers['age']);
220
221 array_unshift($entries, array($storedEnv, $headers));
222
223 if (false === $this->save($key, serialize($entries))) {
224 throw new \RuntimeException('Unable to store the metadata.');
225 }
226
227 return $key;
228 }
229
230 /**
231 * Returns content digest for $response.
232 *
233 * @param Response $response
234 *
235 * @return string
236 */
237 protected function generateContentDigest(Response $response)
238 {
239 return 'en'.hash('sha256', $response->getContent());
240 }
241
242 /**
243 * Invalidates all cache entries that match the request.
244 *
245 * @param Request $request A Request instance
246 *
247 * @throws \RuntimeException
248 */
249 public function invalidate(Request $request)
250 {
251 $modified = false;
252 $key = $this->getCacheKey($request);
253
254 $entries = array();
255 foreach ($this->getMetadata($key) as $entry) {
256 $response = $this->restoreResponse($entry[1]);
257 if ($response->isFresh()) {
258 $response->expire();
259 $modified = true;
260 $entries[] = array($entry[0], $this->persistResponse($response));
261 } else {
262 $entries[] = $entry;
263 }
264 }
265
266 if ($modified && false === $this->save($key, serialize($entries))) {
267 throw new \RuntimeException('Unable to store the metadata.');
268 }
269 }
270
271 /**
272 * Determines whether two Request HTTP header sets are non-varying based on
273 * the vary response header value provided.
274 *
275 * @param string $vary A Response vary header
276 * @param array $env1 A Request HTTP header array
277 * @param array $env2 A Request HTTP header array
278 *
279 * @return bool true if the two environments match, false otherwise
280 */
281 private function requestsMatch($vary, $env1, $env2)
282 {
283 if (empty($vary)) {
284 return true;
285 }
286
287 foreach (preg_split('/[\s,]+/', $vary) as $header) {
288 $key = str_replace('_', '-', strtolower($header));
289 $v1 = isset($env1[$key]) ? $env1[$key] : null;
290 $v2 = isset($env2[$key]) ? $env2[$key] : null;
291 if ($v1 !== $v2) {
292 return false;
293 }
294 }
295
296 return true;
297 }
298
299 /**
300 * Gets all data associated with the given key.
301 *
302 * Use this method only if you know what you are doing.
303 *
304 * @param string $key The store key
305 *
306 * @return array An array of data associated with the key
307 */
308 private function getMetadata($key)
309 {
310 if (!$entries = $this->load($key)) {
311 return array();
312 }
313
314 return unserialize($entries);
315 }
316
317 /**
318 * Purges data for the given URL.
319 *
320 * @param string $url A URL
321 *
322 * @return bool true if the URL exists and has been purged, false otherwise
323 */
324 public function purge($url)
325 {
326 $key = $this->getCacheKey(Request::create($url));
327
328 if (isset($this->locks[$key])) {
329 flock($this->locks[$key], LOCK_UN);
330 fclose($this->locks[$key]);
331 unset($this->locks[$key]);
332 }
333
334 if (file_exists($path = $this->getPath($key))) {
335 unlink($path);
336
337 return true;
338 }
339
340 return false;
341 }
342
343 /**
344 * Loads data for the given key.
345 *
346 * @param string $key The store key
347 *
348 * @return string The data associated with the key
349 */
350 private function load($key)
351 {
352 $path = $this->getPath($key);
353
354 return file_exists($path) ? file_get_contents($path) : false;
355 }
356
357 /**
358 * Save data for the given key.
359 *
360 * @param string $key The store key
361 * @param string $data The data to store
362 *
363 * @return bool
364 */
365 private function save($key, $data)
366 {
367 $path = $this->getPath($key);
368
369 if (isset($this->locks[$key])) {
370 $fp = $this->locks[$key];
371 @ftruncate($fp, 0);
372 @fseek($fp, 0);
373 $len = @fwrite($fp, $data);
374 if (strlen($data) !== $len) {
375 @ftruncate($fp, 0);
376
377 return false;
378 }
379 } else {
380 if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
381 return false;
382 }
383
384 $tmpFile = tempnam(dirname($path), basename($path));
385 if (false === $fp = @fopen($tmpFile, 'wb')) {
386 return false;
387 }
388 @fwrite($fp, $data);
389 @fclose($fp);
390
391 if ($data != file_get_contents($tmpFile)) {
392 return false;
393 }
394
395 if (false === @rename($tmpFile, $path)) {
396 return false;
397 }
398 }
399
400 @chmod($path, 0666 & ~umask());
401 }
402
403 public function getPath($key)
404 {
405 return $this->root.DIRECTORY_SEPARATOR.substr($key, 0, 2).DIRECTORY_SEPARATOR.substr($key, 2, 2).DIRECTORY_SEPARATOR.substr($key, 4, 2).DIRECTORY_SEPARATOR.substr($key, 6);
406 }
407
408 /**
409 * Generates a cache key for the given Request.
410 *
411 * This method should return a key that must only depend on a
412 * normalized version of the request URI.
413 *
414 * If the same URI can have more than one representation, based on some
415 * headers, use a Vary header to indicate them, and each representation will
416 * be stored independently under the same cache key.
417 *
418 * @param Request $request A Request instance
419 *
420 * @return string A key for the given Request
421 */
422 protected function generateCacheKey(Request $request)
423 {
424 return 'md'.hash('sha256', $request->getUri());
425 }
426
427 /**
428 * Returns a cache key for the given Request.
429 *
430 * @param Request $request A Request instance
431 *
432 * @return string A key for the given Request
433 */
434 private function getCacheKey(Request $request)
435 {
436 if (isset($this->keyCache[$request])) {
437 return $this->keyCache[$request];
438 }
439
440 return $this->keyCache[$request] = $this->generateCacheKey($request);
441 }
442
443 /**
444 * Persists the Request HTTP headers.
445 *
446 * @param Request $request A Request instance
447 *
448 * @return array An array of HTTP headers
449 */
450 private function persistRequest(Request $request)
451 {
452 return $request->headers->all();
453 }
454
455 /**
456 * Persists the Response HTTP headers.
457 *
458 * @param Response $response A Response instance
459 *
460 * @return array An array of HTTP headers
461 */
462 private function persistResponse(Response $response)
463 {
464 $headers = $response->headers->all();
465 $headers['X-Status'] = array($response->getStatusCode());
466
467 return $headers;
468 }
469
470 /**
471 * Restores a Response from the HTTP headers and body.
472 *
473 * @param array $headers An array of HTTP headers for the Response
474 * @param string $body The Response body
475 *
476 * @return Response
477 */
478 private function restoreResponse($headers, $body = null)
479 {
480 $status = $headers['X-Status'][0];
481 unset($headers['X-Status']);
482
483 if (null !== $body) {
484 $headers['X-Body-File'] = array($body);
485 }
486
487 return new Response($body, $status, $headers);
488 }
489 }
490