From e315f563be9f932eb8548b52824548d8801fdde7 Mon Sep 17 00:00:00 2001 From: Kamisama Date: Mon, 26 Aug 2013 18:26:33 -0400 Subject: [PATCH 1/9] Add MemcachedEngine --- lib/Cake/Cache/Engine/MemcachedEngine.php | 280 +++++++++ .../Case/Cache/Engine/MemcachedEngineTest.php | 570 ++++++++++++++++++ 2 files changed, 850 insertions(+) create mode 100755 lib/Cake/Cache/Engine/MemcachedEngine.php create mode 100755 lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php new file mode 100755 index 000000000..9729283ac --- /dev/null +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -0,0 +1,280 @@ + 127.0.0.1. If an + * array MemcacheEngine will use them as a pool. + * - compress = boolean, default => false + * + * @var array + */ + public $settings = array(); + +/** + * Initialize the Cache Engine + * + * Called automatically by the cache frontend + * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array()); + * + * @param array $settings array of setting for the engine + * @return boolean True if the engine has been successfully initialized, false if not + */ + public function init($settings = array()) { + if (!class_exists('Memcached')) { + return false; + } + if (!isset($settings['prefix'])) { + $settings['prefix'] = Inflector::slug(APP_DIR) . '_'; + } + $settings += array( + 'engine' => 'Memcached', + 'servers' => array('127.0.0.1'), + 'compress' => false, + 'persistent' => true, + 'persistent_id' => 'mc', + 'login' => null, + 'password' => null, + ); + parent::init($settings); + + if (!is_array($this->settings['servers'])) { + $this->settings['servers'] = array($this->settings['servers']); + } + if (!isset($this->_Memcached)) { + $this->_Memcached = new Memcached($this->settings['persistent'] ? $this->settings['persistent_id'] : null); + $this->_setOptions(); + + if (!count($this->_Memcached->getServerList())) { + $servers = array(); + foreach ($this->settings['servers'] as $server) { + $servers[] = $this->_parseServerString($server); + } + + if (!$this->_Memcached->addServers($servers)) { + return false; + } + + if ($this->settings['login'] !== null && $this->settings['password'] !== null) { + $this->_Memcached->setSaslAuthData($this->settings['login'], $this->settings['password']); + } + } + + return true; + } + + return true; + } + +/** + * Settings the memcached instance + * + */ + protected function _setOptions() + { + $this->_Memcached->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true); + //$this->_Memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); + + if (Memcached::HAVE_IGBINARY) { + $this->_Memcached->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY); + } + + $this->_Memcached->setOption(Memcached::OPT_COMPRESSION, (bool)$this->settings['compress']); + } + +/** + * Parses the server address into the host/port. Handles both IPv6 and IPv4 + * addresses and Unix sockets + * + * @param string $server The server address string. + * @return array Array containing host, port + */ + protected function _parseServerString($server) { + if ($server[0] == 'u') { + return array($server, 0); + } + if (substr($server, 0, 1) == '[') { + $position = strpos($server, ']:'); + if ($position !== false) { + $position++; + } + } else { + $position = strpos($server, ':'); + } + $port = 11211; + $host = $server; + if ($position !== false) { + $host = substr($server, 0, $position); + $port = substr($server, $position + 1); + } + return array($host, (int)$port); + } + +/** + * Write data for key into cache. When using memcached as your cache engine + * remember that the Memcached pecl extension does not support cache expiry times greater + * than 30 days in the future. Any duration greater than 30 days will be treated as never expiring. + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param integer $duration How long to cache the data, in seconds + * @return boolean True if the data was successfully cached, false on failure + * @see http://php.net/manual/en/memcache.set.php + */ + public function write($key, $value, $duration) { + if ($duration > 30 * DAY) { + $duration = 0; + } + + return $this->_Memcached->set($key, $value, $duration); + } + +/** + * Read a key from the cache + * + * @param string $key Identifier for the data + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + */ + public function read($key) { + return $this->_Memcached->get($key); + } + +/** + * Increments the value of an integer cached key + * + * @param string $key Identifier for the data + * @param integer $offset How much to increment + * @return New incremented value, false otherwise + * @throws CacheException when you try to increment with compress = true + */ + public function increment($key, $offset = 1) { + return $this->_Memcached->increment($key, $offset); + } + +/** + * Decrements the value of an integer cached key + * + * @param string $key Identifier for the data + * @param integer $offset How much to subtract + * @return New decremented value, false otherwise + * @throws CacheException when you try to decrement with compress = true + */ + public function decrement($key, $offset = 1) { + return $this->_Memcached->decrement($key, $offset); + } + +/** + * Delete a key from the cache + * + * @param string $key Identifier for the data + * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed + */ + public function delete($key) { + return $this->_Memcached->delete($key); + } + +/** + * Delete all keys from the cache + * + * @param boolean $check + * @return boolean True if the cache was successfully cleared, false otherwise + */ + public function clear($check) { + if ($check) { + return true; + } + + $keys = $this->_Memcached->getAllKeys(); + + foreach($keys as $key) { + if (strpos($key, $this->settings['prefix']) === 0) { + $this->_Memcached->delete($key); + } + } + + return true; + } + +/** + * Returns the `group value` for each of the configured groups + * If the group initial value was not found, then it initializes + * the group accordingly. + * + * @return array + */ + public function groups() { + if (empty($this->_compiledGroupNames)) { + foreach ($this->settings['groups'] as $group) { + $this->_compiledGroupNames[] = $this->settings['prefix'] . $group; + } + } + + $groups = $this->_Memcached->getMulti($this->_compiledGroupNames); + if (count($groups) !== count($this->settings['groups'])) { + foreach ($this->_compiledGroupNames as $group) { + if (!isset($groups[$group])) { + $this->_Memcached->set($group, 1, 0); + $groups[$group] = 1; + } + } + ksort($groups); + } + + $result = array(); + $groups = array_values($groups); + foreach ($this->settings['groups'] as $i => $group) { + $result[] = $group . $groups[$i]; + } + + return $result; + } + +/** + * Increments the group value to simulate deletion of all keys under a group + * old values will remain in storage until they expire. + * + * @return boolean success + */ + public function clearGroup($group) { + return (bool)$this->_Memcached->increment($this->settings['prefix'] . $group); + } +} diff --git a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php new file mode 100755 index 000000000..f284c0461 --- /dev/null +++ b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php @@ -0,0 +1,570 @@ + + * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests + * @package Cake.Test.Case.Cache.Engine + * @since CakePHP(tm) v 1.2.0.5434 + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +App::uses('Cache', 'Cache'); +App::uses('MemcachedEngine', 'Cache/Engine'); + +/** + * Class TestMemcachedEngine + * + * @package Cake.Test.Case.Cache.Engine + */ +class TestMemcachedEngine extends MemcachedEngine { + +/** + * public accessor to _parseServerString + * + * @param string $server + * @return array + */ + public function parseServerString($server) { + return $this->_parseServerString($server); + } + + public function setMemcached($memcached) { + $this->_Memcached = $memcached; + } + + public function getMemcached() { + return $this->_Memcached; + } + +} + +/** + * MemcachedEngineTest class + * + * @package Cake.Test.Case.Cache.Engine + */ +class MemcachedEngineTest extends CakeTestCase { + +/** + * setUp method + * + * @return void + */ + public function setUp() { + parent::setUp(); + $this->skipIf(!class_exists('Memcached'), 'Memcached is not installed or configured properly.'); + + $this->_cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + Cache::config('memcached', array( + 'engine' => 'Memcached', + 'prefix' => 'cake_', + 'duration' => 3600 + )); + } + +/** + * tearDown method + * + * @return void + */ + public function tearDown() { + parent::tearDown(); + Configure::write('Cache.disable', $this->_cacheDisable); + Cache::drop('memcached'); + Cache::drop('memcached_groups'); + Cache::drop('memcached_helper'); + Cache::config('default'); + } + +/** + * testSettings method + * + * @return void + */ + public function testSettings() { + $settings = Cache::settings('memcached'); + unset($settings['serialize'], $settings['path']); + $expecting = array( + 'prefix' => 'cake_', + 'duration' => 3600, + 'probability' => 100, + 'servers' => array('127.0.0.1'), + 'persistent' => true, + 'persistent_id' => 'mc', + 'compress' => false, + 'engine' => 'Memcached', + 'login' => null, + 'password' => null, + 'groups' => array() + ); + $this->assertEquals($expecting, $settings); + } + +/** + * testCompressionSetting method + * + * @return void + */ + public function testCompressionSetting() { + $Memcached = new TestMemcachedEngine(); + $Memcached->init(array( + 'engine' => 'Memcached', + 'servers' => array('127.0.0.1:11211'), + 'compress' => false + )); + + $this->assertFalse($Memcached->getMemcached()->getOption(Memcached::OPT_COMPRESSION)); + + $MemcachedCompressed = new TestMemcachedEngine(); + $MemcachedCompressed->init(array( + 'engine' => 'Memcached', + 'servers' => array('127.0.0.1:11211'), + 'compress' => true + )); + + $this->assertTrue($MemcachedCompressed->getMemcached()->getOption(Memcached::OPT_COMPRESSION)); + } + +/** + * testSettings method + * + * @return void + */ + public function testMultipleServers() { + $servers = array('127.0.0.1:11211', '127.0.0.1:11222'); + $available = true; + $Memcached = new Memcached(); + + foreach ($servers as $server) { + list($host, $port) = explode(':', $server); + //@codingStandardsIgnoreStart + if (!$Memcached->addServer($host, $port)) { + $available = false; + } + //@codingStandardsIgnoreEnd + } + + $this->skipIf(!$available, 'Need memcached servers at ' . implode(', ', $servers) . ' to run this test.'); + + $Memcached = new MemcachedEngine(); + $Memcached->init(array('engine' => 'Memcached', 'servers' => $servers)); + + $settings = $Memcached->settings(); + $this->assertEquals($settings['servers'], $servers); + Cache::drop('dual_server'); + } + +/** + * test connecting to an ipv6 server. + * + * @return void + */ + public function testConnectIpv6() { + $Memcached = new MemcachedEngine(); + $result = $Memcached->init(array( + 'prefix' => 'cake_', + 'duration' => 200, + 'engine' => 'Memcached', + 'servers' => array( + '[::1]:11211' + ) + )); + $this->assertTrue($result); + } + +/** + * test non latin domains. + * + * @return void + */ + public function testParseServerStringNonLatin() { + $Memcached = new TestMemcachedEngine(); + $result = $Memcached->parseServerString('schülervz.net:13211'); + $this->assertEquals(array('schülervz.net', '13211'), $result); + + $result = $Memcached->parseServerString('sülül:1111'); + $this->assertEquals(array('sülül', '1111'), $result); + } + +/** + * test unix sockets. + * + * @return void + */ + public function testParseServerStringUnix() { + $Memcached = new TestMemcachedEngine(); + $result = $Memcached->parseServerString('unix:///path/to/memcachedd.sock'); + $this->assertEquals(array('unix:///path/to/memcachedd.sock', 0), $result); + } + +/** + * testReadAndWriteCache method + * + * @return void + */ + public function testReadAndWriteCache() { + Cache::set(array('duration' => 1), null, 'memcached'); + + $result = Cache::read('test', 'memcached'); + $expecting = ''; + $this->assertEquals($expecting, $result); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('test', $data, 'memcached'); + $this->assertTrue($result); + + $result = Cache::read('test', 'memcached'); + $expecting = $data; + $this->assertEquals($expecting, $result); + + Cache::delete('test', 'memcached'); + } + +/** + * testExpiry method + * + * @return void + */ + public function testExpiry() { + Cache::set(array('duration' => 1), 'memcached'); + + $result = Cache::read('test', 'memcached'); + $this->assertFalse($result); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data, 'memcached'); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test', 'memcached'); + $this->assertFalse($result); + + Cache::set(array('duration' => "+1 second"), 'memcached'); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data, 'memcached'); + $this->assertTrue($result); + + sleep(3); + $result = Cache::read('other_test', 'memcached'); + $this->assertFalse($result); + + Cache::config('memcached', array('duration' => '+1 second')); + + $result = Cache::read('other_test', 'memcached'); + $this->assertFalse($result); + + Cache::config('memcached', array('duration' => '+29 days')); + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('long_expiry_test', $data, 'memcached'); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('long_expiry_test', 'memcached'); + $expecting = $data; + $this->assertEquals($expecting, $result); + + Cache::config('memcached', array('duration' => 3600)); + } + +/** + * testDeleteCache method + * + * @return void + */ + public function testDeleteCache() { + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('delete_test', $data, 'memcached'); + $this->assertTrue($result); + + $result = Cache::delete('delete_test', 'memcached'); + $this->assertTrue($result); + } + +/** + * testDecrement method + * + * @return void + */ + public function testDecrement() { + $result = Cache::write('test_decrement', 5, 'memcached'); + $this->assertTrue($result); + + $result = Cache::decrement('test_decrement', 1, 'memcached'); + $this->assertEquals(4, $result); + + $result = Cache::read('test_decrement', 'memcached'); + $this->assertEquals(4, $result); + + $result = Cache::decrement('test_decrement', 2, 'memcached'); + $this->assertEquals(2, $result); + + $result = Cache::read('test_decrement', 'memcached'); + $this->assertEquals(2, $result); + + Cache::delete('test_decrement', 'memcached'); + } + +/** + * test decrementing compressed keys + * + * @return void + */ + public function testDecrementCompressedKeys() { + Cache::config('compressed_memcached', array( + 'engine' => 'Memcached', + 'duration' => '+2 seconds', + 'servers' => array('127.0.0.1:11211'), + 'compress' => true + )); + + $result = Cache::write('test_decrement', 5, 'compressed_memcached'); + $this->assertTrue($result); + + $result = Cache::decrement('test_decrement', 1, 'compressed_memcached'); + $this->assertEquals(4, $result); + + $result = Cache::read('test_decrement', 'compressed_memcached'); + $this->assertEquals(4, $result); + + $result = Cache::decrement('test_decrement', 2, 'compressed_memcached'); + $this->assertEquals(2, $result); + + $result = Cache::read('test_decrement', 'compressed_memcached'); + $this->assertEquals(2, $result); + + Cache::delete('test_decrement', 'compressed_memcached'); + } + +/** + * testIncrement method + * + * @return void + */ + public function testIncrement() { + $result = Cache::write('test_increment', 5, 'memcached'); + $this->assertTrue($result); + + $result = Cache::increment('test_increment', 1, 'memcached'); + $this->assertEquals(6, $result); + + $result = Cache::read('test_increment', 'memcached'); + $this->assertEquals(6, $result); + + $result = Cache::increment('test_increment', 2, 'memcached'); + $this->assertEquals(8, $result); + + $result = Cache::read('test_increment', 'memcached'); + $this->assertEquals(8, $result); + + Cache::delete('test_increment', 'memcached'); + } + +/** + * test incrementing compressed keys + * + * @return void + */ + public function testIncrementCompressedKeys() { + Cache::config('compressed_memcached', array( + 'engine' => 'Memcached', + 'duration' => '+2 seconds', + 'servers' => array('127.0.0.1:11211'), + 'compress' => true + )); + + $result = Cache::write('test_increment', 5, 'compressed_memcached'); + $this->assertTrue($result); + + $result = Cache::increment('test_increment', 1, 'compressed_memcached'); + $this->assertEquals(6, $result); + + $result = Cache::read('test_increment', 'compressed_memcached'); + $this->assertEquals(6, $result); + + $result = Cache::increment('test_increment', 2, 'compressed_memcached'); + $this->assertEquals(8, $result); + + $result = Cache::read('test_increment', 'compressed_memcached'); + $this->assertEquals(8, $result); + + Cache::delete('test_increment', 'compressed_memcached'); + } + +/** + * test that configurations don't conflict, when a file engine is declared after a memcached one. + * + * @return void + */ + public function testConfigurationConflict() { + Cache::config('long_memcached', array( + 'engine' => 'Memcached', + 'duration' => '+2 seconds', + 'servers' => array('127.0.0.1:11211'), + )); + Cache::config('short_memcached', array( + 'engine' => 'Memcached', + 'duration' => '+1 seconds', + 'servers' => array('127.0.0.1:11211'), + )); + Cache::config('some_file', array('engine' => 'File')); + + $this->assertTrue(Cache::write('duration_test', 'yay', 'long_memcached')); + $this->assertTrue(Cache::write('short_duration_test', 'boo', 'short_memcached')); + + $this->assertEquals('yay', Cache::read('duration_test', 'long_memcached'), 'Value was not read %s'); + $this->assertEquals('boo', Cache::read('short_duration_test', 'short_memcached'), 'Value was not read %s'); + + sleep(1); + $this->assertEquals('yay', Cache::read('duration_test', 'long_memcached'), 'Value was not read %s'); + + sleep(2); + $this->assertFalse(Cache::read('short_duration_test', 'short_memcached'), 'Cache was not invalidated %s'); + $this->assertFalse(Cache::read('duration_test', 'long_memcached'), 'Value did not expire %s'); + + Cache::delete('duration_test', 'long_memcached'); + Cache::delete('short_duration_test', 'short_memcached'); + } + +/** + * test clearing memcached. + * + * @return void + */ + public function testClear() { + Cache::config('memcached2', array( + 'engine' => 'Memcached', + 'prefix' => 'cake2_', + 'duration' => 3600 + )); + + Cache::write('some_value', 'cache1', 'memcached'); + $result = Cache::clear(true, 'memcached'); + $this->assertTrue($result); + $this->assertEquals('cache1', Cache::read('some_value', 'memcached')); + + Cache::write('some_value', 'cache2', 'memcached2'); + $result = Cache::clear(false, 'memcached'); + $this->assertTrue($result); + $this->assertFalse(Cache::read('some_value', 'memcached')); + $this->assertEquals('cache2', Cache::read('some_value', 'memcached2')); + + Cache::clear(false, 'memcached2'); + } + +/** + * test that a 0 duration can successfully write. + * + * @return void + */ + public function testZeroDuration() { + Cache::config('memcached', array('duration' => 0)); + $result = Cache::write('test_key', 'written!', 'memcached'); + + $this->assertTrue($result); + $result = Cache::read('test_key', 'memcached'); + $this->assertEquals('written!', $result); + } + +/** + * test that durations greater than 30 days never expire + * + * @return void + */ + public function testLongDurationEqualToZero() { + $memcached = new TestMemcachedEngine(); + $memcached->settings['compress'] = false; + + $mock = $this->getMock('Memcached'); + $memcached->setMemcached($mock); + $mock->expects($this->once()) + ->method('set') + ->with('key', 'value', 0); + + $value = 'value'; + $memcached->write('key', $value, 50 * DAY); + } + +/** + * Tests that configuring groups for stored keys return the correct values when read/written + * Shows that altering the group value is equivalent to deleting all keys under the same + * group + * + * @return void + */ + public function testGroupReadWrite() { + Cache::config('memcached_groups', array( + 'engine' => 'Memcached', + 'duration' => 3600, + 'groups' => array('group_a', 'group_b'), + 'prefix' => 'test_' + )); + Cache::config('memcached_helper', array( + 'engine' => 'Memcached', + 'duration' => 3600, + 'prefix' => 'test_' + )); + $this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups')); + $this->assertEquals('value', Cache::read('test_groups', 'memcached_groups')); + + Cache::increment('group_a', 1, 'memcached_helper'); + $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); + $this->assertTrue(Cache::write('test_groups', 'value2', 'memcached_groups')); + $this->assertEquals('value2', Cache::read('test_groups', 'memcached_groups')); + + Cache::increment('group_b', 1, 'memcached_helper'); + $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); + $this->assertTrue(Cache::write('test_groups', 'value3', 'memcached_groups')); + $this->assertEquals('value3', Cache::read('test_groups', 'memcached_groups')); + } + +/** + * Tests that deleteing from a groups-enabled config is possible + * + * @return void + */ + public function testGroupDelete() { + Cache::config('memcached_groups', array( + 'engine' => 'Memcached', + 'duration' => 3600, + 'groups' => array('group_a', 'group_b') + )); + $this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups')); + $this->assertEquals('value', Cache::read('test_groups', 'memcached_groups')); + $this->assertTrue(Cache::delete('test_groups', 'memcached_groups')); + + $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); + } + +/** + * Test clearing a cache group + * + * @return void + */ + public function testGroupClear() { + Cache::config('memcached_groups', array( + 'engine' => 'Memcached', + 'duration' => 3600, + 'groups' => array('group_a', 'group_b') + )); + + $this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups')); + $this->assertTrue(Cache::clearGroup('group_a', 'memcached_groups')); + $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); + + $this->assertTrue(Cache::write('test_groups', 'value2', 'memcached_groups')); + $this->assertTrue(Cache::clearGroup('group_b', 'memcached_groups')); + $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); + } +} From 6a8913361f6fc43cd3b06778bd81433b5a8af3f4 Mon Sep 17 00:00:00 2001 From: Kamisama Date: Mon, 26 Aug 2013 19:10:25 -0400 Subject: [PATCH 2/9] Fix coding standards --- lib/Cake/Cache/Engine/MemcachedEngine.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php index 9729283ac..6cdab23f9 100755 --- a/lib/Cake/Cache/Engine/MemcachedEngine.php +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -109,8 +109,7 @@ class MemcachedEngine extends CacheEngine { * Settings the memcached instance * */ - protected function _setOptions() - { + protected function _setOptions() { $this->_Memcached->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true); //$this->_Memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); @@ -225,7 +224,7 @@ class MemcachedEngine extends CacheEngine { $keys = $this->_Memcached->getAllKeys(); - foreach($keys as $key) { + foreach ($keys as $key) { if (strpos($key, $this->settings['prefix']) === 0) { $this->_Memcached->delete($key); } From 7c33878f4cc403ac0fae1753c23d626bc88559d2 Mon Sep 17 00:00:00 2001 From: Kamisama Date: Mon, 26 Aug 2013 20:55:19 -0400 Subject: [PATCH 3/9] Coding standard fix --- lib/Cake/Cache/Engine/MemcachedEngine.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php index 6cdab23f9..4814b066f 100755 --- a/lib/Cake/Cache/Engine/MemcachedEngine.php +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -6,16 +6,17 @@ * PHP 5 * * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) + * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * - * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package Cake.Cache.Engine - * @since CakePHP(tm) v 1.2.0.4933 - * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + * @since CakePHP(tm) v 2.5.0 + * @license http://www.opensource.org/licenses/mit-license.php MIT License */ /** @@ -111,7 +112,6 @@ class MemcachedEngine extends CacheEngine { */ protected function _setOptions() { $this->_Memcached->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true); - //$this->_Memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); if (Memcached::HAVE_IGBINARY) { $this->_Memcached->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY); From 2b20ddc81c02f654ed6130c4a6da00211552b7ae Mon Sep 17 00:00:00 2001 From: Kamisama Date: Mon, 26 Aug 2013 20:55:38 -0400 Subject: [PATCH 4/9] Drop backup/restore Configure values --- lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php index f284c0461..c1e926640 100755 --- a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php +++ b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php @@ -14,7 +14,7 @@ * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine - * @since CakePHP(tm) v 1.2.0.5434 + * @since CakePHP(tm) v 2.5.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ @@ -64,8 +64,6 @@ class MemcachedEngineTest extends CakeTestCase { parent::setUp(); $this->skipIf(!class_exists('Memcached'), 'Memcached is not installed or configured properly.'); - $this->_cacheDisable = Configure::read('Cache.disable'); - Configure::write('Cache.disable', false); Cache::config('memcached', array( 'engine' => 'Memcached', 'prefix' => 'cake_', @@ -80,7 +78,6 @@ class MemcachedEngineTest extends CakeTestCase { */ public function tearDown() { parent::tearDown(); - Configure::write('Cache.disable', $this->_cacheDisable); Cache::drop('memcached'); Cache::drop('memcached_groups'); Cache::drop('memcached_helper'); From c7dacb05e70fe14472663535e5734872a795daa4 Mon Sep 17 00:00:00 2001 From: Kamisama Date: Mon, 26 Aug 2013 21:27:08 -0400 Subject: [PATCH 5/9] Add support for Amazon ElastiCache --- lib/Cake/Cache/Engine/MemcachedEngine.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php index 4814b066f..2155318c9 100755 --- a/lib/Cake/Cache/Engine/MemcachedEngine.php +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -117,6 +117,11 @@ class MemcachedEngine extends CacheEngine { $this->_Memcached->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY); } + // Check for Amazon ElastiCache instance + if (defined('Memcached::OPT_CLIENT_MODE') && defined('Memcached::DYNAMIC_CLIENT_MODE')) { + $this->_Memcached->setOption(Memcached::OPT_CLIENT_MODE, Memcached::DYNAMIC_CLIENT_MODE); + } + $this->_Memcached->setOption(Memcached::OPT_COMPRESSION, (bool)$this->settings['compress']); } From f093a31740a1c6a841980e1606c966d3ac9f3a13 Mon Sep 17 00:00:00 2001 From: Kamisama Date: Tue, 27 Aug 2013 13:22:03 -0400 Subject: [PATCH 6/9] Remove unnecessary return --- lib/Cake/Cache/Engine/MemcachedEngine.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php index 2155318c9..f7fbca3e2 100755 --- a/lib/Cake/Cache/Engine/MemcachedEngine.php +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -99,8 +99,6 @@ class MemcachedEngine extends CacheEngine { $this->_Memcached->setSaslAuthData($this->settings['login'], $this->settings['password']); } } - - return true; } return true; From bdd00c230702e5074fd3fb0a792fac4e66132beb Mon Sep 17 00:00:00 2001 From: Kamisama Date: Tue, 27 Aug 2013 13:57:57 -0400 Subject: [PATCH 7/9] Throw an exception if attempting to use authentication without SASL support installed --- lib/Cake/Cache/Engine/MemcachedEngine.php | 5 ++++ .../Case/Cache/Engine/MemcachedEngineTest.php | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php index f7fbca3e2..6b5dd738f 100755 --- a/lib/Cake/Cache/Engine/MemcachedEngine.php +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -96,6 +96,11 @@ class MemcachedEngine extends CacheEngine { } if ($this->settings['login'] !== null && $this->settings['password'] !== null) { + if (!method_exists($this->_Memcached, 'setSaslAuthData')) { + throw new CacheException( + __d('cake_dev', 'Memcached extension is not build with SASL support') + ); + } $this->_Memcached->setSaslAuthData($this->settings['login'], $this->settings['password']); } } diff --git a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php index c1e926640..73741dabc 100755 --- a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php +++ b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php @@ -133,6 +133,33 @@ class MemcachedEngineTest extends CakeTestCase { $this->assertTrue($MemcachedCompressed->getMemcached()->getOption(Memcached::OPT_COMPRESSION)); } +/** + * test using authentication without memcached installed with SASL support + * throw an exception + * + * @return void + */ + public function testSaslAuthException() { + $Memcached = new TestMemcachedEngine(); + $settings = array( + 'engine' => 'Memcached', + 'servers' => array('127.0.0.1:11211'), + 'persistent' => false, + 'login' => 'test', + 'password' => 'password' + ); + + $this->skipIf( + method_exists($Memcached->getMemcached(), 'setSaslAuthData'), + 'Memcached extension is installed with SASL support' + ); + + $this->setExpectedException( + 'CacheException', 'Memcached extension is not build with SASL support' + ); + $Memcached->init($settings); + } + /** * testSettings method * From 244df0764fe6475bf416a919ccc58baecb7475f6 Mon Sep 17 00:00:00 2001 From: Kamisama Date: Tue, 27 Aug 2013 14:22:45 -0400 Subject: [PATCH 8/9] Coding standard and readability --- lib/Cake/Cache/Engine/MemcachedEngine.php | 48 +++++++++++-------- .../Case/Cache/Engine/MemcachedEngineTest.php | 4 +- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php index 6b5dd738f..3538becb3 100755 --- a/lib/Cake/Cache/Engine/MemcachedEngine.php +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -59,6 +59,7 @@ class MemcachedEngine extends CacheEngine { * * @param array $settings array of setting for the engine * @return boolean True if the engine has been successfully initialized, false if not + * @throws CacheException when you try use authentication without Memcached compiled with SASL support */ public function init($settings = array()) { if (!class_exists('Memcached')) { @@ -81,29 +82,34 @@ class MemcachedEngine extends CacheEngine { if (!is_array($this->settings['servers'])) { $this->settings['servers'] = array($this->settings['servers']); } - if (!isset($this->_Memcached)) { - $this->_Memcached = new Memcached($this->settings['persistent'] ? $this->settings['persistent_id'] : null); - $this->_setOptions(); - if (!count($this->_Memcached->getServerList())) { - $servers = array(); - foreach ($this->settings['servers'] as $server) { - $servers[] = $this->_parseServerString($server); - } + if (isset($this->_Memcached)) { + return true; + } - if (!$this->_Memcached->addServers($servers)) { - return false; - } + $this->_Memcached = new Memcached($this->settings['persistent'] ? $this->settings['persistent_id'] : null); + $this->_setOptions(); - if ($this->settings['login'] !== null && $this->settings['password'] !== null) { - if (!method_exists($this->_Memcached, 'setSaslAuthData')) { - throw new CacheException( - __d('cake_dev', 'Memcached extension is not build with SASL support') - ); - } - $this->_Memcached->setSaslAuthData($this->settings['login'], $this->settings['password']); - } + if (count($this->_Memcached->getServerList())) { + return true; + } + + $servers = array(); + foreach ($this->settings['servers'] as $server) { + $servers[] = $this->_parseServerString($server); + } + + if (!$this->_Memcached->addServers($servers)) { + return false; + } + + if ($this->settings['login'] !== null && $this->settings['password'] !== null) { + if (!method_exists($this->_Memcached, 'setSaslAuthData')) { + throw new CacheException( + __d('cake_dev', 'Memcached extension is not build with SASL support') + ); } + $this->_Memcached->setSaslAuthData($this->settings['login'], $this->settings['password']); } return true; @@ -136,10 +142,10 @@ class MemcachedEngine extends CacheEngine { * @return array Array containing host, port */ protected function _parseServerString($server) { - if ($server[0] == 'u') { + if ($server[0] === 'u') { return array($server, 0); } - if (substr($server, 0, 1) == '[') { + if (substr($server, 0, 1) === '[') { $position = strpos($server, ']:'); if ($position !== false) { $position++; diff --git a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php index 73741dabc..cc8b61495 100755 --- a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php +++ b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php @@ -155,8 +155,8 @@ class MemcachedEngineTest extends CakeTestCase { ); $this->setExpectedException( - 'CacheException', 'Memcached extension is not build with SASL support' - ); + 'CacheException', 'Memcached extension is not build with SASL support' + ); $Memcached->init($settings); } From 70f321e32747b3a7717ff9ca956a0746b8d0caec Mon Sep 17 00:00:00 2001 From: Kamisama Date: Tue, 27 Aug 2013 14:48:36 -0400 Subject: [PATCH 9/9] Starting the memcached server on travis To properly test the memcached cache engine --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3a9e45b74..507135c11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,9 @@ env: - DB=pgsql - DB=sqlite +services: + - memcached + matrix: include: - php: 5.4 @@ -29,6 +32,7 @@ before_script: - sudo apt-get install lighttpd - sh -c "if [ '$PHPCS' = '1' ]; then pear channel-discover pear.cakephp.org; fi" - sh -c "if [ '$PHPCS' = '1' ]; then pear install --alldeps cakephp/CakePHP_CodeSniffer; fi" + - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - phpenv rehash - set +H - echo "