Skip to content

Commit

Permalink
add CascadeCache
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander A. Zaytsev committed Oct 12, 2011
1 parent 22135df commit 1530384
Show file tree
Hide file tree
Showing 3 changed files with 344 additions and 0 deletions.
186 changes: 186 additions & 0 deletions core/Cache/CascadeCache.class.php
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;
}
}
?>
5 changes: 5 additions & 0 deletions doc/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2011-10-12 Alexander A. Zaytsev

* core/Cache/CascadeCache.class.php,
test/core/CascadeCacheTest.class.php: add CascadeCache.

2011-09-30 Evgeny V. Kokovikhin

* core/Cache/Memcached.class.php, core/Cache/PeclMemcached.class.php,
Expand Down
153 changes: 153 additions & 0 deletions test/core/CascadeCacheTest.class.php
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');
}
}
?>

0 comments on commit 1530384

Please sign in to comment.