Hello !
If somebody is interested, here is a small hack I've done today.
There are still some curious effects, but I'm pretty satisfied by the
results, since PHP is not very flexible.
Let me know what you think, I'm looking into talking about somethin ;)
Cheers,
zimba
---------
<?php
/*
* AUTHOR : Created on 5 janv. 2005 by Jonas Pfenniger
* LICENSE : You are free to use this code
* DESC : Mixins are pretty similar to multiple inheritance. The
goal is to
* add capabilites from a class to another class or instance.
* I don't know what it's usefull for, but I'm sure you'll find out ;)
* Because of the limitations of the PHP language, I was not able to do
some
* things, but it was fun to find workarouds where it's possible..
*
* This hack was inspired by the Ruby language, who supports mixins
natively
* See: http://www.ruby-lang.org and http://www.rubyonrails.com
*
*/
/**
* Mixin : allow to inheritate methods and properties of multiple
objects
* - Limitations :
* - Cannot inherit __get and __set
* - Reflection does not show the new methods
* - Some behaviors are still weird
*/
class Object
{
/**
* Contains class_name => instances
*/
private $mixin_objects = array();
/**
* Contains method_name => calling code
*/
private $mixin_methods = array();
/**
* For the tests
*/
public $hoi = 0;
/**
* Method and variable mixing
* @var string A class name
*/
public function mixin($class_na me)
{
if (!class_exists( $class_name))
trigger_error(" Class name $class_name is not loaded", E_USER_ERROR);
if (array_key_exis ts($class_name, $this->mixin_objects) )
{
trigger_error(" Mixin $class_name allready registered");
return;
}
// Variable argsnum on constructor
$args = func_get_args() ;
$c = 'return new '.$class_name.' (';
for($i=1; $i<count($args) ; $i++)
{
$c .= '$args['.$i.']';
if ($i < count($args) - 1) $c .= ',';
}
$c .= ');';
// Create instance
$x = eval($c);
if ($x instanceof MixinChild)
{
$x->setMixinParent ($this);
}
$this->mixin_object s[$class_name] = $x;
// Link methods
$refl_class = new ReflectionClass ($class_name);
$refl_methods = $refl_class->getMethods() ;
foreach($refl_m ethods as $refl_method)
{
// TODO : Inherited methods should not be added
// Do not private and protected methods
if ($refl_method->isPublic())
$this->mixin_method s[$refl_method->getName()] =
'$this->mixin_object s['.
$class_name .
']->'. $refl_method->getName();
}
// Link parameters
foreach ($x as $k => &$v)
{
if (!isset($this->$k))
$this->$k = &$v;
}
}
/**
* Method overloading
* @var string Method name
* @var array Method arguments
*/
public function __call($method_ name, $args)
{
if (!array_key_exi sts($method_nam e, $this->mixin_methods) )
{
trigger_error(" Method $method_name does not exist");
return;
}
$c = 'return '. $this->mixin_method s[$method_name] .'($args[0]';
for($i=1; $i<count($args) ; $i++) $c .= ',$args['.$i.']';
$c .= ');';
return eval($c);
}
public function hasMixin($class _name)
{
return array_key_exist s($class_name, $this->mixin_objects) ;
}
/**
* For the tests
*/
public function directCall()
{
return;
}
}
/**
* Use this class if you want the mixed class to have access to the
parent
*/
abstract class MixinChild extends Object
{
protected $mixin_parent = null;
public function setMixinParent( Object $mixin_parent)
{
$this->mixin_parent = $mixin_parent;
}
}
/**
* Implementation example
*/
class Prout extends MixinChild
{
public $woot = 2;
public function __construct($wo ot)
{
$this->woot = $woot;
}
/**
* Woot is so cool
*/
public function Woot($a, $b, $c)
{
return "$a, $b and $c are reading /.";
}
public function get()
{
return $this->woot;
}
public function set($x)
{
$this->woot = $x;
$this->mixin_parent->hoi = $x;
}
public function indirectCall()
{
return;
}
public function __set($k, $v)
{
$this->mixin_parent->$k = "PHP";
}
public function __get($k)
{
return "I love ".$this->mixin_parent->$k;
}
}
/***\
|***|==> Start demo code HEHE
\***/
define('BR', "<br />\n");
echo "<h3>Mixin demo</h3>";
$x = new Object();
// Constructor assignation
$x->mixin('Prout ', 6);
echo "Constructo r test : ". ($x->woot==6?'true' :'false'), BR;
// Settest
$x->set(5);
echo "Set test: " . ($x->woot==5?'true' :'false'), BR;
echo "Set parent test: " . ($x->hoi==5?'true': 'false'), BR;
// Gettest
$x->woot = 3;
echo "Get test: ". ($x->get()==3?'true ':'false'), BR;
// Call test
echo "Method call test: " . $x->Woot('nitro' , 'tritoul', 'zimba'), BR;
// __get and __set test
$x->notAssignedV ar = 3;
echo "__set and __get test : " . ($x->notAssignedV ar == 'I love
PHP'?'true':'fa lse'), BR;
//********** BENCHMARKS ***********
$loops = 10000;
// Direct call
$start_time = (float) array_sum(explo de(' ', microtime()));
for($i=0; $i<$loops; $i++)
{
$x->directCall() ;
}
$end_time = (float) array_sum(explo de(' ', microtime()));
$direct_time = $end_time - $start_time;
echo "Direct call bench($loops) : ". $direct_time, BR;
// Mixin call
$start_time = (float) array_sum(explo de(' ', microtime()));
for($i=0; $i<$loops; $i++)
{
$x->indirectCall() ;
}
$end_time = (float) array_sum(explo de(' ', microtime()));
$mixin_time = $end_time - $start_time;
echo "Mixin call bench($loops) : " . $mixin_time, BR;
// Difference
echo "Execution time difference : ".$mixin_ti me / $direct_time, BR;
echo "<b>the end</b>", BR;
echo "<pre>";
print_r($x);
echo "</pre>";
?>