From a6649ca16aad5705b8849b96932e88c828f6f916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20S=C3=A1nchez=20Chordi?= Date: Wed, 26 Nov 2014 01:14:53 +0100 Subject: [PATCH 1/6] Update to use Memcached Added a bunch of extra functionality too --- Client/MemcacheClient.php | 155 ++++++++++++++++++++++++++++++---- Resources/config/services.yml | 2 +- 2 files changed, 140 insertions(+), 17 deletions(-) diff --git a/Client/MemcacheClient.php b/Client/MemcacheClient.php index 33417b6..d56de79 100644 --- a/Client/MemcacheClient.php +++ b/Client/MemcacheClient.php @@ -15,31 +15,31 @@ */ class MemcacheClient implements CacheClientInterface { + const PREFIX_MAX_LENGTH = 128; + protected $safe = false; protected $mem = null; protected $servers = array(); protected $sockttl = 0.2; - protected $compression = false; - protected $prefix = ''; /** * Constructs the cache client using an injected Memcache instance * * @access public */ - public function __construct(\Memcache $memcache) + public function __construct(\Memcached $memcached) { - $this->mem = $memcache; + $this->mem = $memcached; } /** - * Add a server to the memcache pool. + * Add a server to the memcached pool. * * Does not probe server, does not set Safe to true. * * Should really be private, or modified to handle the probeServer action itself. * - * @param string $ip Location of memcache server + * @param string $ip Location of memcached server * @param int $port Optional: Port number (default: 11211) * @access public * @return void @@ -52,7 +52,7 @@ public function addServer($ip, $port = 11211) } /** - * Add an array of servers to the memcache pool + * Add an array of servers to the memcached pool * * Uses ProbeServer to verify that the connection is valid. * @@ -95,7 +95,7 @@ public function addServers(array $servers) * flawed way to go about this. * * @param string $ip IP address (or hostname, possibly) - * @param int $port Port that memcache is running on + * @param int $port Port that memcached is running on * @access public * @return boolean True if the socket opens successfully, or false if it fails */ @@ -115,16 +115,16 @@ public function probeServer($ip, $port) } /** - * Retrieve a value from memcache + * Retrieve a value from memcached * - * @param string|array $key Unique identifier or array of identifiers + * @param string $key Unique identifier * @access public * @return mixed Requested value, or false if an error occurs */ public function get($key) { if ($this->isSafe()) { - $key = $this->prefix . $key; + return $this->mem->get($key); } @@ -132,7 +132,25 @@ public function get($key) } /** - * Add a value to the memcache + * Retrieve a set of values from memcached + * + * @param array $keys of Unique identifiers + * @access public + * @return mixed Requested value, or false if an error occurs + */ + public function getMulti($keys) + { + if ($this->isSafe()) { + $key = $this->prependPrefix($keys); + + return $this->mem->getMulti($key); + } + + return false; + } + + /** + * Add a value to the memcached * * @param string $key Unique key * @param mixed $value A value. I recommend a string, be it serialized or not - other values haven't been tested :) @@ -143,7 +161,7 @@ public function get($key) public function set($key, $value, $ttl) { if ($this->isSafe()) { - $key = $this->prefix . $key; + return $this->mem->set($key, $value, $this->compression, $ttl); } @@ -151,7 +169,24 @@ public function set($key, $value, $ttl) } /** - * Delete a value from the memcache + * Add a set of values to the memcached + * + * @param array $values An array of pairs key-value. + * @param int $ttl Number of seconds for the value to be valid for + * @access public + * @return void + */ + public function setMulti($values, $ttl) + { + if ($this->isSafe()) { + return $this->mem->set($values, $this->compression, $ttl); + } + + return false; + } + + /** + * Delete a value from the memcached * * @param string $key Unique key * @access public @@ -160,13 +195,79 @@ public function set($key, $value, $ttl) public function delete($key) { if ($this->isSafe()) { - $key = $this->prefix . $key; + return $this->mem->delete($key, 0); } return false; } + /** + * Delete a set of values from the memcached + * + * @param array $keys Unique key + * @param int $time Time to wait before delete + * @access public + * @return void + */ + public function deleteMulti($keys, $time = 0) + { + if ($this->isSafe()) { + + return $this->mem->deleteMulti($keys, $time); + } + + return false; + } + + /** + * Delete a set of values from the memcached + * + * @param array $regex Regular Expression + * @param array $time Time to wait before delete + * @access public + * @return void + */ + public function deleteMultiRegex($regex, $time = 0) + { + if ($this->isSafe()) { + $keys = $this->getKeys(); + $matchingKeys = array(); + + foreach ($keys as $key) { + if (preg_match($regex, $key)) { + $matchingKeys[] = $key; + } + } + + return $this->mem->deleteMulti($matchingKeys, $time); + } + + return false; + } + + /** + * Get all the cache keys + * + * @access public + * @return void + */ + public function getKeys() + { + return $this->mem->getAllKeys(); + } + + /** + * Clear all the cache + * + * @access public + * @return void + */ + public function flush() + { + return $this->mem->flush(); + } + /** * Check if the cache is live * @@ -195,6 +296,28 @@ public function getStats() */ public function setPrefix($prefix) { - $this->prefix = $prefix; + if (strlen($prefix) <= self::PREFIX_MAX_LENGTH) { + $this->mem->setOption(\Memcached::OPT_PREFIX_KEY, $prefix); + } + } + + /** + * Enable compression + * + * return self + */ + public function enableCompression() + { + $this->mem->setOption(\Memcached::OPT_COMPRESSION, true); + } + + /** + * Enable compression + * + * return self + */ + public function diableCompression() + { + $this->mem->setOption(\Memcached::OPT_COMPRESSION, false); } } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 7c95642..950ac02 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -1,7 +1,7 @@ parameters: beryllium_cache.class: Beryllium\CacheBundle\Cache beryllium_cache.client.class: Beryllium\CacheBundle\Client\MemcacheClient - beryllium_cache.client.memcache.class: Memcache + beryllium_cache.client.memcache.class: Memcached beryllium_cache.client.servers: { 127.0.0.1 : 11211 } beryllium_cache.client.prefix: '' beryllium_cache.default_ttl: 300 From 22efa4d35ddd221511dff9691d7d5a040de44a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20S=C3=A1nchez=20Chordi?= Date: Wed, 26 Nov 2014 11:52:22 +0100 Subject: [PATCH 2/6] Fix bugs --- Client/MemcacheClient.php | 11 +++++------ composer.json | 8 ++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Client/MemcacheClient.php b/Client/MemcacheClient.php index d56de79..34934e3 100644 --- a/Client/MemcacheClient.php +++ b/Client/MemcacheClient.php @@ -5,7 +5,7 @@ use Beryllium\CacheBundle\CacheClientInterface; /** - * Client interface for Memcache servers + * Client interface for Memcached servers * * @uses CacheClientInterface * @package @@ -141,9 +141,8 @@ public function get($key) public function getMulti($keys) { if ($this->isSafe()) { - $key = $this->prependPrefix($keys); - return $this->mem->getMulti($key); + return $this->mem->getMulti($keys); } return false; @@ -162,7 +161,7 @@ public function set($key, $value, $ttl) { if ($this->isSafe()) { - return $this->mem->set($key, $value, $this->compression, $ttl); + return $this->mem->set($key, $value, $ttl); } return false; @@ -179,7 +178,7 @@ public function set($key, $value, $ttl) public function setMulti($values, $ttl) { if ($this->isSafe()) { - return $this->mem->set($values, $this->compression, $ttl); + return $this->mem->setMulti($values, $ttl); } return false; @@ -312,7 +311,7 @@ public function enableCompression() } /** - * Enable compression + * Disable compression * * return self */ diff --git a/composer.json b/composer.json index b23ef10..7a648be 100644 --- a/composer.json +++ b/composer.json @@ -4,19 +4,19 @@ "keywords": ["memcache","cache"], "type": "symfony-bundle", "license": "MIT", - "authors": + "authors": [ - { + { "name": "Kevin Boyd (aka Beryllium)", "homepage": "http://www.beryllium.ca/" } ], - "require": + "require": { "php": ">=5.3.0", "symfony/framework-bundle": "2.*" }, - "autoload": + "autoload": { "psr-0": { "Beryllium\\CacheBundle": "" } }, From 7e9e44fb95ab88c4fc810c3b4f770bbd65349c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20S=C3=A1nchez=20Chordi?= Date: Wed, 26 Nov 2014 11:52:44 +0100 Subject: [PATCH 3/6] Add basic test suite --- Tests/Client/MemcacheClientTest.php | 139 ++++++++++++++++++++++++++++ phpunit.xml | 22 +++++ 2 files changed, 161 insertions(+) create mode 100644 Tests/Client/MemcacheClientTest.php create mode 100644 phpunit.xml diff --git a/Tests/Client/MemcacheClientTest.php b/Tests/Client/MemcacheClientTest.php new file mode 100644 index 0000000..637c182 --- /dev/null +++ b/Tests/Client/MemcacheClientTest.php @@ -0,0 +1,139 @@ +mc = new MemcacheClient(new \Memcached()); + $this->mc->addServers(array('127.0.0.1' => 11211)); + } + + public function testSetAndGetValue() + { + $this->mc->set('test', 42, 1000); + $test = $this->mc->get('test'); + + $this->assertEquals(42, $test); + } + + public function testGetKeysValue() + { + $this->mc->set('test', 42, 1000); + $keys = $this->mc->getKeys(); + + $this->assertContains('test', $keys); + } + + public function testSetMultiAndGetMultiValue() + { + $this->mc->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + + $test = $this->mc->getMulti(array( + 'test_1', + 'test_2', + 'test_3', + )); + + $this->assertArrayHasKey('test_1', $test); + $this->assertContains(1, $test); + + $this->assertArrayHasKey('test_2', $test); + $this->assertContains(2, $test); + + $this->assertArrayHasKey('test_3', $test); + $this->assertContains(3, $test); + } + + public function testDelete() + { + $this->mc->set('test_1', 1, 1000); + $this->assertContains('test_1', $this->mc->getKeys()); + + $this->mc->delete('test_1'); + $this->assertNotContains('test_1', $this->mc->getKeys()); + } + + public function testDeleteMulti() + { + $this->mc->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + + $this->mc->deleteMulti(array('test_1', 'test_2', 'test_3')); + + $keys = $this->mc->getKeys(); + $this->assertNotContains('test_1', $keys); + $this->assertNotContains('test_2', $keys); + $this->assertNotContains('test_3', $keys); + } + + public function testDeleteMultiRegex() + { + $this->mc->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + + $this->mc->deleteMultiRegex('/test_(\d)/'); + + $keys = $this->mc->getKeys(); + $this->assertNotContains('test_1', $keys); + $this->assertNotContains('test_2', $keys); + $this->assertNotContains('test_3', $keys); + } + + public function testSetPrefix() + { + $this->mc->setPrefix('test_'); + $this->mc->set('string', 'value', 1000); + + $test = $this->mc->getKeys(); + + $this->assertContains('test_string', $test); + } + + public function testFlushCache() + { + $this->mc->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + $this->mc->flush(); + + $keys = $this->mc->getKeys(); + + $this->assertEmpty($keys); + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..6de7dc5 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,22 @@ + + + + + + + + Tests + + + + \ No newline at end of file From bbe621fffc7229f6967223f677ed514bd5e111ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20S=C3=A1nchez=20Chordi?= Date: Wed, 26 Nov 2014 12:15:12 +0100 Subject: [PATCH 4/6] Refactor Cache class and add tests --- Cache.php | 71 ++++++++++++++++++++---- Tests/CacheTest.php | 132 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 Tests/CacheTest.php diff --git a/Cache.php b/Cache.php index 28e28b5..ab84466 100644 --- a/Cache.php +++ b/Cache.php @@ -32,12 +32,7 @@ class Cache implements CacheInterface public function __construct(CacheClientInterface $client = null) { if (!empty($client)) { - if (is_object($client) && ($client instanceof CacheClientInterface)) { - $this->client = $client; - } - else { - throw new \Exception('Invalid Cache Client Interface'); - } + $this->setClient($client); } } @@ -74,9 +69,9 @@ public function setTtl($ttl) */ public function setClient(CacheClientInterface $client) { - if (is_object($client) && ($client instanceof CacheClientInterface)) + if (is_object($client) && ($client instanceof CacheClientInterface)) { $this->client = $client; - else { + } else { throw new \Exception('Invalid Cache Client Interface'); } } @@ -91,6 +86,10 @@ public function setClient(CacheClientInterface $client) public function get($key) { if ($this->isSafe() && !empty($key)) { + if (is_array($key)) { + return $this->client->getMulti($key); + } + return $this->client->get($key); } @@ -117,20 +116,70 @@ public function set($key, $value, $ttl = null) return false; } + /** + * Add a set of values to the memcached + * + * @param array $values An array of pairs key-value. + * @param int $ttl Number of seconds for the value to be valid for + * @access public + * @return void + */ + public function setMulti($values, $ttl) + { + return $this->client->setMulti($values, $ttl); + } + /** * Delete a key from the cache * - * @param string $key Unique key + * @param string|array $key Unique key or a bunch of unique keys * @access public * @return void */ public function delete($key) { - if ($this->isSafe() && !empty($key)) { + if ($this->isSafe()) { + if (is_array($key)) { + return $this->client->deleteMulti($key); + } + return $this->client->delete($key); } + } - return false; + /** + * Delete a set of values from the memcached + * + * @param array $regex Regular Expression + * @param array $time Time to wait before delete + * @access public + * @return void + */ + public function deleteRegex($regex, $time = 0) + { + return $this->client->deleteMultiRegex($regex, $time); + } + + /** + * Get all the cache keys + * + * @access public + * @return void + */ + public function getKeys() + { + return $this->client->getKeys(); + } + + /** + * Clear all the cache + * + * @access public + * @return void + */ + public function flush() + { + return $this->client->flush(); } /** diff --git a/Tests/CacheTest.php b/Tests/CacheTest.php new file mode 100644 index 0000000..7f164ff --- /dev/null +++ b/Tests/CacheTest.php @@ -0,0 +1,132 @@ +addServers(array('127.0.0.1' => 11211)); + + $this->client = new Cache($mc); + } + + public function testSetAndGetValue() + { + $this->client->set('test', 42, 1000); + $test = $this->client->get('test'); + + $this->assertEquals(42, $test); + } + + public function testGetKeysValue() + { + $this->client->set('test', 42, 1000); + $keys = $this->client->getKeys(); + + $this->assertContains('test', $keys); + } + + public function testSetMultiAndGetMultiValue() + { + $this->client->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + + $test = $this->client->get(array( + 'test_1', + 'test_2', + 'test_3', + )); + + $this->assertArrayHasKey('test_1', $test); + $this->assertContains(1, $test); + + $this->assertArrayHasKey('test_2', $test); + $this->assertContains(2, $test); + + $this->assertArrayHasKey('test_3', $test); + $this->assertContains(3, $test); + } + + public function testDelete() + { + $this->client->set('test_1', 1, 1000); + $this->assertContains('test_1', $this->client->getKeys()); + + $this->client->delete('test_1'); + $this->assertNotContains('test_1', $this->client->getKeys()); + } + + public function testDeleteMulti() + { + $this->client->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + + $this->client->delete(array('test_1', 'test_2', 'test_3')); + + $keys = $this->client->getKeys(); + $this->assertNotContains('test_1', $keys); + $this->assertNotContains('test_2', $keys); + $this->assertNotContains('test_3', $keys); + } + + public function testDeleteMultiRegex() + { + $this->client->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + + $this->client->deleteRegex('/test_(\d)/'); + + $keys = $this->client->getKeys(); + $this->assertNotContains('test_1', $keys); + $this->assertNotContains('test_2', $keys); + $this->assertNotContains('test_3', $keys); + } + + public function testFlushCache() + { + $this->client->setMulti( + array( + 'test_1' => 1, + 'test_2' => 2, + 'test_3' => 3, + ), + 1000 + ); + $this->client->flush(); + + $keys = $this->client->getKeys(); + + $this->assertEmpty($keys); + } +} From 1f5071410678edad890a73307bc69cd30f71448d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20S=C3=A1nchez=20Chordi?= Date: Wed, 26 Nov 2014 12:17:58 +0100 Subject: [PATCH 5/6] Add compression parameter and refactor method --- Client/MemcacheClient.php | 16 +++------------- Resources/config/services.yml | 2 ++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Client/MemcacheClient.php b/Client/MemcacheClient.php index 34934e3..ba7fe2b 100644 --- a/Client/MemcacheClient.php +++ b/Client/MemcacheClient.php @@ -301,22 +301,12 @@ public function setPrefix($prefix) } /** - * Enable compression + * Set compression * * return self */ - public function enableCompression() + public function setCompression($status) { - $this->mem->setOption(\Memcached::OPT_COMPRESSION, true); - } - - /** - * Disable compression - * - * return self - */ - public function diableCompression() - { - $this->mem->setOption(\Memcached::OPT_COMPRESSION, false); + $this->mem->setOption(\Memcached::OPT_COMPRESSION, $status); } } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 950ac02..61f9e81 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -4,6 +4,7 @@ parameters: beryllium_cache.client.memcache.class: Memcached beryllium_cache.client.servers: { 127.0.0.1 : 11211 } beryllium_cache.client.prefix: '' + beryllium_cache.client.compression: true beryllium_cache.default_ttl: 300 services: @@ -15,6 +16,7 @@ services: calls: - [ addServers, [ %beryllium_cache.client.servers% ] ] - [ setPrefix, [ %beryllium_cache.client.prefix% ] ] + - [ setCompression, [ %beryllium_cache.client.compression% ] ] beryllium_cache: class: %beryllium_cache.class% calls: From 45aec4a0ab62069e43fa08d998abf0eeadecd0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20S=C3=A1nchez=20Chordi?= Date: Wed, 26 Nov 2014 12:31:50 +0100 Subject: [PATCH 6/6] Add friendly configuration --- .../BerylliumCacheExtension.php | 6 +++++- DependencyInjection/Configuration.php | 19 ++++++++++++++++++- Resources/config/services.yml | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/DependencyInjection/BerylliumCacheExtension.php b/DependencyInjection/BerylliumCacheExtension.php index a49b415..ca20b92 100644 --- a/DependencyInjection/BerylliumCacheExtension.php +++ b/DependencyInjection/BerylliumCacheExtension.php @@ -22,7 +22,11 @@ public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); + $container->setParameter('beryllium_cache.client.prefix', $config['prefix']); + $container->setParameter('beryllium_cache.client.default_ttl', $config['default_ttl']); + $container->setParameter('beryllium_cache.client.compression', $config['compression']); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); } -} \ No newline at end of file +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 4648417..987042d 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -20,10 +20,27 @@ public function getConfigTreeBuilder() $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('beryllium_cache'); + $rootNode + ->children() + ->scalarNode('prefix') + ->defaultValue('') + ->cannotBeEmpty() + ->end() + ->integerNode('default_ttl') + ->defaultValue(300) + ->cannotBeEmpty() + ->end() + ->booleanNode('compression') + ->defaultValue(true) + ->cannotBeEmpty() + ->end() + ->end() + ; + // Here you should define the parameters that are allowed to // configure your bundle. See the documentation linked above for // more information on that topic. return $treeBuilder; } -} \ No newline at end of file +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 61f9e81..d31eff6 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -4,8 +4,8 @@ parameters: beryllium_cache.client.memcache.class: Memcached beryllium_cache.client.servers: { 127.0.0.1 : 11211 } beryllium_cache.client.prefix: '' - beryllium_cache.client.compression: true beryllium_cache.default_ttl: 300 + beryllium_cache.client.compression: true services: beryllium_cache.client.memcache: