Итак, основной, рабочий пример:
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();
Все идет нормально. Но что, если это Test
кому-то нужно? Что если абстракция вступит в силу?
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)
{
}
}
сюда:
$test = new Test();
$testAdapter = new TestAdapter($test);
$wannaTest = new WannaTest($testAdapter); // would throw fatal!
это не сработает, так как WannaTest
надеется Test
,
Конечно, я мог бы продолжить TestAdapter
:
class TestAdapter extends Test
{
public function t()
{
// now I cant mock it!
}
}
но в этом случае, если у меня есть 10 абстрактных методов, я должен был бы реализовать их, даже если используется только один из них. Таким образом, я не могу использовать __call
либо в качестве прокси. Так что это немного вонючий. Как обойти это? Удаление typehint не вариант …
Вы можете создать встроенный класс, который расширяет Test
и украшает метод, как вам нужно. Вот пример.
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-хинтинг Test
теперь должен работать, так как декорированный класс действительно расширяется Test
Других решений пока нет …