Cet extrait de l'ouvrage Dynamisez PHP5 vous est offert par Micro Application10
| 10.1 Créer du bytecode | ... | 274 |
| 10.2 Lire le bytecode | ... | 282 |
| 10.3 Check-list | ... | 285 |
Compiler le code PHP
Bcompiler permet de compiler votre code PHP. Mais à quoi cela sert-il ? Tout simplement à protéger vos lignes de code si durement créées à la sueur de votre front. En effet, les fichiers compilés par Bcompiler sont illisibles pour un être humain, mais parfaitement utilisables par un ordinateur. Vous pouvez ainsi diffuser vos fichiers PHP compilés sans craindre pour leur intégrité.
10.1 Créer du bytecode
Compiler un fichier
Afin de s'initier à Bcompiler, nous allons commencer par le plus simple : la compilation d'un fichier PHP complet.
Dans un dossier intitulé chap10, nous allons créer un dossier bytecode ainsi que les fichiers bcompiler_fichier.php et bcompiler_fichier_proprietaire.php.
Voici le contenu de bcompiler_fichier_proprietaire.php :
| bcompiler_fichier_proprietaire.php |
|---|
| <?php |
| $secret = "mot de passe hyper_secret"; |
| echo "mon fichier PHP<br/>\n"; |
| ?> |
Si l'on invoque ce fichier via la commande include, la phrase "mon fichier PHP" apparaîtra simplement à l'écran.
Le second fichier, bcompiler_fichier.php va seulement nous servir à compiler le fichier bcompiler_fichier_proprietaire.php, puis à afficher son contenu :
| bcompiler_fichier.php |
|---|
| <?php |
| // adresse du futur fichier compilé : |
| $bytecode = "bytecode/bcompiler_fichier_proprietaire.phb"; |
| // adresse du code source : |
| $codeSource = "bcompiler_fichier_proprietaire.php"; |
| // création du fichier compilé : |
| $fichierBytecode = fopen($bytecode, "w"); |
| // écriture de l'en-tête du fichier : |
| bcompiler_write_header($fichierBytecode); |
| //écriture du corps du fichier : |
| bcompiler_write_file($fichierBytecode, $codeSource); |
| // écriture du pied de page du fichier : |
| bcompiler_write_footer($fichierBytecode); |
| // fermeture du fichier : |
| fclose($fichierBytecode); |
| // appel du fichier compilé : |
| include 'bytecode/bcompiler_fichier_proprietaire.phb'; |
| ?> |
Lorsque vous lancez ce fichier, la phrase "mon fichier PHP" apparaît comme convenu à l'écran : le fichier de bytecode a bien été créé. Vous pouvez remarquer l'extension de ce nouveau fichier : .phb, comme "PHp Bcompiler" ; mais il ne s'agit là que d'une convention. Nous aurions pu choisir n'importe quelle autre.
Les étapes pour créer un fichier de bytecode sont assez simples. D'abord, nous créons le fichier lui-même, via la fonction fopen()Ensuite, nous écrivons le contenu du fichier via les fonctions bcompiler_write_header(), bcompiler_write_file() et bcompiler_write _footer(). Nous en déduisons donc qu'un fichier de bytecode, comme n'importe quel fichier, dispose d'un en-tête, d'un corps et d'un pied de page.
Attardons-nous un peu sur ce fichier de bytecode : ouvrons-le avec un éditeur de texte, tel que Notepad. Que voyons-nous ? Des carrés et des espaces à foison. Ainsi, notre code est protégé. Et pourtant… Si nous regardons un peu plus attentivement, nous voyons très lisiblement écrite la chaîne de caractères "mon fichier PHP", ainsi que le contenu de la variable $secret. C'est tout à fait normal : une chaîne de caractères n'étant pas, à proprement parler, du code, il serait absurde de tenter de la compiler… Il ne faut donc pas laisser de données sensibles, comme un mot de passe, par exemple, dans un fichier de bytecode, à moins de les crypter au préalable.
Comparons maintenant le poids du fichier de bytecode et celui du fichier source :

Fig. 10.1 : Notre fichier source

Fig. 10.2 : Notre fichier compilé
Vous vous rendez compte que le fichier de bytecode pèse environ cinq fois plus lourd que le fichier source ! C'est hélas le prix à payer pour protéger ses algorithmes.
Test de vitesse
Nous allons maintenant comparer la vitesse d'exécution d'un fichier de bytecode et du fichier source. Pour ce faire, nous allons créer trois fichiers : bcompiler_vitesse.php, bcompiler_vitesse_ouvert.php et bcompiler_vitesse_secret.php.
| bcompiler_vitesse_ouvert.php |
|---|
| <?php |
| function ouvert(){ |
| for($i = 0 ; $i < 100000 ; $i++){ |
| $a = $i." : ".sqrt($i*5)."<br/>\n"; |
| } |
| } |
| ?> |
Ce fichier ne contient qu'une seule fonction dont le but avoué est de faire travailler le processeur un certain temps.
| bcompiler_vitesse_secret.php |
|---|
| <?php |
| function secret(){ |
| for($i = 0 ; $i < 100000 ; $i++){ |
| $a = $i." : ".sqrt($i*5)."<br/>\n"; |
| } |
| } |
| ?> |
Le fichier bcompiler_vitesse_secret.php a exactement le même but que le fichier bcompiler_vitesse_ouvert.php, à ceci près qu'il va être compilé.
Enfin, voici le code du troisième fichier (bcompiler_vitesse.php) :
| bcompiler_vitesse.php |
|---|
| <?php |
| // adresse du futur fichier compilé : |
| $bytecode = "bytecode/bcompiler_fonctions_secret.phb"; |
| // adresse du code source : |
| $codeSource = "bcompiler_fonctions_secret.php"; |
| // création du fichier compilé : |
| $fichierBytecode = fopen($bytecode, "w"); |
| // écriture de l'en-tête du fichier : |
| bcompiler_write_header($fichierBytecode); |
| //écriture du corps du fichier : |
| bcompiler_write_file($fichierBytecode, $codeSource); |
| // écriture du pied de page du fichier : |
| bcompiler_write_footer($fichierBytecode); |
| fclose($fichierBytecode); |
| echo "poids avant compilation : <b>".filesize($codeSource)." octets</b><br/>"; |
| include 'bcompiler_fonctions_ouvert.php'; |
| include 'bytecode/bcompiler_fonctions_secret.phb'; |
| $tempsDebut = microtime(true); |
| ouvert(); |
| $tempsFin = microtime(true); |
| $temps = $tempsFin - $tempsDebut; |
| echo "Nous avons mis <b>$temps</b> secondes avec le code source.<br/>\n"; |
| echo "poids après compilation : <b>".filesize($bytecode)." octets</b>.<br/>"; |
| $tempsDebut = microtime(true); |
| secret(); |
| $tempsFin = microtime(true); |
| $temps = $tempsFin - $tempsDebut; |
| echo "Nous avons mis <b>$temps</b> secondes avec le bytecode.<br/>\n"; |
| ?> |
Ce dernier fichier va compiler bcompiler_vitesse_secret.php, puis exécuter les deux fonctions et comparer les vitesses d'exécution de l'une et de l'autre, ainsi que le poids du fichier source et du fichier de bytecode.
Voici les résultats obtenus lorsque nous lançons ce script depuis la machine sur laquelle nous écrivons :

Fig. 10.3 : Résultat du script
Nous pouvons voir qu'un fichier de bytecode n'est pas exécuté plus rapidement que s'il n'avait pas été compilé.
Compiler des classes et des fonctions
Les fonctions
Bcompiler, en plus de permettre la compilation complète d'un fichier, permet de n'en compiler que certaines fonctions. Cela est très pratique pour obtenir un fichier compilé contenant quelques utilitaires.
Nous allons donc, toujours dans notre dossier chap10, créer deux nouveaux fichiers. Le premier, bcompiler_fonction_source.php, contiendra le code source de quelques fonctions et les compilera. Le second, bcompiler_fonction.php, nous servira à tester le fichier de bytecode généré par le premier fichier.
Voici le code de notre premier fichier :
| bcompiler_fonction_source.php |
|---|
| <?php |
| // adresse du futur fichier compilé : |
| $bytecode = "bytecode/bcompiler_fonction.phb"; |
| // Création du fichier compilé |
| $fichierBytecode = fopen($bytecode,"w"); |
| // écriture de l'en-tête du fichier : |
| bcompiler_write_header($fichierBytecode); |
| // écriture des fonctions au sein du fichier compilé |
| bcompiler_write_function($fichierBytecode,"phraseEnCouleur"); |
| bcompiler_write_function($fichierBytecode,"ajoutPhrase"); |
| bcompiler_write_function($fichierBytecode,"ecritPhrase"); |
| // écriture du pied de page du fichier : |
| bcompiler_write_footer($fichierBytecode); |
| fclose($fichierBytecode); |
| // les fonctions à compiler : |
| function phraseEnCouleur($phrase = "Phrase par défaut", |
| $couleur = "#DE0059"){ |
| return '<div style="color:'.$couleur.'">'.$phrase.'</div>'; |
| } |
| function ajoutPhrase(&$tab, $phrase){ |
| $tab[] = $phrase; |
| } |
| function ecritPhrase(&$tab){ |
| for($i = 0 ; $i < count($tab) ; $i++){ |
| echo $tab[$i]; |
| } |
| } |
| ?> |
Dans ce fichier, nous utilisons différents types de fonctions : avec ou sans valeur de retour, avec des arguments disposant ou non de valeur par défaut ou avec des arguments passés par références.
Afin de les écrire dans le corps du fichier de bytecode, nous utilisons la fonction bcompiler_write_function() pour chacune de nos fonctions.
Regardons maintenant le simplissime code du fichier bcompiler_fonction.php :
| bcompiler_fonction.php |
|---|
| <?php |
| include 'bytecode/bcompiler_fonction.phb'; |
| $tableau = array(); |
| $str = phraseEnCouleur("Salut à vous !<br/>La forme ?", "#455FDB"); |
| ajoutPhrase($tableau, $str); |
| ajoutPhrase($tableau, phraseEnCouleur()); |
| ecritPhrase($tableau); |
| ?> |
L'utilisation est plutôt simple, n'est-ce pas ?
Cela dit, imaginez que vous ayez une cinquantaine de fonctions à compiler : il serait vraiment fastidieux d'écrire cinquante fois la fonction bcompiler_write_function(). Si toutes vos fonctions se trouvent au sein d'un même fichier, vous pouvez utiliser la fonction bcompiler_write_functions_from_file(). En une seule ligne, toutes les fonctions d'un même fichier seront compilées et ajoutées au bytecode.
Ouvrons à nouveau le fichier bcompiler_fonction_source.php, et remplaçons les trois appels à la fonction bcompiler_write_function()par cette seule ligne :
| Bcompiler_write_functions_from_file($fichierBytecode, ‘bcompiler_fonction_source.php’); |
Le tour est joué. Il ne reste plus qu'à compiler notre fichier de bytecode (en lançant le fichier bcompiler_fonction_source.php), et à exécuter de nouveau le fichier bcompiler_fonction.php pour s'assurer que tout fonctionne bien.
Les classes
Bcompiler met aussi à notre disposition un outil pour compiler des classes. Afin d'illustrer notre propos, nous allons créer deux fichiers : bcompiler_class_source.php et bcompiler_class.php. Le premier contiendra le code de quelques classes ainsi que les quelques lignes nécessaires à leur compilation. Le second servira bien sûr à tester le fichier de bytecode.
| bcompiler_class_source.php |
|---|
| <?php |
| // adresse du futur fichier compilé : |
| $bytecode = "bytecode/bcompiler_class.phb"; |
| // Création du fichier compilé |
| $fichierBytecode = fopen($bytecode,"w"); |
| // écriture de l'en-tête du fichier : |
| bcompiler_write_header($fichierBytecode); |
| // écriture des classes au sein du fichier compilé |
| bcompiler_write_class($fichierBytecode, "Animal"); |
| bcompiler_write_class($fichierBytecode, "Mammifere"); |
| // écriture du pied de page du fichier : |
| bcompiler_write_footer($fichierBytecode); |
| fclose($fichierBytecode); |
| // les classes à compiler : |
| class Animal { |
| public $nom; |
| function __construct($nom){ |
| $this->nom = $nom; |
| } |
| function presentation(){ |
| return ' Je suis : <b>'.$this->nom.'</b>'; |
| } |
| function seFaitManger($animal){ |
| return $this->nom." : \"Rosebud !\"<br/>"; |
| } |
| } |
| class Mammifere extends Animal{ |
| public $regime; |
| function __construct($nom, $regime){ |
| parent::__construct($nom); |
| $this->regime = $regime; |
| } |
| function presentation(){ |
| return parent::presentation(). |
| '. Mon régime : <b>'. |
| $this->regime.'</b>'; |
| } |
| function mange($animal){ |
| return $this->nom. |
| ' est en train de manger '. |
| $animal->nom."<br/>". |
| $animal->seFaitManger($this); |
| } |
| } |
| ?> |
Dans cet exemple, nous utilisons deux classes : Animal et Mammifere. La classe Mammifere héritant de la classe Animal, il est nécessaire de compiler d'abord Animal, puis Mammifere. Essayons de compiler de cette manière :
| bcompiler_write_class($fichierBytecode, "Mammifere"); |
| bcompiler_write_class($fichierBytecode, "Animal"); |
Ce code génère une erreur car la classe Mammifere ne trouve pas sa classe mère dans le bytecode !
Une fois que nous avons exécuté ce fichier (avec les classes dans le bon ordre), le fichier bcompiler_class.phb a été créé. Testons-le donc :
| bcompiler_class.php |
|---|
| <?php |
| include 'bytecode/bcompiler_class.phb'; |
| $bestiole = new Animal("lapin"); |
| echo $bestiole->presentation()."<br/>"; |
| $bestiole2 = new Mammifere("tigre", "carnivore"); |
| echo $bestiole2->presentation()."<br/>"; |
| echo $bestiole2->mange($bestiole); |
| ?> |
Comme pour la compilation de fonctions, l'usage est relativement simple. Cependant, Bcompiler n'offre pas de fonction permettant en une seule ligne de code la compilation de toutes les classes d'un fichier : en effet, rien ne lui indiquerait quelle classe descend de quelle autre, et Bcompiler ne peut pas le déduire tout seul.
10.2 Lire le bytecode
Jusqu'à présent, nous avons toujours utilisé include pour lire le contenu d'un fichier de bytecode. Les commandes include et require resteront les manières les plus simples et les plus portables d'utiliser un fichier compilé. Mais il existe d'autres méthodes.
Par exemple, renommons le fichier bcompiler_fichier_proprietaire.phb (qui devrait se trouver dans votre répertoire bytecode) en bcompiler_fichier_proprietaire.php : nous changeons seulement l'extension. Ouvrez ensuite ce fichier depuis votre navigateur préféré. Comme il s'agit là d'un simple script pouvant fonctionner seul, le navigateur affiche bien la phrase "mon fichier PHP".
Un fichier compilé, pour peu qu'il porte l'extension .php, peut donc être lu directement.
La fonction bcompiler_read()
Nous pouvons accéder au contenu d'un fichier compilé grâce à la fonction bcompiler_read(). Cette fonction, contrairement aux structures include ou require, n'exécute pas le code contenu dans le fichier.
Créons donc un fichier bcompiler_lire.php. Ce fichier devra ouvrir trois fichiers de bytecode (l'un contenant un script, l'autre des fonctions et le dernier des classes. Si vous avez fait chaque exemple de ce chapitre, vous disposez déjà de ces fichiers : bcompiler_fichier_proprietaire.phb pour le fichier de script, bcompiler_fonction.phb pour le fichier de fonctions et bcompiler_class.phb pour le fichier de classes), puis tenter d'en exécuter le contenu. Voici le code de bcompiler_lire.php :
| bcompiler_lire.php |
|---|
| <?php |
| // ouverture du fichier de bytecode contenant un script |
| $fichierBytecode = fopen("bytecode/bcompiler_fichier_proprietaire.phb","r"); |
| bcompiler_read($fichierBytecode); |
| fclose($fichierBytecode); |
| // essai de lecture d'une variable contenue dans le script |
| echo $secret; |
| // ouverture du fichier de bytecode contenant des fonctions |
| $fichierBytecode = fopen("bytecode/bcompiler_fonction.phb","r"); |
| bcompiler_read($fichierBytecode); |
| fclose($fichierBytecode); |
| // essai d'utilisation de ces fonctions |
| $tableau = array(); |
| $str = phraseEnCouleur("Salut à vous !<br/>La forme ?", "#455FDB"); |
| ajoutPhrase($tableau, $str); |
| ajoutPhrase($tableau, phraseEnCouleur()); |
| ecritPhrase($tableau); |
| // ouverture du fichier de bytecode contenant des classes |
| $fichierBytecode = fopen("bytecode/bcompiler_class.phb","r"); |
| bcompiler_read($fichierBytecode); |
| fclose($fichierBytecode); |
| // essai d'utilisation de ces classes |
| $bestiole = new Animal("lapin"); |
| echo $bestiole->presentation()."<br/>"; |
| $bestiole2 = new Mammifere("tigre", "carnivore"); |
| echo $bestiole2->presentation()."<br/>"; |
| echo $bestiole2->mange($bestiole); |
| ?> |
Lorsque nous lançons ce fichier, nous nous rendons compte que le fichier de script ne s'exécute pas (la phrase "mon fichier PHP" n'apparaît pas à l'écran). De plus, la variable $secret ne s'affiche pas non plus. Et pourtant, aucune erreur ne nous est renvoyée.
Par contre, les fichiers de fonctions et de classes s'exécutent correctement.
Quels avantages y a-t-il à utiliser cette fonction ? Si nous comparons les temps d'exécution, nous ne pouvons noter de différences significatives. Le seul avantage est de pouvoir charger ces fichiers compilés en un seul et même endroit du code, pour plus de clarté.
La fonction bcompiler_load()
Comme nous l'avons vu précédemment, un fichier compilé pèse environ cinq fois plus lourd que son alter ego non compilé. Heureusement, l'extension Bcompiler nous propose une petite fonction afin de résoudre ce problème de poids. En effet, bcompiler_load() permet de charger un fichier de bytecode compressé avec BZIP2 !
Vous pouvez vous reporter au chapitre Comprimez vos données, traitant de la compression, pour plus de détails sur la manière d'utiliser BZIP2.
Nous allons, en un seul petit fichier (placé dans votre répertoire chap10), nommé bcompiler_bzip.php, compresser le fichier bcompiler_class.phb (si vous avez effectué les exemples de ce chapitre, il devrait déjà exister dans le dossier bytecode) puis utiliser les classes contenues dans notre fichier compilé-compressé.
Voici le code de bcompiler_bzip.php :
| bcompiler_bzip.php |
|---|
| <?php |
| $fichierSource = 'bytecode/bcompiler_class.phb'; |
| // récupération, sous la forme d'une chaîne |
| // de caractères, du contenu du fichier de bytecode : |
| $contenu = file_get_contents($fichierSource); |
| // on écrit le contenu du fichier de bytecode |
| // à l'écran : |
| echo "<p>"; |
| echo $contenu; |
| echo "</p>"; |
| // On s'aperçoit bien, grâce à tous les caractères |
| // bizarres inscrits à l'écran, qu'il s'agit effectivement |
| // du contenu du fichier de bytecode ! |
| // ouverture (ou création) du fichier compressé : |
| $bzo = bzopen('bytecode/compile_compresse.bz2', "w"); |
| // Ecriture du code compilé dans le fichier compressé : |
| bzwrite($bzo, $contenu, strlen($contenu)); |
| // Fermeture du fichier compressé : |
| bzclose($bzo); |
| // chargement des classes contenues |
| // dans notre fichier compilé-compressé : |
| bcompiler_load('bytecode/compile_compresse.bz2'); |
| // Exécution de ces classes : |
| $bestiole = new Animal("lapin"); |
| echo $bestiole->presentation()."<br/>"; |
| $bestiole2 = new Mammifere("tigre", "carnivore"); |
| echo $bestiole2->presentation()."<br/>"; |
| echo $bestiole2->mange($bestiole); |
| ?> |
Et voilà ! Nos classes ont été d'abord compilées en un fichier de bytecode, puis compressées grâce à BZIP2. À cet effet, le fichier compile_compresse.bz2 pèse environ quatre fois moins lourd que bcompiler_class.phb, son homologue non compressé.
Malheureusement, bcompiler_load()ne s'utilise qu'avec des classes. Si vous espériez compresser du script ou des fonctions compilées, il va falloir passer votre chemin.
Enfin, si nous gagnons en place, nous perdons en vitesse d'exécution. C'est évidemment normal, puisque nous subissons une étape de décompression. Un fichier compilé et compressé met environ deux fois plus de temps qu'un autre à s'exécuter.
Maintenant, c'est à vous de déterminer quelle méthode de compilation va être la plus adaptée à votre projet : faut-il compiler du script ? Des fonctions ? Des classes ? Tout à la fois ? Faut-il compresser ?
Et surtout, n'oubliez pas de vous poser la plus importante de toutes les questions : quelles parties de mon programme sont suffisamment sensibles pour justifier leur compilation et la perte de performances (en termes de poids ou de rapidité d'exécution) qui va avec ?
10.3 Check-list
Dans ce chapitre, nous avons découvert ce qu'était Bcompiler, ses avantages et ses inconvénients.
Nous avons vu comment :
• compiler vos fichiers PHP ;
• compiler des classes et des fonctions ;
• lire des fichiers, des classes ou des fonctions compilés ;
• lire des fichiers compilés et compressés.