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

  10 Responses to “How I PHP: multiple inheritance”

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

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

    ReplyReply
  3. 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. 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. PS: sorry I didn’t make myself clear, My comments don’t refer to you yoda, or Giorgio.

    ReplyReply
  6. 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. 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
  8. 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
  9. 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

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">

   

Questions? Just ask!

About the author

Hi, I'm Arnold Daniels. How nice that you like to know a bit more about little old me :).

I've spend a big part of my life behind a computer (and not playing games). I've learned a lot about databases, programming and system administration especially on. the LAMP stack (Linux, Apache, MySQL & PHP).

Have a look at what I'm working on now!
© 2012 Jasny · web development Suffusion theme by Sayontan Sinha