## Bien commencer la compilation d'un plugin mysqlnd

Il est important de se souvenir qu'un plugin `mysqlnd` est lui-même une extension PHP.

Le code suivant montre la structure basique d'une fonction MINIT utilisée dans un plugin typique `mysqlnd` :

    /* my_php_mysqlnd_plugin.c */

     static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
      /* globales, entrées ini, ressources, classes */

      /* enregistrement du plugin mysqlnd */
      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)(/* ... */) {
      /* ... */
    }

Tâche d'analyse : depuis C vers l'espace utilisateur

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

Processus :

1. PHP : l'utilisateur enregistre une fonction de rappel pour le plugin
2. PHP : l'utilisateur appelle une méthode de l'API PHP MySQL pour se connecter à MySQL
3. C : ext/*mysql* appelle la méthode mysqlnd
4. C : mysqlnd se termine dans ext/mysqlnd_plugin

5. C : ext/mysqlnd_plugin

   1. Appel de la fonction de rappel de l'espace utilisateur
   2. Ou la méthode originale `mysqlnd`, si l'espace utilisateur n'a pas défini de fonction de rappel

Il faut effectuer les opérations suivantes :

1. Écrire une classe "mysqlnd_plugin_connection" en C
2. Accepter et enregistrer l'objet proxy via "mysqlnd_plugin_set_conn_proxy()"
3. Appeler les méthodes de proxy de l'espace utilisateur depuis C (optimisation - zend_interfaces.h)

Les méthodes de l'objet de l'espace utilisateur peuvent soit être appelées en utilisant `call_user_function()`, soit il est possible d'opérer à un niveau en dessous du moteur Zend et utiliser `zend_call_method()`.

Optimisation : appel des méthodes depuis C en utilisant zend_call_method

Le code suivant montre un prototype pour la fonction `zend_call_method`, issue de 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
    );

L'API Zend supporte 2 arguments. Il est possible d'en avoir besoin de plus, par exemple :

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

Pour contourner ce problème, il faudra faire une copie de `zend_call_method()` et ajouter une fonctionnalité pour ajouter des paramètres. L'on peut réaliser ceci en créant un jeu de macros `MY_ZEND_CALL_METHOD_WRAPPER`.

Appel de l'espace utilisateur PHP

Le code ci-dessous montre la méthode optimisée pour effectuer un appel à une fonction de l'espace utilisateur depuis 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) {
        /* appel du proxy de l'espace utilisateur */
        ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
      } else {
        /* ou la méthode originale mysqlnd = ne rien faire, être transparent */
        ret = org_methods.connect(conn, host, user, passwd,
              passwd_len, db, db_len, port,
              socket, mysql_flags TSRMLS_CC);
      }
      return ret;
    }

Appel de l'espace utilisateur: arguments simples

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

Appel de l'espace utilisateur : structures comme 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);
        /* ... */
      }
      /* ... */
    }

Le premier argument de toutes les méthodes `mysqlnd` est un objet C. Par exemple, le premier argument de la méthode connect() est un pointeur vers `MYSQLND`. La structure MYSQLND représente un objet de connexion `mysqlnd`.

Le pointeur de l'objet de connexion `mysqlnd` peut être comparé à un pointeur de fichier standard I/O. Tout comme un pointeur de fichier standard I/O, un objet de connexion `mysqlnd` doit être lié à l'espace utilisateur en utilisant une variable PHP de type ressource.

Depuis C vers l'espace utilisateur, puis, retour

     class proxy extends mysqlnd_plugin_connection {
      public function connect($conn, $host, ...) {
        /* "pre" hook */
        printf("Connexion à l'hôte = '%s'\n", $host);
        debug_print_backtrace();
        return parent::connect($conn);
      }

      public function query($conn, $query) {
        /* "post" hook */
        $ret = parent::query($conn, $query);
        printf("Requête = '%s'\n", $query);
        return $ret;
      }
    }
    mysqlnd_plugin_set_conn_proxy(new proxy());

Les utilisateurs PHP doivent pouvoir appeler l'implémentation du parent d'une méthode écrasée.

Comme résultat d'un sous-classement, il est possible de redéfinir uniquement les méthodes sélectionnées, et l'on peut choisir d'avoir des actions "pre" ou "post".

Construction d'une classe : mysqlnd_plugin_connection::connect()

    /*  my_mysqlnd_plugin_classes.c */

     PHP_METHOD("mysqlnd_plugin_connection", connect) {
      /* ... simplifié ! ... */
      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, /* simplifié! */ TSRMLS_CC))
        RETVAL_TRUE;
      else
        RETVAL_FALSE;
    }

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