Reputation: 49713
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
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
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
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