Modernizing PHP 4 style constructors in both a forwards and backwards-compatible fashion requires two things:
- renaming the PHP4 style constructor to
__construct()
. - implementing a PHP4 style method that invokes the PHP5
__construct()
.
The typical fix of renaming the PHP 4 style constructor to __construct
works perfectly for code that is available to be scanned and fixed.
If code over which the maintainers have no control extends any of these classes and invokes the constructors by the old method name, that extending code will break, because it will be calling a method name which no longer exists.
Given the following starting classes PHP_4_Class_Parent.php
<?php
class PHP_4_Class_Parent
{
function PHP_4_Class_Parent($parameter)
{
$this->parameter = $parameter;
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
}
// more methods
}
PHP_4_Class_Child.php
<?php
require_once __DIR__ . '/PHP_4_Class_Parent.php';
class PHP_4_Class_Child extends PHP_4_Class_Parent
{
function PHP_4_Class_Child($parameter)
{
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
$this->PHP_4_Class_Parent($parameter);
}
// more methods
}
PHP_5_Class_Child.php
<?php
require_once __DIR__ . '/PHP_4_Class_Parent.php';
class PHP_5_Class_Child extends PHP_4_Class_Parent
{
function __construct($parameter)
{
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
// Does not know about modernization of the parent class.
// Should still work, regardless
$this->PHP_4_Class_Parent($parameter);
}
// more methods
}
PHP_4_Class_Grandchild.php
<?php
require_once __DIR__ . '/PHP_4_Class_Child.php';
class PHP_4_Class_Grandchild extends PHP_4_Class_Child
{
function PHP_4_Class_Grandchild($parameter)
{
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
$this->PHP_4_Class_Child($parameter);
}
// more methods
}
PHP_5_Class_Grandchild.php
<?php
require_once __DIR__ . '/PHP_5_Class_Child.php';
class PHP_5_Class_Grandchild extends PHP_5_Class_Child
{
function __construct($parameter)
{
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
// Does not know about modernization of the parent class.
// Should still work, regardless
parent::__construct($parameter);
}
// more methods
}
And this test script testPHP_4_Classes.php
<?php
require_once __DIR__ . '/PHP_4_Class_Parent.php';
require_once __DIR__ . '/PHP_4_Class_Child.php';
require_once __DIR__ . '/PHP_5_Class_Child.php';
require_once __DIR__ . '/PHP_4_Class_GrandChild.php';
require_once __DIR__ . '/PHP_5_Class_Grandchild.php';
print "======\n";
$parent = new PHP_4_Class_Parent('P');
print "------\n";
$fourChild = new PHP_4_Class_Child('C4');
print "------\n";
$fiveChild = new PHP_5_Class_Child('C5');
print "------\n";
$fourGrandChild = new PHP_4_Class_Grandchild('G4');
print "------\n";
$fiveGrandchild = new PHP_5_Class_Grandchild('G5');
print "------\n";
$fourChild->PHP_4_Class_Child('Invoke PHP4 chld constructor directly.');
print "------\n";
$fiveChild->__construct('Invoke PHP5 child constructor directly.');
print "------\n";
$fourGrandChild->PHP_4_Class_Grandchild('Invoke PHP4 grandchild constructor directly.');
print "------\n";
$fiveGrandchild->__construct('Invoke PHP5 grandhchild constructor directly.');
Running the test script php testPHP_4_Classes.php
yields the following output:
======
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: P
------
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: C4
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: C4
------
We are in PHP_5_Class_Child::__construct, argument: C5
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: C5
------
We are in PHP_4_Class_Grandchild::PHP_4_Class_Grandchild, argument: G4
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: G4
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: G4
------
We are in PHP_5_Class_Grandchild::__construct, argument: G5
We are in PHP_5_Class_Child::__construct, argument: G5
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: G5
------
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: Invoke PHP4 chld constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP4 chld constructor directly.
------
We are in PHP_5_Class_Child::__construct, argument: Invoke PHP5 child constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP5 child constructor directly.
------
We are in PHP_4_Class_Grandchild::PHP_4_Class_Grandchild, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP4 grandchild constructor directly.
------
We are in PHP_5_Class_Grandchild::__construct, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_5_Class_Child::__construct, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP5 grandhchild constructor directly.
Changing only the parent class (that is, not changing any other file) to this:
PHP_4_Class_Parent.php
<?php
class PHP_4_Class_Parent
{
function __construct($parameter)
{
$this->parameter = $parameter;
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
}
function PHP_4_Class_Parent($parameter)
{
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
self::__construct($parameter);
}
// more methods
}
And running the same test script yields the following results:
======
We are in PHP_4_Class_Parent::__construct, argument: P
------
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: C4
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: C4
We are in PHP_4_Class_Parent::__construct, argument: C4
------
We are in PHP_5_Class_Child::__construct, argument: C5
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: C5
We are in PHP_4_Class_Parent::__construct, argument: C5
------
We are in PHP_4_Class_Grandchild::PHP_4_Class_Grandchild, argument: G4
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: G4
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: G4
We are in PHP_4_Class_Parent::__construct, argument: G4
------
We are in PHP_5_Class_Grandchild::__construct, argument: G5
We are in PHP_5_Class_Child::__construct, argument: G5
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: G5
We are in PHP_4_Class_Parent::__construct, argument: G5
------
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: Invoke PHP4 chld constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP4 chld constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP4 chld constructor directly.
------
We are in PHP_5_Class_Child::__construct, argument: Invoke PHP5 child constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP5 child constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP5 child constructor directly.
------
We are in PHP_4_Class_Grandchild::PHP_4_Class_Grandchild, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP4 grandchild constructor directly.
------
We are in PHP_5_Class_Grandchild::__construct, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_5_Class_Child::__construct, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP5 grandhchild constructor directly.
This shows that child classes which have not been modernized will continue to work correctly, and that child classes with a modern constructor - but still calling the PHP 4 style parent method - also continue to work correctly, unmodified. The parent class code is now both backwards and forwards compatible.
We can do better, of course, by modernizing the PHP4 style child classes of which we have knowledge, as follows:
PHP_4_Class_Child.php
<?php
require_once __DIR__ . '/PHP_4_Class_Parent.php';
class PHP_4_Class_Child extends PHP_4_Class_Parent
{
function __construct($parameter)
{
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
$this->PHP_4_Class_Parent($parameter);
}
function PHP_4_Class_Child($parameter)
{
print 'We are in ' . __METHOD__. ", argument: $parameter\n";
self::__construct($parameter);
}
// more methods
}
Running the test script again yields the following output:
======
We are in PHP_4_Class_Parent::__construct, argument: P
------
We are in PHP_4_Class_Child::__construct, argument: C4
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: C4
We are in PHP_4_Class_Parent::__construct, argument: C4
------
We are in PHP_5_Class_Child::__construct, argument: C5
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: C5
We are in PHP_4_Class_Parent::__construct, argument: C5
------
We are in PHP_4_Class_Grandchild::PHP_4_Class_Grandchild, argument: G4
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: G4
We are in PHP_4_Class_Child::__construct, argument: G4
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: G4
We are in PHP_4_Class_Parent::__construct, argument: G4
------
We are in PHP_5_Class_Grandchild::__construct, argument: G5
We are in PHP_5_Class_Child::__construct, argument: G5
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: G5
We are in PHP_4_Class_Parent::__construct, argument: G5
------
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: Invoke PHP4 chld constructor directly.
We are in PHP_4_Class_Child::__construct, argument: Invoke PHP4 chld constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP4 chld constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP4 chld constructor directly.
------
We are in PHP_5_Class_Child::__construct, argument: Invoke PHP5 child constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP5 child constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP5 child constructor directly.
------
We are in PHP_4_Class_Grandchild::PHP_4_Class_Grandchild, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Child::PHP_4_Class_Child, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Child::__construct, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP4 grandchild constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP4 grandchild constructor directly.
------
We are in PHP_5_Class_Grandchild::__construct, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_5_Class_Child::__construct, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_4_Class_Parent::PHP_4_Class_Parent, argument: Invoke PHP5 grandhchild constructor directly.
We are in PHP_4_Class_Parent::__construct, argument: Invoke PHP5 grandhchild constructor directly.
This shows that Grandchild classes which remains unmodified from their original form, continue to work, demonstrating that the modifications to parent and child classes remain backwards and forwards compatible.