One of the beauties of our current generation of languages is the retirement of the dependency diamond and all of the other oddities that multiple inheritance can wreak on unwary developers. Multiple Implementation of Interfaces is much preferable to multiple inheritance for many reasons, but throwing all caution to the wind lets say we did desire some way to mimic multiple inheritance within php?
Building on my pseudo decorator class I have written a class that allows us to encapsulate multiple third party objects within our “decorator” class. Essentially with this Union class we can mimic multiple inheritance.
In order to deal with attribute and function selection between various encapsulated objects i’ve added a few concepts to this model.
This class is much less polished than our pseudo decorator class and I would advise caution in copy and pasting it into your source base but I’ve included it for curiosities sake. I will update this post the next time I find free time to play more with this concept class and update the out of sync phpdoc comments.
/*
* This simple class allows us to easily wrap multiple third party classes into
* a single interface.
*
* Essentialy we are mimicking a public multiple inheritance.
*/
function unionusbw($a, $b) {
return $a >= $b;
}
/**
* @author kbrings
*/
class Union {
public $_bases = array();
protected $_bases_seq = array();
protected $_bases_sub = array();
protected $_attr_lookup = array();
protected $_ovr_attr_lookup = array();
protected $_func_lookup = array();
protected $_ovr_func_lookup = array();
const default_weight = 10;
public function __construct(&$object,$name = "default", $weight = self::default_weight) {
$this->addBase($name, $object, $weight);
}
protected function wipeReferences($name) {
if(isset($this->_bases_sub[$name]['_attr_ovr'])) {
foreach($this->_bases_sub[$name]['_attr_ovr'] as $entry) {
if(isset($this->_ovr_attr_lookup[$entry])) {
unset($this->_ovr_attr_lookup[$entry]);
}
}
}
if(isset($this->_bases_sub[$name]['_attr_def'])) {
foreach($this->_bases_sub[$name]['_attr_def'] as $entry) {
if(isset($this->_attr_lookup[$entry])) {
unset($this->_attr_lookup[$entry]);
}
}
}
if(isset($this->_bases_sub[$name]['_func_ovr'])) {
foreach($this->_bases_sub[$name]['_func_ovr'] as $entry) {
if(isset($this->_ovr_func_lookup[$entry])) {
unset($this->_ovr_func_lookup[$entry]);
}
}
}
if(isset($this->_bases_sub[$name]['_func_def'])) {
foreach($this->_bases_sub[$name]['_func_def'] as $entry) {
if(isset($this->_func_lookup[$entry])) {
unset($this->_func_lookup[$entry]);
}
}
}
unset($this->_bases_sub[$name]);
}
public function addBase($name, &$object, $weight = self::default_weight) {
$this->_bases[$name]['object'] =& $object;
$this->_bases[$name]['weight'] = $weight;
$this->_bases_seq[$name] = $weight;
uasort($this->_bases_seq, 'unionusbw');
$this->wipeReferences($name);
}
public function removeBase($name) {
unset($this->_bases[$name]);
unset($this->_bases_seq[$name]);
uasort($this->_bases_seq, 'unionusbw');
$this->wipeReferences($name);
$this->_attr_lookup = array();
$this->_func_lookup = array();
}
public function changeWeight($name, $weight) {
if(isset($this->_bases[$name])) {
$this->_bases[$name]['weight'] = $weight;
$this->_bases_seq[$name] = $weight;
uasort($this->_bases_seq, 'unionusbw');
$this->_attr_lookup = array();
$this->_func_lookup = array();
}
}
public function &getBase($name = 'default') {
if(isset($this->_bases[$name])) {
return $this->_bases[$name]['object'];
}
else return $k = null;
}
/**
* sets an attribute in one of our underlying class
*
* @param string $name
* @param string $attribute
* @param mixed $default
*/
public function setBaseAttribute($name, $attribute, $value = null) {
if(isset($this->_bases[$name])) {
$this->_bases[$name]['object']->$attribute = $value;
}
}
public function overrideAttributeSource($name,$attribute,$default = null) {
if(isset($this->_bases[$name])) {
if(!isset($this->_bases[$name]['object']->$attribute)) {
$this->_bases[$name]['object']->$attribute = $default;
}
$this->_ovr_attr_lookup[$attribute]['src'] = $name;
$this->_ovr_attr_lookup[$attribute]['value'] =& $this->_bases[$name]['object']->$attribute;
$this->_bases_sub[$name]['_attr_ovr'][] = $attribute;
}
}
public function overrideFunctionSource($name,$function) {
if(isset($this->_bases[$name])) {
$this->_ovr_func_lookup[$function] = $name;
$this->_bases_sub[$name]['_func_ovr'][] = $function;
}
}
public function &__get($attribute) {
if(isset($this->_ovr_attr_lookup[$attribute])) {
return isset($this->_ovr_attr_lookup[$attribute]['value']) ? $this->_ovr_attr_lookup[$attribute]['value'] : null;
}
if(isset( $this->_attr_lookup[$attribute])) {
return $this->_attr_lookup[$attribute]['value'];
}
foreach ( $this->_bases_seq as $base => $weight) {
if(isset($this->_bases[$base]['object']->$attribute)) {
if(!isset($this->_bases_sub[$base])) {
$this->_bases_sub[$base] = array();
}
$this->_attr_lookup[$attribute] = array();
$this->_attr_lookup[$attribute]['value'] =& $this->_bases[$base]['object']->$attribute;
$this->_attr_lookup[$attribute]['src'] = $base;
$this->_bases_sub[$base]['_attr_def'][] =& $this->_attr_lookup[$attribute];
return $this->_attr_lookup[$attribute]['value'];
}
}
}
/**
* If any underlying classes have attribute update its value, otherwise
* set attribute within this class.
*
* If you need to modify a non existing base attribute directly use
* bindAttribute($attribute) or $this->_base->attribute.
*
* set an attribute of our underlying class.
* @param string $attribute
* @param mixed $value
*/
public function &__set($attribute, $value) {
if(isset($this->_ovr_attr_lookup[$attribute]) ) {
$this->_ovr_attr_lookup[$attribute]['value'] = $value;
return $this->_ovr_attr_lookup[$attribute]['value'];
}
if(isset( $this->_attr_lookup[$attribute])) {
$this->_bases[$this->_attr_lookup[$attribute]['src']]['object']->$attribute = $value;
return $this->_bases[$this->_attr_lookup[$attribute]['src']]['object']->$attribute;
}
foreach ( $this->_bases_seq as $base => $weight) {
if(isset($this->_bases[$base]['object']->$attribute)) {
if(!isset($this->_bases_sub[$base])) {
$this->_bases_sub[$base] = array();
}
$this->_attr_lookup[$attribute] = array();
$this->_attr_lookup[$attribute]['value'] =& $this->_bases[$base]['object']->$attribute;
$this->_attr_lookup[$attribute]['src'] = $base;
$this->_bases_sub[$base]['_attr_def'][] =& $this->_attr_lookup[$attribute];
$this->_bases[$base]['object']->$attribute = $value;
return $this->_bases[$base]['object']->$attribute;
}
}
$this->$attribute = $value;
return $this->$attribute;
}
/**
* calls a function in underlying object.
* @param string $function
* @param array $args
* @return mixed
*/
public function &__call($function, $args) {
if (isset($this->_ovr_func_lookup[$function])) {
if (isset($this->_bases[$this->_ovr_func_lookup[$function]]) &&
method_exists($this->_bases[$this->_ovr_func_lookup[$function]]['object'], $function)) {
return call_user_func_array(array($this->_bases[$this->_ovr_func_lookup[$function]]['object'], $function), $args);
}
}
if (isset($this->_func_lookup[$function])) {
return call_user_func_array(array($this->_bases[$this->_func_lookup[$function]['src']]['object'], $function), $args);
}
foreach ( $this->_bases_seq as $base => $weight) {
if (method_exists($this->_bases[$base]['object'], $function)) {
/**
* It would be possible to compare arguments with passed args array using the
* reflections class. This would help us to avoid mis calling identically named functions.
*/
$this->_func_lookup[$function] = array('src' => $base);
$this->_bases_sub[$base]['_func_def'][] =& $this->_func_lookup[$function];
return call_user_func_array(array($this->_bases[$this->_func_lookup[$function]['src']]['object'], $function), $args);
}
}
trigger_error("Undefined Method Invoked", E_USER_ERROR);
}
/**
* checks if a value has been set in this object or the underlying _base class.
* @param string $attribute
* @return bool
*/
public function __isset($attribute) {
// Shortcut (this will set the _attr_lookup entry);
$t = $this->$attribute;
return isset($this->$attribute) || isset($this->_attr_lookup[$attribute]);
}
/**
* unsets given attribute in $this and $this->_bases[$first_match].
*
* Caution - This will only unset the local instance or the current lookup value. If another base class
* has the same attribute it will be returned by next get request.
*
* @param string $attribute
*/
public function __unset($attribute) {
if(isset($this->$attribute)) {
unset($this->$attribute);
}
if(isset($this->_attr_lookup[$attribute])) {
unset($this->_bases[$this->_attr_lookup[$attribute]['src']]['object']->$attribute);
unset($this->_attr_lookup[$attribute]);
}
}
}
require_once 'Union.php';
require_once 'PHPUnit/Framework.php';
class object {
public $myProperty = 2;
}
class base1 {
public $myProperty = 1;
public $baseProp = 4;
public $myObject;
public function __construct() {
$this->myObject = new object();
}
public function MyBase1() {
return "b1";
}
public function MyFunction1($a) {
return "1" . $a;
}
public function MyFunction2($a) {
return "2" . $a;
}
}
class base2 {
public $myProperty = 'b2';
public $base2Prop = 3;
public $myObject;
public function __construct() {
$this->myObject = new object();
}
public function MyBase2() {
return "b2";
}
public function MyFunction1($a) {
return "b21" . $a;
}
public function MyFunction2($a) {
return "2" . $a;
}
}
class base3 {
public $myProperty = 1;
public $base3Prop = 4;
public $myObject;
public function __construct() {
$this->myObject = new object();
}
public function MyBase3() {
return "b3";
}
public function MyFunction1($a) {
return "b31" . $a;
}
public function MyFunction2($a) {
return "2" . $a;
}
}
class wrap extends Union {
public $decAttribute = 3;
public function MyFunction2($a) {
return $this->decAttribute . $this->_bases['default']['object']->MyFunction2($a);
}
}
class UnionTest extends PHPUnit_Framework_TestCase {
//==========================================================================
// Union Mode
//==========================================================================
public function testSmokeUnionMode() {
$d = new wrap(new base1(),'base1',1);
$d->addBase("base2", new base2(), 2);
$d->addBase("base3", new base3(), 3);
}
public function testUnionFunctionLookup() {
$d = new wrap(new base1(),'base1',1);
$d->addBase("base2", new base2(), 2);
$d->addBase("base3", new base3(), 3);
$this->assertEquals($d->MyBase3(), "b3");
$this->assertEquals($d->MyBase3(), "b3");
$this->assertEquals($d->MyBase2(), "b2");
$this->assertEquals($d->MyBase2(), "b2");
$this->assertEquals($d->MyBase1(), "b1");
$this->assertEquals($d->MyBase1(), "b1");
}
public function testUnionSetDefaultFunction() {
$d = new wrap(new base1(),'base1',1);
$d->addBase("base2", new base2(), 2);
$d->addBase("base3", new base3(), 3);
$r = $d->MyFunction1("apple");
$this->AssertEquals("1apple", $r, "failure calling base function");
$d->overrideFunctionSource("base3", "MyFunction1");
$r = $d->MyFunction1("apple");
$this->AssertEquals("b31apple", $r, "failure calling base function");
$d->removeBase("base3");
$r = $d->MyFunction1("apple");
$this->AssertEquals("1apple", $r, "failure calling base function");
$d->addBase("base3", new base3(), 3);
$r = $d->MyFunction1("apple");
$this->AssertEquals("1apple", $r, "failure calling base function");
}
public function testUnionSetDefaultAttribute() {
$d = new wrap(new base1(),'base1',1);
$d->addBase("base2", new base2(), 2);
$d->addBase("base3", new base3(), 3);
$this->AssertEquals(1, $d->myProperty, "failure accessing base1 property");
$d->overrideAttributeSource("base2", "myProperty");
$this->AssertEquals("b2", $d->myProperty, "failure accessing base2 property");
$d->removeBase("base2");
$this->AssertEquals(1, $d->myProperty, "failure accessing base1 property");
$d->addBase("base2", new base2(), 2);
$this->AssertEquals(1, $d->myProperty, "failure accessing base1 property");
}
//==========================================================================
// Decorator Mode
//==========================================================================
public function testSmoke() {
$d = new wrap(new base1());
}
public function testCallBaseFunction() {
$d = new wrap(new base1());
$r = $d->MyFunction1("apple");
$this->AssertEquals("1apple", $r, "failure calling base function");
}
public function testAccessBaseAtr() {
$d = new wrap(new base1());
$this->assertEquals($d->myProperty, 1, "Failure Accessing _bases['default'] attribute");
$d->myProperty = 2;
$this->assertEquals($d->myProperty, 2, "Failure Modifying _bases['default'] attribute");
}
public function testAccessBaseAtrAtr() {
$d = new wrap(new base1());
$this->assertEquals($d->myObject->myProperty, 2, "Failure Accessing _bases['default'] attributes' attribute");
$d->myObject->myProperty = 4;
$this->assertEquals($d->myObject->myProperty, 4, "Failure Modifying _bases['default'] attributes' attribute");
}
public function testAccesDecAtr() {
$d = new wrap(new base1());
$this->assertEquals($d->decAttribute, 3, "Failure Accessing _dec attribute");
$d->decAttribute = 2;
$this->assertEquals($d->decAttribute, 2, "Failure Modifying _dec attribute");
}
public function testWrapperFunction() {
$d = new wrap(new base1());
$d->decAttribute = 'dec';
$r = $d->MyFunction2("pine");
$this->AssertEquals("dec2pine", $r, "failure calling wrapped function");
}
public function testGetBaseFunction() {
$d = new wrap(new base1());
$b = $d->getBase();
$this->AssertEquals(get_class($b), "base1", "Did not recieve correct base object");
}
public function testIssetBaseAttr() {
$d = new wrap(new base1());
$this->AssertTrue(isset($d->decAttribute));
$this->AssertTrue(isset($d->myProperty));
unset($d->decAttribute);
unset($d->myProperty);
$this->AssertFalse(isset($d->decAttribute));
$this->AssertFalse(isset($d->myProperty));
}
public function testSetBaseAttribute() {
$d = new wrap(new base1());
$d->setBaseAttribute('default', 'apple', 7);
$d->apple = "25";
$this->AssertEquals($d->apple, $d->_bases['default']['object']->apple);
$this->AssertEquals($d->apple, "25");
}
public function testSetNewAttribute() {
$d = new wrap(new base1());
$d->apple = "25";
$d->_bases['default']['object']->apple = 4;
$this->AssertNotEquals($d->apple, $d->_bases['default']['object']->apple);
$this->AssertEquals($d->apple, "25");
}
}
unit tests output
PHPUnit 3.3.16 by Sebastian Bergmann.
..............
Time: 0 seconds
OK (14 tests, 31 assertions)

Categories
Tag Cloud
Blog RSS
Comments RSS
Last 50 Posts
Back
Void « Default
Life
Earth
Wind
Water
Fire
Light 
why not:)
Can i post on your blog as well? I`m reading your posts since few monts and i think you`ve got amazing blog. Lot`s of interesting things and nice theme
If you want to give me an access write me by e-mail.
Like the blog