Matthieu Napoli
Matthieu Napoli

Reputation: 49713

Call a PHP method while ignoring type-hinting on parameters

Given:

class Foo {
    private $bar;

    public function setBar(Bar $bar) {
         $this->bar = $bar;
    }
}

Is there any way to call setBar() with a parameter that is not an instance of Bar?

$proxy = new Proxy();

$foo = new Foo();
$foo->setBar($proxy);

// with
class Proxy  {
}

The idea is to inject a proxy object instead of a Bar instance. The Proxy class is generic (in a dependency injection framework) and cannot be made to implement/extend Bar.


The Reflection API doesn't seem to provide anything making that possible. I'm looking to do that without using a PHP extension (standard PHP install > 5.3) (and I'm trying to leave eval alone if possible).

Note: yes this is weird stuff, I'm expecting comments about that, but please leave the "answers" for actual answers to the question. This is not intended to be production code.

Upvotes: 3

Views: 1176

Answers (3)

hakre
hakre

Reputation: 198217

In your example the call to

$foo->setBar($proxy);

will trigger a Fatal error. If you can live with a Strict standards warning like:

Strict standards: Declaration of TestFoo::setBar() should be compatible with Foo::setBar(Bar $bar)

You could overwrite that function and then change the parents private property via reflection:

class TestFoo extends Foo
{
    public function setBar(Proxy $bar)
    {
        $this->setPrivateParentProperty('bar', $bar);
    }

    private function setPrivateParentProperty($name, $value)
    {
        $refl     = new ReflectionObject($this);
        $property = $refl->getParentClass()->getProperty($name);
        $property->setAccessible(true);
        $property->setValue($this, $value);
    }
}

You can then use the TestFoo type instead of the Foo type (unless there is an interface or an abstract or a final preventing this):

$foo = new TestFoo();
$foo->setBar($proxy);

However as the strict standards warning shows, this is not really well thought. Compare as well with: Why does PHP allow “incompatible” constructors? for more detailed information why that strict standards error is given.

Upvotes: 0

Francois Bourgeois
Francois Bourgeois

Reputation: 3690

With reflection you can ignore private/protected modifiers, so you can go for the attribute directly. If that is not an option, I fear you have to sell your soul:

eval("class EvilProxy extends Proxy implements Bar {}");
$foo->setBar(new EvilProxy());

(I assume that Bar is dynamic - otherwise you could simply create the EvilProxy class statically without any eval().)

Upvotes: 0

K. Norbert
K. Norbert

Reputation: 10684

Is it a requirement to call setBar(), or can you settle with just setting the private $bar property?

The latter is possible with ReflectionProperty::setAccessible, then you can assign anything to the property you want, as if it was a public property. It won't actually turn it into public, but it will be modifiable through the reflection classes.

AFAIK, the former is not possible without creating temporary classes in strings, then eval()ing those, this is what the mocking frameworks do.

Upvotes: 1

Related Questions