Започнување со градење на mysqlnd приклучок
Почист и полокален преглед на PHP референцата, со задржана структура од PHP.net и подобра читливост за примери, секции и белешки.
Започнување со градење на mysqlnd приклучок
Референца за `mysqlnd.plugin.developing.php` со подобрена типографија и навигација.
Започнување со градење на mysqlnd приклучок
Важно е да се запомни дека а mysqlnd приклучок е сам по себе PHP екстензија.
Следната шифра ја покажува основната структура на функцијата MINIT што ќе се користи во типичниот mysqlnd приклучок:
/* my_php_mysqlnd_plugin.c */
static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
/* globals, ini entries, resources, classes */
/* register mysqlnd plugin */
mysqlnd_plugin_id = mysqlnd_plugin_register();
conn_m = mysqlnd_get_conn_methods();
memcpy(org_conn_m, conn_m,
sizeof(struct st_mysqlnd_conn_methods));
conn_m->query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
conn_m->connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
}
/* my_mysqlnd_plugin.c */
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, query)(/* ... */) {
/* ... */
}
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, connect)(/* ... */) {
/* ... */
}
Анализа на задачи: од C до кориснички простор
class proxy extends mysqlnd_plugin_connection {
public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
Процес:
- PHP: корисникот регистрира callback на приклучокот
- PHP: корисникот повикува било кој PHP MySQL API за поврзување со MySQL
- C: ext/*mysql* повикува mysqlnd метод
- C: mysqlnd завршува во ext/mysqlnd_plugin
-
C: ext/mysqlnd_plugin
- Повикува кориснички callback
-
Или оригиналниот
mysqlndметод, ако корисничкиот callback не е поставен
Треба да го направите следново:
- Напишете класа "mysqlnd_plugin_connection" во C
- Прифатете и регистрирајте proxy објект преку "mysqlnd_plugin_set_conn_proxy()"
- Повикајте кориснички proxy методи од C (оптимизација - zend_interfaces.h)
Корисничките методи на објектот можат да бидат повикани или со користење на
call_user_function() или можете да работите на ниво поблиску до Zend Engine и да користите
zend_call_method().
Оптимизација: повикување методи од C користејќи zend_call_method
Следнава шифра покажува прототипот за
zend_call_method функција, земена од
zend_interfaces.h.
ZEND_API zval* zend_call_method( zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2 TSRMLS_DC );
Zend API поддржува само два аргументи. Можеби ќе ви требаат повеќе, на пример:
enum_func_status (*func_mysqlnd_conn__connect)( MYSQLND *conn, const char *host, const char * user, const char * passwd, unsigned int passwd_len, const char * db, unsigned int db_len, unsigned int port, const char * socket, unsigned int mysql_flags TSRMLS_DC );
За да го заобиколите овој проблем, ќе треба да направите копија од
zend_call_method() и да додадете можност за дополнителни параметри. Ова можете да го направите со креирање на сет од
MY_ZEND_CALL_METHOD_WRAPPER macros.
Повикување PHP кориснички простор
Оваа шифра покажува оптимизиран метод за повикување корисничка функција од C:
/* my_mysqlnd_plugin.c */
MYSQLND_METHOD(my_conn_class,connect)(
MYSQLND *conn, const char *host /* ... */ TSRMLS_DC) {
enum_func_status ret = FAIL;
zval * global_user_conn_proxy = fetch_userspace_proxy();
if (global_user_conn_proxy) {
/* call userspace proxy */
ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
} else {
/* or original mysqlnd method = do nothing, be transparent */
ret = org_methods.connect(conn, host, user, passwd,
passwd_len, db, db_len, port,
socket, mysql_flags TSRMLS_CC);
}
return ret;
}
Повикување кориснички простор: едноставни аргументи
/* my_mysqlnd_plugin.c */
MYSQLND_METHOD(my_conn_class,connect)(
/* ... */, const char *host, /* ...*/) {
/* ... */
if (global_user_conn_proxy) {
/* ... */
zval* zv_host;
MAKE_STD_ZVAL(zv_host);
ZVAL_STRING(zv_host, host, 1);
MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_host /*, ...*/);
zval_ptr_dtor(&zv_host);
/* ... */
}
/* ... */
}
Повикување кориснички простор: структури како аргументи
/* my_mysqlnd_plugin.c */
MYSQLND_METHOD(my_conn_class, connect)(
MYSQLND *conn, /* ...*/) {
/* ... */
if (global_user_conn_proxy) {
/* ... */
zval* zv_conn;
ZEND_REGISTER_RESOURCE(zv_conn, (void *)conn, le_mysqlnd_plugin_conn);
MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_conn, zv_host /*, ...*/);
zval_ptr_dtor(&zv_conn);
/* ... */
}
/* ... */
}
Првиот аргумент од многу mysqlnd методи е C "објект". На пример, првиот аргумент на методот connect() е покажувач до MYSQLND. Структурата MYSQLND претставува mysqlnd објект за конекција.
На mysqlnd покажувачот на објект за конекција може да се спореди со стандарден I/O рачка за датотека. Како стандардна I/O рачка за датотека mysqlnd објектот за конекција треба да биде поврзан со корисничкиот простор користејќи го типот на променлива ресурс на PHP.
Од C до кориснички простор и назад
class proxy extends mysqlnd_plugin_connection {
public function connect($conn, $host, ...) {
/* "pre" hook */
printf("Connecting to host = '%s'\n", $host);
debug_print_backtrace();
return parent::connect($conn);
}
public function query($conn, $query) {
/* "post" hook */
$ret = parent::query($conn, $query);
printf("Query = '%s'\n", $query);
return $ret;
}
}
mysqlnd_plugin_set_conn_proxy(new proxy());
Корисниците на PHP мора да можат да ја повикаат родителската имплементација на препишаниот метод.
Како резултат на наследување, можно е да се рафинираат само избрани методи и можете да изберете да имате "пре" или "пост" куки.
Вградена класа: mysqlnd_plugin_connection::connect()
/* my_mysqlnd_plugin_classes.c */
PHP_METHOD("mysqlnd_plugin_connection", connect) {
/* ... simplified! ... */
zval* mysqlnd_rsrc;
MYSQLND* conn;
char* host; int host_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",
&mysqlnd_rsrc, &host, &host_len) == FAILURE) {
RETURN_NULL();
}
ZEND_FETCH_RESOURCE(conn, MYSQLND* conn, &mysqlnd_rsrc, -1,
"Mysqlnd Connection", le_mysqlnd_plugin_conn);
if (PASS == org_methods.connect(conn, host, /* simplified! */ TSRMLS_CC))
RETVAL_TRUE;
else
RETVAL_FALSE;
}