Développement WordPress: différences entre PHP 4 et PHP 5

La publication du plugin EG-Attachments m’a montré l’importance des tests avant publication. Globalement, la première version de ce plugin était stable, mais à condition que les utilisateurs se trouvent dans la même configuration que moi.
J’ai corrigé assez rapidement la plupart des erreurs, mais il restait un bug récalcitrant qui empêchait le bouton de s’afficher dans la barre de TinyMCE. Les très nombreux tests sur la configuration de WordPress n’ont rien donné. Je me suis intéressé alors, à la plateforme d’hébergement elle-même en installant une version 4 de PHP, à la place de la version 5 utilisée habituellement. Et là, grosse surprise, le plugin ne fonctionnait quasiment plus. J’étais donc fier d’annoncer la publication d’un plugin quasiment inopérant. Mais quels éléments pouvaient expliquer une telle différence de comportement?

Mes plugins ont tous à peu près la même structure. Je développe « orienté objet » principalement pour deux raisons: d’une part, ce type de développement limite considérablement les risques de conflits de noms (fonctions ou variables de plugins différents portant le même nom), et d’autre part, il permet de réutiliser plus facilement certains bouts de code.

J’essaie autant que possible, de tout gérer dans les objets: globalement seules les constantes ou les options par défaut restent à l’extérieur des classes.

C’est justement cette façon de faire qui m’a posé problème.

Différence de comportement

Le plugin a la structure suivante:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class PlugIn {
    var $textdomain;
    var $stylesheet;
 
    // PHP 4 constructor
    function PlugIn( ... ) {
        $this->__construct( ... );
    }
 
    // PHP 5 constructor
    function __construct() {
       ... ...
       add_action('init', array(&$this, 'init'));
       add_action('head', array(&$this, 'head'));
    }
 
    function init() {
       load_plugin_textdomain($this->textdomain, FALSE, ...);
    }
 
    function head() {
       echo '<link rel="stylesheet" href="'.$this->stylesheet.'" type="text/css" />';
    }
 
    function set_textdomain($textdomain) {
       $this->textdomain = $textdomain;
    }
 
    function set_stylesheet($stylesheet) {
       $this->stylesheet = $stylesheet;
    }
}
 
$my_plugin = new PlugIn( ... );
$my_plugin->set_textdomain('textdomain');
$my_plugin->set_stylesheet('plugin_style.css');
$my_plugin->add_mce_button( ... );

Quelques commentaires:

  • Les deux constructeurs permettent de couvrir les deux versions de PHP,
  • Dans le constructeur justement (lignes 14 et 15), nous « connectons » les fonctions du plugin, aux fonctions de WordPress,
  • En ligne 34, 35 et 36, nous initialisons les variables de notre objet, qui sont ensuite utilisées par les méthodes init et head.

La séquence d’exécution de ce code est la suivante:

  • Etape 1: Quelque part dans le fichier wp-settings.php, WordPress va charger un à un les plugins actifs via l’instruction include_once. Les lignes 34 à 37 sont alors exécutées,
  • Etape 2: Toujours dans le fichier wp-settings.php, mais un peu plus loin, WordPress lance la fonction init, qui va elle-même exécuter notre init (celui de la ligne 17),
  • Etape 3: Plus tard, une fois son initialisation terminée, WordPress va charger le thème, et lancer la fonction head, qui exécutera elle-même notre fonction head (ligne 21).

En PHP4, comme en PHP5, tout se passe exactement comme indiqué. En PHP4 cependant, les variables $this->textdomain et $this->stylesheet sont vides !
Les lignes 34 à 37 sont pourtant bien exécutées. En fouillant un peu, le constat se généralise: en PHP 4, toutes les variables qui ne sont pas initialisées dans le constructeur, sont inutilisables par la suite.

J’ai donc essayé de placer l’initialisation de $textdomain et de $stylesheet dans le constructeur:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class PlugIn {
    var $textdomain;
    var $stylesheet;
 
    function PlugIn( ... ) {
       $this->__construct( ... );
    }
 
    function __construct() {
       ... ...
	$this->set_textdomain('textdomain');
	$this->set_stylesheet('plugin_style.css');
 
       add_action('init', array(&$this, 'init'));
       add_action('head', array(&$this, 'head'));
    }
 
    function init() {
       load_plugin_textdomain($this->textdomain, FALSE, ...);
    }
 
    function head() {
       echo '<link rel="stylesheet" href="'.$this->stylesheet.'" type="text/css" />';
    }
 
    function set_textdomain($textdomain) {
       $this->textdomain = $textdomain;
    }
 
    function set_stylesheet($stylesheet) {
       $this->stylesheet = $stylesheet;
    }
}
 
$my_plugin = new PlugIn( ... );

Cette fois le plugin fonctionne parfaitement, les variables $textdomain et de $stylesheet contiennent bien les valeurs correctes lorsque les fonctions init et head
sont exécutées.

Cette façon de faire ne me gênait pas particulièrement, mais je voulais comprendre pourquoi le second cas fonctionne parfaitement, et pas le premier.

J’avoue ne pas avoir LA réponse, mais plutôt une hypothèse. La question a se poser est la suivante: lorsque nous sommes dans un constructeur, que signifie exactement la séquence $this alors que l’objet est en cours de création?
Mon hypothèse est que

  • En PHP 5, les variables $this (lignes 11 et 12), et $my_plugin (ligne 36) font référence au même objet,
  • En PHP 4, ce sont deux objets sont différents. Les lignes add_action('<action>', array(&$this,'<fonction>')), s’applique à quelque chose
    qui n’est pas notre objet, mais plutôt un objet ou un pointeur temporaire.

Deux solutions pour contourner cette différence de comportement:

  • Tout initialiser dans le constructeur,
  • Ne pas utiliser de séquence add_action dans le constructeur.

La première solution est simple, mais peut-être un peu lourde dans certains cas. La seconde solution laisse plus de souplesse. Nous pouvons avoir une structure comme celle qui suit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class PlugIn {
    var $textdomain;
    var $stylesheet;
 
    function PlugIn( ... ) {
        $this->__construct( ... );
    }
 
    function __construct() {
       ... ...
    }
 
    function load() {
       ... ...
       add_action('init', array(&$this, 'init'));
       add_action('head', array(&$this, 'head'));
       ... ...
    }
 
    function init() {
       load_plugin_textdomain($this->textdomain, FALSE, ...);
    }
 
    function head() {
       echo '<link rel="stylesheet" href="'.$this->stylesheet.'" type="text/css" />';
    }
 
    function set_textdomain($textdomain) {
       $this->textdomain = $textdomain;
    }
 
    function set_stylesheet($stylesheet) {
       $this->stylesheet = $stylesheet;
    }
}
 
$my_plugin = new PlugIn( ... );
$my_plugin->set_textdomain('textdomain');
$my_plugin->set_stylesheet('plugin_style.css');
$my_plugin->add_mce_button( ... );
$my_plugin->load();

La fonction load() permet de déclarer tous les hooks précédemment définis dans le constructeur. Le &$this correspond bien à notre objet, il n’y a donc plus de différence de comportement entre PHP 4 et PHP 5.

Modèle objet très différent

Au-delà du comportement que je viens de décrire, j’ai clairement sous-évaluer les différences entre PHP4 et PHP5, qui sont très importantes, dès que l’on aborde les concepts « objets ». Le modèle « objet » n’est vraiment pas évolué. Il n’offre pas, par exemple, de concept de membres ou méthodes statiques, ou de classe abstraites.

Cela peut paraître anodin, mais lors d’un développement avec WordPress, ce genre de détail peut prendre une grande importance. Un plugin peut avoir besoin d’une class statique, pour gérer, par exemple, les données commune à un shortcode et un widget.

Je conseille donc à ceux qui débutent dans le développement avec WordPress,

  • Soit un développement procédural classique,
  • Soit un développement objet, mais à partir d’une plateforme PHP4.

Pourquoi garder PHP4?

Parce que PHP4 fait partie des pré-requis de WordPress (PHP 4.3 pour être précis), et qu’il me semble naturel de se caler sur ces pré-requis.
Cependant, aujourd’hui, PHP4 n’est officiellement plus supporté, il faudra donc un jour prochain, que les développeurs de WordPress se décident à faire évoluer ses pré-requis. Un changement brutal serait certainement mal accepté, mais la publication d’une roadmap sur 6 mois ou 1 an serait intéressant.

Conclusion

Le problème soulevé dans cet article est peut-être trivial pour un professionnel, mais il me semble toutefois assez subtile pour quelqu’un qui ne connaît pas le comportement intime de PHP. Il m’a coûté quelques heures de travail et quelques litres de café.

2 thoughts on “Développement WordPress: différences entre PHP 4 et PHP 5”

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.