Skip to content

Commit

Permalink
added NewMemcachedStorage using memcached extension [Closes #38]
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach authored and dg committed Feb 22, 2016
1 parent e15dc97 commit 0cb5c2c
Show file tree
Hide file tree
Showing 13 changed files with 470 additions and 20 deletions.
190 changes: 190 additions & 0 deletions src/Caching/Storages/NewMemcachedStorage.php
@@ -0,0 +1,190 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

namespace Nette\Caching\Storages;

use Nette;
use Nette\Caching\Cache;


/**
* Memcached storage using memcached extension.
*/
class NewMemcachedStorage extends Nette\Object implements Nette\Caching\IStorage
{
/** @internal cache structure */
const META_CALLBACKS = 'callbacks',
META_DATA = 'data',
META_DELTA = 'delta';

/** @var \Memcached */
private $memcached;

/** @var string */
private $prefix;

/** @var IJournal */
private $journal;


/**
* Checks if Memcached extension is available.
* @return bool
*/
public static function isAvailable()
{
return extension_loaded('memcached');
}


public function __construct($host = 'localhost', $port = 11211, $prefix = '', IJournal $journal = NULL)
{
if (!static::isAvailable()) {
throw new Nette\NotSupportedException("PHP extension 'memcached' is not loaded.");
}

$this->prefix = $prefix;
$this->journal = $journal;
$this->memcached = new \Memcached;
if ($host) {
$this->addServer($host, $port);
}
}


public function addServer($host = 'localhost', $port = 11211)
{
if ($this->memcached->addServer($host, $port, 1) === FALSE) {
$error = error_get_last();
throw new Nette\InvalidStateException("Memcached::addServer(): $error[message].");
}
}


/**
* @return \Memcached
*/
public function getConnection()
{
return $this->memcached;
}


/**
* Read from cache.
* @param string key
* @return mixed|NULL
*/
public function read($key)
{
$key = urlencode($this->prefix . $key);
$meta = $this->memcached->get($key);
if (!$meta) {
return NULL;
}

// meta structure:
// array(
// data => stored data
// delta => relative (sliding) expiration
// callbacks => array of callbacks (function, args)
// )

// verify dependencies
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
$this->memcached->delete($key, 0);
return NULL;
}

if (!empty($meta[self::META_DELTA])) {
$this->memcached->replace($key, $meta, $meta[self::META_DELTA] + time());
}

return $meta[self::META_DATA];
}


/**
* Prevents item reading and writing. Lock is released by write() or remove().
* @param string key
* @return void
*/
public function lock($key)
{
}


/**
* Writes item into the cache.
* @param string key
* @param mixed data
* @param array dependencies
* @return void
*/
public function write($key, $data, array $dp)
{
if (isset($dp[Cache::ITEMS])) {
throw new Nette\NotSupportedException('Dependent items are not supported by MemcachedStorage.');
}

$key = urlencode($this->prefix . $key);
$meta = array(
self::META_DATA => $data,
);

$expire = 0;
if (isset($dp[Cache::EXPIRATION])) {
$expire = (int) $dp[Cache::EXPIRATION];
if (!empty($dp[Cache::SLIDING])) {
$meta[self::META_DELTA] = $expire; // sliding time
}
}

if (isset($dp[Cache::CALLBACKS])) {
$meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS];
}

if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) {
if (!$this->journal) {
throw new Nette\InvalidStateException('CacheJournal has not been provided.');
}
$this->journal->write($key, $dp);
}

$this->memcached->set($key, $meta, $expire);
}


/**
* Removes item from the cache.
* @param string key
* @return void
*/
public function remove($key)
{
$this->memcached->delete(urlencode($this->prefix . $key), 0);
}


/**
* Removes items from the cache by conditions & garbage collector.
* @param array conditions
* @return void
*/
public function clean(array $conditions)
{
if (!empty($conditions[Cache::ALL])) {
$this->memcached->flush();

} elseif ($this->journal) {
foreach ($this->journal->clean($conditions) as $entry) {
$this->memcached->delete($entry, 0);
}
}
}

}
4 changes: 3 additions & 1 deletion tests/Storages/Memcached.expiration.phpt
Expand Up @@ -16,8 +16,10 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

Tester\Environment::lock('memcached-expiration', TEMP_DIR);

$key = 'nette-expiration-key';

$key = 'nette-memcache-expiration-key';
$value = 'rulez';

$cache = new Cache(new MemcachedStorage('localhost'));
Expand Down
6 changes: 4 additions & 2 deletions tests/Storages/Memcached.files.phpt
Expand Up @@ -16,14 +16,16 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

Tester\Environment::lock('memcached-files', TEMP_DIR);

$key = 'nette-files-key';

$key = 'nette-memcache-files-key';
$value = 'rulez';

$cache = new Cache(new MemcachedStorage('localhost'));


$dependentFile = TEMP_DIR . '/spec.file';
$dependentFile = TEMP_DIR . '/spec-memcache.file';
@unlink($dependentFile);

// Writing cache...
Expand Down
17 changes: 9 additions & 8 deletions tests/Storages/Memcached.priority.phpt
Expand Up @@ -17,33 +17,34 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

Tester\Environment::lock('memcached-priority', TEMP_DIR);

$storage = new MemcachedStorage('localhost', 11211, '', new FileJournal(TEMP_DIR));
$cache = new Cache($storage);


// Writing cache...
$cache->save('nette-priority-key1', 'value1', array(
$cache->save('nette-memcache-priority-key1', 'value1', array(
Cache::PRIORITY => 100,
));

$cache->save('nette-priority-key2', 'value2', array(
$cache->save('nette-memcache-priority-key2', 'value2', array(
Cache::PRIORITY => 200,
));

$cache->save('nette-priority-key3', 'value3', array(
$cache->save('nette-memcache-priority-key3', 'value3', array(
Cache::PRIORITY => 300,
));

$cache->save('nette-priority-key4', 'value4');
$cache->save('nette-memcache-priority-key4', 'value4');


// Cleaning by priority...
$cache->clean(array(
Cache::PRIORITY => '200',
));

Assert::null($cache->load('nette-priority-key1'));
Assert::null($cache->load('nette-priority-key2'));
Assert::truthy($cache->load('nette-priority-key3'));
Assert::truthy($cache->load('nette-priority-key4'));
Assert::null($cache->load('nette-memcache-priority-key1'));
Assert::null($cache->load('nette-memcache-priority-key2'));
Assert::truthy($cache->load('nette-memcache-priority-key3'));
Assert::truthy($cache->load('nette-memcache-priority-key4'));
4 changes: 3 additions & 1 deletion tests/Storages/Memcached.sliding.phpt
Expand Up @@ -16,8 +16,10 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

Tester\Environment::lock('memcached-sliding', TEMP_DIR);

$key = 'nette-sliding-key';

$key = 'nette-memcache-sliding-key';
$value = 'rulez';

$cache = new Cache(new MemcachedStorage('localhost'));
Expand Down
17 changes: 9 additions & 8 deletions tests/Storages/Memcached.tags.phpt
Expand Up @@ -17,33 +17,34 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

Tester\Environment::lock('memcached-tags', TEMP_DIR);

$storage = new MemcachedStorage('localhost', 11211, '', new FileJournal(TEMP_DIR));
$cache = new Cache($storage);


// Writing cache...
$cache->save('nette-tags-key1', 'value1', array(
$cache->save('nette-memcache-tags-key1', 'value1', array(
Cache::TAGS => array('one', 'two'),
));

$cache->save('nette-tags-key2', 'value2', array(
$cache->save('nette-memcache-tags-key2', 'value2', array(
Cache::TAGS => array('one', 'three'),
));

$cache->save('nette-tags-key3', 'value3', array(
$cache->save('nette-memcache-tags-key3', 'value3', array(
Cache::TAGS => array('two', 'three'),
));

$cache->save('nette-tags-key4', 'value4');
$cache->save('nette-memcache-tags-key4', 'value4');


// Cleaning by tags...
$cache->clean(array(
Cache::TAGS => 'one',
));

Assert::null($cache->load('nette-tags-key1'));
Assert::null($cache->load('nette-tags-key2'));
Assert::truthy($cache->load('nette-tags-key3'));
Assert::truthy($cache->load('nette-tags-key4'));
Assert::null($cache->load('nette-memcache-tags-key1'));
Assert::null($cache->load('nette-memcache-tags-key2'));
Assert::truthy($cache->load('nette-memcache-tags-key3'));
Assert::truthy($cache->load('nette-memcache-tags-key4'));
41 changes: 41 additions & 0 deletions tests/Storages/NewMemcached.expiration.phpt
@@ -0,0 +1,41 @@
<?php

/**
* Test: Nette\Caching\Storages\NewMemcachedStorage expiration test.
*/

use Nette\Caching\Storages\NewMemcachedStorage;
use Nette\Caching\Cache;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


if (!NewMemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcached.');
}

Tester\Environment::lock('memcached-expiration', TEMP_DIR);


$key = 'nette-memcached-expiration-key';
$value = 'rulez';

$cache = new Cache(new NewMemcachedStorage('localhost'));


// Writing cache...
$cache->save($key, $value, array(
Cache::EXPIRATION => time() + 3,
));


// Sleeping 1 second
sleep(1);
Assert::truthy($cache->load($key));


// Sleeping 3 seconds
sleep(3);
Assert::null($cache->load($key));

0 comments on commit 0cb5c2c

Please sign in to comment.