mysqlnd-php-plugin-php-developing-8

  • MySQL Native Driver Plugin
    API
  • Getting started building a mysqlnd plugin

  • Getting started building a mysqlnd plugin
  • Getting started building a mysqlnd plugin

    Getting started building a mysqlnd plugin

    It is important to remember that a mysqlnd
    plugin is itself a PHP extension.

    The following code shows the basic structure of the
    MINIT function that will be used in the typical mysqlnd
    plugin:

    /* 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)(/* ... */) {
      /* ... */
    }
    

    Task analysis: from C to
    userspace

     class proxy extends mysqlnd_plugin_connection {
      public function connect($host, ...) { .. }
    }
    mysqlnd_plugin_set_conn_proxy(new proxy());
    

    Process:

    1. PHP: user registers plugin callback

    2. PHP: user calls any PHP MySQL API to connect to
      MySQL

    3. C: ext/*mysql* calls mysqlnd method

    4. C: mysqlnd ends up in ext/mysqlnd_plugin

    5. C: ext/mysqlnd_plugin

      1. Calls userspace callback

      2. Or original mysqlnd method, if userspace
        callback not set

    You need to carry out the following:

    1. Write a class “mysqlnd_plugin_connection” in C

    2. Accept and register proxy object through
      “mysqlnd_plugin_set_conn_proxy()”

    3. Call userspace proxy methods from C (optimization –
      zend_interfaces.h)

    Userspace object methods can either be called using
    call_user_function() or you can operate at a level closer
    to the Zend Engine and use zend_call_method().

    Optimization: calling methods
    from C using zend_call_method

    The following code snippet shows the prototype for
    the zend_call_method function, taken from 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 supports only two arguments. You may need
    more, for example:

     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
    );
    

    To get around this problem you will need to make a
    copy of zend_call_method() and add a facility for
    additional parameters. You can do this by creating a set of
    MY_ZEND_CALL_METHOD_WRAPPER macros.

    Calling PHP userspace

    This code snippet shows the optimized method for
    calling a userspace function from 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;
    }
    

    Calling userspace: simple
    arguments

    /* 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);
        /* ... */
      }
      /* ... */
    }
    

    Calling userspace: structs as
    arguments

    /* 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);
        /* ... */
      }
      /* ... */
    }
    

    The first argument of many mysqlnd methods
    is a C “object”. For example, the first argument of the connect()
    method is a pointer to MYSQLND. The struct MYSQLND
    represents a mysqlnd connection object.

    The mysqlnd connection object pointer can
    be compared to a standard I/O file handle. Like a standard I/O file
    handle a mysqlnd connection object shall be linked to the
    userspace using the PHP resource variable type.

    From C to userspace and
    back

     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 users must be able to call the parent
    implementation of an overwritten method.

    As a result of subclassing it is possible to refine
    only selected methods and you can choose to have “pre” or “post”
    hooks.

    Buildin class:
    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;
    }