3

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.

hakre
  • 193,403
  • 52
  • 435
  • 836
Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
  • I don't think there are any solution but making your class extend Bar. – n1xx1 Jan 29 '13 at 22:22
  • Are you able to extend `Foo`? – Seth Battin Jan 29 '13 at 22:59
  • This is essentially what PHPUnit and SimpleUnit do with Mocks. They use eval() to extend the base class on the fly, override all the methods and pass in an instance of the newly defined class. – Tom B Feb 05 '13 at 14:26

3 Answers3

1

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.

K. Norbert
  • 10,494
  • 5
  • 49
  • 48
  • This is a good idea, but I didn't specify in my question that the method is not always a setter. I.e. it will take one parameter and probably set a private property, but I can't guarantee it will do just that and I can't guarantee it will be named "setXXX" where XXX is the name of the property. So I have to call the method. – Matthieu Napoli Jan 30 '13 at 07:23
0

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

Francois Bourgeois
  • 3,650
  • 5
  • 30
  • 41
  • unfortunately it seems I'll have to "sell my soul" to eval :/ Only problem is that Bar may be a class, so I have to extend it, so that means I have to override all its methods (like Doctrine for example)... – Matthieu Napoli Feb 05 '13 at 15:26
0

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.

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
  • The thing is I have no guarantee that the method I'll be calling is a setter. It may be a method doing stuff, so I can't just update the parents properties, I need to call it. As for the "strict standards" warning, indeed it's not very good since it's for a library that will be reused. – Matthieu Napoli Mar 31 '13 at 21:42
  • I've come to the conclusion that I *need* to call the method with a proxy that extends the original class, because even if I find a solution to my original question I'll have problems with `instanceof` calls (the proxy object will *not* be an "instance of" Bar). – Matthieu Napoli Mar 31 '13 at 21:44