WordPress coding: differences between PHP 4 and 5

When I published the plugin EG-Attachments, I (re)discovered the importance of tests phase. The first version of this plugin was running properly, but only if users had the same configuration than me.
I solved some bugs, but there was still one error with button that did not appear in the TinyMCE toolbar. After a lot of tests with the WordPress configuration, I tried to check my host platform, and changed the PHP version by replacing a version 5 with a version 4. Big surprise: the plugin didn’t work anymore. So, I was very proud to announce the publication of a plugin that doesn’t work. But how explain the differences of behavior between the two versions?

All of my plugins was built with the same structure. I choose to develop in « object oriented » mode for two main reasons: first, this kind of coding avoid problems such as name conflicts, and then because I can re-use some code snippets.
I try, as possible, managing all things inside the objects: only the constants, or default options are stored outside classes.

This kind of method, generated in fact, my problems.

Differences of behavior

A plugin can have the following structure:

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( ... );

Some comments

  • The two constructors allow a compatibility with PHP 4 and PHP 5,
  • In this constructor (lines 14 and 15), we « connect » the plugin’s functions, and the WordPress’ one,
  • In line 34, 35 et 36, we initialize some variables, which are used by init and head methods.

The execution sequence is the following:

  • Step 1: somewhere in the file wp-settings.php, WordPress loads the active plugins, using the function include_once. Lines 343 à 37 are executed at this time,
  • Step 2: somewhere else in the file wp-settings.php, WordPress runs the init function, which calls our init method (the init function in line 17),
  • Step 3: later, once the initialization is done, WordPress will load the theme, and run head function, which will execute also our head method (coded in line 21).

With both PHP4, and PHP5, all are running as described. But with PHP4 the variables $this->textdomain and $this->stylesheet are empty !
The lines 34 to 37 are executed. In fact, with PHP 4, all variables that are not initialized in the constructor, are empty.
Then I tried to move the initialization of $textdomain and $stylesheet in the constructor:

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( ... );

With this modification, the plugin is running properly, $textdomain and $stylesheet contains the right values, when they are used in init and head functions.

This method doesn’t particularly bother me, but I wanted to understand why it’s work, while the first method failed.

In fact, I don’t have THE solution, but just an assumption. The right question is: when we are running the constructor of a class, what is exactly $this, while objet creation is on-going?
My assumption is:

  • With PHP 5, $this (lines 11 and 12), and $my_plugin (line 36) are the same object,
  • With PHP 4, these variables are two distinct objects. The lines add_action('<action>', array(&$this,'<fonction>')), are using something that is not our object.

Two solutions to avoid this problem:

  • Initialize all things in the constructor,
  • Do Not use add_action function in the constructor.

The first solution is simple, but it can be heavy, in some cases. The second one provides more flexibility. We can have the following plugin structure:

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();

The load() function allows to declare all hooks previously defined in the class constructor. &$this is referencing our object, and there is no difference of behavior between PHP 4 and PHP 5.

Object model very different

Beyond this problem, I definitely undervalued the differences between PHP 4 and PHP 5, which are very important when we speak about objects. For example, PHP 4 doesn’t include some concepts such as static class member or static method, or abstract class.

This difference can be consider as harmless, but with WordPress developments, this kind of details can be important. For example, we can need a static class, to manage common data between a widget and a shortcode.

Therefore, I advise beginners to

  • don’t develop with objects,
  • or develop object, but with the PHP 4 platform.

Why keeping PHP4?

Because PHP 4 is still the main requirement of WordPress (PHP 4.3), and for me it’s important to follow the platform requirements when we develop a plugin.
However, today, PHP 4 is no longer supported. Then WordPress developers have to evolve these requirements. An abrupt change would certainly be not accepted, but the publication of a roadmap to 6 months or 1 year would be interesting.

Conclusion

The problem discussed in this post was perhaps obvious for professionnal developers, but it seems to me quite subtle for someone who does not know the intimate behavior of PHP. It cost me a few hours and a few liters of coffee …