-
Notifications
You must be signed in to change notification settings - Fork 12
Add memcache memoize support #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
6fee3ea
1cb275f
ae8a491
0541038
56380ff
06d0eb2
c119d44
c914269
8b50ff3
0224353
338c3bc
44ebc38
ecc8795
f6444e0
01b9c9d
a583758
eb60f59
d64748b
28038a5
7b2ce7f
226ecb3
6d11f67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| extension=memcache.so | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| extension=memcached.so | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| <?php | ||
|
|
||
| namespace TraderInteractive\Memoize; | ||
|
|
||
| /** | ||
| * A memoizer that caches the results in memcache. | ||
| */ | ||
| class Memcache implements Memoize | ||
| { | ||
| /** | ||
| * The memcache client | ||
| * | ||
| * @var \Memcache | ||
| */ | ||
| private $client; | ||
|
|
||
| /** | ||
| * Cache refresh | ||
| * | ||
| * @var boolean | ||
| */ | ||
| private $refresh; | ||
|
|
||
| /** | ||
| * Sets the memcache client. | ||
| * | ||
| * @param \Memcache $client The memcache client to use | ||
| * @param boolean $refresh If true we will always overwrite cache even if it is already set | ||
| */ | ||
| public function __construct(\Memcache $client, bool $refresh = false) | ||
| { | ||
| $this->client = $client; | ||
| $this->refresh = $refresh; | ||
| } | ||
|
|
||
| /** | ||
| * The value is stored in memcache as a json_encoded string, | ||
| * so make sure that the value you return from $compute is json-encode-able. | ||
| * | ||
| * @see Memoize::memoizeCallable | ||
| * | ||
| * @param string $key | ||
| * @param callable $compute | ||
| * @param int|null $cacheTime | ||
| * @param bool $refresh | ||
| * | ||
| * @return mixed | ||
| */ | ||
| public function memoizeCallable(string $key, callable $compute, int $cacheTime = null, bool $refresh = false) | ||
| { | ||
| if (!$this->refresh && !$refresh) { | ||
| try { | ||
| $cached = $this->client->get($key, $flags, $flags); | ||
| if ($cached !== false && $cached != null) { | ||
| $data = json_decode($cached, true); | ||
| return $data['result']; | ||
| } | ||
| } catch (\Exception $e) { | ||
| return call_user_func($compute); | ||
| } | ||
| } | ||
|
|
||
| $result = call_user_func($compute); | ||
|
|
||
| // If the result is false/null/empty, then there is no point in storing it in cache. | ||
| if ($result === false || $result == null || empty($result)) { | ||
| return $result; | ||
| } | ||
|
|
||
| $this->cache($key, json_encode(['result' => $result]), $cacheTime); | ||
|
|
||
| return $result; | ||
| } | ||
|
|
||
| /** | ||
| * Caches the value into memcache with errors suppressed. | ||
| * | ||
| * @param string $key The key. | ||
| * @param string $value The value. | ||
| * @param int $cacheTime The optional cache time | ||
| * | ||
| * @return void | ||
| */ | ||
| private function cache(string $key, string $value, int $cacheTime = null) | ||
| { | ||
| try { | ||
| $this->client->set($key, $value, 0, $cacheTime); | ||
| } catch (\Exception $e) { | ||
| // We don't want exceptions in accessing the cache to break functionality. | ||
| // The cache should be as transparent as possible. | ||
| // If insight is needed into these exceptions, | ||
| // a better way would be by notifying an observer with the errors. | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <?php | ||
|
|
||
| namespace TraderInteractive\Memoize; | ||
|
|
||
| class MemcacheMockable extends \Memcache | ||
| { | ||
| public function get($name, &$flags, &$cas) | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,140 @@ | ||||||||||||||||||||||
| <?php | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| namespace TraderInteractive\Memoize; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| use PHPUnit\Framework\TestCase; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * @coversDefaultClass \TraderInteractive\Memoize\Memcache | ||||||||||||||||||||||
| * @covers ::<private> | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| class MemcacheTest extends TestCase | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * @test | ||||||||||||||||||||||
| * @covers ::__construct | ||||||||||||||||||||||
| * @covers ::memoizeCallable | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public function memoizeCallableWithCachedValue() | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| $count = 0; | ||||||||||||||||||||||
| $key = 'foo'; | ||||||||||||||||||||||
| $value = 'bar'; | ||||||||||||||||||||||
| $cachedValue = json_encode(['result' => $value]); | ||||||||||||||||||||||
| $compute = function () use (&$count, $value) { | ||||||||||||||||||||||
| $count++; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return $value; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $client = $this->getMemcacheMock(); | ||||||||||||||||||||||
| $client->expects( | ||||||||||||||||||||||
| $this->once() | ||||||||||||||||||||||
| )->method('get')->with($this->equalTo($key))->will($this->returnValue($cachedValue)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $memoizer = new Memcache($client); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $this->assertSame($value, $memoizer->memoizeCallable($key, $compute)); | ||||||||||||||||||||||
| $this->assertSame(0, $count); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * @test | ||||||||||||||||||||||
| * @covers ::__construct | ||||||||||||||||||||||
| * @covers ::memoizeCallable | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public function memoizeCallableWithExceptionOnGet() | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| $count = 0; | ||||||||||||||||||||||
| $key = 'foo'; | ||||||||||||||||||||||
| $value = 'bar'; | ||||||||||||||||||||||
| $compute = function () use (&$count, $value) { | ||||||||||||||||||||||
| $count++; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return $value; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $client = $this->getMemcacheMock(); | ||||||||||||||||||||||
| $client->expects( | ||||||||||||||||||||||
| $this->once() | ||||||||||||||||||||||
| )->method('get')->with($this->equalTo($key))->will($this->throwException(new \Exception())); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $memoizer = new Memcache($client); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $this->assertSame($value, $memoizer->memoizeCallable($key, $compute)); | ||||||||||||||||||||||
| $this->assertSame(1, $count); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * @test | ||||||||||||||||||||||
| * @covers ::__construct | ||||||||||||||||||||||
| * @covers ::memoizeCallable | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public function memoizeCallableWithUncachedKey() | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| $count = 0; | ||||||||||||||||||||||
| $key = 'foo'; | ||||||||||||||||||||||
| $value = 'bar'; | ||||||||||||||||||||||
| $cachedValue = json_encode(['result' => $value]); | ||||||||||||||||||||||
| $cacheTime = 1234; | ||||||||||||||||||||||
| $compute = function () use (&$count, $value) { | ||||||||||||||||||||||
| $count++; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return $value; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $client = $this->getMemcacheMock(); | ||||||||||||||||||||||
| $client->expects($this->once())->method('get')->with($this->equalTo($key))->will($this->returnValue(null)); | ||||||||||||||||||||||
| $client->expects($this->once())->method('set') | ||||||||||||||||||||||
| ->with($this->equalTo($key), $this->equalTo($cachedValue), $this->equalTo(0), $this->equalTo($cacheTime)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $memoizer = new Memcache($client); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $this->assertSame($value, $memoizer->memoizeCallable($key, $compute, $cacheTime)); | ||||||||||||||||||||||
| $this->assertSame(1, $count); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * @test | ||||||||||||||||||||||
| * @covers ::__construct | ||||||||||||||||||||||
| * @covers ::memoizeCallable | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public function memoizeCallableWithUncachedKeyWithExceptionOnSet() | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| $count = 0; | ||||||||||||||||||||||
| $key = 'foo'; | ||||||||||||||||||||||
| $value = 'bar'; | ||||||||||||||||||||||
| $cachedValue = json_encode(['result' => $value]); | ||||||||||||||||||||||
| $compute = function () use (&$count, $value) { | ||||||||||||||||||||||
| $count++; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return $value; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $client = $this->getMemcacheMock(); | ||||||||||||||||||||||
| $client->expects( | ||||||||||||||||||||||
| $this->once() | ||||||||||||||||||||||
| )->method('get')->with($this->equalTo($key))->will($this->returnValue(null)); | ||||||||||||||||||||||
| $setExpectation = $client->expects( | ||||||||||||||||||||||
| $this->once() | ||||||||||||||||||||||
| )->method('set')->with($this->equalTo($key), $this->equalTo($cachedValue)); | ||||||||||||||||||||||
| $setExpectation->will($this->throwException(new \Exception())); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $memoizer = new Memcache($client); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $this->assertSame($value, $memoizer->memoizeCallable($key, $compute)); | ||||||||||||||||||||||
| $this->assertSame(1, $count); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public function getMemcacheMock() : \Memcache | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if (PHP_VERSION_ID < 80000) { | ||||||||||||||||||||||
| require_once 'MemcacheMockable.php'; | ||||||||||||||||||||||
| return $this->getMockBuilder(MemcacheMockable::class) | ||||||||||||||||||||||
|
||||||||||||||||||||||
| ->setMethods(['get', 'set'])->getMock(); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| return $this->getMockBuilder(\Memcache::class)->setMethods(['get', 'set'])->getMock(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
||||||||||||||||||||||
| if (PHP_VERSION_ID < 80000) { | |
| require_once 'MemcacheMockable.php'; | |
| return $this->getMockBuilder(MemcacheMockable::class) | |
| ->setMethods(['get', 'set'])->getMock(); | |
| } else { | |
| return $this->getMockBuilder(\Memcache::class)->setMethods(['get', 'set'])->getMock(); | |
| } | |
| $isOlderPHPVersion = PHP_VERSION_ID < 80000; | |
| $memcacheClass =$isOlderPHPVersion ? MemcacheMockable::class : Memcache::class; | |
| return $this->getMockBuilder(\Memcache::class)->setMethods(['get', 'set'])->getMock(); |
Just a suggestion to avoid the inline require_once call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should be removed