From 8ce9f0de5688ddf10f06e4f7a3a684b7dc25f6ec Mon Sep 17 00:00:00 2001 From: kamijo Date: Thu, 25 Jul 2013 08:37:24 +0900 Subject: [PATCH] Added Phalcon\Cache\Backend\Libmemcached cache backend class by using memcached PECL extension. --- ext/cache/backend/libmemcached.c | 568 ++++++++++++++++++++++++++++++ ext/cache/backend/libmemcached.h | 72 ++++ ext/config.m4 | 1 + ext/phalcon.c | 2 + ext/phalcon.h | 1 + unit-tests/CacheResultsetTest.php | 93 +++++ unit-tests/CacheTest.php | 218 ++++++++++++ 7 files changed, 955 insertions(+) create mode 100644 ext/cache/backend/libmemcached.c create mode 100644 ext/cache/backend/libmemcached.h diff --git a/ext/cache/backend/libmemcached.c b/ext/cache/backend/libmemcached.c new file mode 100644 index 00000000000..18fdf977fb3 --- /dev/null +++ b/ext/cache/backend/libmemcached.c @@ -0,0 +1,568 @@ + +/* + +------------------------------------------------------------------------+ + | Phalcon Framework | + +------------------------------------------------------------------------+ + | Copyright (c) 2011-2013 Phalcon Team (http://www.phalconphp.com) | + +------------------------------------------------------------------------+ + | This source file is subject to the New BSD License that is bundled | + | with this package in the file docs/LICENSE.txt. | + | | + | If you did not receive a copy of the license and are unable to | + | obtain it through the world-wide-web, please send an email | + | to license@phalconphp.com so we can send you a copy immediately. | + +------------------------------------------------------------------------+ + | Authors: Andres Gutierrez | + | Eduar Carvajal | + +------------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_phalcon.h" +#include "phalcon.h" + +#include "Zend/zend_operators.h" +#include "Zend/zend_exceptions.h" +#include "Zend/zend_interfaces.h" + +#include "kernel/main.h" +#include "kernel/memory.h" + +#include "kernel/array.h" +#include "kernel/fcall.h" +#include "kernel/object.h" +#include "kernel/exception.h" +#include "kernel/concat.h" +#include "kernel/operators.h" +#include "kernel/hash.h" +#include "kernel/string.h" + +/** + * Phalcon\Cache\Backend\Libmemcached + * + * Allows to cache output fragments, PHP data or raw data to a libmemcached backend + * + * This adapter uses the special memcached key "_PHCM" to store all the keys internally used by the adapter + * + * + * + * // Cache data for 2 days + * $frontCache = new Phalcon\Cache\Frontend\Data(array( + * "lifetime" => 172800 + * )); + * + * //Create the Cache setting memcached connection options + * $cache = new Phalcon\Cache\Backend\Libmemcached($frontCache, array( + * 'servers' => array( + * array('host' => 'localhost', + * 'port' => 11211, + * 'weight' => 1), + * ), + * 'client' => array( + * Memcached::OPT_HASH => Memcached::HASH_MD5, + * Memcached::OPT_PREFIX_KEY => 'prefix.', + * ) + * )); + * + * //Cache arbitrary data + * $cache->save('my-data', array(1, 2, 3, 4, 5)); + * + * //Get data + * $data = $cache->get('my-data'); + * + * + */ + + +/** + * Phalcon\Cache\Backend\Libmemcached initializer + */ +PHALCON_INIT_CLASS(Phalcon_Cache_Backend_Libmemcached){ + + PHALCON_REGISTER_CLASS_EX(Phalcon\\Cache\\Backend, Libmemcached, cache_backend_libmemcached, "phalcon\\cache\\backend", phalcon_cache_backend_libmemcached_method_entry, 0); + + zend_declare_property_null(phalcon_cache_backend_libmemcached_ce, SL("_memcache"), ZEND_ACC_PROTECTED TSRMLS_CC); + + zend_class_implements(phalcon_cache_backend_libmemcached_ce TSRMLS_CC, 1, phalcon_cache_backendinterface_ce); + + return SUCCESS; +} + +/** + * Phalcon\Cache\Backend\Libmemcached constructor + * + * @param Phalcon\Cache\FrontendInterface $frontend + * @param array $options + */ +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, __construct){ + + zval *frontend, *options = NULL; + + PHALCON_MM_GROW(); + + phalcon_fetch_params(1, 1, 1, &frontend, &options); + + if (!options) { + PHALCON_INIT_VAR(options); + } else { + PHALCON_SEPARATE_PARAM(options); + } + + if (Z_TYPE_P(options) != IS_ARRAY) { + PHALCON_INIT_NVAR(options); + array_init(options); + } + + if (!phalcon_array_isset_string(options, SS("servers"))) { + zval *server = NULL, *servers = NULL; + PHALCON_INIT_NVAR(servers); + PHALCON_INIT_NVAR(server); + array_init(servers); + array_init(server); + + phalcon_array_update_string_string(&server, SL("host"), SL("127.0.0.1"), PH_SEPARATE); + phalcon_array_update_string_string(&server, SL("port"), SL("11211"), PH_SEPARATE); + phalcon_array_update_string_string(&server, SL("weight"), SL("1"), PH_SEPARATE); + + phalcon_array_update_long(&servers, 0, &server, PH_COPY); + + phalcon_array_update_string(&options, SL("servers"), &servers, PH_COPY); + } + + if (!phalcon_array_isset_string(options, SS("statsKey"))) { + phalcon_array_update_string_string(&options, SL("statsKey"), SL("_PHCM"), PH_SEPARATE); + } + + PHALCON_CALL_PARENT_PARAMS_2_NORETURN(this_ptr, "Phalcon\\Cache\\Backend\\Libmemcached", "__construct", frontend, options); + + PHALCON_MM_RESTORE(); +} + +/** + * Create internal connection to memcached + */ +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, _connect){ + + zval *options, *memcache, *servers, *client; + zend_class_entry *ce0; + + PHALCON_MM_GROW(); + + PHALCON_OBS_VAR(options); + phalcon_read_property_this(&options, this_ptr, SL("_options"), PH_NOISY_CC); + ce0 = zend_fetch_class(SL("Memcached"), ZEND_FETCH_CLASS_AUTO TSRMLS_CC); + + PHALCON_INIT_VAR(memcache); + object_init_ex(memcache, ce0); + if (phalcon_has_constructor(memcache TSRMLS_CC)) { + phalcon_call_method_noret(memcache, "__construct"); + } + + PHALCON_OBS_VAR(servers); + phalcon_array_fetch_string(&servers, options, SL("servers"), PH_NOISY); + if (Z_TYPE_P(servers) != IS_ARRAY) { + PHALCON_THROW_EXCEPTION_STR(phalcon_cache_exception_ce, "The servers to validate must be an array"); + } + + if (phalcon_array_isset_string(options, SS("client"))) { + PHALCON_OBS_VAR(client); + phalcon_array_fetch_string(&client, options, SL("client"), PH_NOISY); + } else { + client = NULL; + } + + phalcon_call_method_p1(return_value, memcache, "addServers", servers); + if (!zend_is_true(return_value)) { + PHALCON_THROW_EXCEPTION_STR(phalcon_cache_exception_ce, "Cannot connect to Memcached server"); + return; + } + + if (client && Z_TYPE_P(client) == IS_ARRAY) { + HashTable *ah; + HashPosition hp; + zval **hd; + zval *option = NULL, *value = NULL, *res = NULL, *opt = NULL; + + phalcon_is_iterable(client, &ah, &hp, 0, 0); + while (zend_hash_get_current_data_ex(ah, (void**) &hd, &hp) == SUCCESS) { + + PHALCON_GET_HKEY(option, ah, hp); + PHALCON_GET_HVALUE(value); + + if (Z_TYPE_P(option) == IS_STRING) { + PHALCON_INIT_NVAR(res); + phalcon_call_func_p1(res, "defined", option); + if (zend_is_true(res)) { + PHALCON_INIT_NVAR(opt); + phalcon_call_func_p1(opt, "constant", option); + phalcon_call_method_p2_noret(memcache, "setOption", opt, value); + } + } else { + convert_to_long(option); + phalcon_call_method_p2_noret(memcache, "setOption", option, value); + } + + zend_hash_move_forward_ex(ah, &hp); + } + } + + phalcon_update_property_this(this_ptr, SL("_memcache"), memcache TSRMLS_CC); + + RETURN_MM(); +} + +/** + * Returns a cached content + * + * @param int|string $keyName + * @param long $lifetime + * @return mixed + */ +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, get){ + + zval *key_name, *lifetime = NULL, *memcache = NULL, *frontend; + zval *prefix, *prefixed_key, *cached_content; + + PHALCON_MM_GROW(); + + phalcon_fetch_params(1, 1, 1, &key_name, &lifetime); + + if (!lifetime) { + PHALCON_INIT_VAR(lifetime); + } + + PHALCON_OBS_VAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + if (Z_TYPE_P(memcache) != IS_OBJECT) { + phalcon_call_method_noret(this_ptr, "_connect"); + PHALCON_OBS_NVAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + } + + PHALCON_OBS_VAR(frontend); + phalcon_read_property_this(&frontend, this_ptr, SL("_frontend"), PH_NOISY_CC); + + PHALCON_OBS_VAR(prefix); + phalcon_read_property_this(&prefix, this_ptr, SL("_prefix"), PH_NOISY_CC); + + PHALCON_INIT_VAR(prefixed_key); + PHALCON_CONCAT_VV(prefixed_key, prefix, key_name); + phalcon_update_property_this(this_ptr, SL("_lastKey"), prefixed_key TSRMLS_CC); + + PHALCON_INIT_VAR(cached_content); + phalcon_call_method_p1(cached_content, memcache, "get", prefixed_key); + if (PHALCON_IS_FALSE(cached_content)) { + RETURN_MM_NULL(); + } + + phalcon_call_method_p1(return_value, frontend, "afterretrieve", cached_content); + RETURN_MM(); +} + +/** + * Stores cached content into the Memcached backend and stops the frontend + * + * @param int|string $keyName + * @param string $content + * @param long $lifetime + * @param boolean $stopBuffer + */ +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, save){ + + zval *key_name = NULL, *content = NULL, *lifetime = NULL, *stop_buffer = NULL; + zval *last_key = NULL, *prefix, *frontend, *memcache = NULL, *cached_content = NULL; + zval *prepared_content, *ttl = NULL, *success; + zval *options, *special_key, *keys = NULL, *is_buffering; + + PHALCON_MM_GROW(); + + phalcon_fetch_params(1, 0, 4, &key_name, &content, &lifetime, &stop_buffer); + + if (!key_name) { + PHALCON_INIT_VAR(key_name); + } + + if (!content) { + PHALCON_INIT_VAR(content); + } + + if (!lifetime) { + PHALCON_INIT_VAR(lifetime); + } + + if (!stop_buffer) { + PHALCON_INIT_VAR(stop_buffer); + ZVAL_BOOL(stop_buffer, 1); + } + + if (Z_TYPE_P(key_name) == IS_NULL) { + PHALCON_OBS_VAR(last_key); + phalcon_read_property_this(&last_key, this_ptr, SL("_lastKey"), PH_NOISY_CC); + } else { + PHALCON_OBS_VAR(prefix); + phalcon_read_property_this(&prefix, this_ptr, SL("_prefix"), PH_NOISY_CC); + + PHALCON_INIT_NVAR(last_key); + PHALCON_CONCAT_VV(last_key, prefix, key_name); + } + if (!zend_is_true(last_key)) { + PHALCON_THROW_EXCEPTION_STR(phalcon_cache_exception_ce, "The cache must be started first"); + return; + } + + PHALCON_OBS_VAR(frontend); + phalcon_read_property_this(&frontend, this_ptr, SL("_frontend"), PH_NOISY_CC); + + /** + * Check if a connection is created or make a new one + */ + PHALCON_OBS_VAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + if (Z_TYPE_P(memcache) != IS_OBJECT) { + phalcon_call_method_noret(this_ptr, "_connect"); + + PHALCON_OBS_NVAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + } + + if (Z_TYPE_P(content) == IS_NULL) { + PHALCON_INIT_VAR(cached_content); + phalcon_call_method(cached_content, frontend, "getcontent"); + } else { + PHALCON_CPY_WRT(cached_content, content); + } + + /** + * Prepare the content in the frontend + */ + PHALCON_INIT_VAR(prepared_content); + phalcon_call_method_p1(prepared_content, frontend, "beforestore", cached_content); + if (Z_TYPE_P(lifetime) == IS_NULL) { + PHALCON_INIT_VAR(ttl); + phalcon_call_method(ttl, frontend, "getlifetime"); + } else { + PHALCON_CPY_WRT(ttl, lifetime); + } + + PHALCON_INIT_VAR(success); + phalcon_call_method_p3(success, memcache, "set", last_key, prepared_content, ttl); + if (!zend_is_true(success)) { + PHALCON_THROW_EXCEPTION_STR(phalcon_cache_exception_ce, "Failed storing data in memcached"); + return; + } + + PHALCON_OBS_VAR(options); + phalcon_read_property_this(&options, this_ptr, SL("_options"), PH_NOISY_CC); + + PHALCON_OBS_VAR(special_key); + phalcon_array_fetch_string(&special_key, options, SL("statsKey"), PH_NOISY); + + /** + * Update the stats key + */ + PHALCON_INIT_VAR(keys); + phalcon_call_method_p1(keys, memcache, "get", special_key); + if (Z_TYPE_P(keys) != IS_ARRAY) { + PHALCON_INIT_NVAR(keys); + array_init(keys); + } + + if (!phalcon_array_isset(keys, last_key)) { + phalcon_array_update_zval(&keys, last_key, &ttl, PH_COPY | PH_SEPARATE); + phalcon_call_method_p2_noret(memcache, "set", special_key, keys); + } + + PHALCON_INIT_VAR(is_buffering); + phalcon_call_method(is_buffering, frontend, "isbuffering"); + if (PHALCON_IS_TRUE(stop_buffer)) { + phalcon_call_method_noret(frontend, "stop"); + } + + if (PHALCON_IS_TRUE(is_buffering)) { + zend_print_zval(cached_content, 0); + } + + phalcon_update_property_bool(this_ptr, SL("_started"), 0 TSRMLS_CC); + + PHALCON_MM_RESTORE(); +} + +/** + * Deletes a value from the cache by its key + * + * @param int|string $keyName + * @return boolean + */ +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, delete){ + + zval *key_name, *memcache = NULL, *prefix, *prefixed_key; + zval *options, *special_key, *keys; + + PHALCON_MM_GROW(); + + phalcon_fetch_params(1, 1, 0, &key_name); + + PHALCON_OBS_VAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + if (Z_TYPE_P(memcache) != IS_OBJECT) { + phalcon_call_method_noret(this_ptr, "_connect"); + + PHALCON_OBS_NVAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + } + + PHALCON_OBS_VAR(prefix); + phalcon_read_property_this(&prefix, this_ptr, SL("_prefix"), PH_NOISY_CC); + + PHALCON_INIT_VAR(prefixed_key); + PHALCON_CONCAT_VV(prefixed_key, prefix, key_name); + + PHALCON_OBS_VAR(options); + phalcon_read_property_this(&options, this_ptr, SL("_options"), PH_NOISY_CC); + + PHALCON_OBS_VAR(special_key); + phalcon_array_fetch_string(&special_key, options, SL("statsKey"), PH_NOISY); + + PHALCON_INIT_VAR(keys); + phalcon_call_method_p1(keys, memcache, "get", special_key); + if (Z_TYPE_P(keys) == IS_ARRAY) { + phalcon_array_unset(&keys, prefixed_key, PH_SEPARATE); + phalcon_call_method_p2_noret(memcache, "set", special_key, keys); + } + + /** + * Delete the key from memcached + */ + phalcon_call_method_p1(return_value, memcache, "delete", prefixed_key); + RETURN_MM(); +} + +/** + * Query the existing cached keys + * + * @param string $prefix + * @return array + */ +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, queryKeys){ + + zval *prefix = NULL, *memcache = NULL, *options, *special_key; + zval *keys, *prefixed_keys, *ttl = NULL, *key = NULL; + HashTable *ah0; + HashPosition hp0; + zval **hd; + + PHALCON_MM_GROW(); + + phalcon_fetch_params(1, 0, 1, &prefix); + + if (!prefix) { + PHALCON_INIT_VAR(prefix); + } + + PHALCON_OBS_VAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + if (Z_TYPE_P(memcache) != IS_OBJECT) { + phalcon_call_method_noret(this_ptr, "_connect"); + + PHALCON_OBS_NVAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + } + + PHALCON_OBS_VAR(options); + phalcon_read_property_this(&options, this_ptr, SL("_options"), PH_NOISY_CC); + + PHALCON_OBS_VAR(special_key); + phalcon_array_fetch_string(&special_key, options, SL("statsKey"), PH_NOISY); + + /** + * Get the key from memcached + */ + PHALCON_INIT_VAR(keys); + phalcon_call_method_p1(keys, memcache, "get", special_key); + if (Z_TYPE_P(keys) == IS_ARRAY) { + + PHALCON_INIT_VAR(prefixed_keys); + array_init(prefixed_keys); + + phalcon_is_iterable(keys, &ah0, &hp0, 0, 0); + + while (zend_hash_get_current_data_ex(ah0, (void**) &hd, &hp0) == SUCCESS) { + + PHALCON_GET_HKEY(key, ah0, hp0); + PHALCON_GET_HVALUE(ttl); + + if (zend_is_true(prefix)) { + if (!phalcon_start_with(key, prefix, NULL)) { + zend_hash_move_forward_ex(ah0, &hp0); + continue; + } + } + phalcon_array_append(&prefixed_keys, key, PH_SEPARATE); + + zend_hash_move_forward_ex(ah0, &hp0); + } + + RETURN_CTOR(prefixed_keys); + } + + RETURN_MM_EMPTY_ARRAY(); +} + +/** + * Checks if cache exists and it hasn't expired + * + * @param string $keyName + * @param long $lifetime + * @return boolean + */ +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, exists){ + + zval *key_name = NULL, *lifetime = NULL, *last_key = NULL, *prefix, *memcache = NULL; + zval *cache_exists; + + PHALCON_MM_GROW(); + + phalcon_fetch_params(1, 0, 2, &key_name, &lifetime); + + if (!key_name) { + PHALCON_INIT_VAR(key_name); + } + + if (!lifetime) { + PHALCON_INIT_VAR(lifetime); + } + + if (Z_TYPE_P(key_name) == IS_NULL) { + PHALCON_OBS_VAR(last_key); + phalcon_read_property_this(&last_key, this_ptr, SL("_lastKey"), PH_NOISY_CC); + } else { + PHALCON_OBS_VAR(prefix); + phalcon_read_property_this(&prefix, this_ptr, SL("_prefix"), PH_NOISY_CC); + + PHALCON_INIT_NVAR(last_key); + PHALCON_CONCAT_VV(last_key, prefix, key_name); + } + if (zend_is_true(last_key)) { + + PHALCON_OBS_VAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + if (Z_TYPE_P(memcache) != IS_OBJECT) { + phalcon_call_method_noret(this_ptr, "_connect"); + + PHALCON_OBS_NVAR(memcache); + phalcon_read_property_this(&memcache, this_ptr, SL("_memcache"), PH_NOISY_CC); + } + + PHALCON_INIT_VAR(cache_exists); + phalcon_call_method_p1(cache_exists, memcache, "get", last_key); + if (PHALCON_IS_NOT_FALSE(cache_exists)) { + RETURN_MM_TRUE; + } + } + + RETURN_MM_FALSE; +} diff --git a/ext/cache/backend/libmemcached.h b/ext/cache/backend/libmemcached.h new file mode 100644 index 00000000000..99f2ec61583 --- /dev/null +++ b/ext/cache/backend/libmemcached.h @@ -0,0 +1,72 @@ + +/* + +------------------------------------------------------------------------+ + | Phalcon Framework | + +------------------------------------------------------------------------+ + | Copyright (c) 2011-2013 Phalcon Team (http://www.phalconphp.com) | + +------------------------------------------------------------------------+ + | This source file is subject to the New BSD License that is bundled | + | with this package in the file docs/LICENSE.txt. | + | | + | If you did not receive a copy of the license and are unable to | + | obtain it through the world-wide-web, please send an email | + | to license@phalconphp.com so we can send you a copy immediately. | + +------------------------------------------------------------------------+ + | Authors: Andres Gutierrez | + | Eduar Carvajal | + +------------------------------------------------------------------------+ +*/ + +extern zend_class_entry *phalcon_cache_backend_libmemcached_ce; + +PHALCON_INIT_CLASS(Phalcon_Cache_Backend_Libmemcached); + +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, __construct); +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, _connect); +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, get); +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, save); +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, delete); +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, queryKeys); +PHP_METHOD(Phalcon_Cache_Backend_Libmemcached, exists); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phalcon_cache_backend_libmemcached___construct, 0, 0, 1) + ZEND_ARG_INFO(0, frontend) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phalcon_cache_backend_libmemcached_get, 0, 0, 1) + ZEND_ARG_INFO(0, keyName) + ZEND_ARG_INFO(0, lifetime) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phalcon_cache_backend_libmemcached_save, 0, 0, 0) + ZEND_ARG_INFO(0, keyName) + ZEND_ARG_INFO(0, content) + ZEND_ARG_INFO(0, lifetime) + ZEND_ARG_INFO(0, stopBuffer) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phalcon_cache_backend_libmemcached_delete, 0, 0, 1) + ZEND_ARG_INFO(0, keyName) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phalcon_cache_backend_libmemcached_querykeys, 0, 0, 0) + ZEND_ARG_INFO(0, prefix) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phalcon_cache_backend_libmemcached_exists, 0, 0, 0) + ZEND_ARG_INFO(0, keyName) + ZEND_ARG_INFO(0, lifetime) +ZEND_END_ARG_INFO() + +PHALCON_INIT_FUNCS(phalcon_cache_backend_libmemcached_method_entry){ + PHP_ME(Phalcon_Cache_Backend_Libmemcached, __construct, arginfo_phalcon_cache_backend_libmemcached___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_ME(Phalcon_Cache_Backend_Libmemcached, _connect, NULL, ZEND_ACC_PROTECTED) + PHP_ME(Phalcon_Cache_Backend_Libmemcached, get, arginfo_phalcon_cache_backend_libmemcached_get, ZEND_ACC_PUBLIC) + PHP_ME(Phalcon_Cache_Backend_Libmemcached, save, arginfo_phalcon_cache_backend_libmemcached_save, ZEND_ACC_PUBLIC) + PHP_ME(Phalcon_Cache_Backend_Libmemcached, delete, arginfo_phalcon_cache_backend_libmemcached_delete, ZEND_ACC_PUBLIC) + PHP_ME(Phalcon_Cache_Backend_Libmemcached, queryKeys, arginfo_phalcon_cache_backend_libmemcached_querykeys, ZEND_ACC_PUBLIC) + PHP_ME(Phalcon_Cache_Backend_Libmemcached, exists, arginfo_phalcon_cache_backend_libmemcached_exists, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + diff --git a/ext/config.m4 b/ext/config.m4 index 24f81081852..0fa0f4bae1e 100644 --- a/ext/config.m4 +++ b/ext/config.m4 @@ -258,6 +258,7 @@ cache/backend/apc.c \ cache/backend/xcache.c \ cache/backend/mongo.c \ cache/backend/memcache.c \ +cache/backend/libmemcached.c \ cache/backend/memory.c \ cache/exception.c \ cache/backendinterface.c \ diff --git a/ext/phalcon.c b/ext/phalcon.c index f04e8f83088..eb6bc0a1d64 100644 --- a/ext/phalcon.c +++ b/ext/phalcon.c @@ -59,6 +59,7 @@ zend_class_entry *phalcon_cache_frontend_igbinary_ce; zend_class_entry *phalcon_cache_backendinterface_ce; zend_class_entry *phalcon_cache_frontend_output_ce; zend_class_entry *phalcon_cache_backend_memcache_ce; +zend_class_entry *phalcon_cache_backend_libmemcached_ce; zend_class_entry *phalcon_tag_select_ce; zend_class_entry *phalcon_tag_exception_ce; zend_class_entry *phalcon_paginator_exception_ce; @@ -467,6 +468,7 @@ static PHP_MINIT_FUNCTION(phalcon){ PHALCON_INIT(Phalcon_Cache_Backend_Xcache); PHALCON_INIT(Phalcon_Cache_Backend_Mongo); PHALCON_INIT(Phalcon_Cache_Backend_Memcache); + PHALCON_INIT(Phalcon_Cache_Backend_Libmemcached); PHALCON_INIT(Phalcon_Cache_Frontend_Json); PHALCON_INIT(Phalcon_Cache_Frontend_Output); PHALCON_INIT(Phalcon_Cache_Frontend_None); diff --git a/ext/phalcon.h b/ext/phalcon.h index dc236cfdc6f..5f5add1c846 100644 --- a/ext/phalcon.h +++ b/ext/phalcon.h @@ -115,6 +115,7 @@ #include "cache/backend/xcache.h" #include "cache/backend/mongo.h" #include "cache/backend/memcache.h" +#include "cache/backend/libmemcached.h" #include "cache/frontend/json.h" #include "cache/frontend/output.h" #include "cache/frontend/none.h" diff --git a/unit-tests/CacheResultsetTest.php b/unit-tests/CacheResultsetTest.php index 8411c5ad60f..6fc3b2eebc6 100644 --- a/unit-tests/CacheResultsetTest.php +++ b/unit-tests/CacheResultsetTest.php @@ -77,6 +77,17 @@ protected function _getCache($adapter='File'){ "port" => "11211" )); break; + case 'Libmemcached': + $cache = new Phalcon\Cache\Backend\Libmemcached($frontCache, array( + "servers" => array( + array( + "host" => "localhost", + "port" => "11211", + "weight" => "1", + ) + ) + )); + break; default: throw new Exception("Unknown cache adapter"); } @@ -273,4 +284,86 @@ public function testCacheResultsetSimpleMemcached() $this->assertEquals($number, 35); } + public function testCacheResultsetSimpleLibmemcached() + { + if (!class_exists('Memcached')) { + $this->markTestSkipped("Memcached class does not exist, test skipped"); + return; + } + + $cache = $this->_getCache('Libmemcached'); + + $key = 'test-resultset-'.mt_rand(0, 9999); + + //Single + $people = People::findFirst(array( + 'cache' => array( + 'key' => $key + ) + )); + + $this->assertTrue(is_object($people)); + + $people = $cache->get($key); + $this->assertEquals(get_class($people->getFirst()), 'People'); + + $people = $cache->get($key); + $this->assertEquals(get_class($people->getFirst()), 'People'); + + //Re-get from the cache + $people = People::findFirst(array( + 'cache' => array( + 'key' => $key + ) + )); + + $this->assertTrue(is_object($people)); + + $key = 'test-resultset-'.mt_rand(0, 9999); + + //Multiple + $people = People::find(array( + 'limit' => 35, + 'cache' => array( + 'key' => $key + ) + )); + + $number = 0; + foreach ($people as $individual) { + $this->assertTrue(is_object($individual)); + $number++; + } + $this->assertEquals($number, 35); + + $people = $cache->get($key); + $this->assertEquals(get_class($people), 'Phalcon\Mvc\Model\Resultset\Simple'); + + $number = 0; + foreach ($people as $individual) { + $this->assertTrue(is_object($individual)); + $number++; + } + $this->assertEquals($number, 35); + + $people = $cache->get($key); + $this->assertEquals(get_class($people), 'Phalcon\Mvc\Model\Resultset\Simple'); + + //Re-get the data from the cache + $people = People::find(array( + 'limit' => 35, + 'cache' => array( + 'key' => $key + ) + )); + + $number = 0; + foreach ($people as $individual) { + $this->assertTrue(is_object($individual)); + $number++; + } + $this->assertEquals($number, 35); + + } + } diff --git a/unit-tests/CacheTest.php b/unit-tests/CacheTest.php index 20e20fa5a5c..4bea949aab2 100644 --- a/unit-tests/CacheTest.php +++ b/unit-tests/CacheTest.php @@ -681,4 +681,222 @@ public function testDataXcache() $this->assertTrue($cache->delete('test-data')); } + + private function _prepareLibmemcached() + { + + if (!extension_loaded('memcached')) { + $this->markTestSkipped('Warning: memcached extension is not loaded'); + return false; + } + + $memcache = new Memcached(); + $this->assertFalse(!$memcache->addServers(array(array('127.0.0.1', 11211, 1)))); + + return $memcache; + } + + public function testOutputLibmemcachedCache() + { + + $memcache = $this->_prepareLibmemcached(); + if (!$memcache) { + return false; + } + + $memcache->delete('test-output'); + + $time = date('H:i:s'); + + $frontCache = new Phalcon\Cache\Frontend\Output(array( + 'lifetime' => 2 + )); + + $cache = new Phalcon\Cache\Backend\Libmemcached($frontCache); + + ob_start(); + + //First time cache + $content = $cache->start('test-output'); + if ($content !== null) { + $this->assertTrue(false); + } + + echo $time; + + $cache->save(null, null, null, true); + + $obContent = ob_get_contents(); + ob_end_clean(); + + $this->assertEquals($time, $obContent); + $this->assertEquals($time, $memcache->get('test-output')); + + //Expect same cache + $content = $cache->start('test-output'); + if ($content === null) { + $this->assertTrue(false); + } + + $this->assertEquals($time, $obContent); + + //Refresh cache + sleep(3); + + $time2 = date('H:i:s'); + + ob_start(); + + $content = $cache->start('test-output'); + if($content!==null){ + $this->assertTrue(false); + } + echo $time2; + $cache->save(null, null, null, true); + + $obContent2 = ob_get_contents(); + ob_end_clean(); + + $this->assertNotEquals($time, $obContent2); + $this->assertEquals($time2, $obContent2); + $this->assertEquals($time2, $memcache->get('test-output')); + + //Check if exists + $this->assertTrue($cache->exists('test-output')); + + //Delete entry from cache + $this->assertTrue($cache->delete('test-output')); + + $memcache->quit(); + + } + + public function testDataLibmemcachedCache() + { + + $memcache = $this->_prepareLibmemcached(); + if (!$memcache) { + return false; + } + + $memcache->delete('test-data'); + + $frontCache = new Phalcon\Cache\Frontend\Data(); + + $cache = new Phalcon\Cache\Backend\Libmemcached($frontCache, array( + 'servers' => array( + array( + 'host' => '127.0.0.1', + 'port' => '11211', + 'weight' => '1'), + ) + )); + + $keys = $cache->queryKeys(); + foreach ($keys as $key) { + $cache->delete($key); + } + + $data = array(1, 2, 3, 4, 5); + + $cache->save('test-data', $data); + + $cachedContent = $cache->get('test-data'); + $this->assertEquals($cachedContent, $data); + + $cache->save('test-data', "sure, nothing interesting"); + + $cachedContent = $cache->get('test-data'); + $this->assertEquals($cachedContent, "sure, nothing interesting"); + + $this->assertEquals($cache->queryKeys(), array( + 0 => 'test-data', + )); + + //Check if exists + $this->assertTrue($cache->exists('test-data')); + + //Delete + $this->assertTrue($cache->delete('test-data')); + + $memcache->quit(); + + } + + public function testDataLibmemcachedCacheOption() + { + + $memcache = $this->_prepareLibmemcached(); + if (!$memcache) { + return false; + } + + $memcache->delete('test-data'); + + $frontCache = new Phalcon\Cache\Frontend\Data(); + + //Memcached OPT_PREFIX_KEY: prefix. + $cache = new Phalcon\Cache\Backend\Libmemcached($frontCache, array( + 'servers' => array( + array( + 'host' => '127.0.0.1', + 'port' => '11211', + 'weight' => '1'), + ), + 'client' => array( + Memcached::OPT_PREFIX_KEY => 'prefix.', + ) + )); + + $data = array(1, 2, 3, 4, 5); + + $cache->save('test-data', $data); + + $cachedContent = $cache->get('test-data'); + $this->assertEquals($cachedContent, $data); + + $cachedContent = $memcache->get('test-data'); + //$this->assertNotEquals($cachedContent, $data); + $this->assertFalse($cachedContent); + + $cachedContent = $memcache->get('prefix.test-data'); + $cachedUnserialize = unserialize($cachedContent); + $this->assertEquals($cachedUnserialize, $data); + + //Memcached Option None + $cache2 = new Phalcon\Cache\Backend\Libmemcached($frontCache, array( + 'servers' => array( + array( + 'host' => '127.0.0.1', + 'port' => '11211', + 'weight' => '1'), + ), + 'client' => array(), + )); + + $cachedContent = $cache2->get('test-data'); + $this->assertNotEquals($cachedContent, $data); + + $cache2->save('test-data', "sure, nothing interesting"); + + $cachedContent = $cache2->get('test-data'); + $this->assertEquals($cachedContent, "sure, nothing interesting"); + + $cachedContent = $memcache->get('test-data'); + $cachedUnserialize = unserialize($cachedContent); + $this->assertEquals($cachedUnserialize, "sure, nothing interesting"); + + $cachedContent = $memcache->get('prefix.test-data'); + $cachedUnserialize = unserialize($cachedContent); + $this->assertNotEquals($cachedUnserialize, "sure, nothing interesting"); + $this->assertEquals($cachedUnserialize, $data); + + //Delete + $this->assertTrue($cache->delete('test-data')); + $this->assertTrue($cache2->delete('test-data')); + + $memcache->quit(); + + } + }