Files
doc-fr/reference/mysqlnd/plugin.xml

1465 lines
47 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: e50e79746736dbdfbabe9bd3566793b3ddf38f58 Maintainer: yannick Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="mysqlnd.plugin" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>API du plugin du driver natif MySQL</title>
<para>
L'API du plugin du driver natif MySQL est une fonctionnalité
du driver natif MySQL, ou <literal>mysqlnd</literal>.
Le plugin <literal>Mysqlnd</literal> opère sur la couche entre
les applications PHP et le serveur MySQL. Il est comparable
à un proxy MySQL. Un proxy MySQL opère sur une couche entre toutes
les applications clientes MySQL, par exemple, une application PHP
et un serveur MySQL. Le plugin <literal>Mysqlnd</literal>
peut entreprendre des tâches typiques de proxy MySQL comme
l'équilibrage de charge, ainsi que le suivi et l'optimisation
des performances. En raison d'une architecture et d'une localisation
différente, le plugin <literal>mysqlnd</literal> n'a pas tous les
inconvénients d'un proxy MySQL. Par exemple, avec le plugin, il
n'y a pas qu'un seul point d'échec, pas de serveur de proxy dédié
à déployer, et pas de nouveau langage à apprendre (Lua).
</para>
<para>
Un plugin <literal>mysqlnd</literal> peut être exécuté comme une extension
à <literal>mysqlnd</literal>. Un plugin peut intercepter la majorité des
fonctions <literal>mysqlnd</literal>. Les fonctions <literal>mysqlnd</literal>
sont appelées par l'extension PHP MySQL comme
<literal>ext/mysql</literal>, <literal>ext/mysqli</literal>, et
<literal>PDO_MYSQL</literal>. Comme résultat, il est possible pour un
plugin <literal>mysqlnd</literal> d'intercepter tous les appels effectués
par ces extensions depuis une application cliente.
</para>
<para>
Les appels aux fonctions internes <literal>mysqlnd</literal> peuvent
également être interceptés ou remplacés. Il n'y a aucune restriction
sur la manipulation des tables de fonctions internes <literal>mysqlnd</literal>.
Il est possible de définir des actions pour faire que lorsque
certaines fonctions <literal>mysqlnd</literal> sont appelées
par l'extension qui utilise <literal>mysqlnd</literal>, l'appel
est redirigé vers la fonction appropriée du plugin
<literal>mysqlnd</literal>. La possibilité de manipuler les tables
de fonctions internes <literal>mysqlnd</literal> dans ce sens permet
un maximum de flexibilité.
</para>
<para>
Le plugin <literal>Mysqlnd</literal> est en faite, une extension PHP,
écrit en C, qui utilise l'API du plugin <literal>mysqlnd</literal>
(qui est compilé dans le driver natif MySQL, <literal>mysqlnd</literal>).
Le plugin peut être à 100% transparent pour les applications PHP. Aucune
modification aux applications n'est nécessaire car le plugin opère
sur une couche différente. Le plugin <literal>mysqlnd</literal>
peut être utilisé dans une couche en dessous de <literal>mysqlnd</literal>.
</para>
<para>
La liste suivante représente quelques applications possibles
du plugin <literal>mysqlnd</literal>.
</para>
<itemizedlist>
<listitem>
<para>
L'équilibrage de charge.
</para>
<itemizedlist>
<listitem>
<para>
Séparation des lectures et des écritures. Un exemple de cette fonctionnalité
est l'extension PECL/mysqlnd_ms (Maître/esclave). Cette extension sépare
les requêtes de lecture et d'écriture pour une configuration de réplication.
</para>
</listitem>
<listitem>
<para>
Basculement
</para>
</listitem>
<listitem>
<para>
Round-Robin, le moins chargé
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
Surveillance
</para>
<itemizedlist>
<listitem>
<para>
Journalisation des requêtes
</para>
</listitem>
<listitem>
<para>
Analyse de requêtes
</para>
</listitem>
<listitem>
<para>
Audite de requêtes. Un exemple de ceci est l'extension
PECL/mysqlnd_sip (SQL Injection Protection). Cette extension
inspecte les requêtes et exécute uniquement celles qui sont
autorisées suivants des ensembles de règles.
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
Performance
</para>
<itemizedlist>
<listitem>
<para>
La mise en cache. Un exemple de ceci est l'extension
PECL/mysqlnd_qc (Query Cache).
</para>
</listitem>
<listitem>
<para>
Étranglement
</para>
</listitem>
<listitem>
<para>
Fragmentation. Un exemple de ceci est l'extension
PECL/mysqlnd_mc (Multi Connect). Cette extension tente
de séparer une requête SELECT en n parties, en utilisant
des requêtes du type SELECT ... LIMIT part_1, SELECT LIMIT part_n.
L'extension envoie les requêtes à des serveurs MySQL distincts
et fusionne ensuite le résultat à destination du client.
</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
<para>
<emphasis role="bold">PLugins du driver natif MySQL disponibles</emphasis>
</para>
<para>
Il y a déjà plusieurs plugins mysqlnd de disponible.
</para>
<itemizedlist>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_mc</emphasis> -Plugin Multi Connexion.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_ms</emphasis> - Plugin Maître Esclave.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_qc</emphasis> - Plugin de mise en cache de requêtes.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_pscache</emphasis> - Plugin de mise en cache de gestionnaire
de requêtes préparées.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_sip</emphasis> - Plugin permettant la protection
contre les injections SQL.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_uh</emphasis> - Plugin de gestionnaire d'utilisateurs.
</para>
</listitem>
</itemizedlist>
<section xml:id="mysqlnd.plugin.mysql-proxy">
<title>Comparaison des plugins mysqlnd avec proxy MySQL</title>
<para>
Les plugins <literal>Mysqlnd</literal> et le proxy MySQL sont
des technologies différentes utilisant différentes approches.
Les deux sont des outils valides pour résoudre bon nombre
de tâches classiques, comme l'équilibrage de charge, la surveillance,
et l'amélioration des performances. Une importante différence est
que le proxy MySQL fonctionne avec tous les clients MySQL alors que
les plugins <literal>mysqlnd</literal> sont spécifiques aux
applications PHP.
</para>
<para>
Comme une extension PHP, un plugin <literal>mysqlnd</literal>
doit être installé sur le serveur d'applications PHP, en plus du
reste de PHP. Un proxy MySQL peut soit fonctionner sur le serveur
d'applications PHP, soit être installé sur une machine dédiée pour
gérer plusieurs serveurs d'applications PHP.
</para>
<para>
Le déploiement d'un proxy MySQL sur un serveur d'applications
a 2 avantages :
</para>
<orderedlist>
<listitem>
<para>
Pas un seul point d'échec
</para>
</listitem>
<listitem>
<para>
Facile à redimensionner (redimensionnement horizontal,
redimensionnement par le client)
</para>
</listitem>
</orderedlist>
<para>
Un proxy MySQL (et les plugins <literal>mysqlnd</literal>) peut
résoudre des problèmes facilement, qui sinon, auraient dû nécessiter
des modifications aux applications existantes.
</para>
<para>
Cependant, un proxy MySQL a quelques désavantages :
</para>
<itemizedlist>
<listitem>
<para>
Un proxy MySQL est un nouveau composant, une nouvelle technologie à
appliquer au maître et à déployer.
</para>
</listitem>
<listitem>
<para>
Un proxy MySQL nécessite la connaissance du langage de script Lua.
</para>
</listitem>
</itemizedlist>
<para>
Un proxy MySQL peut être personnalisé en utilisant les langages de
programmation C et Lua. Lua est le langage préféré pour un proxy MySQL.
Pour la plupart des experts PHP, Lua est un nouveau langage à apprendre.
Un plugin <literal>mysqlnd</literal> peut être écrit en C. Il est également
possible d'écrire un plugin en PHP en utilisant
<link xlink:href="http://pecl.php.net/package/mysqlnd_uh">PECL/mysqlnd_uh</link>.
</para>
<para>
Un proxy MySQL fonctionne comme un démon - un processus en arrière-plan.
Un proxy MySQL peut rappeler des décisions prises antérieurement,
vu que tous les états peuvent être conservés. Cependant, un plugin
<literal>mysqlnd</literal> est lié au cycle de vie d'une requête PHP.
Un proxy MySQL peut également partager des résultats calculés une seule fois
sur plusieurs serveurs d'applications. Un plugin <literal>mysqlnd</literal>
peut donc avoir besoin de stocker des données dans un médium persistant.
Un autre démon peut être utilisé dans ce but, comme par exemple,
Memcache. Ce mécanisme donne un avantage au proxy MySQL.
</para>
<para>
Un proxy MySQL fonctionne au dessus de la couche physique. Avec un
proxy MySQL, vous devez analyser et effectuer du "reverse engineering"
du protocole client serveur MySQL. Les actions sont limitées à celles
qui peuvent être effectuées par la manipulation du protocole
de communication. Si la couche physique change (ce qui arrive très rarement),
les scripts du proxy MySQL peut devoir être adaptés.
</para>
<para>
Les plugins <literal>Mysqlnd</literal> fonctionnent au dessus de l'API C,
reflétant ainsi les APIs client <literal>libmysqlclient</literal>.
Cette API C est essentiellement une enveloppe du protocole Serveur Client MySQL,
ou de la couche physique, vu qu'elle est appelée quelques fois. Vous pouvez
intercepter tous les appels à l'API C. PHP utilise l'API C, toutefois,
vous pouvez connecter tous les appels PHP, sans avoir besoin de programmer
au niveau de la couche physique.
</para>
<para>
<literal>Mysqlnd</literal> implémente la couche physique. Les plugins
peuvent toutefois analyser, effectuer un "reverse engineering", manipuler
et toujours remplacer le protocole de communication. Cependant, ce n'est
généralement pas nécessaire.
</para>
<para>
Vu que les plugins vous autorisent à créer des implémentations qui
utilisent les 2 niveaux (API C et couche physique), ils ont plus de flexibilité
que le proxy MySQL. Si un plugin <literal>mysqlnd</literal> est implémenté
en utilisant l'API C, toutes les modifications ultérieures à la couche
physique ne nécessiteront pas de modification au plugin en tant que tel.
</para>
</section>
<section xml:id="mysqlnd.plugin.obtaining">
<title>Obtenir l'API du plugin mysqlnd</title>
<para>
L'API du plugin <literal>mysqlnd</literal> est simplement une partie
de l'extension du driver PHP Natif MySQL, <literal>ext/mysqlnd</literal>.
Le développement de l'API du plugin <literal>mysqlnd</literal> commença
en Décembre 2009. Il est développé comme une partie du dépôt source de PHP,
et ainsi, est disponible depuis soit le dépôt public Git, soit depuis
le téléchargement des sources.
</para>
<para>
Les développeurs de plugin peuvent déterminer la version de
<literal>mysqlnd</literal> via la variable
<literal>MYSQLND_VERSION</literal>, au format
<quote>mysqlnd 5.0.7-dev - 091210 - $Revision: 300535</quote>,
ou via <literal>MYSQLND_VERSION_ID</literal>, qui est un entier
comme par exemple 50007. Les développeurs peuvent calculer le numéro
de version comme ceci :
</para>
<table xml:id="mysqlnd.plugin.version-id">
<title>Table de calcul des MYSQLND_VERSION_ID</title>
<tgroup cols="2">
<thead>
<row>
<entry>Version (partie)</entry>
<entry>Exemple</entry>
</row>
</thead>
<tbody>
<row>
<entry>Majeur*10000</entry>
<entry>5*10000 = 50000</entry>
</row>
<row>
<entry>Mineur*100</entry>
<entry>0*100 = 0</entry>
</row>
<row>
<entry>Patch</entry>
<entry>7 = 7</entry>
</row>
<row>
<entry>MYSQLND_VERSION_ID</entry>
<entry>50007</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Lors du développement, les développeurs doivent se référer
au numéro de version <literal>mysqlnd</literal> pour des tests
de compatibilité et de version, sachant que plusieurs
versions de <literal>mysqlnd</literal> peuvent survenir durant
un cycle de vie de la branche de développement de PHP.
</para>
</section>
<section xml:id="mysqlnd.plugin.architecture">
<title>Architecture du plugin du driver natif</title>
<para>
Cette section fournit un aperçu de l'architecture du plugin
<literal>mysqlnd</literal>.
</para>
<para>
<emphasis role="bold">Aperçu du driver natif MySQL</emphasis>
</para>
<para>
Avant de développer des plugins <literal>mysqlnd</literal>,
il est utile d'avoir une connaissance minimale sur l'organisation
de <literal>mysqlnd</literal>. <literal>Mysqlnd</literal> est composé
des modules suivants :
</para>
<table xml:id="mysqlnd.plugin.orgchart">
<title>Schéma de l'organisation mysqlnd, par module</title>
<tgroup cols="2">
<thead>
<row>
<entry>Modules de statistiques</entry>
<entry>mysqlnd_statistics.c</entry>
</row>
</thead>
<tbody>
<row>
<entry>Connexion</entry>
<entry>mysqlnd.c</entry>
</row>
<row>
<entry>Jeu de résultats</entry>
<entry>mysqlnd_result.c</entry>
</row>
<row>
<entry>Données méta du jeu de résultats</entry>
<entry>mysqlnd_result_meta.c</entry>
</row>
<row>
<entry>Requête</entry>
<entry>mysqlnd_ps.c</entry>
</row>
<row>
<entry>Réseau</entry>
<entry>mysqlnd_net.c</entry>
</row>
<row>
<entry>Couche physique</entry>
<entry>mysqlnd_wireprotocol.c</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
<emphasis role="bold">Objet C orienté paradigme</emphasis>
</para>
<para>
Au niveau du code, <literal>mysqlnd</literal> utilise un masque C
pour implémenter l'orientation de l'objet.
</para>
<para>
En C, vous utilisez une structure (<literal>struct</literal>)
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.
</para>
<para>
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.
</para>
<para>
<emphasis role="bold">Le cycle de vie PHP</emphasis>
</para>
<para>
Le cycle de vie de PHP comporte 2 cycles basiques :
</para>
<itemizedlist>
<listitem>
<para>
Le cycle de démarrage et d'arrêt du moteur PHP
</para>
</listitem>
<listitem>
<para>
Le cycle d'une demande
</para>
</listitem>
</itemizedlist>
<para>
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.
</para>
<para>
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.
</para>
<para>
<emphasis role="bold">Comment fonctionne un plugin</emphasis>
</para>
<para>
Un plugin <literal>mysqlnd</literal> fonctionne en interceptant les appels
effectués à <literal>mysqlnd</literal> par les extensions qui utilisent
<literal>mysqlnd</literal>. Ceci est possible en obtenant la table
de fonction <literal>mysqlnd</literal>, en la sauvegardant, et en la
remplaçant par une table de fonction personnalisé, qui appelle les fonctions
du plugin.
</para>
<para>
Le code suivant montre la façon dont la table de fonction
<literal>mysqlnd</literal> est remplacée :
</para>
<programlisting>
<![CDATA[
/* 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);
}
]]>
</programlisting>
<para>
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.
</para>
<note>
<para>
N'utilisez aucune logique de taille fixe lors de la manipulation
de la table de fonction <literal>mysqlnd</literal> : 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.
</para>
</note>
<para>
<emphasis role="bold">Appel des méthodes parents</emphasis>
</para>
<para>
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.
</para>
<para>
Dans ce cas, tout comme pour <literal>Connection::stmt_init()</literal>,
il est vital d'appeler la méthode parent avant toute autre activité
dans la méthode dérivée.
</para>
<programlisting>
<![CDATA[
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 */
}
]]>
</programlisting>
<para>
<emphasis role="bold">Étendre des propriétés</emphasis>
</para>
<para>
Un objet <literal>mysqlnd</literal> 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 <literal>mysqlnd</literal>
ne peuvent pas ajouter simplement des propriétés aux objets.
</para>
<para>
Les données arbitraires (propriétés) peuvent être ajoutées aux objets
<literal>mysqlnd</literal> en utilisant une fonction appropriée de la
famille <literal>mysqlnd_plugin_get_plugin_&lt;object&gt;_data()</literal>.
Lors de l'allocation d'un objet, <literal>mysqlnd</literal> réserve
un espace à la fin de l'objet pour accueillir un pointeur
<literal>void *</literal> vers des données arbitraires.
<literal>mysqlnd</literal> réserve un espace pour un pointeur
<literal>void *</literal> par plugin.
</para>
<para>
La table suivante montre comment calculer la position d'un pointeur
pour un plugin spécifique :
</para>
<table xml:id="mysqlnd.plugin.pointercalc">
<title>Calcul des pointeurs pour mysqlnd</title>
<tgroup cols="2">
<thead>
<row>
<entry>Adresse mémoire</entry>
<entry>Contenus</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>Début de la structure C de l'objet mysqlnd</entry>
</row>
<row>
<entry>n</entry>
<entry>Fin de la structure C de l'objet mysqlnd</entry>
</row>
<row>
<entry>n + (m x sizeof(void*))</entry>
<entry>void* vers les données de l'objet du m-ème plugin</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Si vous prévoyez de faire des sous-classes des constructeurs
des objets <literal>mysqlnd</literal>, ce qui est autorisé,
vous devez conserver ceci en mémoire !
</para>
<para>
Le code suivant montre la façon dont on étend des propriétés :
</para>
<programlisting>
<![CDATA[
/* 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;
}
]]>
</programlisting>
<para>
Le développeur du plugin est responsable de la gestion de la mémoire
associée aux données du plugin.
</para>
<para>
L'utilisation de l'allocateur de mémoire <literal>mysqlnd</literal>
est recommandée pour les données du plugin. Ces fonctions sont nommées
en utilisant la convention suivante : <literal>mnd_*loc()</literal>.
L'allocateur <literal>mysqlnd</literal> a quelques fonctionnalités bien utiles,
comme la possibilité d'utiliser un allocateur de débogage dans une compilation
non-débogue.
</para>
<table xml:id="mysqlnd.plugin.subclass">
<title>Quand et comment faire une sous-classe</title>
<tgroup cols="4">
<thead>
<row>
<entry></entry>
<entry>Quand faire une sous-classe ?</entry>
<entry>Est-ce que chaque instance a sa table de fonction privée ?</entry>
<entry>Comment faire une sous-classe ?</entry>
</row>
</thead>
<tbody>
<row>
<entry>Connexion (MYSQLND)</entry>
<entry>MINIT</entry>
<entry>Non</entry>
<entry>mysqlnd_conn_get_methods()</entry>
</row>
<row>
<entry>Jeu de résultats (MYSQLND_RES)</entry>
<entry>MINIT ou après</entry>
<entry>Oui</entry>
<entry>mysqlnd_result_get_methods() ou méthode de l'objet de manipulation de la table de fonction</entry>
</row>
<row>
<entry>Méta du jeu de résultats (MYSQLND_RES_METADATA)</entry>
<entry>MINIT</entry>
<entry>Non</entry>
<entry>mysqlnd_result_metadata_get_methods()</entry>
</row>
<row>
<entry>Requête (MYSQLND_STMT)</entry>
<entry>MINIT</entry>
<entry>Non</entry>
<entry>mysqlnd_stmt_get_methods()</entry>
</row>
<row>
<entry>Réseau (MYSQLND_NET)</entry>
<entry>MINIT ou après</entry>
<entry>Oui</entry>
<entry>mysqlnd_net_get_methods() ou méthode de l'objet de manipulation de la table de fonction</entry>
</row>
<row>
<entry>Couche physique (MYSQLND_PROTOCOL)</entry>
<entry>MINIT ou après</entry>
<entry>Oui</entry>
<entry>mysqlnd_protocol_get_methods() ou méthode de l'objet de manipulation de la table de fonction</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Vous ne devez pas manipuler les tables de fonction après MINIT si
ce n'est pas autorisé suivant la table ci-dessus.
</para>
<para>
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 threadés, ce genre de tables de fonction
ne doit être manipulé que lors du MINIT.
</para>
<para>
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.
</para>
<para>
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.
</para>
<table xml:id="mysqlnd.plugin.constatus">
<title>Statut du constructeur</title>
<tgroup cols="4">
<thead>
<row>
<entry>Type</entry>
<entry>Allocation, construction, réinitialisation</entry>
<entry>Peut-être modifié ?</entry>
<entry>Appelant</entry>
</row>
</thead>
<tbody>
<row>
<entry>Connexion (MYSQLND)</entry>
<entry>mysqlnd_init()</entry>
<entry>Non</entry>
<entry>mysqlnd_connect()</entry>
</row>
<row>
<entry>Jeu de résultats(MYSQLND_RES)</entry>
<entry><para>
Allocation :
</para>
<itemizedlist>
<listitem>
<para>
Connection::result_init()
</para>
</listitem>
</itemizedlist>
<para>
Reset et ré-initialisation lors de :
</para>
<itemizedlist>
<listitem>
<para>
Result::use_result()
</para>
</listitem>
<listitem>
<para>
Result::store_result
</para>
</listitem>
</itemizedlist></entry>
<entry>Oui, mais appel du parent !</entry>
<entry><itemizedlist>
<listitem>
<para>
Connection::list_fields()
</para>
</listitem>
<listitem>
<para>
Statement::get_result()
</para>
</listitem>
<listitem>
<para>
Statement::prepare() (Méta-données uniquement)
</para>
</listitem>
<listitem>
<para>
Statement::resultMetaData()
</para>
</listitem>
</itemizedlist></entry>
</row>
<row>
<entry>Méta du jeu de résultats (MYSQLND_RES_METADATA)</entry>
<entry>Connection::result_meta_init()</entry>
<entry>Oui, mais appel du parent !</entry>
<entry>Result::read_result_metadata()</entry>
</row>
<row>
<entry>Statement (MYSQLND_STMT)</entry>
<entry>Connection::stmt_init()</entry>
<entry>Oui, mais appel du parent !</entry>
<entry>Connection::stmt_init()</entry>
</row>
<row>
<entry>Réseau (MYSQLND_NET)</entry>
<entry>mysqlnd_net_init()</entry>
<entry>Non</entry>
<entry>Connection::init()</entry>
</row>
<row>
<entry>Couche physique (MYSQLND_PROTOCOL)</entry>
<entry>mysqlnd_protocol_init()</entry>
<entry>Non</entry>
<entry>Connection::init()</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
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 <literal>mysqlnd</literal>
ainsi que pour la logique de l'objet <literal>mysqlnd</literal>. 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.
</para>
<para>
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.
</para>
<table xml:id="mysqlnd.plugin.deststatus">
<title>Statut du destructeur</title>
<tgroup cols="3">
<thead>
<row>
<entry>Type</entry>
<entry>La méthode dérivée doit appeler le parent ?</entry>
<entry>Destructeur</entry>
</row>
</thead>
<tbody>
<row>
<entry>Connexion</entry>
<entry>oui, après l'exécution de la méthode</entry>
<entry>free_contents(), end_psession()</entry>
</row>
<row>
<entry>Jeu de résultats</entry>
<entry>oui, après l'exécution de la méthode</entry>
<entry>free_result()</entry>
</row>
<row>
<entry>Méta du jeu de résultats</entry>
<entry>oui, après l'exécution de la méthode</entry>
<entry>free()</entry>
</row>
<row>
<entry>Requête</entry>
<entry>oui, après l'exécution de la méthode</entry>
<entry>dtor(), free_stmt_content()</entry>
</row>
<row>
<entry>Réseau</entry>
<entry>oui, après l'exécution de la méthode</entry>
<entry>free()</entry>
</row>
<row>
<entry>Couche physique</entry>
<entry>oui, après l'exécution de la méthode</entry>
<entry>free()</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Les destructeurs sont les endroits parfaits pour libérer les propriétés,
<literal>mysqlnd_plugin_get_plugin_<replaceable>&lt;object&gt;</replaceable>_data()</literal>.
</para>
<para>
Les destructeurs listés peuvent ne pas être les équivalents aux
méthodes actuelles <literal>mysqlnd</literal> 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 <literal>mysqlnd</literal>.
</para>
<para>
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.
</para>
</section>
<section xml:id="mysqlnd.plugin.api">
<title>L'API du plugin mysqlnd</title>
<para>
Voici la liste des fonctions fournies dans l'API plugin
<literal>mysqlnd</literal> :
</para>
<itemizedlist>
<listitem>
<para>
mysqlnd_plugin_register()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_count()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_connection_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_result_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_stmt_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_net_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_protocol_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_conn_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_result_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_result_meta_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_stmt_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_net_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_protocol_get_methods()
</para>
</listitem>
</itemizedlist>
<para>
Il n'y a pas de définition formelle de ce qu'est un plugin
ainsi de la façon dont fonctionne un plugin.
</para>
<para>
Les composants les plus souvent trouvés dans les mécanismes de plugin sont :
</para>
<itemizedlist>
<listitem>
<para>
Un gestionnaire de plugin
</para>
</listitem>
<listitem>
<para>
Une API du plugin
</para>
</listitem>
<listitem>
<para>
Les services applicatifs (ou modules)
</para>
</listitem>
<listitem>
<para>
Les APIs des services applicatifs (ou APIs du module)
</para>
</listitem>
</itemizedlist>
<para>
Le concept d'un plugin <literal>mysqlnd</literal> utilise ces fonctionnalités,
ainsi que d'autres joyeusetés d'architecture ouverte.
</para>
<para>
<emphasis role="bold">Aucune restriction</emphasis>
</para>
<para>
Un plugin a un accès total aux travaux internes de
<literal>mysqlnd</literal>. Il n'y a aucune limite de sécurité
ou de restrictions. Tout peut être écrasé pour implémenter des
algorithmes utiles ou hostiles. Il est recommandé de ne déployer
que des plugins depuis des sources de confiance.
</para>
<para>
Tel que discuté précédemment, les plugins peuvent utiliser librement
des pointeurs. Ces pointeurs ne sont restreints en aucune manière,
aussi, vous pouvez pointer vers les données d'un autre plugin.
Une simple position arithmétique peut être utilisée pour lire
les données d'un autre plugin.
</para>
<para>
Il est recommandé d'écrire des plugins coopératifs, et ainsi, appeler
toujours la méthode parent. Les plugins devraient toujours coopérer avec
<literal>mysqlnd</literal>.
</para>
<table xml:id="mysqlnd.plugin.chaining">
<title>Enjeux : un exemple de chaînage et de coopération</title>
<tgroup cols="3">
<thead>
<row>
<entry>Extension</entry>
<entry>Pointeur mysqlnd.query()</entry>
<entry>Pile d'appel si on appelle le parent</entry>
</row>
</thead>
<tbody>
<row>
<entry>ext/mysqlnd</entry>
<entry>mysqlnd.query()</entry>
<entry>mysqlnd.query</entry>
</row>
<row>
<entry>ext/mysqlnd_cache</entry>
<entry>mysqlnd_cache.query()</entry>
<entry><orderedlist>
<listitem>
<para>
mysqlnd_cache.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd.query
</para>
</listitem>
</orderedlist></entry>
</row>
<row>
<entry>ext/mysqlnd_monitor</entry>
<entry>mysqlnd_monitor.query()</entry>
<entry><orderedlist>
<listitem>
<para>
mysqlnd_monitor.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd_cache.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd.query
</para>
</listitem>
</orderedlist></entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Dans ce scénario, un plugin cache (<literal>ext/mysqlnd_cache</literal>) et
un plugin de surveillance (<literal>ext/mysqlnd_monitor</literal>)
sont chargés. Les 2 ont une sous-classe de <literal>Connection::query()</literal>.
L'enregistrement du plugin survient lors du <literal>MINIT</literal>
en utilisant la logique évoquée précédemment. PHP appelle les extensions
dans un ordre alphabétique par défaut. Les plugins ne sont pas au courant
les uns les autres et ne peuvent fixer de dépendances.
</para>
<para>
Par défaut, les plugins appellent l'implémentation du parent de la
méthode de requête dans leur version de la méthode dérivée.
</para>
<para>
<emphasis role="bold">Récapitulatif de l'extension PHP</emphasis>
</para>
<para>
Voici un récapitulatif de ce qui survient lors de l'utilisation
d'un plugin d'exemple, <literal>ext/mysqlnd_plugin</literal>,
qui expose l'API C du plugin <literal>mysqlnd</literal> à PHP :
</para>
<itemizedlist>
<listitem>
<para>
Toutes les applications PHP MySQL tente d'établir une connexion
à l'adresse 192.168.2.29
</para>
</listitem>
<listitem>
<para>
L'application PHP utilisera <literal>ext/mysql</literal>,
<literal>ext/mysqli</literal> ou <literal>PDO_MYSQL</literal>.
Ces 3 extensions PHP MySQL utilisent <literal>mysqlnd</literal> pour
établir la connexion à l'adresse 192.168.2.29.
</para>
</listitem>
<listitem>
<para>
<literal>Mysqlnd</literal> appelle sa méthode de connexion, qui a été sous-classé
par <literal>ext/mysqlnd_plugin</literal>.
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd_plugin</literal> appelle la méthode de l'espace utilisateur
<literal>proxy::connect()</literal> enregistrée par l'utilisateur.
</para>
</listitem>
<listitem>
<para>
L'espace utilisateur modifie l'hôte de connexion de 192.168.2.29
à 127.0.0.1 et retourne la connexion établie par
<literal>parent::connect()</literal>.
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd_plugin</literal> exécute l'équivalent de
<literal>parent::connect(127.0.0.1)</literal> en appelant la méthode
originale de <literal>mysqlnd</literal> pour établir une connexion.
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd</literal> établit une connexion et redonne la main
à <literal>ext/mysqlnd_plugin</literal>.
<literal>ext/mysqlnd_plugin</literal> retourne également.
</para>
</listitem>
<listitem>
<para>
Quelque soit l'extension PHP MySQL utilisée par l'application,
elle reçoit une connexion à 127.0.0.1. L'extension PHP MySQL
redonne la main à l'application PHP. Le cycle est clos.
</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="mysqlnd.plugin.developing">
<title>Bien commencer la compilation d'un plugin mysqlnd</title>
<para>
Il est important de se souvenir qu'un plugin <literal>mysqlnd</literal>
est lui-même une extension PHP.
</para>
<para>
Le code suivant montre la structure basique d'une fonction MINIT
utilisée dans un plugin typique <literal>mysqlnd</literal> :
</para>
<programlisting>
<![CDATA[
/* 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);
}
]]>
</programlisting>
<programlisting>
<![CDATA[
/* my_mysqlnd_plugin.c */
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, query)(/* ... */) {
/* ... */
}
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, connect)(/* ... */) {
/* ... */
}
]]>
</programlisting>
<para>
<emphasis role="bold">Tâche d'analyse : depuis C vers l'espace utilisateur</emphasis>
</para>
<programlisting>
<![CDATA[
class proxy extends mysqlnd_plugin_connection {
public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
]]>
</programlisting>
<para>
Process:
</para>
<orderedlist>
<listitem>
<para>
PHP : l'utilisateur enregistre une fonction de rappel pour le plugin
</para>
</listitem>
<listitem>
<para>
PHP : l'utilisateur appelle une méthode de l'API PHP MySQL pour se connecter à MySQL
</para>
</listitem>
<listitem>
<para>
C : ext/*mysql* appelle la méthode mysqlnd
</para>
</listitem>
<listitem>
<para>
C : mysqlnd se termine dans ext/mysqlnd_plugin
</para>
</listitem>
<listitem>
<para>
C : ext/mysqlnd_plugin
<orderedlist>
<listitem>
<para>
Appel de la fonction de rappel de l'espace utilisateur
</para>
</listitem>
<listitem>
<para>
Ou la méthode originale <literal>mysqlnd</literal>, si l'espace
utilisateur n'a pas défini de fonction de rappel
</para>
</listitem>
</orderedlist>
</para>
</listitem>
</orderedlist>
<para>
Vous devez effectuer les opérations suivantes :
</para>
<orderedlist>
<listitem>
<para>
Écrire une classe "mysqlnd_plugin_connection" en C
</para>
</listitem>
<listitem>
<para>
Accepter et enregistrer l'objet proxy via
"mysqlnd_plugin_set_conn_proxy()"
</para>
</listitem>
<listitem>
<para>
Appeler les méthodes de proxy de l'espace utilisateur
depuis C (optimisation - zend_interfaces.h)
</para>
</listitem>
</orderedlist>
<para>
Les méthodes de l'objet de l'espace utilisateur peuvent soit être
appelées en utilisant <literal>call_user_function()</literal>,
soit vous pouvez opérer à un niveau en dessous du moteur Zend et
utiliser <literal>zend_call_method()</literal>.
</para>
<para>
<emphasis role="bold">Optimisation : appel des méthodes depuis C en utilisant
zend_call_method</emphasis>
</para>
<para>
Le code suivant montre un prototype pour la fonction
<literal>zend_call_method</literal>, issue de
<filename>zend_interfaces.h</filename>.
</para>
<programlisting>
<![CDATA[
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
);
]]>
</programlisting>
<para>
L'API Zend supporte 2 arguments. Vous pouvez en avoir besoin de plus, par
exemple :
</para>
<programlisting>
<![CDATA[
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
);
]]>
</programlisting>
<para>
Pour contourner ce problème, vous devrez faire une copie
de <literal>zend_call_method()</literal> et ajouter une
fonctionnalité pour ajouter des paramètres. Vous pouvez
réaliser ceci en créant un jeu de macros
<literal>MY_ZEND_CALL_METHOD_WRAPPER</literal>.
</para>
<para>
<emphasis role="bold">Appel de l'espace utilisateur PHP</emphasis>
</para>
<para>
Le code ci-dessous montre la méthode optimisée pour effectuer un
appel à une fonction de l'espace utilisateur depuis C :
</para>
<programlisting>
<![CDATA[
/* 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;
}
]]>
</programlisting>
<para>
<emphasis role="bold">Appel de l'espace utilisateur: arguments simples</emphasis>
</para>
<programlisting>
<![CDATA[
/* 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);
/* ... */
}
/* ... */
}
]]>
</programlisting>
<para>
<emphasis role="bold">Appel de l'espace utilisateur : structures comme arguments</emphasis>
</para>
<programlisting>
<![CDATA[
/* 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);
/* ... */
}
/* ... */
}
]]>
</programlisting>
<para>
Le premier argument de toutes les méthodes <literal>mysqlnd</literal>
est un objet C. Par exemple, le premier argument de la méthode
connect() est un pointeur vers <literal>MYSQLND</literal>.
La structure MYSQLND représente un objet de connexion
<literal>mysqlnd</literal>.
</para>
<para>
Le pointeur de l'objet de connexion <literal>mysqlnd</literal>
peut être comparé à un pointeur de fichier standard I/O.
Tout comme un pointeur de fichier standard I/O, un objet de
connexion <literal>mysqlnd</literal> doit être lié à l'espace
utilisateur en utilisant une variable PHP de type ressource.
</para>
<para>
<emphasis role="bold">Depuis C vers l'espace utilisateur, puis, retour</emphasis>
</para>
<programlisting>
<![CDATA[
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());
]]>
</programlisting>
<para>
Les utilisateurs PHP doivent pouvoir appeler l'implémentation
du parent d'une méthode écrasée.
</para>
<para>
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".
</para>
<para>
<emphasis role="bold">Construction d'une classe : mysqlnd_plugin_connection::connect()</emphasis>
</para>
<programlisting>
<![CDATA[
/* 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;
}
]]>
</programlisting>
</section>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->