Skip to content

Commit

Permalink
Merge pull request #156 from xavierleune/fix/igbinary_serialize
Browse files Browse the repository at this point in the history
Fix #148 igbinary_serialize cannot serialize resource(stream)
  • Loading branch information
Kevinrob authored Jan 31, 2022
2 parents 9b63aa3 + 726955a commit fda11c5
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 53 deletions.
46 changes: 46 additions & 0 deletions src/BodyStore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Kevinrob\GuzzleCache;

/**
*
* This object is only meant to provide a callable to `GuzzleHttp\Psr7\PumpStream`.
*
* @internal don't use it in your project.
*/
class BodyStore
{
private $body;

private $read = 0;

private $toRead;

public function __construct(string $body)
{
$this->body = $body;
$this->toRead = mb_strlen($this->body);
}

/**
* @param int $length
* @return false|string
*/
public function __invoke(int $length)
{
if ($this->toRead <= 0) {
return false;
}

$length = min($length, $this->toRead);

$body = mb_substr(
$this->body,
$this->read,
$length
);
$this->toRead -= $length;
$this->read += $length;
return $body;
}
}
39 changes: 27 additions & 12 deletions src/CacheEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Kevinrob\GuzzleCache;

use GuzzleHttp\Psr7\PumpStream;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

Expand All @@ -17,14 +18,6 @@ class CacheEntry
*/
protected $response;

/**
* This field is only used for serialize.
* Response::body is a stream and can't be serialized.
*
* @var string
*/
protected $responseBody;

/**
* @var \DateTime
*/
Expand Down Expand Up @@ -265,12 +258,29 @@ public function getAge()

public function __sleep()
{
// Stream/Resource can't be serialized... So we copy the content
// Stream/Resource can't be serialized... So we copy the content into an implementation of `Psr\Http\Message\StreamInterface`
if ($this->response !== null) {
$this->responseBody = (string) $this->response->getBody();
$this->response->getBody()->rewind();
$responseBody = (string)$this->response->getBody();
$this->response = $this->response->withBody(
new PumpStream(
new BodyStore($responseBody),
[
'size' => mb_strlen($responseBody),
]
)
);
}

$requestBody = (string)$this->request->getBody();
$this->request = $this->request->withBody(
new PumpStream(
new BodyStore($requestBody),
[
'size' => mb_strlen($requestBody)
]
)
);

return array_keys(get_object_vars($this));
}

Expand All @@ -280,8 +290,13 @@ public function __wakeup()
if ($this->response !== null) {
$this->response = $this->response
->withBody(
\GuzzleHttp\Psr7\Utils::streamFor($this->responseBody)
\GuzzleHttp\Psr7\Utils::streamFor((string) $this->response->getBody())
);
}
$this->request = $this->request
->withBody(
\GuzzleHttp\Psr7\Utils::streamFor((string) $this->request->getBody())
);
}

}
46 changes: 46 additions & 0 deletions tests/BodyStoreTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Kevinrob\GuzzleCache\Tests;

use http\Message\Body;
use Kevinrob\GuzzleCache\BodyStore;
use PHPUnit\Framework\TestCase;

class BodyStoreTest extends TestCase
{
public function testBodyStoreReturnsAllContentIfAskedLengthIsGreaterThanAvailable()
{
$str = 'Not so long';
$bodyStore = new BodyStore($str);
$this->assertEquals($str, $bodyStore(PHP_INT_MAX));
}

public function testBodyStoreReturnsFalseIsAllHasBeenRead()
{
$str = 'Not so long';
$bodyStore = new BodyStore($str);
$bodyStore(PHP_INT_MAX);
$this->assertFalse($bodyStore(1));
}

public function testBodyStoreCanReadAllContentWhenIteratedEnough()
{
$originalString = <<<EOF
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
EOF;
$bodyStore = new BodyStore($originalString);

$got = '';
while ($str = $bodyStore(1)) {
$got .= $str;
}
$this->assertEquals($originalString, $got);
}

public function testBodyStoreReturnsEmptyStringWhenAsking0()
{
$str = 'Not so long';
$bodyStore = new BodyStore($str);
$this->assertEquals('', $bodyStore(0));
}
}
30 changes: 30 additions & 0 deletions tests/CacheEntryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Kevinrob\GuzzleCache\Tests;

use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Kevinrob\GuzzleCache\CacheEntry;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
Expand Down Expand Up @@ -81,6 +83,34 @@ public function testTtlUsesMaximumPossibleLifetime()
$this->assertEquals(70, $cacheEntry->getTTL());
}

public function testCacheEntryShouldBeSerializableWithIgBinaryWithoutWarning()
{
$request = new Request(
'GET',
'test.local',
[],
'Sample body' // Always include a body in the request to be sure there is a stream in it
);
$response = new Response(
200, [
'Cache-Control' => 'max-age=60',
],
'Test content'
);
$cacheEntry = new CacheEntry($request, $response, $this->makeDateTimeOffset(10));

if(extension_loaded('igbinary')) {
/**
* @var CacheEntry $cacheEntryPostDeserialization
*/
$cacheEntryPostDeserialization = igbinary_unserialize(igbinary_serialize($cacheEntry));
$this->assertEquals((string)$cacheEntry->getOriginalRequest()->getBody(), (string)$cacheEntryPostDeserialization->getOriginalRequest()->getBody());
$this->assertEquals((string)$cacheEntry->getOriginalResponse()->getBody(), (string)$cacheEntryPostDeserialization->getOriginalResponse()->getBody());
} else {
$this->addWarning('Extension igbinary not loaded, not asserting serialization.');
}
}

private function setResponseHeader($name, $value)
{
$this->responseHeaders[$name] = [$value];
Expand Down
91 changes: 50 additions & 41 deletions tests/PrivateCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
use Cache\Adapter\PHPArray\ArrayCachePool;
use Cache\Bridge\SimpleCache\SimpleCacheBridge;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\ChainCache;
use Doctrine\Common\Cache\FilesystemCache;
use Doctrine\Common\Cache\PhpFileCache;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Kevinrob\GuzzleCache\Storage\CacheStorageInterface;
use Kevinrob\GuzzleCache\Storage\CompressedDoctrineCacheStorage;
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
use Kevinrob\GuzzleCache\Storage\FlysystemStorage;
Expand All @@ -23,22 +23,14 @@

class PrivateCacheTest extends TestCase
{
public function testCacheProvider()
/**
* @param CacheStorageInterface $cacheProvider
* @param $TMP_DIR
* @return void
* @dataProvider cacheProvider
*/
public function testCacheProvider(CacheStorageInterface $cacheProvider, $TMP_DIR = null)
{
$TMP_DIR = __DIR__.'/tmp/';

$cacheProviders = [
new DoctrineCacheStorage(new ArrayCache()),
new DoctrineCacheStorage(new ChainCache([new ArrayCache()])),
new DoctrineCacheStorage(new FilesystemCache($TMP_DIR)),
new DoctrineCacheStorage(new PhpFileCache($TMP_DIR)),
new FlysystemStorage(new Local($TMP_DIR)),
new Psr6CacheStorage(new ArrayCachePool()),
new Psr16CacheStorage(new SimpleCacheBridge(new ArrayCachePool())),
new CompressedDoctrineCacheStorage(new ArrayCache()),
new VolatileRuntimeStorage(),
];

$request = new Request('GET', 'test.local');
$response = new Response(
200, [
Expand All @@ -53,39 +45,56 @@ public function testCacheProvider()
'Test new content'
);

/** @var CacheProvider $cacheProvider */
foreach ($cacheProviders as $cacheProvider) {
if ($TMP_DIR !== null) {
$this->rrmdir($TMP_DIR);
}

$cache = new PrivateCacheStrategy(
$cacheProvider
);
$cache->cache($request, $response);
$entry = $cache->fetch($request);

$cache = new PrivateCacheStrategy(
$cacheProvider
);
$cache->cache($request, $response);
$entry = $cache->fetch($request);
$this->assertNotNull($entry, get_class($cacheProvider));
$this->assertEquals(
(string) $response->getBody(),
(string) $entry->getResponse()->getBody(),
get_class($cacheProvider)
);

$this->assertNotNull($entry, get_class($cacheProvider));
$this->assertEquals(
(string) $response->getBody(),
(string) $entry->getResponse()->getBody(),
get_class($cacheProvider)
);
$cache->update($request, $response2);
$entry = $cache->fetch($request);

$cache->update($request, $response2);
$entry = $cache->fetch($request);
$this->assertNotNull($entry, get_class($cacheProvider));
$this->assertEquals(
(string) $response2->getBody(),
(string) $entry->getResponse()->getBody(),
get_class($cacheProvider)
);

$this->assertNotNull($entry, get_class($cacheProvider));
$this->assertEquals(
(string) $response2->getBody(),
(string) $entry->getResponse()->getBody(),
get_class($cacheProvider)
);
$cache->delete($request);
$entry = $cache->fetch($request);
$this->assertNull($entry, get_class($cacheProvider));

$cache->delete($request);
$entry = $cache->fetch($request);
$this->assertNull($entry, get_class($cacheProvider));
if ($TMP_DIR !== null) {
$this->rrmdir($TMP_DIR);
}
}

$this->rrmdir($TMP_DIR);
public function cacheProvider()
{
$TMP_DIR = __DIR__.'/tmp/';
return [
'doctrine.arraycache' => [ new DoctrineCacheStorage(new ArrayCache()) ],
'doctrine.chaincache' => [ new DoctrineCacheStorage(new ChainCache([new ArrayCache()])) ],
'doctrine.filesystem' => [ new DoctrineCacheStorage(new FilesystemCache($TMP_DIR)), $TMP_DIR ],
'doctrine.phpfile' => [ new DoctrineCacheStorage(new PhpFileCache($TMP_DIR)), $TMP_DIR ],
'flysystem' => [ new FlysystemStorage(new Local($TMP_DIR)), $TMP_DIR ],
'psr6' => [ new Psr6CacheStorage(new ArrayCachePool()) ],
'psr16' => [ new Psr16CacheStorage(new SimpleCacheBridge(new ArrayCachePool())) ],
'compressedDoctrineStorage' => [ new CompressedDoctrineCacheStorage(new ArrayCache()) ],
'volatileruntimeStorage' => [ new VolatileRuntimeStorage() ]
];
}

/**
Expand Down

0 comments on commit fda11c5

Please sign in to comment.