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

Process:

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

Vous devez 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 vous pouvez 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. Vous pouvez 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, vous devrez faire une copie de zend_call_method() et ajouter une fonctionnalité pour ajouter des paramètres. Vous pouvez 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 vous pouvez 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;
    }