1. Une toute première extension
The effective exploitation of his powers of abstraction must be regarded as one of the most vital activities of a competent programmer.
– Edsger W. Dijkstra
Créer un squelette d’extension PHP, qui ne fait rien, mais qui est reconnue en tant que telle et chargée par le moteur, demande de créer quelques fichiers : un fichier de code source C, un fichier d’en-têtes et un fichier permettant de configurer la compilation de l’extension.
1.1 Fichiers sources et configuration de la compilation
Dans sa version la plus simple qui soit, une extension PHP n’est composée que de quelques fichiers :
- un fichier
.c, qui contiendra le code source de l’extension, - un fichier
.h, qui, à terme, hébergera les définitions de types et de fonctions, - et un fichier
config.m4qui configure la compilation sous les systèmes UNIX.
En complément, on trouve aussi, généralement :
- quatre fichiers texte nommés
README,CREDITS,API-version, etRELEASE-version, - un fichier
LICENSEouCOPYINGindiquant sous quelle licence l’extension est distribuée, - et un fichier
config.w32, qui configure la compilation sous Windows.
1.1.1 Fichiers sources de l’extension
Par convention, le fichier qui contiendra le code source de votre extension (le fichier « principal », tout au moins, si vous en avez plusieurs) porte le nom de l’extension ; autrement dit, si vous développez une extension nommée monext01, alors, son fichier source principal sera nommé monext01.c.
Pour une extension la plus basique qui soit, le code source que vous devrez mettre en place aura la forme suivante :
#include "php_monext.h"
/* {{{ monext_module_entry */
zend_module_entry monext_module_entry = {
STANDARD_MODULE_HEADER,
"monext",
NULL, /* Function entries */
NULL, /* Module init */
NULL, /* Module shutdown */
NULL, /* Request init */
NULL, /* Request shutdown */
NULL, /* Module information */
"0.1", /* Replace with version number for your extension */
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_MONEXT
ZEND_GET_MODULE(monext)
#endif
Ce fichier monext01.c est composé de trois sections :
- tout d’abord, le contenu du fichier
php_monext.h, que nous verrons juste en dessous, est inclus ; exactement comme avec la directiveincludede PHP, cela revient à copier-coller le contenu du fichier vers l’endroit où la directive est écrite. - Ensuite, nous renseignons une structure de type
zend_module_entry, dont le nom est_module_entryprécédé du nom de notre extension :monext_module_entry; cette structure permet au moteur de PHP de charger notre extension. - Et enfin, la dernière section fait appel à
ZEND_GET_MODULE()lorsque notre extension est chargée dynamiquement ; c’est cette ligne qui permettra au moteur de PHP de découvrir la structure que nous avons définie juste au-dessus.
Pour l’instant, nous n’avons renseigné que peu de champs de la structure zend_module_entry de notre extension : le nom et la version. Nous verrons au cours des prochains chapitres de ce livre que cette structure permet de définir la liste des fonctions exportées par notre extension, des fonctions exécutées lors de son chargement et déchargement…
Le fichier php_monext.h auquel faisait référence monext01.c aura, quant à lui, la forme suivante :
#ifndef MONEXT_H_
#define MONEXT_H_
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#endif /* MONEXT_H_ */
Pour l’instant, considérant la simplicité de notre extension, ce fichier d’en-têtes ne fait rien de plus qu’inclure quelques fichiers fournis par PHP 1, dont, en particulier, le fichier d’en-têtes php.h.
Ici, le nom de l’extension et sa version ont été renseignés en dur dans la structure monext_module_entry. À terme, vous aurez généralement tendance à définir depuis le fichier .h de votre extension une constante nommée PHP_nom_de_lextension_VERSION pour définir son numéro de version :
#define PHP_MONEXT_VERSION "0.1"
Cette constante sera alors utilisée dans la définition de la structure déclarant l’extension au moteur de PHP :
zend_module_entry monext_module_entry = {
STANDARD_MODULE_HEADER,
"monext",
NULL, /* Function entries */
NULL, /* Module init */
NULL, /* Module shutdown */
NULL, /* Request init */
NULL, /* Request shutdown */
NULL, /* Module information */
PHP_MONEXT_VERSION, /* Replace with version number for your extension */
STANDARD_MODULE_PROPERTIES
};
Si vous êtes amené à distribuer cette extension via PECL, cette constante sera utilisée pour déterminer le numéro de version de l’extension indiqué sur le site.
Au cours des prochains chapitres, au fur et à mesure de l’ajout de fonctionnalités à notre extension, nous viendrons enrichir ce fichier.
1.1.2 Configuration de la compilation
Une fois les deux fichiers sources de l’extension en place, il faut configurer la compilation de celle-ci. Sous un système UNIX-like, cela se fait via un fichier nommé config.m4.
Ce fichier est responsable de la création d’une option qui permettra d’activer la compilation de l’extension lors de l’appel à ./configure, ainsi que de la déclaration du module et de l’ensemble des fichiers source .c qui le composent.
Voici le fichier config.m4 que nous pouvons utiliser pour configurer la compilation de notre première extension :
PHP_ARG_ENABLE(monext, whether to enable monext support,
[ --enable-monext Enable monext support])
if test "$PHP_MONEXT" = "yes"; then
PHP_NEW_EXTENSION(monext, monext.c, $ext_shared)
fi
La première portion de ce fichier utilise PHP_ARG_ENABLE() pour ajouter une option, --enable-monext, au script de configuration ; cette option pourra être utilisée lors de l’exécution de ./configure, pour activer la compilation de l’extension.
Le dernier paramètre passé à cette option se retrouve dans la sortie de ./configure --help pour indiquer l’utilité de l’option correspondante :
./configure --help
`configure' configures this package to adapt to many kinds of systems.
...
Optional Features and Packages:
...
--enable-monext Enable monext support
...
La seconde partie du fichier config.m4 que nous avons créé ici utilise l’instruction PHP_NEW_EXTENSION() pour déclarer une extension PHP, dans le cas où sa compilation a été demandée via l’option --enable-monext.
Nous verrons plus loin dans ce livre comment mettre en place le fichier config.w32, qui joue le même rôle lorsqu’il s’agit de compiler une extension sous Windows.
1.2 Compilation de l’extension
Une fois que nous avons écrit le code source de notre extension et le fichier configurant sa compilation, cette compilation se fait en trois étapes.
La première étape de la compilation d’une extension est de lancer la commande phpize :
$ phpize
Configuring for:
PHP Api Version: 20100412
Zend Module Api No: 20100525
Zend Extension Api No: 220100525
L’utilitaire phpize va créer le script configure à partir des informations présentes dans le fichier config.m4 et de la configuration de PHP (version de PHP – qui se retrouve dans les numéros de versions affichés en sortie de phpize – mais aussi options qui avaient été utilisées lors de sa compilation, comme activation ou non du débogage).
Après cela, il devient possible d’exécuter le script ./configure qui vient d’être créé :
$ ./configure --enable-monext
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for a sed that does not truncate output... /bin/sed
[...]
[...]
checking whether to build static libraries... no
configure: creating ./config.status
config.status: creating config.h
config.status: executing libtool commands
Le script configure vérifie que les outils et bibliothèques requis pour construire l’extension sont tous présents sur votre système et crée le fichier Makefile qui permettra réellement de compiler celle-ci.
Une fois l’étape de configuration passée avec succès, vous pouvez exécuter la commande make pour lancer la compilation de l’extension :
$ make
[...]
[...]
----------------------------------------------------------------------
Libraries have been installed in:
/home/squale/developpement/book-php-extension/sources/monext01/modules
[...]
[...]
----------------------------------------------------------------------
Build complete.
Don't forget to run 'make test'.
Comme l’indique la sortie de make, l’extension est compilée vers le répertoire modules/ :
$ ls -1 modules
monext.la
monext.so
Notre extension est le fichier monext.so.
1.3 Et voila, une nouvelle extension !
Puisque notre extension est compilée, il ne nous reste plus qu’à utiliser la directive de configuration extension de PHP pour la charger.
1.3.1 Vérifier que l’extension est chargée
En ligne de commande, en exploitant l’option -m de PHP, nous pouvons vérifier que notre nouvelle extension est reconnue par PHP :
$ php -dextension=modules/monext.so -m
[PHP Modules]
apc
bcmath
bz2
[...]
monext
[...]
zip
zlib
Lorsque nous indiquons à PHP qu’il doit charger le fichier monext.so de notre extension, nous pouvons voir que celle-ci fait partie des extensions chargées ; et aucun message d’erreur n’est affiché.
Bien sûr, en l’état, notre extension ne fait rien ; mais maintenant que nous avons un squelette d’extension vide, nous allons pouvoir, petit à petit, lui ajouter les fonctionnalités qui nous intéressent !
1.3.2 Et phpinfo() alors ?
À présent, créons un fichier phpinfo.php contenant un appel à la fonction phpinfo() de PHP, comme ceci :
<?php
phpinfo();
?>
Si nous avons compilé notre extension avec PHP 5.4 ou supérieur, nous pouvons tirer parti du serveur web de test intégré, pour servir ce fichier PHP sans avoir à reconfigurer quoi que soit pour que PHP soit exécuté par un serveur Web distinct (Apache, nginx…) que vous pourriez avoir sur votre machine :
$ php -dextension=modules/monext.so -S localhost:8080
PHP 5.4.6-1ubuntu1.1 Development Server started at Sun Mar 10 17:36:48 2013
Listening on http://localhost:8080
Document root is /.../monext01
Press Ctrl-C to quit.
[Sun Mar 10 17:36:54 2013] 127.0.0.1:53260 [200]: /phpinfo.php
Charger http://localhost:8080/phpinfo.php dans votre navigateur affichera la sortie de phpinfo(), incluant une section – minimaliste – à propos de notre extension :
phpinfo() pour notre extensionComme nous pouvons le constater, par défaut, un squelette d’entrée a été automatiquement généré pour phpinfo(). Nous verrons dans les prochains chapitres comment enrichir celui-ci pour y faire remonter plus d’informations à propos de notre extension.
1.4 Contrôle de sources et fichiers à ignorer
Si vous regardez le contenu du répertoire au sein duquel vous avez travaillé pour ce chapitre, vous verrez que là où vous n’aviez créé que quelques fichiers (un fichier .c, un fichier .h, le fichier config.m4, et éventuellement un fichier .php), vous en avez maintenant plus d’une vingtaine, y compris quelques répertoires.
En effet, le processus de compilation d’une extension PHP crée un nombre conséquent de fichiers – fichiers qui ne font pas en soi partie du code de votre extension et ne devraient pas être commités sur votre gestionnaire de code source, puisqu’ils ont été automatiquement générés, peuvent être recréés au besoin, et, en plus de cela, dépendent pour certains de votre environnement.
Vous voudrez sans aucun doute indiquer à votre gestionnaire de code source2 qu’il doit ignorer le gros de ces fichiers. Si vous travaillez avec Git, cela peut être fait en créant à la racine de votre projet un fichier .gitignore contenant la liste des motifs de fichiers à exclure ; si vous travaillez avec SVN, vous pouvez arriver au même résultat en utilisant la propriété svn:ignore. Dans tous les cas, la liste de fichiers que vous souhaiterez exclure ressemblera à celle-ci :
.deps
*.lo
*.la
.libs
Makefile
Makefile.fragments
Makefile.global
Makefile.objects
*.tgz
acinclude.m4
aclocal.m4
build
config.cache
config.guess
config.h
config.h.in
config.log
config.nice
config.status
config.sub
configure
configure.in
conftest
conftest.c
include
install-sh
libtool
ltmain.sh
missing
mkinstalldirs
modules
sm.php
run-tests.php
autom4te.cache
Avec cela, les seuls fichiers qui seront placés sous gestion de code seront les vrais fichiers sources de notre extension : ceux que nous avons créés au cours de ce chapitre.
En complément, notez que l’option --clean du script phpize permet d’effacer tous les fichiers temporaires qui sont créés lors des différentes étapes de la compilation d’une extension PHP, ainsi que ceux laissés lorsque des tests automatisés échouent.
- En fait, nous aurions pu placer ces directives d’inclusion directement dans
monext.cet ne pas du tout utiliser de fichier.h; mais, puisque nous utiliserons réellement ce fichier par la suite, autant prendre dès maintenant la bonne habitude de le définir.↩ - Bien sûr, comme pour n’importe quel autre projet, vous avez prévu de stocker les sources de votre extension PHP au sein d’un gestionnaire de code source, comme Git ou Subversion. Si non… Eh bien, il n’est jamais trop tard pour bien faire. ;-)↩