2015年6月

php扩展实践zend_execute_ex层获取实参

其实在实现的 php 函数里面是很容易获取到的,参考 php 的 builtin 函数 func_get_args() 就可以知道了。

void **p;
int arg_count;
int i;
zend_execute_data *ex = EG(current_execute_data);

if (!ex || !ex->function_state.arguments) {
    RETURN_FALSE;
}

p = ex->function_state.arguments;
arg_count = (int)(zend_uintptr_t) *p;

for (i = 0; i < arg_count; i++) {
    zval *element, *arg;
    arg = *((zval **) (p - (arg_count - i)));
    php_var_dump(&arg, 1 TSRMLS_CC);
}

但是在 zend_execute_ex 中,是不能使用 function_state.arguments 来获取参数的,需要从 argument_stack 中获取调用函数的实参。

static void (*old_zend_execute_ex) (zend_execute_data *execute_data TSRMLS_DC);

ZEND_API void learn_execute_ex (zend_execute_data *execute_data TSRMLS_DC)
{
    php_printf("====== extension debug start ======\n");
    php_printf("function name: %s\n", get_active_function_name(TSRMLS_C));

    old_zend_execute_ex(execute_data TSRMLS_CC);

    int stacked = 0;
    void **top;
    void **bottom;
    zval *arguments;
    smart_str buf = {0};

    array_init(arguments);
    
    top = zend_vm_stack_top(TSRMLS_C) - 1;
    if (top) {
        stacked = (int)(zend_uintptr_t) *top; // argc
        if (stacked) {
            bottom = zend_vm_stack_top(TSRMLS_C);
            EG(argument_stack)->top = top + 1;
            if (zend_copy_parameters_array(stacked, arguments TSRMLS_CC) == SUCCESS) {
                php_json_encode(&buf, arguments, 0 TSRMLS_CC);
            }
            EG(argument_stack)->top = bottom;
        }
    }

    smart_str_0(&buf);

    php_printf("%s\n", buf.c);

    smart_str_free(&buf);
    zval_dtor(arguments);

    php_printf("====== extension debug end ======\n");
}

PHP_MINIT_FUNCTION(learn)
{
    old_zend_execute_ex = zend_execute_ex;
    zend_execute_ex = learn_execute_ex;

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(learn)
{
    zend_execute_ex = old_zend_execute_ex;

    return SUCCESS;
}

2015-11-04 00:38 更新

后来看到,其实不用上面这中方法就可以实现, php 5.5之后要从 prev 里面去取

/**
 * php_var_dump defined in this head file.
 */
#include "ext/standard/php_var.h"

zend_execute_data *real_execute_data = execute_data->prev_execute_data;

void **p = real_execute_data->function_state.arguments;
int arg_count = (int) (zend_uintptr_t) * p;
zval *argument_element;
int i;
// zval *obj = real_execute_data->object;
unsigned long start = mach_absolute_time();
for (i = 0; i < arg_count; i++) {
    argument_element = *(p - (arg_count - i));
    php_var_dump(&argument_element, 1);
}

php max_input_vars限制

这是一次线上 bug,后台 sku 修改的时候,使用了大量的数组,造成了 php 端 $stock 变量无法完全解析。

stock[1][price]=1234
stock[1][stock]=99
stock[1][id]=1
// ...
stock[500][price]=1234
stock[500][stock]=99
stock[500][id]=500

由于线上的 php 版本是由低版本升级上去的,所以以前是不会有这样的问题的,PHP 5.3.9为了防止hash冲突就加了那么个参数 max_input_vars

曾今天真的以为上面的例子的 input vars 应该是 500 个,看了 PHP 的源码之后才发现应该是 500 * 3;其实 PHP 是按照 & 为 token 来计数的。

// vim main/php_variables.c +438
// 这里的 separator,在 GET POST 中是 &; 在 COOKIE 中是 ;
var = php_strtok_r(res, separator, &strtok_buf);

// :458
while (var) {
    if (++count > PG(max_input_vars)) {
        // ...
    }
}

如何避免这样的问题

  1. 自然是想办法增加 max_input_vars 的大小,但这样有个难以逃避的问题,就是前端传过来数据的个数你是没办法知道应该是多少的
  2. 减少 post 的字段,先 js json_encode 一下,再用一个大字段传过来
  3. 使用 post 的 body 将数据传过来;在 php 端使用 file_get_contents('php://input') 来获取之后在 json_decode;我觉得这中方法比较可取,因为 restful 设计里面就采用的这种方法。

php扩展实践之数组操作

初始化数组

zval *arr;
MAKE_STD_ZVAL(arr);
array_init(arr);

插入和更新

// 索引
add_index_bool(arr, 10, 1);
add_next_index_long(arr, 1000);
// 关联数组
add_assoc_stringl(arr, "name", ZEND_STRL("xiaozi"), 1);

查找和删除

char *name = "name";
int nameLen = strlen(name);
// 查找
zend_hash_exists(Z_ARRVAL_P(arr), name, nameLen + 1);
// 删除
zend_hash_del(Z_ARRVAL_P(arr), name, nameLen + 1);

读取

char *name = "name";
int nameLen = strlen(name);
zval **value;
if (zend_hash_find(Z_ARRVAL_P(attributes), name, nameLen + 1, (void **)&value) == SUCCESS) {
    php_var_dump(value, 1 TSRMLS_CC);
}

php扩展实践之implements内置接口

这次用 php 扩展的方式来实现一下 Laravel5.1 Support 中的 Fluent,实现一下 php 内置接口 ArrayAccess 的方法

method参数的定义

#include "Zend/zend_interfaces.h"

zend_class_entry *fluent_ce;

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___construct, 0, 0, 1)
    ZEND_ARG_ARRAY_INFO(0, attributes, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetexists, 0, 0, 1)
    ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetget, 0, 0, 1)
    ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetset, 0, 0, 2)
    ZEND_ARG_INFO(0, offset)
    ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetunset, 0, 0, 1)
    ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___get, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___set, 0, 0, 2)
    ZEND_ARG_INFO(0, name)
    ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___isset, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___unset, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

定义类方法

ZEND_METHOD(fluent, __construct) {
    zval *attributes, *instance;
    MAKE_STD_ZVAL(attributes);
    array_init(attributes);
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a", &attributes) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    zend_update_property(fluent_ce, instance, ZEND_STRL("attributes"), attributes TSRMLS_DC);
}
ZEND_METHOD(fluent, offsetExists) {
    zval *value, *instance, *member;
    char *offset;
    int offsetLen;
    int exists;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    MAKE_STD_ZVAL(member);
    ZVAL_STRINGL(member, offset, offsetLen, 1);
    exists = std_object_handlers.has_property(instance, member, 0, 0 TSRMLS_DC);
    zval_ptr_dtor(&member);
    RETURN_BOOL(exists);
}
ZEND_METHOD(fluent, offsetGet) {
    zval *value, *instance;
    char *offset;
    int offsetLen;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    php_printf("%s\n", offset);
    value = zend_read_property(fluent_ce, instance, offset, offsetLen, 1 TSRMLS_CC);
    RETURN_ZVAL(value, 1, 0);
}
ZEND_METHOD(fluent, offsetSet) {
    zval *value, *instance;
    char *offset;
    int offsetLen;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &offset, &offsetLen, &value) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    zend_update_property(fluent_ce, instance, offset, offsetLen, value TSRMLS_DC);
}
ZEND_METHOD(fluent, offsetUnset) {
    zval *instance, *member;
    char *offset;
    int offsetLen;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    MAKE_STD_ZVAL(member);
    ZVAL_STRINGL(member, offset, offsetLen, 1);
    std_object_handlers.unset_property(instance, member, 0 TSRMLS_DC);
    zval_ptr_dtor(&member);
}
ZEND_METHOD(fluent, __get) {
    char *name;
    int nameLen;
    zval *attributes, *instance;
    zval **value;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    if (zend_hash_find(Z_ARRVAL_P(attributes), name, nameLen + 1, (void **)&value) == SUCCESS) {
        RETURN_ZVAL(*value, 1, 0);
    } else {
        RETURN_NULL();
    }
}
ZEND_METHOD(fluent, __set) {
    char *name;
    int nameLen;
    zval *value, *attributes, *instance;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name, &nameLen, &value) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    add_assoc_zval(attributes, name, value);
    zval_add_ref(&value);
}
ZEND_METHOD(fluent, __isset) {
    char *name;
    int nameLen;
    zval *attributes, *instance;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    if (zend_hash_exists(Z_ARRVAL_P(attributes), name, nameLen + 1) == SUCCESS) {
        RETURN_BOOL(1);
    } else {
        RETURN_BOOL(0);
    }
}
ZEND_METHOD(fluent, __unset) {
    char *name;
    int nameLen;
    zval *attributes, *instance;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    zend_hash_del(Z_ARRVAL_P(attributes), name, nameLen + 1);
}

static zend_function_entry fluent_methods[] = {
    ZEND_ME(fluent, __construct, arginfo_fluent___construct, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetExists, arginfo_fluent_offsetexists, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetGet, arginfo_fluent_offsetget, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetSet, arginfo_fluent_offsetset, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetUnset, arginfo_fluent_offsetunset, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __get, arginfo_fluent___get, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __set, arginfo_fluent___set, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __isset, arginfo_fluent___isset, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __unset, arginfo_fluent___unset, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

模块初始化的时候注册类

PHP_MINIT_FUNCTION(learn)
{
    zval *attributes;
    MAKE_STD_ZVAL(attributes);
    array_init(attributes);
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Fluent", fluent_methods);
    fluent_ce = zend_register_internal_class(&ce TSRMLS_CC);
    // implements ArrayAccess
    zend_class_implements(fluent_ce TSRMLS_DC, 1, zend_ce_arrayaccess);
    zend_declare_property_null(fluent_ce, ZEND_STRL("attributes"), ZEND_ACC_PROTECTED TSRMLS_CC);

    return SUCCESS;
}

php扩展实践之调用类

PHP_FUNCTION(learn_call) {
    // 实例化类
    zval *learn;
    MAKE_STD_ZVAL(learn);
    object_init_ex(learn, learn_ce);

    char *name = "xiaozi";
    char *methodName = "__construct";
    zval *param, *method, *callResult;
    zval **params[1];
    MAKE_STD_ZVAL(method);
    ZVAL_STRINGL(method, methodName, strlen(methodName), 1);
    MAKE_STD_ZVAL(param);
    ZVAL_STRINGL(param, name, strlen(name), 1);
    params[0] = &param;
    // 调用类的 __construct 方法
    call_user_function_ex(&(learn_ce)->function_table, &learn, method, &callResult, 1, params, 0, NULL TSRMLS_CC);
    zval_ptr_dtor(&method);
    zval_ptr_dtor(&param);
    zval_ptr_dtor(&learn);
    if (callResult) {
        zval_ptr_dtor(&callResult);
    }
}

php扩展实践之定义类

zend_class_entry *learn_ce;

ZEND_BEGIN_ARG_INFO_EX(arginfo_learn___construct, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

// Learn::__construct 的实现
ZEND_METHOD(learn, __construct) {
    char *name = "tianzi", *methodName;
    zval *method, *callResult, *instance;
    int nameLen = strlen(name);
    // 带有默认值的传入参数
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    // 更新类的属性
    zend_update_property_stringl(learn_ce, instance, ZEND_STRL("name"), ZEND_STRL(name) TSRMLS_DC);
}
// Learn::hi 的实现
ZEND_METHOD(learn, hi) {
    zval *instance = getThis();
    // 读取类的属性
    zval *name = zend_read_property(learn_ce, instance, ZEND_STRL("name"), 0 TSRMLS_CC);
    char *theName = estrndup(Z_STRVAL_P(name), Z_STRLEN_P(name));
    php_printf("hi %s\n", theName);
    efree(theName);
}
// 类方法的属性
static zend_function_entry learn_methods[] = {
    ZEND_ME(learn, __construct, arginfo_learn___construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    ZEND_ME(learn, hi, NULL, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

PHP_MINIT_FUNCTION(learn)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Learn", learn_methods);
    // 注册类
    learn_ce = zend_register_internal_class(&ce TSRMLS_CC);
    // 定义类属性
    zend_declare_property_null(learn_ce, ZEND_STRL("name"), ZEND_ACC_PUBLIC TSRMLS_CC);

    return SUCCESS;
}

php扩展实践之定义常量

PHP_MINIT_FUNCTION(learn)
{
    // CONST_CS 区分大小写
    // CONST_PERSISTENT 在模块加载的时候都是有效的,否则每次 request 之后就会删除
    REGISTER_BOOL_CONSTANT("LEARN", 1, CONST_CS | CONST_PERSISTENT);

    return SUCCESS;
}

Vuejs 和 CodeMirror 的绑定

实现 vuejs 和 codemirror 的相互绑定

Vue.directive("codemirror", {
    twoWay: true,
    bind: function () {
        // 请自行初始化CodeMirror, 可使用 $(this.el).data('codemirror')
        this.editor = editor;
        // model主动赋值标记
        this.silent = false;
        this.handler = function () {
            if (!this.silent) {
                this.set(this.editor.getValue(), true); // 加锁,防止相互赋值
            }
        }.bind(this);
        this.editor.on("change", this.handler);
    },
    update: function (value, oldValue) {
        this.silent = true;
        this.editor.setValue(this.vm.$data[this.raw]);
        this.silent = false;
    }
});

使用:

<!-- code 为 Vue data 中使用的变量名 -->
<textarea id="codepad" v-codemirror="code"></textarea>

golang中map的排序

在实现 golang 中发布订阅模式的时候,需要按照优先级排序回调函数;golang 中的 map 是无序的,需要手动取出 key,并对key进行排序,下面是排序一块的代码段:

[
    1 => [func1, func2]
    0 => [func5, func6]
    2 => [func3, func4]
]

...

[func5, func6, func1, func2, func3, func4]
func (ed *eventdispatcher) SortListeners(event string) {
    ed.sorted[event] = nil
    // 发布订阅模式中函数执行的优先级
    priorities := make([]int, 0)
    for priority, _ := range ed.listeners[event] {
        priorities = append(priorities, priority)
    }
    // 对优先级的数字进行排序
    sort.Ints(priorities)
    sorted := make([]func(interface {}) interface {}, 0)
    // 按照优先级顺序合并 map
    for _, priority := range priorities {
        sorted = append(sorted, ed.listeners[event][priority]...)
    }
    ed.sorted[event] = sorted
}