so a basic, working example:
class Test
{
public function test()
{
return 'a';
}
}
/**
* @mixin Adapter
*/
class TestAdapter
{
/**
* @var Test
*/
private $test;
public function __construct(Test $test)
{
$this->test = $test;
}
public function __call($method, $args)
{
switch($method)
{
case 'test' :
return 'decorated: '.$this->test();
default :
throw new \Exception('Unhandled method call: '.$method);
}
}
}
$test = new Test();
$testAdapter = new TestAdapter($test);
$testAdapter->test();
so far so good. But what if this Test
is needed for someone? What if abstraction comes in place?
abstract class TestAbstract
{
public abstract function t();
}
class Test extends TestAbstract
{
public function t()
{
return 't';
}
public function test()
{
return 'test';
}
}
class WannaTest
{
public function __construct(Test $test)
{
}
}
this way:
$test = new Test();
$testAdapter = new TestAdapter($test);
$wannaTest = new WannaTest($testAdapter); // would throw fatal!
this wont work, since WannaTest
expects Test
.
Of course I could extends TestAdapter
:
class TestAdapter extends Test
{
public function t()
{
// now I cant mock it!
}
}
but in that case, if I have 10 abstract method, I would have to implement them, even though one of them is only used. This way I cant use __call
either as a proxy. So its a bit smelly. How to workaround that? Removing typehint is not an option...
You can create an inline class that extends Test
and decorates the method as you need it to. Here's an example.
class TestDecorator //decorator sounds more appropriate
{
public static function decorate(Test $test) {
return new class($test) extends Test {
private $test;
public function __construct(Test $test) {
$this->test = $test;
}
public function test() {
return 'decorated: '.$this->test->test();
}
};
}
}
$test = new Test();
$decorated = TestDecorator::decorate($test);
echo $decorated->test();
Type-hinting Test
should now work since the decorated class does actually extend Test