Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions src/Caching/Storages/PeclMemcachedStorage.php
Original file line number Diff line number Diff line change
@@ -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 PeclMemcachedStorage extends Nette\Object implements Nette\Caching\IStorage
{
/** @internal cache structure */
const META_CALLBACKS = 'callbacks',
META_DATA = 'data',
META_DELTA = 'delta';

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

/** @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 'memcache' is not loaded.");
}

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


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it dangerous? It can kill app when unfortunately fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well,

  • app without cache actually can kill the server, not only one request
  • this is copy & past & modify from the current MemcachedStorage.

}
}


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


/**
* Read from cache.
* @param string key
* @return mixed|NULL
*/
public function read($key)
{
$key = urlencode($this->prefix . $key);
$meta = $this->memcache->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->memcache->delete($key, 0);
return NULL;
}

if (!empty($meta[self::META_DELTA])) {
$this->memcache->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 = [
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->memcache->set($key, $meta, $expire);
}


/**
* Removes item from the cache.
* @param string key
* @return void
*/
public function remove($key)
{
$this->memcache->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->memcache->flush();

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

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

Tester\Environment::lock('memcache-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
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

Tester\Environment::lock('memcache-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
20 changes: 11 additions & 9 deletions tests/Storages/Memcached.priority.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,35 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

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

$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal.s3db'));

$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal-memcache.s3db'));
$cache = new Cache($storage);


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

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

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

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


// Cleaning by priority...
$cache->clean([
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ if (!MemcachedStorage::isAvailable()) {
Tester\Environment::skip('Requires PHP extension Memcache.');
}

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

$key = 'nette-sliding-key';

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

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

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

$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal.s3db'));

$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal-memcache.s3db'));
$cache = new Cache($storage);


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

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

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

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


// Cleaning by tags...
$cache->clean([
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/PeclMemcached.expiration.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

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

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


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


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

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


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

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


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


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


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