Skip to content

Commit d9c3ca5

Browse files
committed
Merge branch 'release/2.1.0'
2 parents 9d11155 + b9a855e commit d9c3ca5

File tree

5 files changed

+99
-13
lines changed

5 files changed

+99
-13
lines changed

.travis.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ matrix:
1010
- php: 7.1
1111
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'
1212
- php: 7.2
13-
env: COVERAGE='--coverage-clover build/logs/clover.xml'
1413
- php: 7.2
1514
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'
15+
- php: 7.3
16+
env: COVERAGE='--coverage-clover build/logs/clover.xml'
17+
- php: 7.3
18+
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'
1619
- php: 7.4snapshot
1720
- php: 7.4snapshot
1821
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ of expired entries on the filesystem trying to prevent flooding the filesystem.
2626
* …allows you to use a different PSR-6 cache adapters as well as a different
2727
lock adapter than the local filesystem ones.
2828
However, **be careful about choosing the right adapters**, see warning below.
29+
* …supports `BinaryFileResponse` instances.
2930

3031
## Installation
3132

@@ -111,6 +112,14 @@ passing an array of `$options` in the constructor:
111112

112113
**Type**: `string`
113114
**Default**: `Cache-Tags`
115+
116+
### Caching `BinaryFileResponse` instances
117+
118+
This cache implementation allows to cache `BinaryFileResponse` instances but the files are not actually copied to
119+
the cache directory. It will just try to fetch the original file and if that does not exist anymore, the store returns
120+
`null`, causing HttpCache to deal with it as a cache miss and continue normally.
121+
It is ideal for use cases such as caching `/favicon.ico` requests where you would like to prevent the application from
122+
being started and thus deliver the response from HttpCache.
114123

115124
### Cache tagging
116125

src/Psr6Store.php

+46-9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
1919
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
2020
use Symfony\Component\Cache\PruneableInterface;
21+
use Symfony\Component\HttpFoundation\BinaryFileResponse;
22+
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
23+
use Symfony\Component\HttpFoundation\File\File;
2124
use Symfony\Component\HttpFoundation\Request;
2225
use Symfony\Component\HttpFoundation\Response;
2326
use Symfony\Component\Lock\Exception\LockReleasingException;
@@ -203,7 +206,11 @@ public function write(Request $request, Response $response)
203206
if (!$response->headers->has('X-Content-Digest')) {
204207
$contentDigest = $this->generateContentDigest($response);
205208

206-
if (false === $this->saveDeferred($contentDigest, $response->getContent())) {
209+
$cacheValue = $this->isBinaryFileResponseContentDigest($contentDigest) ?
210+
$response->getFile()->getPathname() :
211+
$response->getContent();
212+
213+
if (false === $this->saveDeferred($contentDigest, $cacheValue)) {
207214
throw new \RuntimeException('Unable to store the entity.');
208215
}
209216

@@ -462,9 +469,25 @@ public function getVaryKey(array $vary, Request $request)
462469
*/
463470
public function generateContentDigest(Response $response)
464471
{
472+
if ($response instanceof BinaryFileResponse) {
473+
return 'bf'.hash_file('sha256', $response->getFile()->getPathname());
474+
}
475+
465476
return 'en'.hash('sha256', $response->getContent());
466477
}
467478

479+
/**
480+
* Test whether a given digest identifies a BinaryFileResponse.
481+
*
482+
* @param string $digest
483+
*
484+
* @return bool
485+
*/
486+
private function isBinaryFileResponseContentDigest($digest)
487+
{
488+
return 'bf' === substr($digest, 0, 2);
489+
}
490+
468491
/**
469492
* Increases a counter every time an item is stored to the cache and then
470493
* prunes expired cache entries if a configurable threshold is reached.
@@ -522,20 +545,34 @@ private function saveDeferred($key, $data, $expiresAfter = null, $tags = [])
522545
*/
523546
private function restoreResponse(array $cacheData)
524547
{
525-
$body = null;
526-
527548
if (isset($cacheData['headers']['x-content-digest'][0])) {
528549
$item = $this->cache->getItem($cacheData['headers']['x-content-digest'][0]);
529550
if ($item->isHit()) {
530-
$body = $item->get();
551+
$value = $item->get();
552+
553+
if ($this->isBinaryFileResponseContentDigest($cacheData['headers']['x-content-digest'][0])) {
554+
try {
555+
$file = new File($value);
556+
} catch (FileNotFoundException $e) {
557+
return null;
558+
}
559+
560+
return new BinaryFileResponse(
561+
$file,
562+
$cacheData['status'],
563+
$cacheData['headers']
564+
);
565+
}
566+
567+
return new Response(
568+
$value,
569+
$cacheData['status'],
570+
$cacheData['headers']
571+
);
531572
}
532573
}
533574

534-
return new Response(
535-
$body,
536-
$cacheData['status'],
537-
$cacheData['headers']
538-
);
575+
return null;
539576
}
540577

541578
/**

tests/Fixtures/favicon.ico

31.3 KB
Binary file not shown.

tests/Psr6StoreTest.php

+40-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
use Symfony\Component\Cache\Adapter\RedisAdapter;
1919
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
2020
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
21+
use Symfony\Component\HttpFoundation\BinaryFileResponse;
2122
use Symfony\Component\HttpFoundation\Cookie;
23+
use Symfony\Component\HttpFoundation\File\File;
2224
use Symfony\Component\HttpFoundation\Request;
2325
use Symfony\Component\HttpFoundation\Response;
2426
use Symfony\Component\Lock\Exception\LockReleasingException;
@@ -105,7 +107,7 @@ public function testIsLockedReturnsTrueIfLockWasAcquired()
105107
$this->assertTrue($this->store->isLocked($request), 'Request is locked.');
106108
}
107109

108-
public function testUnlockReturnsFalseIfLockWasNotAquired()
110+
public function testUnlockReturnsFalseIfLockWasNotAcquired()
109111
{
110112
$request = Request::create('/');
111113
$this->assertFalse($this->store->unlock($request), 'Request is not locked.');
@@ -130,7 +132,7 @@ public function testLocksAreReleasedOnCleanup()
130132
$this->assertFalse($this->store->isLocked($request), 'Request is no longer locked.');
131133
}
132134

133-
public function testSameLockCanBeAquiredAgain()
135+
public function testSameLockCanBeAcquiredAgain()
134136
{
135137
$request = Request::create('/');
136138

@@ -177,7 +179,7 @@ public function testWriteStoresTheResponseContent()
177179
$this->assertTrue($this->getCache()->hasItem($contentDigest), 'Response content is stored in cache.');
178180
$this->assertSame($response->getContent(), $this->getCache()->getItem($contentDigest)->get(), 'Response content is stored in cache.');
179181
$this->assertSame($contentDigest, $response->headers->get('X-Content-Digest'), 'Content digest is stored in the response header.');
180-
$this->assertSame(\strlen($response->getContent()), $response->headers->get('Content-Length'), 'Response content length is updated.');
182+
$this->assertSame(\strlen($response->getContent()), (int) $response->headers->get('Content-Length'), 'Response content length is updated.');
181183
}
182184

183185
public function testWriteDoesNotStoreTheResponseContentOfNonOriginalResponse()
@@ -347,6 +349,41 @@ public function testRegularLookup()
347349
$this->assertSame('whatever', $result->headers->get('Foobar'));
348350
}
349351

352+
public function testRegularLookupWithBinaryResponse()
353+
{
354+
$request = Request::create('https://foobar.com/');
355+
$response = new BinaryFileResponse(__DIR__.'/Fixtures/favicon.ico');
356+
$response->headers->set('Foobar', 'whatever');
357+
358+
$this->store->write($request, $response);
359+
360+
$result = $this->store->lookup($request);
361+
362+
$this->assertInstanceOf(BinaryFileResponse::class, $result);
363+
$this->assertSame(200, $result->getStatusCode());
364+
$this->assertSame(__DIR__.'/Fixtures/favicon.ico', $result->getFile()->getPathname());
365+
$this->assertSame('whatever', $result->headers->get('Foobar'));
366+
}
367+
368+
public function testRegularLookupWithRemovedBinaryResponse()
369+
{
370+
$request = Request::create('https://foobar.com/');
371+
$file = new File(__DIR__.'/Fixtures/favicon.ico');
372+
$response = new BinaryFileResponse($file);
373+
$response->headers->set('Foobar', 'whatever');
374+
375+
$this->store->write($request, $response);
376+
377+
// Now move (same as remove) the file somewhere else
378+
$movedFile = $file->move(__DIR__.'/Fixtures', 'favicon_bu.ico');
379+
380+
$result = $this->store->lookup($request);
381+
$this->assertNull($result);
382+
383+
// Move back for other tests
384+
$movedFile->move(__DIR__.'/Fixtures', 'favicon.ico');
385+
}
386+
350387
public function testLookupWithVaryOnCookies()
351388
{
352389
// Cookies match

0 commit comments

Comments
 (0)