diff --git a/package.xml b/package.xml
index ec44bd1b..7a6ea475 100644
--- a/package.xml
+++ b/package.xml
@@ -172,7 +172,11 @@ Fixes
-
+
+
+
+
+
diff --git a/php_memcached.c b/php_memcached.c
index 1e218a00..8f464283 100644
--- a/php_memcached.c
+++ b/php_memcached.c
@@ -3502,6 +3502,8 @@ static
void php_memc_server_free_storage(zend_object *object)
{
php_memc_server_t *intern = php_memc_server_fetch_object(object);
+
+ php_memc_proto_handler_destroy(&intern->handler);
zend_object_std_dtor(&intern->zo);
}
@@ -3515,6 +3517,7 @@ zend_object *php_memc_server_new(zend_class_entry *ce)
object_properties_init(&intern->zo, ce);
intern->zo.handlers = &memcached_server_object_handlers;
+ intern->handler = php_memc_proto_handler_new();
return &intern->zo;
}
@@ -3904,7 +3907,6 @@ static
PHP_GINIT_FUNCTION(php_memcached)
{
#ifdef HAVE_MEMCACHED_SESSION
-
php_memcached_globals->session.lock_enabled = 0;
php_memcached_globals->session.lock_wait_max = 150;
php_memcached_globals->session.lock_wait_min = 150;
@@ -3923,8 +3925,12 @@ PHP_GINIT_FUNCTION(php_memcached)
php_memcached_globals->session.persistent_enabled = 0;
php_memcached_globals->session.sasl_username = NULL;
php_memcached_globals->session.sasl_password = NULL;
+#endif
+#ifdef HAVE_MEMCACHED_PROTOCOL
+ memset(&php_memcached_globals->server, 0, sizeof(php_memcached_globals->server));
#endif
+
php_memcached_globals->memc.serializer_name = NULL;
php_memcached_globals->memc.serializer_type = SERIALIZER_DEFAULT;
php_memcached_globals->memc.compression_name = NULL;
diff --git a/php_memcached_server.c b/php_memcached_server.c
index 870209c1..24c328f8 100644
--- a/php_memcached_server.c
+++ b/php_memcached_server.c
@@ -17,11 +17,10 @@
#include "php_memcached.h"
#include "php_memcached_private.h"
#include "php_memcached_server.h"
+#include "php_network.h"
#include
-#undef NDEBUG
-#undef _NDEBUG
#include
#define MEMC_GET_CB(cb_type) (MEMC_SERVER_G(callbacks)[cb_type])
@@ -58,9 +57,9 @@ typedef struct {
static
long s_invoke_php_callback (php_memc_server_cb_t *cb, zval *params, ssize_t param_count)
{
- zval *retval = NULL;
+ zval retval;
- cb->fci.retval = retval;
+ cb->fci.retval = &retval;
cb->fci.params = params;
cb->fci.param_count = param_count;
#if PHP_VERSION_ID < 80000
@@ -73,7 +72,7 @@ long s_invoke_php_callback (php_memc_server_cb_t *cb, zval *params, ssize_t para
efree (buf);
}
- return retval == NULL ? PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND : zval_get_long(retval);
+ return Z_ISUNDEF(retval) ? PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND : zval_get_long(&retval);
}
// memcached protocol callbacks
@@ -96,6 +95,7 @@ protocol_binary_response_status s_add_handler(const void *cookie, const void *ke
ZVAL_LONG(&zflags, flags);
ZVAL_LONG(&zexptime, exptime);
ZVAL_NULL(&zresult_cas);
+ ZVAL_MAKE_REF(&zresult_cas);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zkey);
@@ -142,6 +142,7 @@ protocol_binary_response_status s_append_prepend_handler (php_memc_event_t event
ZVAL_STRINGL(&zvalue, data, data_len);
ZVAL_DOUBLE(&zcas, cas);
ZVAL_NULL(&zresult_cas);
+ ZVAL_MAKE_REF(&zresult_cas);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zkey);
@@ -198,11 +199,13 @@ protocol_binary_response_status s_incr_decr_handler (php_memc_event_t event, con
MEMC_MAKE_ZVAL_COOKIE(zcookie, cookie);
ZVAL_STRINGL(&zkey, key, key_len);
- ZVAL_LONG(&zdelta, (long) delta);
- ZVAL_LONG(&zinital, (long) initial);
- ZVAL_LONG(&zexpiration, (long) expiration);
+ ZVAL_LONG(&zdelta, (zend_long) delta);
+ ZVAL_LONG(&zinital, (zend_long) initial);
+ ZVAL_LONG(&zexpiration, (zend_long) expiration);
ZVAL_LONG(&zresult, 0);
+ ZVAL_MAKE_REF(&zresult);
ZVAL_NULL(&zresult_cas);
+ ZVAL_MAKE_REF(&zresult_cas);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zkey);
@@ -296,6 +299,7 @@ protocol_binary_response_status s_flush_handler(const void *cookie, uint32_t whe
}
MEMC_MAKE_ZVAL_COOKIE(zcookie, cookie);
+ ZVAL_LONG(&zwhen, when);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zwhen);
@@ -322,6 +326,13 @@ protocol_binary_response_status s_get_handler (const void *cookie, const void *k
}
MEMC_MAKE_ZVAL_COOKIE(zcookie, cookie);
+ ZVAL_STRINGL(&zkey, key, key_len);
+ ZVAL_NULL(&zvalue);
+ ZVAL_MAKE_REF(&zvalue);
+ ZVAL_NULL(&zflags);
+ ZVAL_MAKE_REF(&zflags);
+ ZVAL_NULL(&zresult_cas);
+ ZVAL_MAKE_REF(&zresult_cas);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zkey);
@@ -436,11 +447,12 @@ protocol_binary_response_status s_set_replace_handler (php_memc_event_t event, c
MEMC_MAKE_ZVAL_COOKIE(zcookie, cookie);
ZVAL_STRINGL(&zkey, key, key_len);
- ZVAL_STRINGL(&zdata, ((char *) data), (int) data_len);
- ZVAL_LONG(&zflags, (long) flags);
- ZVAL_LONG(&zexpiration, (long) expiration);
+ ZVAL_STRINGL(&zdata, data, data_len);
+ ZVAL_LONG(&zflags, (zend_long) flags);
+ ZVAL_LONG(&zexpiration, (zend_long) expiration);
ZVAL_DOUBLE(&zcas, (double) cas);
ZVAL_NULL(&zresult_cas);
+ ZVAL_MAKE_REF(&zresult_cas);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zkey);
@@ -494,7 +506,7 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
{
zval params[3];
protocol_binary_response_status retval = PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND;
- zval zcookie, zkey, zbody;
+ zval zcookie, zkey, zstats;
if (!MEMC_HAS_CB(MEMC_SERVER_ON_STAT)) {
return retval;
@@ -502,25 +514,49 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
MEMC_MAKE_ZVAL_COOKIE(zcookie, cookie);
- ZVAL_STRINGL(&zkey, key, key_len);
- ZVAL_NULL(&zbody);
+ if (key && key_len) {
+ ZVAL_STRINGL(&zkey, key, key_len);
+ } else {
+ ZVAL_NULL(&zkey);
+ }
+ array_init(&zstats);
+ ZVAL_MAKE_REF(&zstats);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zkey);
- ZVAL_COPY(¶ms[2], &zbody);
+ ZVAL_COPY(¶ms[2], &zstats);
retval = s_invoke_php_callback (&MEMC_GET_CB(MEMC_SERVER_ON_STAT), params, 3);
if (retval == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
- if (Z_TYPE(zbody) == IS_NULL) {
- retval = response_handler(cookie, NULL, 0, NULL, 0);
+ zval *zarray = &zstats;
+ zend_string *key;
+ zend_long idx;
+ zval *val;
+
+ ZVAL_DEREF(zarray);
+ if (Z_TYPE_P(zarray) != IS_ARRAY) {
+ convert_to_array(zarray);
}
- else {
- if (Z_TYPE(zbody) != IS_STRING) {
- convert_to_string(&zbody);
+
+ ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(zarray), idx, key, val)
+ {
+ zend_string *val_str = zval_get_string(val);
+
+ if (key) {
+ retval = response_handler(cookie, key->val, key->len, val_str->val, val_str->len);
+ } else {
+ char buf[0x20], *ptr, *end = &buf[sizeof(buf) - 1];
+ ptr = zend_print_long_to_buf(end, idx);
+ retval = response_handler(cookie, ptr, end - ptr, val_str->val, val_str->len);
+ }
+ zend_string_release(val_str);
+
+ if (retval != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+ break;
}
- retval = response_handler(cookie, key, key_len, Z_STRVAL(zbody), (uint32_t) Z_STRLEN(zbody));
}
+ ZEND_HASH_FOREACH_END();
}
zval_ptr_dtor(¶ms[0]);
@@ -528,7 +564,7 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
zval_ptr_dtor(¶ms[2]);
zval_ptr_dtor (&zcookie);
zval_ptr_dtor (&zkey);
- zval_ptr_dtor (&zbody);
+ zval_ptr_dtor (&zstats);
return retval;
}
@@ -547,12 +583,12 @@ protocol_binary_response_status s_version_handler (const void *cookie,
MEMC_MAKE_ZVAL_COOKIE(zcookie, cookie);
ZVAL_NULL(&zversion);
+ ZVAL_MAKE_REF(&zversion);
ZVAL_COPY(¶ms[0], &zcookie);
ZVAL_COPY(¶ms[1], &zversion);
retval = s_invoke_php_callback (&MEMC_GET_CB(MEMC_SERVER_ON_VERSION), params, 2);
-
if (retval == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
if (Z_TYPE(zversion) != IS_STRING) {
convert_to_string(&zversion);
@@ -581,31 +617,25 @@ void s_handle_memcached_event (evutil_socket_t fd, short what, void *arg)
if (!client->on_connect_invoked) {
if (MEMC_HAS_CB(MEMC_SERVER_ON_CONNECT)) {
- zval zremoteip, zremoteport;
- zval params[2];
+ zend_string *zremoteaddr_str;
+ zval zremoteaddr;
+ zval params[1];
protocol_binary_response_status retval;
- struct sockaddr_in addr_in;
- socklen_t addr_in_len = sizeof(addr_in);
+ ZVAL_NULL(&zremoteaddr);
- if (getpeername (fd, (struct sockaddr *) &addr_in, &addr_in_len) == 0) {
- ZVAL_STRING(&zremoteip, inet_ntoa (addr_in.sin_addr));
- ZVAL_LONG(&zremoteport, ntohs (addr_in.sin_port));
+ if (SUCCESS == php_network_get_peer_name (fd, &zremoteaddr_str, NULL, NULL)) {
+ ZVAL_STR(&zremoteaddr, zremoteaddr_str);
} else {
php_error_docref(NULL, E_WARNING, "getpeername failed: %s", strerror (errno));
- ZVAL_NULL(&zremoteip);
- ZVAL_NULL(&zremoteport);
}
- ZVAL_COPY(¶ms[0], &zremoteip);
- ZVAL_COPY(¶ms[1], &zremoteport);
+ ZVAL_COPY(¶ms[0], &zremoteaddr);
- retval = s_invoke_php_callback (&MEMC_GET_CB(MEMC_SERVER_ON_CONNECT), params, 2);
+ retval = s_invoke_php_callback (&MEMC_GET_CB(MEMC_SERVER_ON_CONNECT), params, 1);
zval_ptr_dtor(¶ms[0]);
- zval_ptr_dtor(¶ms[1]);
- zval_ptr_dtor(&zremoteip);
- zval_ptr_dtor(&zremoteport);
+ zval_ptr_dtor(&zremoteaddr);
if (retval != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
memcached_protocol_client_destroy (client->protocol_client);
@@ -714,22 +744,20 @@ php_memc_proto_handler_t *php_memc_proto_handler_new ()
}
static
-evutil_socket_t s_create_listening_socket (const char *spec)
+evutil_socket_t s_create_listening_socket (const zend_string *spec)
{
evutil_socket_t sock;
struct sockaddr_storage addr;
- int addr_len;
-
+ socklen_t addr_len;
int rc;
addr_len = sizeof (struct sockaddr);
- rc = evutil_parse_sockaddr_port (spec, (struct sockaddr *) &addr, &addr_len);
- if (rc != 0) {
- php_error_docref(NULL, E_WARNING, "Failed to parse bind address");
+ if (SUCCESS != php_network_parse_network_address_with_port(spec->val, spec->len, (struct sockaddr *) &addr, &addr_len)) {
+ php_error_docref(NULL, E_WARNING, "Failed to parse bind address: %s", spec->val);
return -1;
}
- sock = socket (AF_INET, SOCK_STREAM, 0);
+ sock = socket (addr.ss_family, SOCK_STREAM, 0);
if (sock < 0) {
php_error_docref(NULL, E_WARNING, "socket failed: %s", strerror (errno));
return -1;
@@ -770,7 +798,7 @@ evutil_socket_t s_create_listening_socket (const char *spec)
zend_bool php_memc_proto_handler_run (php_memc_proto_handler_t *handler, zend_string *address)
{
struct event *accept_event;
- evutil_socket_t sock = s_create_listening_socket (address->val);
+ evutil_socket_t sock = s_create_listening_socket (address);
if (sock == -1) {
return 0;
diff --git a/server-example/run-server.php b/server-example/run-server.php
index a02c6a60..b7612c80 100644
--- a/server-example/run-server.php
+++ b/server-example/run-server.php
@@ -3,8 +3,8 @@
$server = new MemcachedServer();
$server->on (Memcached::ON_CONNECT,
- function ($remote_ip, $remote_port) {
- echo "Incoming connection from {$remote_ip}:{$remote_port}" . PHP_EOL;
+ function ($remote_addr) {
+ echo "Incoming connection from {$remote_addr}" . PHP_EOL;
return Memcached::RESPONSE_SUCCESS;
});
@@ -89,4 +89,4 @@ function ($client_id) {
return Memcached::RESPONSE_SUCCESS;
});
-$server->run ("127.0.0.1:3434");
\ No newline at end of file
+$server->run ("127.0.0.1:3434");
diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
new file mode 100644
index 00000000..7b058d70
--- /dev/null
+++ b/tests/memcachedserver.phpt
@@ -0,0 +1,119 @@
+--TEST--
+MemcachedServer
+--SKIPIF--
+
+--FILE--
+setOption(Memcached::OPT_BINARY_PROTOCOL, true);
+$cache->setOption(Memcached::OPT_COMPRESSION, false);
+$cache->addServer('127.0.0.1', 3434);
+
+$cache->add("add_key", "hello", 500);
+$cache->append("append_key", "world");
+$cache->prepend("prepend_key", "world");
+
+$cache->increment("incr", 2, 1, 500);
+$cache->decrement("decr", 2, 1, 500);
+
+$cache->delete("delete_k");
+$cache->flush(1);
+
+var_dump($cache->get('get_this'));
+
+$cache->set ('set_key', 'value 1', 100);
+$cache->replace ('replace_key', 'value 2', 200);
+
+var_dump($cache->getVersion());
+var_dump($cache->getStats());
+var_dump($cache->getStats("empty"));
+var_dump($cache->getStats("foobar"));
+var_dump($cache->getStats("scalar"));
+var_dump($cache->getStats("numeric array"));
+
+$cache->quit();
+usleep(50000);
+
+memcached_server_stop($server);
+?>
+Done
+--EXPECTF--
+Listening on 127.0.0.1:3434
+Incoming connection from 127.0.0.1:%s
+Incoming connection from 127.0.0.1:%s
+client_id=[%s]: Add key=[add_key], value=[hello], flags=[0], expiration=[500]
+client_id=[%s]: Append key=[append_key], value=[world], cas=[0]
+client_id=[%s]: Prepend key=[prepend_key], value=[world], cas=[0]
+client_id=[%s]: Incrementing key=[incr], delta=[2], initial=[1], expiration=[500]
+client_id=[%s]: Decrementing key=[decr], delta=[2], initial=[1], expiration=[500]
+client_id=[%s]: Delete key=[delete_k], cas=[0]
+client_id=[%s]: Flush when=[1]
+client_id=[%s]: Get key=[get_this]
+client_id=[%s]: Noop
+string(20) "Hello to you client!"
+client_id=[%s]: Set key=[set_key], value=[value 1], flags=[0], expiration=[100], cas=[0]
+client_id=[%s]: Replace key=[replace_key], value=[value 2], flags=[0], expiration=[200], cas=[0]
+client_id=[%s]: Version
+array(1) {
+ ["127.0.0.1:3434"]=>
+ string(5) "1.1.1"
+}
+client_id=[%s]: Stat key=[]
+array(1) {
+ ["127.0.0.1:3434"]=>
+ array(2) {
+ ["key"]=>
+ string(0) ""
+ ["foo"]=>
+ string(3) "bar"
+ }
+}
+client_id=[%s]: Stat key=[empty]
+array(0) {
+}
+client_id=[%s]: Stat key=[foobar]
+array(1) {
+ ["127.0.0.1:3434"]=>
+ array(2) {
+ ["key"]=>
+ string(6) "foobar"
+ ["foo"]=>
+ string(3) "bar"
+ }
+}
+client_id=[%s]: Stat key=[scalar]
+array(1) {
+ ["127.0.0.1:3434"]=>
+ array(1) {
+ [0]=>
+ string(%d) "you want it, you get it"
+ }
+}
+client_id=[%s]: Stat key=[numeric array]
+array(1) {
+ ["127.0.0.1:3434"]=>
+ array(3) {
+ [-1]=>
+ string(3) "one"
+ [0]=>
+ string(3) "two"
+ [1]=>
+ string(5) "three"
+ }
+}
+client_id=[%s]: Client quit
+Done
diff --git a/tests/memcachedserver6.phpt b/tests/memcachedserver6.phpt
new file mode 100644
index 00000000..bae5b658
--- /dev/null
+++ b/tests/memcachedserver6.phpt
@@ -0,0 +1,119 @@
+--TEST--
+MemcachedServer
+--SKIPIF--
+
+--FILE--
+setOption(Memcached::OPT_BINARY_PROTOCOL, true);
+$cache->setOption(Memcached::OPT_COMPRESSION, false);
+$cache->addServer('[::1]', 3434);
+
+$cache->add("add_key", "hello", 500);
+$cache->append("append_key", "world");
+$cache->prepend("prepend_key", "world");
+
+$cache->increment("incr", 2, 1, 500);
+$cache->decrement("decr", 2, 1, 500);
+
+$cache->delete("delete_k");
+$cache->flush(1);
+
+var_dump($cache->get('get_this'));
+
+$cache->set ('set_key', 'value 1', 100);
+$cache->replace ('replace_key', 'value 2', 200);
+
+var_dump($cache->getVersion());
+var_dump($cache->getStats());
+var_dump($cache->getStats("empty"));
+var_dump($cache->getStats("foobar"));
+var_dump($cache->getStats("scalar"));
+var_dump($cache->getStats("numeric array"));
+
+$cache->quit();
+usleep(50000);
+
+memcached_server_stop($server);
+?>
+Done
+--EXPECTF--
+Listening on [::1]:3434
+Incoming connection from [::1]:%s
+Incoming connection from [::1]:%s
+client_id=[%s]: Add key=[add_key], value=[hello], flags=[0], expiration=[500]
+client_id=[%s]: Append key=[append_key], value=[world], cas=[0]
+client_id=[%s]: Prepend key=[prepend_key], value=[world], cas=[0]
+client_id=[%s]: Incrementing key=[incr], delta=[2], initial=[1], expiration=[500]
+client_id=[%s]: Decrementing key=[decr], delta=[2], initial=[1], expiration=[500]
+client_id=[%s]: Delete key=[delete_k], cas=[0]
+client_id=[%s]: Flush when=[1]
+client_id=[%s]: Get key=[get_this]
+client_id=[%s]: Noop
+string(20) "Hello to you client!"
+client_id=[%s]: Set key=[set_key], value=[value 1], flags=[0], expiration=[100], cas=[0]
+client_id=[%s]: Replace key=[replace_key], value=[value 2], flags=[0], expiration=[200], cas=[0]
+client_id=[%s]: Version
+array(1) {
+ ["[::1]:3434"]=>
+ string(5) "1.1.1"
+}
+client_id=[%s]: Stat key=[]
+array(1) {
+ ["[::1]:3434"]=>
+ array(2) {
+ ["key"]=>
+ string(0) ""
+ ["foo"]=>
+ string(3) "bar"
+ }
+}
+client_id=[%s]: Stat key=[empty]
+array(0) {
+}
+client_id=[%s]: Stat key=[foobar]
+array(1) {
+ ["[::1]:3434"]=>
+ array(2) {
+ ["key"]=>
+ string(6) "foobar"
+ ["foo"]=>
+ string(3) "bar"
+ }
+}
+client_id=[%s]: Stat key=[scalar]
+array(1) {
+ ["[::1]:3434"]=>
+ array(1) {
+ [0]=>
+ string(%d) "you want it, you get it"
+ }
+}
+client_id=[%s]: Stat key=[numeric array]
+array(1) {
+ ["[::1]:3434"]=>
+ array(3) {
+ [-1]=>
+ string(3) "one"
+ [0]=>
+ string(3) "two"
+ [1]=>
+ string(5) "three"
+ }
+}
+client_id=[%s]: Client quit
+Done
diff --git a/tests/server.inc b/tests/server.inc
new file mode 100644
index 00000000..9678f043
--- /dev/null
+++ b/tests/server.inc
@@ -0,0 +1,82 @@
+ STDIN,
+ 1 => STDOUT,
+ 2 => STDERR,
+ );
+
+ $cmd = "{$php_executable} {$php_args} {$code} {$host}:{$port} ";
+ if (substr(PHP_OS, 0, 3) == 'WIN') {
+ $cmd = "{$php_executable} {$php_args} {$code} {$host}:{$port} ";
+
+ $handle = proc_open(addslashes($cmd), $descriptorspec, $pipes, __DIR__, NULL, array("bypass_shell" => true, "suppress_errors" => true));
+ } else {
+ $cmd = "exec {$cmd} 2>/dev/null";
+
+ $handle = proc_open($cmd, $descriptorspec, $pipes, __DIR__);
+ }
+
+ // note: even when server prints 'Listening on localhost:8964...Press Ctrl-C to quit.'
+ // it might not be listening yet...need to wait until fsockopen() call returns
+ $error = "Unable to connect to server\n";
+ for ($i=0; $i < getenv("VALGRIND") ? 1000 : 60; $i++) {
+ usleep(50000); // 50ms per try
+ $status = proc_get_status($handle);
+ $fp = @fsockopen($host, $port);
+ // Failure, the server is no longer running
+ if (!($status && $status['running'])) {
+ $error = "Server is not running {$status['command']}\n";
+ break;
+ }
+ // Success, Connected to servers
+ if ($fp) {
+ $error = '';
+ break;
+ }
+ }
+
+ if ($fp) {
+ fclose($fp);
+ }
+
+ if ($error) {
+ echo $error;
+ proc_terminate($handle);
+ proc_close($handle);
+ exit(1);
+ }
+
+ register_shutdown_function(
+ function($handle) {
+ if (is_resource($handle)) {
+ proc_terminate($handle);
+ proc_close($handle);
+ }
+ },
+ $handle
+ );
+
+ return $handle;
+}
+
+function memcached_server_stop($handle) {
+ $success = FALSE;
+ if ($handle) {
+ proc_terminate($handle);
+ /* Wait for server to shutdown */
+ for ($i = 0; $i < 60; $i++) {
+ $status = proc_get_status($handle);
+ if (!($status && $status['running'])) {
+ $success = TRUE;
+ break;
+ }
+ usleep(50000);
+ }
+ proc_close($handle);
+ }
+ return $success;
+}
diff --git a/tests/server.php b/tests/server.php
new file mode 100644
index 00000000..9a50eb06
--- /dev/null
+++ b/tests/server.php
@@ -0,0 +1,111 @@
+on (Memcached::ON_CONNECT,
+ function ($remote_addr) {
+ echo "Incoming connection from {$remote_addr}" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_ADD,
+ function ($client_id, $key, $value, $flags, $expiration, &$cas) {
+ echo "client_id=[$client_id]: Add key=[$key], value=[$value], flags=[$flags], expiration=[$expiration]" . PHP_EOL;
+ $cas = 15;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_APPEND,
+ function ($client_id, $key, $value, $cas, &$result_cas) {
+ echo "client_id=[$client_id]: Append key=[$key], value=[$value], cas=[$cas]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_PREPEND,
+ function ($client_id, $key, $value, $cas, &$result_cas) {
+ echo "client_id=[$client_id]: Prepend key=[$key], value=[$value], cas=[$cas]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_INCREMENT,
+ function ($client_id, $key, $delta, $initial, $expiration, &$result, &$result_cas) {
+ echo "client_id=[$client_id]: Incrementing key=[$key], delta=[$delta], initial=[$initial], expiration=[$expiration]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_DECREMENT,
+ function ($client_id, $key, $delta, $initial, $expiration, &$result, &$result_cas) {
+ echo "client_id=[$client_id]: Decrementing key=[$key], delta=[$delta], initial=[$initial], expiration=[$expiration]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_DELETE,
+ function ($client_id, $key, $cas) {
+ echo "client_id=[$client_id]: Delete key=[$key], cas=[$cas]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_FLUSH,
+ function ($client_id, $when) {
+ echo "client_id=[$client_id]: Flush when=[$when]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_GET,
+ function ($client_id, $key, &$value, &$flags, &$cas) {
+ echo "client_id=[$client_id]: Get key=[$key]" . PHP_EOL;
+ $value = "Hello to you client!";
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_NOOP,
+ function ($client_id) {
+ echo "client_id=[$client_id]: Noop" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_REPLACE,
+ function ($client_id, $key, $value, $flags, $expiration, $cas, &$result_cas) {
+ echo "client_id=[$client_id]: Replace key=[$key], value=[$value], flags=[$flags], expiration=[$expiration], cas=[$cas]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_SET,
+ function ($client_id, $key, $value, $flags, $expiration, $cas, &$result_cas) {
+ echo "client_id=[$client_id]: Set key=[$key], value=[$value], flags=[$flags], expiration=[$expiration], cas=[$cas]" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_STAT,
+ function ($client_id, $key, array &$values) {
+ echo "client_id=[$client_id]: Stat key=[$key]" . PHP_EOL;
+
+ if ($key === "scalar") {
+ $values = "you want it, you get it";
+ } elseif ($key === "numeric array") {
+ $values = [-1 => "one", "two", "three"];
+ } elseif ($key === "empty") {
+ $values = [];
+ } else {
+ $values["key"] = $key;
+ $values["foo"] = "bar";
+ }
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_VERSION,
+ function ($client_id, &$value) {
+ echo "client_id=[$client_id]: Version" . PHP_EOL;
+ $value = "1.1.1";
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$server->on (Memcached::ON_QUIT,
+ function ($client_id) {
+ echo "client_id=[$client_id]: Client quit" . PHP_EOL;
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+$addr = ($_SERVER['argv'][1] ?? "127.0.0.1:3434");
+echo "Listening on $addr" . PHP_EOL;
+$server->run($addr);