## Einführung in die Erstellung eines mysqlnd-Plugins

Es ist wichtig zu bedenken, dass ein `mysqlnd`-Plugin selbst eine PHP-Erweiterung ist.

Der folgende Code zeigt die grundlegende Struktur der MINIT-Funktion, die in einem typischen `mysqlnd`-Plugin verwendet wird:

    /* my_php_mysqlnd_plugin.c */

     static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
      /* Globals, ini-Einträge, Ressourcen, Klassen */

      /* Registrieren des mysqlnd-Plugins */
      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)(/* ... */) {
      /* ... */
    }

Analyse der Aufgaben: von C zum Userspace

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

Ablauf:

1. PHP: Benutzer registriert den Plugin-Callback

2. PHP: Benutzer ruft eine beliebige PHP-MySQL-API auf, um sich mit MySQL zu verbinden

3. C: ext/*mysql* ruft die mysqlnd-Methode auf

4. C: mysqlnd endet in ext/mysqlnd_plugin

5. C: ext/mysqlnd_plugin

   1. Ruft einen benutzerdefinierten Callback auf

   2. oder die ursprüngliche `mysqlnd`-Methode, wenn der Userspace-Callback nicht definiert ist

Folgende Aufgaben sind dafür auszuführen:

1. In C eine Klasse namens "mysqlnd_plugin_connection" erstellen

2. Ein Proxy-Objekt mittels "mysqlnd_plugin_set_conn_proxy()" annehmen und registrieren

3. Userspace-Proxy-Methoden aus C aufrufen (Optimierung - zend_interfaces.h)

Die Methoden von Userspace-Objekten können entweder mittels `call_user_function()` aufgerufen werden oder auf einer Ebene näher an der Zend Engine mittels `zend_call_method()`.

Optimierung: Aufruf von Methoden aus C mit zend_call_method

Der folgende Codeschnipsel zeigt den Prototyp für die Funktion `zend_call_method` und stammt aus 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
    );

Die Zend-API erlaubt nur zwei Parameter. Eventuell werden mehr benötigt, zum Beispiel:

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

Um dieses Problem zu umgehen, muss eine Kopie von `zend_call_method()` erzeugt werden, die mit zusätzlichen Parametern versehen werden kann. Dafür kann eine Reihe von `MY_ZEND_CALL_METHOD_WRAPPER`-Makros erstellt werden.

Aufruf des PHP-Userspaces

Dieser Codeschnipsel zeigt die optimierte Methode für den Aufruf einer Userspace-Funktion von C aus:

    /* 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) {
        /* Aufrufen eines Userspace-Proxys */
        ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
      } else {
        /* oder die ursprüngliche mysqlnd-Methode = nichts tun, transparent sein */
        ret = org_methods.connect(conn, host, user, passwd,
              passwd_len, db, db_len, port,
              socket, mysql_flags TSRMLS_CC);
      }
      return ret;
    }

Aufruf des Userspaces: einfache Argumente

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

Aufruf des Userspaces: Strukturen als Argumente

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

Bei vielen `mysqlnd`-Methoden ist das erste Argument ein C-"Objekt". Zum Beispiel ist das erste Argument der Methode connect() ein Zeiger auf `MYSQLND`. Die Struktur MYSQLND stellt ein `mysqlnd`-Verbindungsobjekt dar.

Der Zeiger auf das `mysqlnd`-Verbindungsobjekt kann mit einem Standard-I/O-Dateihandle verglichen werden. Genau wie ein Standard-I/O-Dateihandle muss ein `mysqlnd`-Verbindungsobjekt mit Hilfe einer PHP-Variablen vom Typ Ressource mit dem Userspace verbunden werden.

Von C zum Userspace und zurück

     class proxy extends mysqlnd_plugin_connection {
      public function connect($conn, $host, ...) {
        /* vor der Implementierung ("pre"-Hook) */
        printf("Verbinden mit dem Host '%s'\n", $host);
        debug_print_backtrace();
        return parent::connect($conn);
      }

      public function query($conn, $query) {
        /* nach der Implementierung ("post"-Hook) */
        $ret = parent::query($conn, $query);
        printf("Abfrage = '%s'\n", $query);
        return $ret;
      }
    }
    mysqlnd_plugin_set_conn_proxy(new proxy());

PHP-Benutzer müssen die Möglichkeit haben, die übergeordnete Implementierung einer überschriebenen Methode aufzurufen.

Durch Vererbung ist es möglich, nur ausgewählte Methoden zu "verfeinern", und man kann wählen, wann der eigene Code ausgeführt werden soll, vor oder nach der übergeordneten Methode ("pre"- oder "post"-Hook).

Eingebaute Klasse: mysqlnd_plugin_connection::connect()

    /*  my_mysqlnd_plugin_classes.c */

     PHP_METHOD("mysqlnd_plugin_connection", connect) {
      /* ... vereinfacht! ... */
      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-Verbindung", le_mysqlnd_plugin_conn);
      if (PASS == org_methods.connect(conn, host, /* simplified! */ TSRMLS_CC))
        RETVAL_TRUE;
      else
        RETVAL_FALSE;
    }

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open