forked from onPHP/onphp-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alexander A. Zaytsev
committed
Oct 12, 2011
1 parent
22135df
commit 1530384
Showing
3 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<?php | ||
/*************************************************************************** | ||
* Copyright (C) 2011 by Alexander A. Zaytsev * | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU Lesser General Public License as * | ||
* published by the Free Software Foundation; either version 3 of the * | ||
* License, or (at your option) any later version. * | ||
* * | ||
***************************************************************************/ | ||
|
||
final class CascadeCache extends CachePeer | ||
{ | ||
const LOCAL_NULL_VALUE = 'local_nil'; | ||
|
||
private $localPeer = null; | ||
private $remotePeer = null; | ||
private $className = null; | ||
|
||
// map class -> local ttl | ||
private $localTtlMap = array(); | ||
|
||
/** | ||
* @return CascadeCache | ||
**/ | ||
public static function create(CachePeer $localPeer, CachePeer $remotePeer) | ||
{ | ||
return new self($localPeer, $remotePeer); | ||
} | ||
|
||
public function __construct(CachePeer $localPeer, CachePeer $remotePeer) | ||
{ | ||
$this->localPeer = $localPeer; | ||
$this->remotePeer = $remotePeer; | ||
} | ||
|
||
/** | ||
* @return CascadeCache | ||
**/ | ||
public function mark($className) | ||
{ | ||
$this->className = $className; | ||
|
||
$this->localPeer->mark($className); | ||
$this->remotePeer->mark($className); | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @return CascadeCache | ||
**/ | ||
public function setLocalTtlMap(array $map) | ||
{ | ||
$this->localTtlMap = $map; | ||
|
||
return $this; | ||
} | ||
|
||
public function increment($key, $value) | ||
{ | ||
$value = $this->remotePeer->increment($key, $value); | ||
|
||
$this->cacheLocal($key, $value); | ||
|
||
return $value; | ||
} | ||
|
||
public function decrement($key, $value) | ||
{ | ||
$value = $this->remotePeer->decrement($key, $value); | ||
|
||
$this->cacheLocal($key, $value); | ||
|
||
return $value; | ||
} | ||
|
||
public function getList($indexes) | ||
{ | ||
$valueList = $this->localPeer->getList($indexes); | ||
|
||
$returnList = array(); | ||
foreach ($valueList as $key => $value) | ||
if ($value !== self::LOCAL_NULL_VALUE) | ||
$returnList[$key] = $value; | ||
|
||
$fetchIndexes = array_diff($indexes, array_keys($valueList)); | ||
if (!empty($fetchIndexes)) { | ||
$valueList = $this->remotePeer->getList($fetchIndexes); | ||
$returnList = array_merge($returnList, $valueList); | ||
|
||
foreach ($fetchIndexes as $key) | ||
$this->cacheLocal( | ||
$key, | ||
array_key_exists($key, $valueList) | ||
? $valueList[$key] | ||
: null | ||
); | ||
} | ||
|
||
return $returnList; | ||
} | ||
|
||
public function get($key) | ||
{ | ||
$value = $this->localPeer->get($key); | ||
|
||
if ($value === self::LOCAL_NULL_VALUE) | ||
return null; | ||
|
||
if (!$value) { | ||
$value = $this->remotePeer->get($key); | ||
|
||
$this->cacheLocal($key, $value); | ||
} | ||
|
||
return $value; | ||
} | ||
|
||
public function delete($key) | ||
{ | ||
$result = $this->remotePeer->delete($key); | ||
|
||
$this->cacheLocal($key, null); | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* @return CascadeCache | ||
**/ | ||
public function clean() | ||
{ | ||
$this->remotePeer->clean(); | ||
$this->localPeer->clean(); | ||
|
||
return $this; | ||
} | ||
|
||
public function isAlive() | ||
{ | ||
return $this->localPeer->isAlive() && $this->remotePeer->isAlive(); | ||
} | ||
|
||
public function append($key, $data) | ||
{ | ||
$result = $this->remotePeer->append($key, $data); | ||
|
||
$this->localPeer->delete($key); | ||
|
||
return $result; | ||
} | ||
|
||
protected function store( | ||
$action, $key, $value, $expires = Cache::EXPIRES_MEDIUM | ||
) | ||
{ | ||
$result = $this->remotePeer->store($action, $key, $value, $expires); | ||
|
||
if ($result) | ||
$this->cacheLocal($key, $value); | ||
elseif ($action == 'replace') | ||
$this->cacheLocal($key, null); | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* @return CascadeCache | ||
**/ | ||
private function cacheLocal($key, $value) | ||
{ | ||
if (!$value) | ||
$value = self::LOCAL_NULL_VALUE; | ||
|
||
$expires = | ||
array_key_exists($this->className, $this->localTtlMap) | ||
? $this->localTtlMap[$this->className] | ||
: Cache::EXPIRES_MEDIUM; | ||
|
||
$this->localPeer->set($key, $value, $expires); | ||
|
||
return $this; | ||
} | ||
} | ||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
<?php | ||
/*************************************************************************** | ||
* Copyright (C) 2011 by Alexander A. Zaytsev * | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU Lesser General Public License as * | ||
* published by the Free Software Foundation; either version 3 of the * | ||
* License, or (at your option) any later version. * | ||
* * | ||
***************************************************************************/ | ||
|
||
final class CascadeCacheTest extends TestCase | ||
{ | ||
private $localPeer; | ||
private $remotePeer; | ||
private $peer; | ||
|
||
public function __construct() | ||
{ | ||
$this->localPeer = PeclMemcached::create('localhost', 11211); | ||
$this->remotePeer = PeclMemcached::create('localhost', 11212); | ||
$this->peer = CascadeCache::create($this->localPeer, $this->remotePeer); | ||
} | ||
|
||
protected function setUp() | ||
{ | ||
if (!$this->peer->isAlive()) | ||
return $this->markTestSkipped('Local or remote peer is not alive'); | ||
|
||
$this->peer->clean(); | ||
} | ||
|
||
public function testGet() | ||
{ | ||
$this->remotePeer->add('value', 42); | ||
|
||
$this->assertEquals($this->peer->get('value'), 42); | ||
$this->assertEquals($this->localPeer->get('value'), 42); | ||
|
||
$this->assertFalse($this->peer->get('neg_value')); // PeclMemcached | ||
$this->assertEquals( | ||
$this->localPeer->get('neg_value'), | ||
CascadeCache::LOCAL_NULL_VALUE | ||
); | ||
$this->remotePeer->add('neg_value', 'stuff'); | ||
$this->assertNull($this->peer->get('neg_value')); // cached | ||
} | ||
|
||
public function testStore() | ||
{ | ||
$this->assertTrue($this->peer->add('value', 42)); | ||
$this->assertEquals($this->remotePeer->get('value'), 42); | ||
$this->assertEquals($this->localPeer->get('value'), 42); | ||
|
||
$this->assertTrue($this->peer->replace('value', 11)); | ||
$this->assertEquals($this->remotePeer->get('value'), 11); | ||
$this->assertEquals($this->localPeer->get('value'), 11); | ||
|
||
$this->assertTrue($this->peer->set('value', 100)); | ||
$this->assertEquals($this->remotePeer->get('value'), 100); | ||
$this->assertEquals($this->localPeer->get('value'), 100); | ||
|
||
$this->assertFalse($this->peer->replace('not_exist_value', 42)); | ||
$this->assertEquals( | ||
$this->localPeer->get('not_exist_value'), | ||
CascadeCache::LOCAL_NULL_VALUE | ||
); | ||
$this->assertFalse($this->peer->add('value', 44)); | ||
$this->assertEquals($this->localPeer->get('value'), 100); | ||
} | ||
|
||
public function testIncrementDecrement() | ||
{ | ||
$this->remotePeer->add('value', 42); | ||
|
||
$this->assertEquals($this->peer->increment('value', 8), 50); | ||
$this->assertEquals($this->remotePeer->get('value'), 50); | ||
$this->assertEquals($this->localPeer->get('value'), 50); | ||
|
||
$this->assertEquals($this->peer->decrement('value', 2), 48); | ||
$this->assertEquals($this->remotePeer->get('value'), 48); | ||
$this->assertEquals($this->localPeer->get('value'), 48); | ||
} | ||
|
||
public function testDelete() | ||
{ | ||
$this->remotePeer->add('value', 42); | ||
|
||
$this->assertTrue($this->peer->delete('value')); | ||
$this->assertEquals( | ||
$this->localPeer->get('value'), | ||
CascadeCache::LOCAL_NULL_VALUE | ||
); | ||
} | ||
|
||
/** | ||
* @depends testStore | ||
**/ | ||
public function testGetList() | ||
{ | ||
$indexes = array('value1', 'value2'); | ||
|
||
$this->assertTrue( | ||
ArrayUtils::isEmpty($this->peer->getList($indexes)) | ||
); | ||
foreach ($indexes as $key) | ||
$this->assertEquals( | ||
$this->localPeer->get($key), | ||
CascadeCache::LOCAL_NULL_VALUE | ||
); | ||
|
||
$this->remotePeer->set('value1', 'one'); | ||
$this->peer->set('value2', 'two'); | ||
|
||
$valueList = $this->peer->getList($indexes); | ||
$this->assertArrayNotHasKey('value1', $valueList); | ||
$this->assertArrayHasKey('value2', $valueList); | ||
$this->assertEquals($valueList['value2'], 'two'); | ||
} | ||
|
||
/** | ||
* @depends testStore | ||
**/ | ||
public function testLocalTtl() | ||
{ | ||
$this->peer->setLocalTtlMap( | ||
array( | ||
'ttl_mark_2sec' => 2, | ||
'ttl_mark_1sec' => 1 | ||
) | ||
); | ||
$this->peer->mark('ttl_mark_1sec')->set('value1', 'some data'); | ||
$this->peer->mark('ttl_mark_2sec')->set('value2', 'something'); | ||
$this->assertEquals($this->localPeer->get('value1'), 'some data'); | ||
$this->assertEquals($this->localPeer->get('value2'), 'something'); | ||
sleep(1); | ||
$this->assertFalse($this->localPeer->get('value1')); // PeclMemcached | ||
$this->assertEquals($this->localPeer->get('value2'), 'something'); | ||
} | ||
|
||
/** | ||
* NOTE: valid only for PeclMemcached (not Memcached) | ||
* @depends testStore | ||
**/ | ||
public function testAppend() | ||
{ | ||
$this->peer->set('value', 'stuff_'); | ||
$this->assertTrue($this->peer->append('value', 'append')); | ||
$this->assertFalse($this->localPeer->get('value')); | ||
$this->assertEquals($this->peer->get('value'), 'stuff_append'); | ||
} | ||
} | ||
?> |