Memcache拡張モジュールを透過的に扱えるパッチを書いてみた。

PHPなんだから透過的にmemcachedとか扱えないとまずいでしょうということで。

要はphp.iniに

# 以下の書式が利用可能
# hostname
# hostname:tcpport
# hostname:tcpport:udpport
# [hostname]
# [hostname]:tcpport
# [hostname]:tcpport:udpport

memcache.auto_connect_hosts = 10.1.4.1, 10.1.4.2:11211, [::::::10.1.4.3]::11211

と書いておくと、

<?php
$_MEMCACHE['test'] = 'test'; // Memcache::set() 相当
var_dump($_MEMCACHE['test']); // Memcache::get() 相当
?>

のように、オートグローバル変数 $_MEMCACHE を操作した際に自動的に接続を行ってmemcachedにアクセスできるという話。

以下より memcache-3.0.2.tar.gz を落としてきて、パッチを当ててからビルドしてください。

http://pecl.php.net/package/memcache

追記:
$_MEMCACHE の中身はどうなってるんだという話。var_dump($_MEMCACHE);してみると分かるとおり、単なるMemcacheオブジェクトで、 配列アクセス「[]」がオーバーロードされているだけです。

diff -ur memcache-3.0.2~/memcache.c memcache-3.0.2/memcache.c
--- memcache-3.0.2~/memcache.c	2008-09-12 05:03:23.000000000 +0900
+++ memcache-3.0.2/memcache.c	2008-11-19 10:37:50.000000000 +0900
@@ -123,6 +123,32 @@
 };
 /* }}} */
 
+/* {{{ mmc_object_handlers */
+static zend_object_handlers mmc_object_handlers = {
+	ZEND_OBJECTS_STORE_HANDLERS,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL, 
+};
+/* }}} */
+
 #ifdef COMPILE_DL_MEMCACHE
 ZEND_GET_MODULE(memcache)
 #endif
@@ -224,6 +250,7 @@
 PHP_INI_BEGIN()
 	STD_PHP_INI_ENTRY("memcache.allow_failover",		"1",			PHP_INI_ALL, OnUpdateLong,			allow_failover,	zend_memcache_globals,	memcache_globals)
 	STD_PHP_INI_ENTRY("memcache.max_failover_attempts",	"20",			PHP_INI_ALL, OnUpdateFailoverAttempts,		max_failover_attempts,	zend_memcache_globals,	memcache_globals)
+	STD_PHP_INI_ENTRY("memcache.auto_connect_hosts",		"localhost:11211",		PHP_INI_ALL, OnUpdateString,			auto_connect_hosts,	zend_memcache_globals,	memcache_globals)
 	STD_PHP_INI_ENTRY("memcache.default_port",			"11211",		PHP_INI_ALL, OnUpdateLong,			default_port,	zend_memcache_globals,	memcache_globals)
 	STD_PHP_INI_ENTRY("memcache.chunk_size",			"32768",		PHP_INI_ALL, OnUpdateChunkSize,		chunk_size,		zend_memcache_globals,	memcache_globals)
 	STD_PHP_INI_ENTRY("memcache.protocol",				"ascii",		PHP_INI_ALL, OnUpdateProtocol,		protocol,		zend_memcache_globals,	memcache_globals)
@@ -244,6 +271,9 @@
 static void _mmc_server_list_dtor(zend_rsrc_list_entry * TSRMLS_DC);
 static void php_mmc_set_failure_callback(mmc_pool_t *, zval *, zval * TSRMLS_DC);
 static void php_mmc_failure_callback(mmc_pool_t *, mmc_t *, void * TSRMLS_DC);
+static zend_bool _php_mmc_auto_global_cb(char *name, uint name_len TSRMLS_DC);
+static zval *_php_mmc_read_dimension(zval *object, zval *offset, int type TSRMLS_DC);
+static void _php_mmc_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC);
 /* }}} */
 
 /* {{{ php_memcache_init_globals()
@@ -260,7 +290,10 @@
 PHP_MINIT_FUNCTION(memcache)
 {
 	zend_class_entry ce;
-	
+	mmc_object_handlers = *zend_get_std_object_handlers();
+	mmc_object_handlers.read_dimension = _php_mmc_read_dimension;
+	mmc_object_handlers.write_dimension = _php_mmc_write_dimension;
+
 	INIT_CLASS_ENTRY(ce, "MemcachePool", php_memcache_pool_class_functions);
 	memcache_pool_ce = zend_register_internal_class(&ce TSRMLS_CC);
 	
@@ -286,6 +319,8 @@
 	REGISTER_LONG_CONSTANT("MEMCACHE_HAVE_SESSION", 0, CONST_CS | CONST_PERSISTENT);
 #endif
 
+	zend_register_auto_global("_MEMCACHE", sizeof("_MEMCACHE") - 1, _php_mmc_auto_global_cb);
+
 	return SUCCESS;
 }
 /* }}} */
@@ -523,7 +558,7 @@
 	long value = 1, defval = 0, exptime = 0;
 	mmc_request_t *request;
 	void *value_handler_param[3];
-	                          
+	               
 	if (mmc_object == NULL) {
 		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oz|lll", &mmc_object, memcache_pool_ce, &keys, &value, &defval, &exptime) == FAILURE) {
 			return;
@@ -986,6 +1021,238 @@
 }
 /* }}} */
 
+static zend_bool _php_mmc_auto_global_cb(char *name, uint name_len TSRMLS_DC) /* {{{ */
+{
+	zval *mmc_object = NULL;
+	if (!MEMCACHE_G(auto_connect_hosts)) {
+		return FALSE;
+	}
+	{
+		int list_id;
+		zend_object_value ov;
+		mmc_pool_t *pool = mmc_pool_new(TSRMLS_C);
+		pool->failure_callback_param = &php_mmc_failure_callback;
+		pool->failure_callback = &php_mmc_failure_callback;
+
+		ALLOC_INIT_ZVAL(mmc_object);
+		object_init_ex(mmc_object, memcache_ce);
+		list_id = zend_list_insert(pool, le_memcache_pool);
+		add_property_resource(mmc_object, "connection", list_id);
+		Z_OBJ_HT_P(mmc_object) = &mmc_object_handlers;
+	}
+	{
+		const char *host;
+		int host_len;
+		int tcp_port;
+		int udp_port;
+		{
+			const char *p, *pt;
+			p = MEMCACHE_G(auto_connect_hosts);
+			for (;;) {
+				while (*p != '\0' && isspace(*(unsigned char *)p)) {
+					++p;
+				}
+				if (*p == '\0') {
+					break;
+				}
+				if (*p == '[') {
+					host = ++p;
+					while (*p != '\0' && *p != ']') {
+						++p;
+					}
+					if (*p == '\0') {
+						pt = host;
+					} else {
+						pt = p;
+						++p;
+						while (*p != '\0' && isspace(*(unsigned char *)p)) {
+							++p;
+						}
+						if (*p != '\0' && *p != ':' && *p != ',') {
+							pt = host;
+						}
+					}
+				} else {
+					host = p;
+					while (*p != '\0' && *p != ':' && *p != ',') {
+						++p;
+					}
+					pt = p;
+					while (pt > host && isspace(*(unsigned char *)(pt - 1))) {
+						--pt;
+					}
+				}
+				host_len = pt - host;
+				tcp_port = MEMCACHE_G(default_port);
+				udp_port = 0;
+				if (*p == ':') {
+					++p;
+					while (*p != '\0' && isspace(*(unsigned char *)p)) {
+						++p;
+					}
+					if (*p != '\0') {
+						const char *tcp_port_str = p;
+						while (*p != '\0' && *p != ':' && *p != ',') {
+							++p;
+						}
+						pt = p;
+						while (pt > tcp_port_str && isspace(*(unsigned char *)(pt - 1))) {
+							--pt;
+						}
+						if (pt != tcp_port_str) {
+							errno = 0;
+							tcp_port = strtol(tcp_port_str, NULL, 10);
+							switch (errno) {
+							case EINVAL: case ERANGE:
+								tcp_port = -1;
+								break;
+							}
+						}
+					}
+					if (*p == ':') {
+						++p;
+						while (*p != '\0' && isspace(*(unsigned char *)p)) {
+							++p;
+						}
+						if (*p != '\0') {
+							const char *udp_port_str = p;
+							while (*p != '\0' && *p != ',') {
+								++p;
+							}
+							pt = p;
+							while (pt > udp_port_str && isspace(*(unsigned char *)(pt - 1))) {
+								--pt;
+							}
+							if (pt != udp_port_str) {
+								errno = 0;
+								udp_port = strtol(udp_port_str, NULL, 10);
+								switch (errno) {
+								case EINVAL: case ERANGE:
+									udp_port = -1;
+									break;
+								}
+							}
+							if (*p == ',') {
+								++p;
+							}
+						}
+					} else if (*p == ',') {
+						++p;
+					}
+				} else if (*p == ',') {
+					++p;
+				}
+				if (host_len == 0) {
+					php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.auto_connect_hosts contains an invalid host name");
+					goto fail;
+				}
+				if (tcp_port < 0 || udp_port < 0) {
+					php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.auto_connect_hosts contains an invalid port number");
+					goto fail;
+				}
+
+				if (!php_mmc_pool_addserver(mmc_object, host, host_len, tcp_port, 0, 1, 1, MMC_DEFAULT_TIMEOUT, MMC_DEFAULT_RETRY, 1, NULL TSRMLS_CC)) {
+					goto fail;
+				}
+			}
+		}
+	}
+	zend_hash_update(&EG(symbol_table), "_MEMCACHE", sizeof("_MEMCACHE"), &mmc_object, sizeof(zval *), NULL);
+	return 0;
+fail:
+	if (mmc_object) {
+		zval_ptr_dtor(&mmc_object);
+	}
+	return 1;
+}
+/* }}} */
+
+static zval *_php_mmc_read_dimension(zval *mmc_object, zval *offset, int type TSRMLS_DC) /* {{{ */
+{
+	mmc_pool_t *pool;
+	void *value_handler_param[3], *failover_handler_param[2];
+	zval *return_value;
+	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
+		return NULL;
+	}
+
+	ALLOC_INIT_ZVAL(return_value);
+	ZVAL_FALSE(return_value);
+
+	value_handler_param[0] = return_value;
+	value_handler_param[1] = NULL;
+	value_handler_param[2] = NULL;
+	
+	{
+		mmc_request_t *request;
+		
+		/* allocate request */
+		request = mmc_pool_request_get(
+			pool, MMC_PROTO_UDP,
+			mmc_value_handler_single, value_handler_param, 
+			mmc_pool_failover_handler, NULL TSRMLS_CC);
+
+		if (mmc_prepare_key(offset, request->key, &(request->key_len)) != MMC_OK) {
+			mmc_pool_release(pool, request);
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
+			zval_ptr_dtor(&return_value);
+			return NULL;
+		}
+
+		pool->protocol->get(request, MMC_OP_GET, offset, request->key, request->key_len);
+		
+		/* schedule request */
+		if (mmc_pool_schedule_key(pool, request->key, request->key_len, request, 1 TSRMLS_CC) != MMC_OK) {
+			zval_ptr_dtor(&return_value);
+			return NULL;
+		}
+	}
+	
+	/* execute all requests */
+	mmc_pool_run(pool TSRMLS_CC);
+
+	return return_value;
+}
+/* }}} */
+
+static void _php_mmc_write_dimension(zval *mmc_object, zval *offset, zval *value TSRMLS_DC) /* {{{ */
+{
+	mmc_pool_t *pool;
+	mmc_request_t *request;
+	zval return_value;
+
+	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
+		return;
+	}
+
+	{
+		mmc_request_t *request;
+		/* allocate request */
+		request = mmc_pool_request(pool, MMC_PROTO_TCP, mmc_stored_handler, &return_value, mmc_pool_failover_handler, NULL TSRMLS_CC);
+
+		if (mmc_prepare_key(offset, request->key, &(request->key_len)) != MMC_OK) {
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
+			mmc_pool_release(pool, request);
+			return;
+		}
+		
+		/* assemble command */
+		if (pool->protocol->store(pool, request, MMC_OP_SET, request->key, request->key_len, 0, 0, 0, value TSRMLS_CC) != MMC_OK) {
+			mmc_pool_release(pool, request);
+			return;
+		}
+		
+		/* schedule request */
+		if (mmc_pool_schedule_key(pool, request->key, request->key_len, request, MEMCACHE_G(redundancy) TSRMLS_CC) != MMC_OK) {
+			return;
+		}
+	}
+
+	/* execute all requests */
+	mmc_pool_run(pool TSRMLS_CC);
+}
+/* }}} */
+
 /* ----------------
    module functions
    ---------------- */
diff -ur memcache-3.0.2~/memcache_pool.h memcache-3.0.2/memcache_pool.h
--- memcache-3.0.2~/memcache_pool.h	2008-09-12 05:03:23.000000000 +0900
+++ memcache-3.0.2/memcache_pool.h	2008-11-18 09:43:24.000000000 +0900
@@ -348,6 +348,7 @@
 	long max_failover_attempts;
 	long redundancy;
 	long session_redundancy;
+	char* auto_connect_hosts;
 ZEND_END_MODULE_GLOBALS(memcache)
 
 #ifdef ZTS