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, on utilise 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 :
Lorsque le moteur PHP dĂ©marre, il appelle 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 appelle 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 appellera 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 appelle 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ée, qui appelle 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 manipulations 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 multithread, 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'utiliser 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 toute 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 l'on prévoit de faire des sous-classes des constructeurs
des objets mysqlnd, ce qui est autorisé,
il faut 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 |
Il ne faut pas manipuler les tables de fonction aprÚs MINIT si ce n'est pas autorisé suivant la table ci-dessus.
Quelques classes contiennent un pointeur 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 threadĂ©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 donne 2 options : il est possible de manipuler la table de fonction par dĂ©faut d'un objet au moment du MINIT, et il est Ă©galement possible d'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.
| Type | Allocation, construction, rĂ©initialisation | Peut-ĂȘtre modifiĂ© ? | Appelant |
|---|---|---|---|
| Connexion (MYSQLND) | mysqlnd_init() | Non | mysqlnd_connect() |
| Jeu de résultats(MYSQLND_RES) |
Allocation :
|
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
l'on ne se soucie pas des alertes et que l'on insiste pour
remplacer les constructeurs, il est recommandé d'au moins appeler
le constructeur parent avant de faire quoi que ce soit dans le
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 physique.
| Type | 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 libérer
les données du plugin. Tout comme les constructeurs, il est possible de
remplacer les méthodes entiÚres mais ce n'est pas recommandé.
Si plusieurs méthodes sont listées dans la table ci-dessus, il faut
modifier toutes les méthodes listées et libérer les données du
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 la mémoire et appeler l'implémentation du parent immédiatement aprÚs.