CakeFest 2024: The Official CakePHP Conference

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

Erstellen der 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;
}
add a note

User Contributed Notes

There are no user contributed notes for this page.
To Top