How I PHP: multiple inheritance
Officially PHP doesn’t support multiple inheritance. There are several ways around this, without having to duplicate code.
Wrapper
The most commonly used method is to use a wrapper object.
<?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.
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
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.
abstract class FsNode { protected $mixin; // Same as above } class File extends FsNode { // Same as above } class Dir extends FsNode { // Same as above } interface Symlink {} abstract 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
26 Sep 2009 Arnold Daniels 8 comments




