Architecture du plugin du driver natif
Cette section fournit un aperçu de l'architecture du plugin mysqlnd.
Aperçu du driver natif MySQL
Avant de développer des plugins mysqlnd, il est utile d'avoir une connaissance minimale sur l'organisation de mysqlnd. Mysqlnd est composé des modules suivants :
Modules de statistiques | mysqlnd_statistics.c |
Connexion | mysqlnd.c |
Jeu de résultats | mysqlnd_result.c |
Données méta du jeu de résultats | mysqlnd_result_meta.c |
Requête | mysqlnd_ps.c |
Réseau | mysqlnd_net.c |
Couche physique | mysqlnd_wireprotocol.c |
Objet C orienté paradigme
Au niveau du code, mysqlnd utilise un masque C pour implémenter l'orientation de l'objet.
En C, vous utilisez une structure (struct) pour représenter un objet. Les membres de cette structure représentent les propriétés de l'objet. Les membres de la structure pointant vers des fonctions représentent les méthodes.
Contrairement aux autres langages comme C++ ou Java, il n'y a pas de règles fixes sur l'héritage dans les objets C orientés paradigme. Cependant, il y a quelques conventions qui doivent être suivies qui seront abordées ultérieurement.
Le cycle de vie PHP
Le cycle de vie de PHP comporte 2 cycles basiques :
-
Le cycle de démarrage et d'arrêt du moteur PHP
-
Le cycle d'une demande
Lorsque le moteur PHP démarre, il appèle la fonction d'initialisation du module (MINIT) de chaque extension enregistrée. Ceci permet à chaque module de définir les variables et d'allouer les ressources qui doivent exister pour la durée de vie du processus correspondant au moteur PHP. Lorsque le moteur PHP s'arrête, il appèle la fonction d'arrêt du module (MSHUTDOWN) pour chaque extension.
Pendant la durée de vie du moteur PHP, il recevra des demandes. Chaque demande constitue un autre cycle de vie. Pour chaque requête, le moteur PHP appèlera la fonction d'initialisation de chaque extension. L'extension peut effectuer toutes les définitions de variables ainsi que les allocations de ressources nécessaires pour traiter la demande. Lorsque le cycle de la demande se termine, le moteur appèle la fonction d'arrêt (RSHUTDOWN) pour chaque extension, ainsi, l'extension peut lancer tout le nettoyage nécessaire.
Comment fonctionne un plugin
Un plugin mysqlnd fonctionne en interceptant les appels effectués à mysqlnd par les extensions qui utilisent mysqlnd. Ceci est possible en obtenant la table de fonction mysqlnd, en la sauvegardant, et en la remplaçant par une table de fonction personnalisé, qui appèle les fonctions du plugin.
Le code suivant montre la façon dont la table de fonction mysqlnd est remplacée :
/* un endroit pour stocker la table de fonction originale */ struct st_mysqlnd_conn_methods org_methods; void minit_register_hooks(TSRMLS_D) { /* table de fonction active */ struct st_mysqlnd_conn_methods * current_methods = mysqlnd_conn_get_methods(); /* sauvegarde de la table de fonction originale */ memcpy(&org_methods, current_methods, sizeof(struct st_mysqlnd_conn_methods); /* installation des nouvelles méthodes */ current_methods->query = MYSQLND_METHOD(my_conn_class, query); }
Les manipulation de la table de fonction de connexion doivent être effectuées lors de l'initialisation du module (MINIT). La table de fonction est une ressource globale partagée. Dans un environnement multi-thread, avec une compilation TSRM, la manipulation d'une ressource globale partagée lors d'un processus de demande entraînera la plupart du temps des conflits.
Note:
N'utilisez aucune logique de taille fixe lors de la manipulation de la table de fonction mysqlnd : les nouvelles méthodes peuvent être ajoutées à la fin de la table de fonction. La table de fonction peut être modifiée à tout moment par la suite.
Appel des méthodes parents
Si la table de fonction originale est sauvegardée, il est toujours possible d'appeler les entrées de la table de fonction originale - les méthodes parents.
Dans ce cas, tout comme pour Connection::stmt_init(), il est vital d'appeler la méthode parent avant tout autre activité dans la méthode dérivée.
MYSQLND_METHOD(my_conn_class, query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC) { php_printf("my_conn_class::query(query = %s)\n", query); query = "SELECT 'query rewritten' FROM DUAL"; query_len = strlen(query); return org_methods.query(conn, query, query_len); /* retour avec appel du parent */ }
Étendre des propriétés
Un objet mysqlnd est représenté par une structure C. Il n'est pas possible d'ajouter un membre à une structure C au moment de l'exécution. Les utilisateurs d'objets mysqlnd ne peuvent pas ajouter simplement des propriétés aux objets.
Les données arbitraires (propriétés) peuvent être ajoutées aux objets mysqlnd en utilisant une fonction appropriée de la famille mysqlnd_plugin_get_plugin_<object>_data(). Lors de l'allocation d'un objet, mysqlnd réserve un espace à la fin de l'objet pour accueillir un pointeur void * vers des données arbitraires. mysqlnd réserve un espace pour un pointeur void * par plugin.
La table suivante montre comment calculer la position d'un pointeur pour un plugin spécifique :
Adresse mémoire | Contenus |
0 | Début de la structure C de l'objet mysqlnd |
n | Fin de la structure C de l'objet mysqlnd |
n + (m x sizeof(void*)) | void* vers les données de l'objet du m-ème plugin |
Si vous prévoyez de faire de sous-classe des constructeurs des objets mysqlnd, ce qui est autorisé, vous devez conserver ceci en mémoire !
Le code suivant montre la façon dont on étend des propriétés :
/* toutes les données que nous voulons associer */ typedef struct my_conn_properties { unsigned long query_counter; } MY_CONN_PROPERTIES; /* id du plugin */ unsigned int my_plugin_id; void minit_register_hooks(TSRMLS_D) { /* on obtient un ID unique pour le plugin */ my_plugin_id = mysqlnd_plugin_register(); /* snip - voir l'extension de la connexion : méthodes */ } static MY_CONN_PROPERTIES** get_conn_properties(const MYSQLND *conn TSRMLS_DC) { MY_CONN_PROPERTIES** props; props = (MY_CONN_PROPERTIES**)mysqlnd_plugin_get_plugin_connection_data( conn, my_plugin_id); if (!props || !(*props)) { *props = mnd_pecalloc(1, sizeof(MY_CONN_PROPERTIES), conn->persistent); (*props)->query_counter = 0; } return props; }
Le développeur du plugin est responsable de la gestion de la mémoire associée aux données du plugin.
L'utilisation de l'allocateur de mémoire mysqlnd est recommandée pour les données du plugin. Ces fonctions sont nommées en utilisant la convention suivante : mnd_*loc(). L'allocateur mysqlnd a quelques fonctionnalités bien utiles, comme la possibilité d'utiliser un allocateur de débogage dans une compilation non-débogue.
Quand faire une sous-classe ? | Est-ce que chaque instance a sa table de fonction privée ? | Comment faire une sous-classe ? | |
Connexion (MYSQLND) | MINIT | Non | mysqlnd_conn_get_methods() |
Jeu de résultats (MYSQLND_RES) | MINIT ou après | Oui | mysqlnd_result_get_methods() ou méthode de l'objet de manipulation de la table de fonction |
Méta du jeu de résultats (MYSQLND_RES_METADATA) | MINIT | Non | mysqlnd_result_metadata_get_methods() |
Requête (MYSQLND_STMT) | MINIT | Non | mysqlnd_stmt_get_methods() |
Réseau (MYSQLND_NET) | MINIT ou après | Oui | mysqlnd_net_get_methods() ou méthode de l'objet de manipulation de la table de fonction |
Couche physique (MYSQLND_PROTOCOL) | MINIT ou après | Oui | mysqlnd_protocol_get_methods() ou méthode de l'objet de manipulation de la table de fonction |
Vous ne devez pas manipuler les tables de fonction après MINIT si ce n'est pas autorisé suivant la table ci-dessus.
Quelques classes contiennent un point vers une méthode de la table de fonction. Toutes les instances d'une telle classe partageront la même table de fonction. Pour éviter le chaos, en particulier dans les environnements treahdés, ce genre de tables de fonction ne doit être manipulé que lors du MINIT.
Les autres classes utilisent une copie de la table de fonction globale partagée. Cette copie est créée en même temps que l'objet. Chaque objet utilise sa propre table de fonction. Ceci vous donne 2 options : vous pouvez manipuler la table de fonction par défaut d'un objet au moment du MINIT, et vous pouvez aussi affiner des méthodes d'un objet sans impacter les autres instances de la même classe.
L'avantage de l'approche avec une table de fonction partagée est la performance. Il n'est pas nécessaire de copier une table de fonction pour chaque objet.
Allocation, construction, réinitialisation | Peut-être modifié ? | Appelant | |
Connexion (MYSQLND) | mysqlnd_init() | Non | mysqlnd_connect() |
Jeu de résultats(MYSQLND_RES) | Allocation :
Reset et ré-initialisation lors de :
|
Oui, mais appel du parent ! |
|
Méta du jeu de résultats (MYSQLND_RES_METADATA) | Connection::result_meta_init() | Oui, mais appel du parent ! | Result::read_result_metadata() |
Statement (MYSQLND_STMT) | Connection::stmt_init() | Oui, mais appel du parent ! | Connection::stmt_init() |
Réseau (MYSQLND_NET) | mysqlnd_net_init() | Non | Connection::init() |
Couche physique (MYSQLND_PROTOCOL) | mysqlnd_protocol_init() | Non | Connection::init() |
Il est vivement recommandé de ne pas remplacer entièrement un constructeur. Les constructeurs effectuent les allocations mémoires. Les allocations mémoires sont vitales pour l'API du plugin mysqlnd ainsi que pour la logique de l'objet mysqlnd. Si vous ne vous souciez pas des alertes et que vous insistez pour remplacer les constructeurs, vous devriez au moins appeler le constructeur parent avant de faire quoi que ce soit dans votre constructeur.
Au niveau de toutes les alertes, il peut être utile de faire des sous-classes des constructeurs. Les constructeurs sont les endroits parfaits pour modifier les tables de fonction des objets avec les tables d'objets non partagés, comme les jeux de résultats, le réseau ou encore la couche phyique.
La méthode dérivée doit appeler le parent ? | Destructeur | |
Connexion | oui, après l'exécution de la méthode | free_contents(), end_psession() |
Jeu de résultats | oui, après l'exécution de la méthode | free_result() |
Méta du jeu de résultats | oui, après l'exécution de la méthode | free() |
Requête | oui, après l'exécution de la méthode | dtor(), free_stmt_content() |
Réseau | oui, après l'exécution de la méthode | free() |
Couche physique | oui, après l'exécution de la méthode | free() |
Les destructeurs sont les endroits parfaits pour libérer les propriétés, mysqlnd_plugin_get_plugin_<object>_data().
Les destructeurs listés peuvent ne pas être les équivalents aux méthodes actuelles mysqlnd libérant l'objet lui-même. Cependant, ils sont les meilleurs endroits pour vous pour libérer les données de votre plugin. Tout comme les constructeurs, vous pouvez remplacer les méthodes entières mais ce n'est pas recommandé. Si plusieurs méthodes sont listées dans la table ci-dessus, vous devez modifier toutes les méthodes listées et libérer les données de votre plugin dans la méthode appelée en premier par mysqlnd.
La méthode recommandée pour les plugins est de modifier simplement les méthodes, libérer votre mémoire et appeler l'implémentation du parent immédiatement après.
En raison d'un bogue dans les versions PHP 5.3.0 à 5.3.3, les plugins n'associent pas les données du plugin avec une connexion persistante. Ceci est dû au fait que ext/mysql et ext/mysqli ne lancent pas tous les appels à la méthode mysqlnd end_psession() et le plugin peut subir une fuite mémoire. Ce bogue est corrigé en PHP 5.3.4.