LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

Java PHP共享Memcache数据

如果在应用中同时存在Java和PHP两套系统,并且需要共享数据,这种情况下MySQL是比较容易处理的,而Memcache则会面临两个问题,首先采用了基于一致性散列算法的分布式架构下需要Java和PHP使用同一种算法,已保证可以根据键找到对应的Memcache实例,另外就是数据存储格式上需要具有一致性,一般都用JSON格式序列化即可,但还需要保证存储时压缩算法和标志位使用的一致性,才能正确的处理压缩过的数据。

1. Java和PHP访问Memcache

对于PHP目前使用较为广泛的是php-memcached扩展,其使用的一致性散列算法为Ketama,是一种简单并且广泛支持的算法,其它大多语言中都有对此算法的支持(Python Perl Go等)。为此需要Java中使用支持这一算法的连接驱动,这里尝试使用了xmemcached

PHP通过php-memcached扩展访问Memcache

<?php
$mem = new Memcached();
$mem->setOption(Memcached::DISTRIBUTION_CONSISTENT, true);
$mem->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$mem->addServers(array(
array('127.0.0.1', 11211),
array('127.0.0.1', 11212),
array('127.0.0.1', 11213),
array('127.0.0.1', 11214),
));

Java通过xmemcached访问Memcache

try {
    XMemcachedClientBuilder builder = new XMemcachedClientBuilder();
    //注意这里需要开启cwNginxUpstreamConsistent方式,兼容libmemcached中的11211 hack
    builder.setSessionLocator(new KetamaMemcachedSessionLocator(true));
    client = builder.build();
    client.addServer("127.0.0.1", 11211);
    client.addServer("127.0.0.1", 11212);
    client.addServer("127.0.0.1", 11213);
    client.addServer("127.0.0.1", 11214);
    client.shutdown();
} catch (Exception ex) {
    ex.printStackTrace();
}

2. libmemcached 11211 hack

这里简单说明一下libmemcached中的11211 hack,其为了让不加端口号的情况与使用默认11211端口号时保持一致性,其在端口号为11211时会自动去掉:11211,为此在使用很多其它的ketama库时会发现散列结果不一致,其大多是因为这个逻辑导致的,为了保持兼容在使用时也做相同的处理即可,具体逻辑可以参考以下libmemcached的源代码。

if (list[host_index].port() == MEMCACHED_DEFAULT_PORT)
{   
  sort_host_length= snprintf(sort_host, sizeof(sort_host),
                             "%s-%u",
                             list[host_index]._hostname,
                             pointer_index - 1); 
}   
else
{   
  sort_host_length= snprintf(sort_host, sizeof(sort_host),
                             "%s:%u-%u",
                             list[host_index]._hostname,
                             (uint32_t)list[host_index].port(),
                             pointer_index - 1); 
} 

3. 带权重的一致性散列算法

在xmemcached中带权重的一致性散列算法的处理方法是将当前节点的虚节点个数(默认为160)直接乘以其权重,而在libmemcached中除了乘以了权重之外,还乘以了总节点个数/总权重值(好处是只要各节点权重比相同,散列的结果就是相同的)。为了保证散列结果的一致性,需要修改xmemcached的源代码(或者增加一个散列算法类型 AbstractMemcachedSessionLocator),其patch如下

diff --git a/src/main/java/net/rubyeye/xmemcached/impl/KetamaMemcachedSessionLocator.java b/src/main/java/net/rubyeye/xmemcached/impl/KetamaMemcachedSessionLocator.java
index 2e9548d..2c5a191 100644
--- a/src/main/java/net/rubyeye/xmemcached/impl/KetamaMemcachedSessionLocator.java
+++ b/src/main/java/net/rubyeye/xmemcached/impl/KetamaMemcachedSessionLocator.java
@@ -91,7 +91,12 @@ AbstractMemcachedSessionLocator {

        private final void buildMap(Collection<Session> list, HashAlgorithm alg) {
                TreeMap<Long, List<Session>> sessionMap = new TreeMap<Long, List<Session>>();
-
+               int totalWeight = 0;
+               for (Session session : list) {
+                       if (session instanceof MemcachedTCPSession) {
+                               totalWeight += ((MemcachedSession) session).getWeight();
+                       }
+               }
                for (Session session : list) {
                        String sockStr = null;
                        if (this.cwNginxUpstreamConsistent) {
@@ -117,7 +122,9 @@ AbstractMemcachedSessionLocator {
                         */
                        int numReps = NUM_REPS;
                        if (session instanceof MemcachedTCPSession) {
-                               numReps *= ((MemcachedSession) session).getWeight();
+                               int weight = ((MemcachedSession) session).getWeight();
+                               float pct = (float) weight / (float) totalWeight;
+                               numReps = (int) ((Math.floor((float)(pct * NUM_REPS / 4 * list.size() + 0.0000000001))) * 4);
                        }
                        if (alg == HashAlgorithm.KETAMA_HASH) {
                                for (int i = 0; i < numReps / 4; i++) {

4. 压缩算法和标志位的处理

在较早版本的php-memcached中使用的是zlib进行的数据压缩,新版本中默认使用fastlz,这里为了保证兼容性依然使用zlib进行数据压缩,同时Java中默认就可以支持zlib压缩。同时在php-memcached的不同版本中有两套标志位的用法,这里需要分别予以支持。

xmemcached中默认情况下进行数据的读写时会采用其自身的序列化和标志位的规则进行处理,但可以通过传递Transcoder自行定制对原始数据在存储和获取时的处理行为。具体的处理方法如下

//定义针对PHP的数据处理器
abstract class PHPTranscoder extends PrimitiveTypeTranscoder<String> {
}

//针对旧版本的php-memcached,由于在PHP5中使用了这个版本,所以叫做PHP5
class PHP5Transcoder extends PHPTranscoder {
   
    //使用压缩的最小长度
    final int COMPRESSION_THRESHOLD = 100;
    //压缩的标志位
    final int COMPRESSION_MASK = 1 << 1;

    //取出数据的处理
    public String decode(CachedData d) {
        byte[] data = d.getData();
        int flag = d.getFlag();
        //如果带有压缩标志位,则进行解压缩处理
        if ((flag & COMPRESSION_MASK) == COMPRESSION_MASK) {
            setCompressionMode(CompressionMode.ZIP);
            data = decompress(data);
        }
        return new String(data);
    }

    //存储数据的处理
    public CachedData encode(String o) {
        byte[] data = o.getBytes();
        int flag = 0;
        //如果数据超过了压缩的最小长度,则进行压缩,并更改标志位
        if (data.length > COMPRESSION_THRESHOLD) {
            setCompressionMode(CompressionMode.ZIP);
            data = compress(data);
            flag = COMPRESSION_MASK;
        }
        return new CachedData(flag, data);
    }
}

//针对新版本的php-memcached,由于在PHP7中使用了这个版本,所以叫做PHP7
class PHP7Transcoder extends PHPTranscoder {

    final int COMPRESSION_THRESHOLD = 2000;
    final int COMPRESSION_MASK = 3 << 4;

    public String decode(CachedData d) {
        byte[] data = d.getData();
        int flag = d.getFlag();
        if ((flag & COMPRESSION_MASK) == COMPRESSION_MASK) {
            setCompressionMode(CompressionMode.ZIP);
            //解压缩时需要处理size_t hack,存储时开头使用了一个size_t保存了压缩前数据的长度,为了支持fastlz
            byte[] realdata = new byte[data.length - 4];
            System.arraycopy(data, 4, realdata, 0, data.length - 4);
            data = decompress(realdata);
        }
        return new String(data);
    }

    public CachedData encode(String o) {
        byte[] realdata = o.getBytes();
        byte[] data = realdata;
        int flag = 0;
        if (realdata.length > COMPRESSION_THRESHOLD) {
            //上面提到的size_t hack,将长度转换为四个字节
            byte[] lenbytes = new byte[4];
            for (int i = 0; i < lenbytes.length; i++) {
                lenbytes[i] = (byte) ((realdata.length >> (8 * i)) & 0xff);
            }
            //压缩数据
            setCompressionMode(CompressionMode.ZIP);
            realdata = compress(realdata);           
            //合并数据
            data = new byte[realdata.length + 4];
            System.arraycopy(lenbytes, 0, data, 0, 4);
            System.arraycopy(realdata, 0, data, 4, realdata.length);
            flag = COMPRESSION_MASK;
        }
        return new CachedData(flag, data);
    }
}

这里没有对PHP中的数据类型做相关的处理,只是处理了压缩,即PHP中的任何数据类型在Java中都会变为String类型,并且Java中目前也只是提供了写入String的接口

5. Java接口类

仿照php-memcached中的Memcached类实现了Java的访问接口,只实现了对String类型的读写操作

class Memcached {

    private MemcachedClient client;
    protected PHPTranscoder transcoder;

    public Memcached() throws IOException {
        transcoder = new PHP7Transcoder();
        XMemcachedClientBuilder builder = new XMemcachedClientBuilder();
        builder.setSessionLocator(new KetamaMemcachedSessionLocator(true));
        client = builder.build();
    }

    public void setTranscoder(PHPTranscoder transcoder) {
        this.transcoder = transcoder;
    }

    public void addServer(String server, int port, int weight) {
        try {
            client.addServer(server, port, weight);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public void addServer(String server, int port) {
        this.addServer(server, port, 1);
    }

    public String get(String key) throws TimeoutException, InterruptedException, MemcachedException {
        return client.get(key, transcoder);
    }

    public boolean set(String key, String value, int expire) {
        boolean b = false;
        try {
            b = client.set(key, expire, value, transcoder);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return b;
    }

    public boolean set(String key, String value) {
        return set(key, value, 0);
    }

    public boolean add(String key, String value, int expire) {
        boolean b = false;
        try {
            b = client.add(key, expire, value, transcoder);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return b;
    }

    public boolean add(String key, String value) {
        return add(key, value, 0);
    }

    public boolean replace(String key, String value, int expire) {
        boolean b = false;
        try {
            b = client.add(key, expire, value, transcoder);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return b;
    }

    public boolean replace(String key, String value) {
        return replace(key, value, 0);
    }

    public void quit() throws IOException {
        client.shutdown();
    }
}

PHP 7中增加Magic Quotes GPC

PHP 7 虽然没有能够如期发布,不过在明年来临之前应该能够发布正式版,由于其在功能和性能上都有很大的改进,官方声称性能是之前一个版本的两倍,因此业界的关注度和期待都很高,很多用户都准备将PHP升级到这一版本。

1. MySQL模块

如果是从PHP 5.6升级到PHP 7则最大的变化是PHP 7中删除了MySQL模块,建议使用PDO或MySQLi进行替换,但如果代码中已经大量的使用了MySQL模块,完成代码的改造工作可能需要较长的时间。然而发现单独进行维护的MySQL模块是可以支持PHP 7的,尽管说明中已经不再建议使用,项目的地址为https://github.com/php/pecl-database-mysql。期安置方法与普通的PHP模块完全相同,即

phpize
./configure
make
make install

2. Magic Quotes GPC

PHP 5.4中删除了PHP的Magic Quotes GPC功能,即对保存用户提交数据的三个全局变量$_GET,$_POST,$_COOKIE中的引号和反斜杠自动增加转义,其初衷是为了增加安全性,防止XSS和SQL注入攻击,但由于其并不能彻底的防止攻击,同时还会引入一些麻烦,在PHP 5.3中已经不再建议使用,对于防止XSS和SQL注入,建议在SQL语句中使用预处理。然而如果代码是急于使用了这一功能而进行开发的,则如果关闭这一功能会导致大量的查询错误,影响范围极大,而且不易确定具体的影响范围,修改代码会存在困难。同样出于能够快速过渡到PHP 7,这里想到的处理方法是将这个功能重新增加到PHP 7的源代码中。

3. PHP 7中相关方法的改变

由于PHP 7是一个全新的版本,其底层做了相当大的调整,想直接将PHP 5.3中的代码简单的合并到PHP 7中显然是不能够实现的。这里简单介绍修改中涉及的一些用法上的变化,首先其涉及PHP的php_addslashes函数,其参数类型和返回值都在PHP 7发生了改变,具体如下

//PHP 5
PHPAPI char *php_addslashes(char *str, int length, int *new_length, int freeit TSRMLS_DC);

//PHP 7
PHPAPI zend_string *php_addslashes(zend_string *str, int should_free);

PHP 7中定义了zend_string数据类型,替代了原来使用的char*,相比之下其在内存申请和释放的操作更加简单,如下是初始化一个zend_string的方法

zend_string* zstr = zend_string_init(*val, val_len, 0);

zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0);

另外PHP 7中动态修改配置的方法zend_alter_ini_entry_ex的参数列表也发生了变化,其中主要也是将一些参数改为了zend_string

//PHP 5
ZEND_API int zend_alter_ini_entry_ex(char *name, uint name_length, char *new_value, uint new_value_length, int modify_type, int stage, int force_change TSRMLS_DC);

//PHP 7
ZEND_API int zend_alter_ini_entry_ex(zend_string *name, zend_string *new_value, int modify_type, int stage, int force_change);

4. 需要进行的修改

重新增加Magic Quotes GPC的功能需要大概以下过程,首先重新添加Magic Quotes GPC相关的配置,然后在PHP注册全局变量的过程中对其进行addslashes操作,另外在对注册环境变量时临时关闭Magic Quotes GPC功能,最后在filter扩展中也增加addslashes,由于使用了此模块注册全局变量的逻辑将由这里进行处理。对PHP 7RC7的修改的patch大致如下

diff -Nur php-7.0.0RC8/ext/filter/filter.c php-7.0.0RC8-patched/ext/filter/filter.c
--- php-7.0.0RC8/ext/filter/filter.c	2015-11-25 12:04:24.000000000 +0800
+++ php-7.0.0RC8-patched/ext/filter/filter.c	2015-11-30 09:08:18.476676725 +0800
@@ -467,6 +467,8 @@
 		if (IF_G(default_filter) != FILTER_UNSAFE_RAW) {
 			ZVAL_STRINGL(&new_var, *val, val_len);
 			php_zval_filter(&new_var, IF_G(default_filter), IF_G(default_filter_flags), NULL, NULL, 0);
+		} else if (PG(magic_quotes_gpc) && !retval) {
+			ZVAL_NEW_STR(&new_var, php_addslashes(zend_string_init(*val, val_len, 0), 0));
 		} else {
 			ZVAL_STRINGL(&new_var, *val, val_len);
 		}
diff -Nur php-7.0.0RC8/ext/standard/basic_functions.c php-7.0.0RC8-patched/ext/standard/basic_functions.c
--- php-7.0.0RC8/ext/standard/basic_functions.c	2015-11-25 12:04:21.000000000 +0800
+++ php-7.0.0RC8-patched/ext/standard/basic_functions.c	2015-11-30 09:08:18.476676725 +0800
@@ -4619,10 +4619,7 @@
    Get the current active configuration setting of magic_quotes_gpc */
 PHP_FUNCTION(get_magic_quotes_gpc)
 {
-	if (zend_parse_parameters_none() == FAILURE) {
-		return;
-	}
-	RETURN_FALSE;
+	RETURN_LONG(PG(magic_quotes_gpc));
 }
 /* }}} */

diff -Nur php-7.0.0RC8/main/main.c php-7.0.0RC8-patched/main/main.c
--- php-7.0.0RC8/main/main.c	2015-11-25 12:04:24.000000000 +0800
+++ php-7.0.0RC8-patched/main/main.c	2015-11-30 09:09:44.176170262 +0800
@@ -507,6 +507,7 @@
 	STD_PHP_INI_BOOLEAN("ignore_repeated_source",	"0",	PHP_INI_ALL,		OnUpdateBool,			ignore_repeated_source,	php_core_globals,	core_globals)
 	STD_PHP_INI_BOOLEAN("report_memleaks",		"1",		PHP_INI_ALL,		OnUpdateBool,			report_memleaks,		php_core_globals,	core_globals)
 	STD_PHP_INI_BOOLEAN("report_zend_debug",	"1",		PHP_INI_ALL,		OnUpdateBool,			report_zend_debug,		php_core_globals,	core_globals)
+	STD_PHP_INI_BOOLEAN("magic_quotes_gpc",		"1",		PHP_INI_PERDIR|PHP_INI_SYSTEM,	OnUpdateBool,	magic_quotes_gpc,		php_core_globals,	core_globals)
 	STD_PHP_INI_ENTRY("output_buffering",		"0",		PHP_INI_PERDIR|PHP_INI_SYSTEM,	OnUpdateLong,	output_buffering,		php_core_globals,	core_globals)
 	STD_PHP_INI_ENTRY("output_handler",			NULL,		PHP_INI_PERDIR|PHP_INI_SYSTEM,	OnUpdateString,	output_handler,		php_core_globals,	core_globals)
 	STD_PHP_INI_BOOLEAN("register_argc_argv",	"1",		PHP_INI_PERDIR|PHP_INI_SYSTEM,	OnUpdateBool,	register_argc_argv,		php_core_globals,	core_globals)
@@ -2233,6 +2234,7 @@
 				E_DEPRECATED,
 				"Directive '%s' is deprecated in PHP 5.3 and greater",
 				{
+					"magic_quotes_gpc",
 					NULL
 				}
 			},
@@ -2244,7 +2246,6 @@
 					"asp_tags",
 					"define_syslog_variables",
 					"highlight.bg",
-					"magic_quotes_gpc",
 					"magic_quotes_runtime",
 					"magic_quotes_sybase",
 					"register_globals",
diff -Nur php-7.0.0RC8/main/php_globals.h php-7.0.0RC8-patched/main/php_globals.h
--- php-7.0.0RC8/main/php_globals.h	2015-11-25 12:04:24.000000000 +0800
+++ php-7.0.0RC8-patched/main/php_globals.h	2015-11-30 09:08:29.765651533 +0800
@@ -54,6 +54,8 @@
 } arg_separators;

 struct _php_core_globals {
+	zend_bool magic_quotes_gpc;
+
 	zend_bool implicit_flush;

 	zend_long output_buffering;
diff -Nur php-7.0.0RC8/main/php_variables.c php-7.0.0RC8-patched/main/php_variables.c
--- php-7.0.0RC8/main/php_variables.c	2015-11-25 12:04:24.000000000 +0800
+++ php-7.0.0RC8-patched/main/php_variables.c	2015-11-30 09:08:18.480011725 +0800
@@ -49,7 +49,11 @@
 	assert(strval != NULL);

 	/* Prepare value */
-	ZVAL_NEW_STR(&new_entry, zend_string_init(strval, str_len, 0));
+	if (PG(magic_quotes_gpc)) {
+		ZVAL_NEW_STR(&new_entry, php_addslashes(zend_string_init(strval, str_len, 0), 0));
+	} else {
+		ZVAL_NEW_STR(&new_entry, zend_string_init(strval, str_len, 0));
+	}
 	php_register_variable_ex(var, &new_entry, track_vars_array);
 }

@@ -57,7 +61,7 @@
 {
 	char *p = NULL;
 	char *ip = NULL;		/* index pointer */
-	char *index;
+	char *index, *escaped_index = NULL;
 	char *var, *var_orig;
 	size_t var_len, index_len;
 	zval gpc_element, *gpc_element_p;
@@ -180,11 +184,18 @@
 					return;
 				}
 			} else {
-				gpc_element_p = zend_symtable_str_find(symtable1, index, index_len);
+				if (PG(magic_quotes_gpc)) {
+					zend_string* zstr = php_addslashes(zend_string_init(index, index_len, 0), 0);
+					escaped_index = ZSTR_VAL(zstr);
+					index_len = ZSTR_LEN(zstr);
+				} else {
+					escaped_index = index;
+				}
+				gpc_element_p = zend_symtable_str_find(symtable1, escaped_index, index_len);
 				if (!gpc_element_p) {
 					zval tmp;
 					array_init(&tmp);
-					gpc_element_p = zend_symtable_str_update_ind(symtable1, index, index_len, &tmp);
+					gpc_element_p = zend_symtable_str_update_ind(symtable1, escaped_index, index_len, &tmp);
 				} else {
 					if (Z_TYPE_P(gpc_element_p) == IS_INDIRECT) {
 						gpc_element_p = Z_INDIRECT_P(gpc_element_p);
@@ -216,6 +227,13 @@
 				zval_ptr_dtor(&gpc_element);
 			}
 		} else {
+			if (PG(magic_quotes_gpc)) {
+				zend_string* zstr = php_addslashes(zend_string_init(index, index_len, 0), 0);
+				escaped_index = ZSTR_VAL(zstr);
+				index_len = ZSTR_LEN(zstr);
+			} else {
+				escaped_index = index;
+			}
 			/*
 			 * According to rfc2965, more specific paths are listed above the less specific ones.
 			 * If we encounter a duplicate cookie name, we should skip it, since it is not possible
@@ -224,10 +242,10 @@
 			 */
 			if (Z_TYPE(PG(http_globals)[TRACK_VARS_COOKIE]) != IS_UNDEF &&
 				symtable1 == Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]) &&
-				zend_symtable_str_exists(symtable1, index, index_len)) {
+				zend_symtable_str_exists(symtable1, escaped_index, index_len)) {
 				zval_ptr_dtor(&gpc_element);
 			} else {
-				gpc_element_p = zend_symtable_str_update_ind(symtable1, index, index_len, &gpc_element);
+				gpc_element_p = zend_symtable_str_update_ind(symtable1, escaped_index, index_len, &gpc_element);
 			}
 		}
 	}
@@ -496,6 +514,13 @@
 	char **env, *p, *t = buf;
 	size_t alloc_size = sizeof(buf);
 	unsigned long nlen; /* ptrdiff_t is not portable */
+	
+	/* turn off magic_quotes while importing environment variables */
+	int magic_quotes_gpc = PG(magic_quotes_gpc);
+
+	if (magic_quotes_gpc) {
+		zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("0", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+	}

 	for (env = environ; env != NULL && *env != NULL; env++) {
 		p = strchr(*env, '=');
@@ -514,6 +539,10 @@
 	if (t != buf && t != NULL) {
 		efree(t);
 	}
+	
+	if (magic_quotes_gpc) {
+		zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("1", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+	}
 }

 zend_bool php_std_auto_global_callback(char *name, uint name_len)
@@ -593,9 +622,14 @@
 static inline void php_register_server_variables(void)
 {
 	zval request_time_float, request_time_long;
+	/* turn off magic_quotes while importing server variables */
+	int magic_quotes_gpc = PG(magic_quotes_gpc);

 	zval_ptr_dtor(&PG(http_globals)[TRACK_VARS_SERVER]);
 	array_init(&PG(http_globals)[TRACK_VARS_SERVER]);
+	if (magic_quotes_gpc) {
+		zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("0", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+	}

 	/* Server variables */
 	if (sapi_module.register_server_variables) {
@@ -618,6 +652,10 @@
 	php_register_variable_ex("REQUEST_TIME_FLOAT", &request_time_float, &PG(http_globals)[TRACK_VARS_SERVER]);
 	ZVAL_LONG(&request_time_long, zend_dval_to_lval(Z_DVAL(request_time_float)));
 	php_register_variable_ex("REQUEST_TIME", &request_time_long, &PG(http_globals)[TRACK_VARS_SERVER]);
+
+	if (magic_quotes_gpc) {
+		zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("1", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+	}
 }
 /* }}} */

diff -Nur php-7.0.0RC8/sapi/cgi/cgi_main.c php-7.0.0RC8-patched/sapi/cgi/cgi_main.c
--- php-7.0.0RC8/sapi/cgi/cgi_main.c	2015-11-25 12:04:12.000000000 +0800
+++ php-7.0.0RC8-patched/sapi/cgi/cgi_main.c	2015-11-30 09:08:18.500021725 +0800
@@ -632,8 +632,16 @@
 	php_php_import_environment_variables(array_ptr);

 	if (fcgi_is_fastcgi()) {
+		int magic_quotes_gpc = PG(magic_quotes_gpc);
 		fcgi_request *request = (fcgi_request*) SG(server_context);
+
+		if (magic_quotes_gpc) {
+			zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("0", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+		}
 		fcgi_loadenv(request, cgi_php_load_env_var, array_ptr);
+		if (magic_quotes_gpc) {
+			zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("1", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+		}
 	}
 }

diff -Nur php-7.0.0RC8/sapi/fpm/fpm/fpm_main.c php-7.0.0RC8-patched/sapi/fpm/fpm/fpm_main.c
--- php-7.0.0RC8/sapi/fpm/fpm/fpm_main.c	2015-11-25 12:04:12.000000000 +0800
+++ php-7.0.0RC8-patched/sapi/fpm/fpm/fpm_main.c	2015-11-30 09:08:18.516696725 +0800
@@ -562,6 +562,7 @@
 void cgi_php_import_environment_variables(zval *array_ptr) /* {{{ */
 {
 	fcgi_request *request = NULL;
+	int magic_quotes_gpc;

 	if (Z_TYPE(PG(http_globals)[TRACK_VARS_ENV]) == IS_ARRAY &&
 		Z_ARR_P(array_ptr) != Z_ARR(PG(http_globals)[TRACK_VARS_ENV]) &&
@@ -583,7 +584,14 @@
 	php_php_import_environment_variables(array_ptr);

 	request = (fcgi_request*) SG(server_context);
+	magic_quotes_gpc = PG(magic_quotes_gpc);
+	if (magic_quotes_gpc) {
+		zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("0", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+	}
 	fcgi_loadenv(request, cgi_php_load_env_var, array_ptr);
+	if (magic_quotes_gpc) {
+		zend_alter_ini_entry_ex(zend_string_init("magic_quotes_gpc", sizeof("magic_quotes_gpc") - 1, 0), zend_string_init("1", 1, 0), ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE, 1);
+	}
 }
 /* }}} */

5. 修改后的效果

通过patch的方式将以上patch合并到PHP源代码中,并编译PHP,启动后执行以下代码,即http://127.0.0.1:8088/?v%22=%27\1%27

<?php
print_r($_GET);
print_r(PHP_VERSION);

会得到以下输出

Array ( [v\"] => \'\\1\' ) 7.0.0RC7

可以看到其自动对单引号、双引号、反斜杠都增加了转义,实现了Magic Quotes GPC的作用。虽然官方已经不再建议用这个功能,但为了能够尽快的升级PHP 7,如果一直等待代码的修改完成恐怕就要等到PHP 8了。