summaryrefslogtreecommitdiffstats
path: root/474.patch
diff options
context:
space:
mode:
Diffstat (limited to '474.patch')
-rw-r--r--474.patch1402
1 files changed, 1402 insertions, 0 deletions
diff --git a/474.patch b/474.patch
new file mode 100644
index 0000000..6f109e6
--- /dev/null
+++ b/474.patch
@@ -0,0 +1,1402 @@
+From 2096982a8022527e2820ab9cc78a238cc07f4289 Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Tue, 19 Jan 2021 16:16:30 +0100
+Subject: [PATCH 01/16] fix MemcachedServer
+
+related: #418, m6w6/libmemcached#94
+---
+ php_memcached.c | 3 ++
+ php_memcached_server.c | 80 +++++++++++++++++++++++------------
+ server-example/run-server.php | 2 +-
+ 3 files changed, 56 insertions(+), 29 deletions(-)
+
+diff --git a/php_memcached.c b/php_memcached.c
+index 1e218a00..adf2348d 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;
+ }
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index 870209c1..e816b90d 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -17,13 +17,16 @@
+ #include "php_memcached.h"
+ #include "php_memcached_private.h"
+ #include "php_memcached_server.h"
++#include "php_network.h"
+
+ #include <event2/listener.h>
+
+-#undef NDEBUG
+-#undef _NDEBUG
+ #include <assert.h>
+
++#if HAVE_ARPA_INET_H
++# include <arpa/inet.h>
++#endif
++
+ #define MEMC_GET_CB(cb_type) (MEMC_SERVER_G(callbacks)[cb_type])
+ #define MEMC_HAS_CB(cb_type) (MEMC_GET_CB(cb_type).fci.size > 0)
+
+@@ -58,9 +61,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 +76,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 +99,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(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zkey);
+@@ -142,6 +146,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(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zkey);
+@@ -198,11 +203,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(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zkey);
+@@ -322,6 +329,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(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zkey);
+@@ -436,11 +450,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(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zkey);
+@@ -504,6 +519,7 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
+
+ ZVAL_STRINGL(&zkey, key, key_len);
+ ZVAL_NULL(&zbody);
++ ZVAL_MAKE_REF(&zbody);
+
+ ZVAL_COPY(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zkey);
+@@ -584,17 +600,27 @@ void s_handle_memcached_event (evutil_socket_t fd, short what, void *arg)
+ zval zremoteip, zremoteport;
+ zval params[2];
+ protocol_binary_response_status retval;
+-
+- struct sockaddr_in addr_in;
+- socklen_t addr_in_len = sizeof(addr_in);
+-
+- 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));
++ struct sockaddr_storage ss;
++ socklen_t ss_len = sizeof(ss);
++
++ ZVAL_NULL(&zremoteip);
++ ZVAL_NULL(&zremoteport);
++
++ if (getpeername (fd, (struct sockaddr *) &ss, &ss_len) == 0) {
++ char addr_buf[0x100];
++
++ switch (ss.ss_family) {
++ case AF_INET6:
++ ZVAL_STRING(&zremoteip, inet_ntop(ss.ss_family, &((struct sockaddr_in6 *) &ss)->sin6_addr, addr_buf, sizeof(addr_buf)));
++ ZVAL_LONG(&zremoteport, ntohs(((struct sockaddr_in6 *) &ss)->sin6_port));
++ break;
++ case AF_INET:
++ ZVAL_STRING(&zremoteip, inet_ntop(ss.ss_family, &((struct sockaddr_in *) &ss)->sin_addr, addr_buf, sizeof(addr_buf)));
++ ZVAL_LONG(&zremoteport, ntohs(((struct sockaddr_in *) &ss)->sin_port));
++ break;
++ }
+ } else {
+ php_error_docref(NULL, E_WARNING, "getpeername failed: %s", strerror (errno));
+- ZVAL_NULL(&zremoteip);
+- ZVAL_NULL(&zremoteport);
+ }
+
+ ZVAL_COPY(&params[0], &zremoteip);
+@@ -714,22 +740,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 +794,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;
+From 277ce3b20ec9753b994d63f4e99c2506ca2cdd5f Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Tue, 19 Jan 2021 16:32:52 +0100
+Subject: [PATCH 02/16] use php_network_get_peer_name
+
+---
+ php_memcached_server.c | 38 ++++++++++-------------------------
+ server-example/run-server.php | 4 ++--
+ 2 files changed, 13 insertions(+), 29 deletions(-)
+
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index e816b90d..fee8d28d 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -597,41 +597,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_storage ss;
+- socklen_t ss_len = sizeof(ss);
+-
+- ZVAL_NULL(&zremoteip);
+- ZVAL_NULL(&zremoteport);
+-
+- if (getpeername (fd, (struct sockaddr *) &ss, &ss_len) == 0) {
+- char addr_buf[0x100];
+-
+- switch (ss.ss_family) {
+- case AF_INET6:
+- ZVAL_STRING(&zremoteip, inet_ntop(ss.ss_family, &((struct sockaddr_in6 *) &ss)->sin6_addr, addr_buf, sizeof(addr_buf)));
+- ZVAL_LONG(&zremoteport, ntohs(((struct sockaddr_in6 *) &ss)->sin6_port));
+- break;
+- case AF_INET:
+- ZVAL_STRING(&zremoteip, inet_ntop(ss.ss_family, &((struct sockaddr_in *) &ss)->sin_addr, addr_buf, sizeof(addr_buf)));
+- ZVAL_LONG(&zremoteport, ntohs(((struct sockaddr_in *) &ss)->sin_port));
+- break;
+- }
++
++ ZVAL_NULL(&zremoteaddr);
++
++ 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_COPY(&params[0], &zremoteip);
+- ZVAL_COPY(&params[1], &zremoteport);
++ ZVAL_COPY(&params[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(&params[0]);
+- zval_ptr_dtor(&params[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);
+From f7731bfe65c9f9312e11018035e7a560e7dde702 Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Tue, 19 Jan 2021 16:34:08 +0100
+Subject: [PATCH 03/16] remove unused include again
+
+---
+ php_memcached_server.c | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index fee8d28d..ce93a2bf 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -23,10 +23,6 @@
+
+ #include <assert.h>
+
+-#if HAVE_ARPA_INET_H
+-# include <arpa/inet.h>
+-#endif
+-
+ #define MEMC_GET_CB(cb_type) (MEMC_SERVER_G(callbacks)[cb_type])
+ #define MEMC_HAS_CB(cb_type) (MEMC_GET_CB(cb_type).fci.size > 0)
+
+
+From c7a2084ac958e93a094866889b56a7b37698ef40 Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Tue, 19 Jan 2021 16:57:39 +0100
+Subject: [PATCH 04/16] as per
+ https://github.com/php-memcached-dev/php-memcached/pull/474#issuecomment-762934191
+
+---
+ php_memcached_server.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index ce93a2bf..1f28d936 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -299,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(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zwhen);
+
+From 09d0f27b6298e525e0bae45db8d915fa1d140466 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Wed, 20 Jan 2021 10:54:13 +0100
+Subject: [PATCH 05/16] add 1 test for MemcachedServer
+
+---
+ tests/memcachedserver.phpt | 58 +++++++++++++++++++++++
+ tests/server.inc | 78 +++++++++++++++++++++++++++++++
+ tests/server.php | 94 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 230 insertions(+)
+ create mode 100644 tests/memcachedserver.phpt
+ create mode 100644 tests/server.inc
+ create mode 100644 tests/server.php
+
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+new file mode 100644
+index 00000000..54b7fe45
+--- /dev/null
++++ b/tests/memcachedserver.phpt
+@@ -0,0 +1,58 @@
++--TEST--
++Memcached::get() with cache callback
++--SKIPIF--
++<?php
++if (!extension_loaded("memcached")) {
++ die("skip memcached is not loaded\n");
++}
++?>
++--FILE--
++<?php
++include __DIR__ . '/server.inc';
++$server = memcached_server_start();
++
++$cache = new Memcached();
++$cache->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->getStats());
++
++$cache->quit();
++
++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]
++bool(false)
++Done
+diff --git a/tests/server.inc b/tests/server.inc
+new file mode 100644
+index 00000000..96cc942f
+--- /dev/null
++++ b/tests/server.inc
+@@ -0,0 +1,78 @@
++<?php
++function memcached_server_start($code = 'server.php', $host = "127.0.0.1", $port = 3434) {
++ $php_executable = getenv('TEST_PHP_EXECUTABLE') ?? PHP_BINARY;
++ $php_args = getenv('TEST_PHP_ARGS') ?? '';
++
++ $descriptorspec = array(
++ 0 => 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 < 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\n";
++ break;
++ }
++ // Success, Connected to servers
++ if ($fp) {
++ $error = '';
++ break;
++ }
++ }
++
++ if ($fp) {
++ fclose($fp);
++ }
++
++ if ($error) {
++ echo $error;
++ proc_terminate($handle);
++ exit(1);
++ }
++
++ register_shutdown_function(
++ function($handle) {
++ proc_terminate($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);
++ }
++ }
++ return $success;
++}
++
+diff --git a/tests/server.php b/tests/server.php
+new file mode 100644
+index 00000000..674f91ac
+--- /dev/null
++++ b/tests/server.php
+@@ -0,0 +1,94 @@
++<?php
++
++$server = new MemcachedServer();
++
++$server->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, &$value) {
++ echo "client_id=[$client_id]: Stat key=[$key]" . PHP_EOL;
++ $value = "Stat reply";
++ 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);
+
+From 2b32140d08acb64bc087de37ea17c8ba2d99092a Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Wed, 20 Jan 2021 12:08:53 +0100
+Subject: [PATCH 06/16] minor fix for version handler
+
+---
+ php_memcached_server.c | 1 +
+ tests/memcachedserver.phpt | 5 +++--
+ tests/server.php | 7 +++++++
+ 3 files changed, 11 insertions(+), 2 deletions(-)
+
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index 1f28d936..57cb749b 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -560,6 +560,7 @@ 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(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zversion);
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+index 54b7fe45..6d4abb9d 100644
+--- a/tests/memcachedserver.phpt
++++ b/tests/memcachedserver.phpt
+@@ -31,7 +31,8 @@ var_dump($cache->get('get_this'));
+ $cache->set ('set_key', 'value 1', 100);
+ $cache->replace ('replace_key', 'value 2', 200);
+
+-var_dump($cache->getStats());
++// TODO var_dump($cache->getVersion());
++// TODO var_dump($cache->getStats());
+
+ $cache->quit();
+
+@@ -54,5 +55,5 @@ 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]
+-bool(false)
++client_id=[%s]: Client quit
+ Done
+diff --git a/tests/server.php b/tests/server.php
+index 674f91ac..c39b04e8 100644
+--- a/tests/server.php
++++ b/tests/server.php
+@@ -79,6 +79,13 @@ function ($client_id, $key, $value, $flags, $expiration, $cas, &$result_cas) {
+ $server->on (Memcached::ON_STAT,
+ function ($client_id, $key, &$value) {
+ echo "client_id=[$client_id]: Stat key=[$key]" . PHP_EOL;
++ $value = "Stat reply for $key";
++ return Memcached::RESPONSE_SUCCESS;
++ });
++
++$server->on (Memcached::ON_VERSION,
++ function ($client_id, &$value) {
++ echo "client_id=[$client_id]: Version" . PHP_EOL;
+ $value = "Stat reply";
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+From 7562317131d7ef75cf35abf982ca3efa44c5e495 Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Wed, 20 Jan 2021 16:43:57 +0100
+Subject: [PATCH 07/16] handle empty STATS key
+
+---
+ php_memcached_server.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index 57cb749b..d1e903a1 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -514,7 +514,11 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
+
+ MEMC_MAKE_ZVAL_COOKIE(zcookie, cookie);
+
+- ZVAL_STRINGL(&zkey, key, key_len);
++ if (key && key_len) {
++ ZVAL_STRINGL(&zkey, key, key_len);
++ } else {
++ ZVAL_NULL(&zkey);
++ }
+ ZVAL_NULL(&zbody);
+ ZVAL_MAKE_REF(&zbody);
+
+@@ -566,7 +570,6 @@ protocol_binary_response_status s_version_handler (const void *cookie,
+ ZVAL_COPY(&params[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);
+
+From a3f9b928b65abc3daa743c709b6404e038b5af7b Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Wed, 20 Jan 2021 16:51:34 +0100
+Subject: [PATCH 08/16] fix running the server from command line
+
+---
+ tests/server.inc | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/tests/server.inc b/tests/server.inc
+index 96cc942f..5700a9d2 100644
+--- a/tests/server.inc
++++ b/tests/server.inc
+@@ -1,7 +1,7 @@
+ <?php
+ function memcached_server_start($code = 'server.php', $host = "127.0.0.1", $port = 3434) {
+- $php_executable = getenv('TEST_PHP_EXECUTABLE') ?? PHP_BINARY;
+- $php_args = getenv('TEST_PHP_ARGS') ?? '';
++ $php_executable = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
++ $php_args = getenv('TEST_PHP_ARGS') ?: '';
+
+ $descriptorspec = array(
+ 0 => STDIN,
+@@ -29,7 +29,7 @@ function memcached_server_start($code = 'server.php', $host = "127.0.0.1", $port
+ $fp = @fsockopen($host, $port);
+ // Failure, the server is no longer running
+ if (!($status && $status['running'])) {
+- $error = "Server is not running\n";
++ $error = "Server is not running {$status['command']}\n";
+ break;
+ }
+ // Success, Connected to servers
+@@ -75,4 +75,3 @@ function memcached_server_stop($handle) {
+ }
+ return $success;
+ }
+-
+
+From c18e8bb36ff634f1dc9f6927fd98b1a20029f50f Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Wed, 20 Jan 2021 16:52:31 +0100
+Subject: [PATCH 09/16] add stats/version tests
+
+---
+ tests/memcachedserver.phpt | 31 ++++++++++++++++++++++++++++---
+ tests/server.php | 2 +-
+ 2 files changed, 29 insertions(+), 4 deletions(-)
+
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+index 6d4abb9d..82601e12 100644
+--- a/tests/memcachedserver.phpt
++++ b/tests/memcachedserver.phpt
+@@ -1,10 +1,13 @@
+ --TEST--
+-Memcached::get() with cache callback
++MemcachedServer
+ --SKIPIF--
+ <?php
+ if (!extension_loaded("memcached")) {
+ die("skip memcached is not loaded\n");
+ }
++if (!class_exists("MemcachedServer")) {
++ die("skip memcached not built with libmemcachedprotocol support\n");
++}
+ ?>
+ --FILE--
+ <?php
+@@ -31,8 +34,9 @@ var_dump($cache->get('get_this'));
+ $cache->set ('set_key', 'value 1', 100);
+ $cache->replace ('replace_key', 'value 2', 200);
+
+-// TODO var_dump($cache->getVersion());
+-// TODO var_dump($cache->getStats());
++var_dump($cache->getVersion());
++var_dump($cache->getStats());
++var_dump($cache->getStats("foobar"));
+
+ $cache->quit();
+
+@@ -55,5 +59,26 @@ 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(1) {
++ [""]=>
++ string(15) "Stat reply for "
++ }
++}
++client_id=[%s]: Stat key=[foobar]
++array(1) {
++ ["127.0.0.1:3434"]=>
++ array(1) {
++ ["foobar"]=>
++ string(21) "Stat reply for foobar"
++ }
++}
+ client_id=[%s]: Client quit
+ Done
+diff --git a/tests/server.php b/tests/server.php
+index c39b04e8..b6ab1cf6 100644
+--- a/tests/server.php
++++ b/tests/server.php
+@@ -86,7 +86,7 @@ function ($client_id, $key, &$value) {
+ $server->on (Memcached::ON_VERSION,
+ function ($client_id, &$value) {
+ echo "client_id=[$client_id]: Version" . PHP_EOL;
+- $value = "Stat reply";
++ $value = "1.1.1";
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+
+From 4cb3b015e05018fccc17d4e40e065ab307e35f22 Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Wed, 20 Jan 2021 17:42:58 +0100
+Subject: [PATCH 10/16] expect an array as STATS value and reply foreach
+ key/value
+
+---
+ php_memcached_server.c | 36 +++++++++++++++++++++++++-----------
+ tests/memcachedserver.phpt | 16 ++++++++++------
+ tests/server.php | 7 +++++--
+ 3 files changed, 40 insertions(+), 19 deletions(-)
+
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index d1e903a1..a166051e 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -506,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;
+@@ -519,24 +519,38 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
+ } else {
+ ZVAL_NULL(&zkey);
+ }
+- ZVAL_NULL(&zbody);
+- ZVAL_MAKE_REF(&zbody);
++ ZVAL_NULL(&zstats);
++ ZVAL_MAKE_REF(&zstats);
+
+ ZVAL_COPY(&params[0], &zcookie);
+ ZVAL_COPY(&params[1], &zkey);
+- ZVAL_COPY(&params[2], &zbody);
++ ZVAL_COPY(&params[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) {
++ if (Z_ISNULL(zstats)) {
+ retval = response_handler(cookie, NULL, 0, NULL, 0);
+- }
+- else {
+- if (Z_TYPE(zbody) != IS_STRING) {
+- convert_to_string(&zbody);
++ } else {
++ zval *zarray = &zstats;
++ zend_string *key;
++ zval *val;
++
++ ZVAL_DEREF(zarray);
++ if (Z_TYPE_P(zarray) != IS_ARRAY) {
++ convert_to_array(zarray);
++ }
++
++ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zarray), key, val)
++ {
++ zend_string *val_str = zval_get_string(val);
++ retval = response_handler(cookie, key->val, key->len, val_str->val, val_str->len);
++ if (retval != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
++ break;
++ }
++ zend_string_release(val_str);
+ }
+- retval = response_handler(cookie, key, key_len, Z_STRVAL(zbody), (uint32_t) Z_STRLEN(zbody));
++ ZEND_HASH_FOREACH_END();
+ }
+ }
+
+@@ -545,7 +559,7 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
+ zval_ptr_dtor(&params[2]);
+ zval_ptr_dtor (&zcookie);
+ zval_ptr_dtor (&zkey);
+- zval_ptr_dtor (&zbody);
++ zval_ptr_dtor (&zstats);
+ return retval;
+ }
+
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+index 82601e12..d47dd73b 100644
+--- a/tests/memcachedserver.phpt
++++ b/tests/memcachedserver.phpt
+@@ -67,17 +67,21 @@ array(1) {
+ client_id=[%s]: Stat key=[]
+ array(1) {
+ ["127.0.0.1:3434"]=>
+- array(1) {
+- [""]=>
+- string(15) "Stat reply for "
++ array(2) {
++ ["key"]=>
++ string(0) ""
++ ["foo"]=>
++ string(3) "bar"
+ }
+ }
+ client_id=[%s]: Stat key=[foobar]
+ array(1) {
+ ["127.0.0.1:3434"]=>
+- array(1) {
+- ["foobar"]=>
+- string(21) "Stat reply for foobar"
++ array(2) {
++ ["key"]=>
++ string(6) "foobar"
++ ["foo"]=>
++ string(3) "bar"
+ }
+ }
+ client_id=[%s]: Client quit
+diff --git a/tests/server.php b/tests/server.php
+index b6ab1cf6..f5ff2f6a 100644
+--- a/tests/server.php
++++ b/tests/server.php
+@@ -77,9 +77,12 @@ function ($client_id, $key, $value, $flags, $expiration, $cas, &$result_cas) {
+ });
+
+ $server->on (Memcached::ON_STAT,
+- function ($client_id, $key, &$value) {
++ function ($client_id, $key, array &$values = null) {
+ echo "client_id=[$client_id]: Stat key=[$key]" . PHP_EOL;
+- $value = "Stat reply for $key";
++ $values = [
++ "key" => $key,
++ "foo" => "bar",
++ ];
+ return Memcached::RESPONSE_SUCCESS;
+ });
+
+
+From 3de2b4d7e27c61be0e4f0017b0b5528d0500b56c Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Wed, 20 Jan 2021 18:00:52 +0100
+Subject: [PATCH 11/16] valgrind
+
+---
+ php_memcached.c | 5 ++++-
+ tests/server.inc | 9 +++++++--
+ 2 files changed, 11 insertions(+), 3 deletions(-)
+
+diff --git a/php_memcached.c b/php_memcached.c
+index adf2348d..8f464283 100644
+--- a/php_memcached.c
++++ b/php_memcached.c
+@@ -3907,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;
+@@ -3926,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/tests/server.inc b/tests/server.inc
+index 5700a9d2..9678f043 100644
+--- a/tests/server.inc
++++ b/tests/server.inc
+@@ -23,7 +23,7 @@ function memcached_server_start($code = 'server.php', $host = "127.0.0.1", $port
+ // 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 < 60; $i++) {
++ for ($i=0; $i < getenv("VALGRIND") ? 1000 : 60; $i++) {
+ usleep(50000); // 50ms per try
+ $status = proc_get_status($handle);
+ $fp = @fsockopen($host, $port);
+@@ -46,12 +46,16 @@ function memcached_server_start($code = 'server.php', $host = "127.0.0.1", $port
+ if ($error) {
+ echo $error;
+ proc_terminate($handle);
++ proc_close($handle);
+ exit(1);
+ }
+
+ register_shutdown_function(
+ function($handle) {
+- proc_terminate($handle);
++ if (is_resource($handle)) {
++ proc_terminate($handle);
++ proc_close($handle);
++ }
+ },
+ $handle
+ );
+@@ -72,6 +76,7 @@ function memcached_server_stop($handle) {
+ }
+ usleep(50000);
+ }
++ proc_close($handle);
+ }
+ return $success;
+ }
+
+From cebd8513bd1cb0a5a67642072cc4dad1907a2e5e Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Mon, 25 Jan 2021 14:45:03 +0100
+Subject: [PATCH 13/16] implement stats callback suggestions by Remi
+
+---
+ php_memcached_server.c | 45 +++++++++++++++++++++-----------------
+ tests/memcachedserver.phpt | 22 +++++++++++++++++++
+ tests/server.php | 19 ++++++++++------
+ 3 files changed, 59 insertions(+), 27 deletions(-)
+
+diff --git a/php_memcached_server.c b/php_memcached_server.c
+index a166051e..24c328f8 100644
+--- a/php_memcached_server.c
++++ b/php_memcached_server.c
+@@ -519,7 +519,7 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
+ } else {
+ ZVAL_NULL(&zkey);
+ }
+- ZVAL_NULL(&zstats);
++ array_init(&zstats);
+ ZVAL_MAKE_REF(&zstats);
+
+ ZVAL_COPY(&params[0], &zcookie);
+@@ -529,29 +529,34 @@ protocol_binary_response_status s_stat_handler (const void *cookie, const void *
+ retval = s_invoke_php_callback (&MEMC_GET_CB(MEMC_SERVER_ON_STAT), params, 3);
+
+ if (retval == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+- if (Z_ISNULL(zstats)) {
+- retval = response_handler(cookie, NULL, 0, NULL, 0);
+- } else {
+- zval *zarray = &zstats;
+- zend_string *key;
+- zval *val;
+-
+- ZVAL_DEREF(zarray);
+- if (Z_TYPE_P(zarray) != IS_ARRAY) {
+- convert_to_array(zarray);
+- }
++ 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);
++ }
+
+- ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zarray), key, val)
+- {
+- zend_string *val_str = zval_get_string(val);
++ 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);
+- if (retval != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+- break;
+- }
+- zend_string_release(val_str);
++ } 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;
+ }
+- ZEND_HASH_FOREACH_END();
+ }
++ ZEND_HASH_FOREACH_END();
+ }
+
+ zval_ptr_dtor(&params[0]);
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+index d47dd73b..8d6bf222 100644
+--- a/tests/memcachedserver.phpt
++++ b/tests/memcachedserver.phpt
+@@ -37,6 +37,8 @@ $cache->replace ('replace_key', 'value 2', 200);
+ var_dump($cache->getVersion());
+ var_dump($cache->getStats());
+ var_dump($cache->getStats("foobar"));
++var_dump($cache->getStats("scalar"));
++var_dump($cache->getStats("numeric array"));
+
+ $cache->quit();
+
+@@ -84,5 +86,25 @@ array(1) {
+ 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/server.php b/tests/server.php
+index f5ff2f6a..bce4c0bf 100644
+--- a/tests/server.php
++++ b/tests/server.php
+@@ -77,13 +77,18 @@ function ($client_id, $key, $value, $flags, $expiration, $cas, &$result_cas) {
+ });
+
+ $server->on (Memcached::ON_STAT,
+- function ($client_id, $key, array &$values = null) {
+- echo "client_id=[$client_id]: Stat key=[$key]" . PHP_EOL;
+- $values = [
+- "key" => $key,
+- "foo" => "bar",
+- ];
+- return Memcached::RESPONSE_SUCCESS;
++ 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"];
++ } else {
++ $values["key"] = $key;
++ $values["foo"] = "bar";
++ }
++ return Memcached::RESPONSE_SUCCESS;
+ });
+
+ $server->on (Memcached::ON_VERSION,
+
+From af7ffcf0a11d653e427d3b2dacd4fdef1623daa6 Mon Sep 17 00:00:00 2001
+From: Michael Wallner <mike@php.net>
+Date: Mon, 25 Jan 2021 14:54:24 +0100
+Subject: [PATCH 14/16] skip test with libmemcached < 1.1.0
+
+---
+ tests/memcachedserver.phpt | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+index 8d6bf222..145acfba 100644
+--- a/tests/memcachedserver.phpt
++++ b/tests/memcachedserver.phpt
+@@ -8,6 +8,10 @@ if (!extension_loaded("memcached")) {
+ if (!class_exists("MemcachedServer")) {
+ die("skip memcached not built with libmemcachedprotocol support\n");
+ }
++
++if (Memcached::LIBMEMCACHED_VERSION_HEX < 0x1001000) {
++ die("skip needs at least libmemcached 1.1.0\n");
++}
+ ?>
+ --FILE--
+ <?php
+
+From 1134c661f092f4a54af73b30fa1d458ab4eca637 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Tue, 26 Jan 2021 11:38:12 +0100
+Subject: [PATCH 15/16] add IPv6 test
+
+---
+ tests/memcachedserver.phpt | 4 ++
+ tests/memcachedserver6.phpt | 114 ++++++++++++++++++++++++++++++++++++
+ tests/server.php | 2 +
+ 3 files changed, 120 insertions(+)
+ create mode 100644 tests/memcachedserver6.phpt
+
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+index 8d6bf222..c4fdd7c6 100644
+--- a/tests/memcachedserver.phpt
++++ b/tests/memcachedserver.phpt
+@@ -36,6 +36,7 @@ $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"));
+@@ -76,6 +77,9 @@ array(1) {
+ string(3) "bar"
+ }
+ }
++client_id=[%s]: Stat key=[empty]
++array(0) {
++}
+ client_id=[%s]: Stat key=[foobar]
+ array(1) {
+ ["127.0.0.1:3434"]=>
+diff --git a/tests/memcachedserver6.phpt b/tests/memcachedserver6.phpt
+new file mode 100644
+index 00000000..8ae0b362
+--- /dev/null
++++ b/tests/memcachedserver6.phpt
+@@ -0,0 +1,114 @@
++--TEST--
++MemcachedServer
++--SKIPIF--
++<?php
++if (!extension_loaded("memcached")) {
++ die("skip memcached is not loaded\n");
++}
++if (!class_exists("MemcachedServer")) {
++ die("skip memcached not built with libmemcachedprotocol support\n");
++}
++?>
++--FILE--
++<?php
++include __DIR__ . '/server.inc';
++$server = memcached_server_start('server.php', '[::1]', 3434);
++
++$cache = new Memcached();
++$cache->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();
++
++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.php b/tests/server.php
+index bce4c0bf..9a50eb06 100644
+--- a/tests/server.php
++++ b/tests/server.php
+@@ -84,6 +84,8 @@ function ($client_id, $key, array &$values) {
+ $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";
+
+From abf4add8a1761a319e533197cf65b9b6abdc028a Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Tue, 26 Jan 2021 11:42:37 +0100
+Subject: [PATCH 16/16] small sleep to avoid loosing message from subprocess
+
+---
+ tests/memcachedserver.phpt | 1 +
+ tests/memcachedserver6.phpt | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/tests/memcachedserver.phpt b/tests/memcachedserver.phpt
+index c4fdd7c6..c46a4ccd 100644
+--- a/tests/memcachedserver.phpt
++++ b/tests/memcachedserver.phpt
+@@ -42,6 +42,7 @@ var_dump($cache->getStats("scalar"));
+ var_dump($cache->getStats("numeric array"));
+
+ $cache->quit();
++usleep(50000);
+
+ memcached_server_stop($server);
+ ?>
+diff --git a/tests/memcachedserver6.phpt b/tests/memcachedserver6.phpt
+index 8ae0b362..a2277b4a 100644
+--- a/tests/memcachedserver6.phpt
++++ b/tests/memcachedserver6.phpt
+@@ -42,6 +42,7 @@ var_dump($cache->getStats("scalar"));
+ var_dump($cache->getStats("numeric array"));
+
+ $cache->quit();
++usleep(50000);
+
+ memcached_server_stop($server);
+ ?>