Doing multiple inheritance in PHP

by Arnold Daniels on 09/26/2009

Officially PHP doesn’t support multiple inheritance. There are several ways around this, without having to duplicate code.

PHP 5.4 will support Traits. This concept is almost similar to mixins. For more information check the PHP manual.

Wrapper

The most commonly used method is to use a wrapper object.

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
42
43
44
45
46
47
48
49
50
51
52
<?php
 
abstract class FsNode
{
  public $path;
  
  public function __construct($path) {
    $this->path = $path;
  }
 
  public function rename($newname) {
    rename($this->path, $newname);
    $this->path = $newname;
  }
}
 
class File extends FsNode
{
  public function getContents() {
    return file_get_contents($this->path);
  }
}
 
class Dir extends FsNode
{
  public function scandir() {
    return scandir($this->path);
  }
}
 
class Symlink
{
  protected $node;
 
  public function __construct($node) {
    $this->node  = $node;
  }
 
  public function target($resolve=false) {
    return $resolve ? realpath($this->node->path) : readlink($this->node->path);
  }
  
  public function __call($method, $args) {
    return call_user_func_array(array($this->node, $method), $args);
  }
}
 
$dir = new Dir("/proc");
$linktodir = new Symlink(new Dir("/proc/self"));
 
var_dump($linktodir->scandir()); // Will be called through __call()
echo $linktodir->target(true), "\n";
<?php

abstract class FsNode
{
  public $path;
  
  public function __construct($path) {
    $this->path = $path;
  }

  public function rename($newname) {
    rename($this->path, $newname);
    $this->path = $newname;
  }
}

class File extends FsNode
{
  public function getContents() {
    return file_get_contents($this->path);
  }
}

class Dir extends FsNode
{
  public function scandir() {
    return scandir($this->path);
  }
}

class Symlink
{
  protected $node;

  public function __construct($node) {
    $this->node  = $node;
  }

  public function target($resolve=false) {
    return $resolve ? realpath($this->node->path) : readlink($this->node->path);
  }
  
  public function __call($method, $args) {
    return call_user_func_array(array($this->node, $method), $args);
  }
}

$dir = new Dir("/proc");
$linktodir = new Symlink(new Dir("/proc/self"));

var_dump($linktodir->scandir()); // Will be called through __call()
echo $linktodir->target(true), "\n";

A disadvantage is that is no longer possible to see if a node is a dir by using instanceof. Also, if most of the methods are defined in the wrapped class, this solution will hurt performance.

Mixin

A far more interesting approach is to use a mixins. When you call a non-static method, $this is always passed to that method. This is also the case if the calling object is not inherited from the called class. We can use that to our advantage to do the reverse of the wrapper.

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
42
43
44
45
46
47
48
49
abstract class FsNode
{
  public $mixin;
  public $path;
  
  public function __construct($path, $mixin=null) {
    $this->path = $path;
    $this->mixin = $mixin;
  }
 
  function rename($newname) {
    rename($this->path, $newname);
    $this->path = $newname;
  }
 
  public  function __call($method, $args) {
    if (isset($this->mixin) && ctype_alnum($method) && is_callable(array($this->mixin, $method))) {
      return eval("return {$this->mixin}::$method(" . (!empty($args) ? '$args[' . join('], $args[', array_keys($args)) . ']' : '') . ");");
    }
    trigger_error("Call to undefined method " . get_class($this) . "::$method()", E_USER_ERROR);
  }
}
 
class File extends FsNode
{
  public function getContents() {
    return file_get_contents($this->path);
  }
}
 
class Dir extends FsNode
{
  public function scandir() {
    return scandir($this->path);
  }
}
 
class Symlink extends FsNode
{
  public function target($resolve=false) {
    return $resolve ? realpath($this->path) : readlink($this->path);
  }
}
 
$dir = new Dir("/proc");
$linktodir = new Dir("/proc/self", 'Symlink');
 
var_dump($linktodir->scandir());
echo $linktodir->target(true), "\n"; // Will be called through __call()
abstract class FsNode
{
  public $mixin;
  public $path;
  
  public function __construct($path, $mixin=null) {
    $this->path = $path;
    $this->mixin = $mixin;
  }

  function rename($newname) {
    rename($this->path, $newname);
    $this->path = $newname;
  }

  public  function __call($method, $args) {
    if (isset($this->mixin) && ctype_alnum($method) && is_callable(array($this->mixin, $method))) {
      return eval("return {$this->mixin}::$method(" . (!empty($args) ? '$args[' . join('], $args[', array_keys($args)) . ']' : '') . ");");
    }
    trigger_error("Call to undefined method " . get_class($this) . "::$method()", E_USER_ERROR);
  }
}

class File extends FsNode
{
  public function getContents() {
    return file_get_contents($this->path);
  }
}

class Dir extends FsNode
{
  public function scandir() {
    return scandir($this->path);
  }
}

class Symlink extends FsNode
{
  public function target($resolve=false) {
    return $resolve ? realpath($this->path) : readlink($this->path);
  }
}

$dir = new Dir("/proc");
$linktodir = new Dir("/proc/self", 'Symlink');

var_dump($linktodir->scandir());
echo $linktodir->target(true), "\n"; // Will be called through __call()

caveat: The Symlink class is never instantiated. Properties defined in the Symlink class are ignored. Also, since Symlink doesn’t extends Dir, it’s not possible to access protected properties defined in Dir.

Compile time mixin

To see if a node is a symlink, you would need to do

1
2
3
if (isset($file->mixin) && ($file->mixin === 'Symlink' || is_subclass_of($file->mixin, 'Symlink'))) {
  //...
}
if (isset($file->mixin) && ($file->mixin === 'Symlink' || is_subclass_of($file->mixin, 'Symlink'))) {
  //...
}

It would be nicer if you could simply use instance of. This is only possible by defining all combination. We can still use mixins though to prevent having to duplicate code.

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
42
43
44
45
46
abstract class FsNode
{
  protected $mixin;
  // Same as above
}
 
class File extends FsNode
{
  // Same as above
}
 
class Dir extends FsNode
{
  // Same as above
}
 
interface Symlink
{}
 
class Symlink_Methods extends FsNode
{
  public function target($resolve=false) {
    return $resolve ? realpath($this->path) : readlink($this->path);
  }
}
 
class SymlinkFile extends File implements Symlink
{
  protected $mixin = 'Symlink_Methods';
}
 
class SymlinkDir extends Dir implements Symlink
{
  protected $mixin = 'Symlink_Methods';
}
 
$dir = new Dir("/proc");
$linktodir = new SymlinkDir("/proc/self");
 
var_dump($linktodir->scandir());
echo $linktodir->target(true), "\n"; // Will be called through __call()
 
if ($dir instanceof Dir)  ; // True
if ($dir instanceof Symlink)  ; // False
if ($linktodir instanceof Dir)  ; // True
if ($linktodir instanceof Symlink)  ; // True
abstract class FsNode
{
  protected $mixin;
  // Same as above
}

class File extends FsNode
{
  // Same as above
}

class Dir extends FsNode
{
  // Same as above
}

interface Symlink
{}

class Symlink_Methods extends FsNode
{
  public function target($resolve=false) {
    return $resolve ? realpath($this->path) : readlink($this->path);
  }
}

class SymlinkFile extends File implements Symlink
{
  protected $mixin = 'Symlink_Methods';
}

class SymlinkDir extends Dir implements Symlink
{
  protected $mixin = 'Symlink_Methods';
}

$dir = new Dir("/proc");
$linktodir = new SymlinkDir("/proc/self");

var_dump($linktodir->scandir());
echo $linktodir->target(true), "\n"; // Will be called through __call()

if ($dir instanceof Dir)  ; // True
if ($dir instanceof Symlink)  ; // False
if ($linktodir instanceof Dir)  ; // True
if ($linktodir instanceof Symlink)  ; // True

Arnold Daniels

I've spend a big part of my life behind a computer, learning about databases (MySQL), programming (PHP) and system administration (Linux). Currently I playing with HTML5, jquery and node.js.

E-mailTwitterLinkedInGithubGittip

There are 10 comments in this article:

  1. 27 September 2009yoda says:

    This is just a bounch of bad pattern designs which should not be used at all.

    ReplyReply
  2. 27 September 2009Giorgio Sironi says:

    I think there are more basic techniques to achieve this, like using interfaces for multiple inheritance and then delegation to helper classes. :)

    ReplyReply
  3. 27 September 2009Les says:

    There is numerous reasons as to why we don’t have multiple inheritance, and we should not try to implement it, so why do you do it?

    This leaves a terrible, vile impression on those who a) are learning about programming for the first time and don’t know any better, and b) those who don’t care about using bad practices.

    Everything you have described in this post is bad; I am sure you want to be innovative (we all do) but this isn’t the end result, unfortunately.

    Please give yourself a slap… before someone else does ;)

    ReplyReply
  4. 27 September 2009Noel Darlow says:

    When I read comments such as above I sometimes wonder if the internet should just be switched off.

    Decorating objects is a common problem (I think that’s what’s at the root of this). Wrappers are one solution. “Mixins” (I like to call that method injection http://aperiplus.sourceforge.net/method-injection.php) are another.

    I think it’s fair to point out that you should first try to find a single home for related behaviours. There is a danger that you could end up spreading bits of logic around in several classes when they ought to be encapsulated in one unit. Mixed in shouldn’t mean mixed up. At the same time, I think there are situations where you might want to assemble a single (apparent) interface from many parts.

    ReplyReply
  5. 27 September 2009Noel Darlow says:

    PS: sorry I didn’t make myself clear, My comments don’t refer to you yoda, or Giorgio.

    ReplyReply
  6. 28 September 2009Artur Ejsmont says:

    Im sorry but it looks bad to me as well.

    PHP projects that use code like that above are notoriously over complicated and debugging or changing anyghing becomaes a real nightmare.

    Wrapper itself without __call is a good thing though ;-)

    I would also discourage from trying to make PHP a language that its not. For example the fact that you can use reflection to mess with an object does not mean you should be doing it. Its an option for cases where its the only way like code analyzers or unit tests frameworks etc.

    ReplyReply
  7. 28 September 2009alexbrina says:

    http://wiki.php.net/rfc/horizontalreuse

    ReplyReply
  8. 28 September 2009alexbrina says:

    This discussion is good to promote things like that Horizontal Reuse with its Traits and Grafts. I too need a way to share states and behaviors that goes beyond Inheritance and Composition.

    ReplyReply
  9. 5 July 2010Jon says:

    There is always an obscure occasion when these kinds of patterns become necessary (e.g. working with Magento where user contributed plug-ins clash due to lack of multiple inheritance). The pattern may not be pretty but it’s ridiculous to dismiss it outright. I never understand people’s fear of programming power.

    ReplyReply
  10. 22 August 2011Arnold Daniels says:

    PHP 5.4 will support Traits. This concept is almost similar to mixins. For more information check http://www.php.net/manual/en/language.oop5.traits.php

    ReplyReply

Write a comment: