Search code examples
phpadapter

What if adapter class needs to be type hinted? (php)


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...


Solution

  • 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