Asked  7 Months ago    Answers:  5   Viewed   32 times

I would like to be able to call a closure that I assign to an object's property directly without reassigning the closure to a variable and then calling it. Is this possible?

The code below doesn't work and causes Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();

 Answers

18

As of PHP7, you can do

$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');

or use Closure::call(), though that doesn't work on a StdClass.


Before PHP7, you'd have to implement the magic __call method to intercept the call and invoke the callback (which is not possible for StdClass of course, because you cannot add the __call method)

class Foo
{
    public function __call($method, $args)
    {
        if(is_callable(array($this, $method))) {
            return call_user_func_array($this->$method, $args);
        }
        // else throw exception
    }
}

$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');

Note that you cannot do

return call_user_func_array(array($this, $method), $args);

in the __call body, because this would trigger __call in an infinite loop.

Wednesday, March 31, 2021
 
maniclorn
answered 7 Months ago
49

Your problem is that you aren't injecting your dependency (the closure), which always makes unit testing harder, and can make isolation impossible.

Inject the closure into SUT::foo() instead of creating it inside there and you'll find testing much easier.

Here is how I would design the method (bearing in mind that I know nothing about your real code, so this may or may not be practical for you):

class SUT 
{
    public function foo($bar, $someFunction) 
    {
        $bar->baz($someFunction);
    }
}

class SUTTest extends PHPUnit_Framework_TestCase 
{
    public function testFoo() 
    {
        $someFunction = function() {};

        $mockBar = $this->getMockBuilder('Bar')
             ->setMethods(array('baz'))
             ->getMock();
        $mockBar->expects($this->once())
             ->method('baz')
             ->with($someFunction);

        $sut = new SUT();

        $sut->foo($mockBar, $someFunction);
    }
}
Wednesday, March 31, 2021
 
mgraph
answered 7 Months ago
57

In the constructor, yes.

class User
{
    public $x = "";
    public $y = null;
    public $w = array();

    public function __construct()
    {
        $this->y = new ErrorVO();
    }
}

Edit

KingCrunch made a good point: You should not hard-code your dependencies. You should inject them to your objects (Inversion of Control (IoC)).

class User
{
    public $x = "";
    public $y = null;
    public $w = array();

    public function __construct(ErrorVO $y)
    {
        $this->y = $y;
    }
}

new User(new ErrorVD());
Sunday, August 15, 2021
 
jedwards
answered 2 Months ago
36

There were two methods added to Groovy to aid serialization of Closures, dehydrate and rehydrate. Basically, they strip (and reconstruct) a Closure's owner, thisObject and delegate. In this example, you could do:

myMethodClosure.rehydrate( myClass2, myClass2, myClass2 )()

To get the output 2, however I'd be wary about doing this as it is not what the method was intended for and there could be serious unforeseen consequences.

A better solution would probably be to write a factory method that gets a method reference for the given instance of MyClass. There may be other -- better -- solutions, but it depends on the situation you are in (that I suspect is not shown by the example in the question)

Thursday, August 19, 2021
 
Mateusz Pryczkowski
answered 2 Months ago
36

I can guess that problem lies in the way you assign your stringForDisplay, eg.:

if you use something like

stringForDisplay_ = anotherString;

setter for property doesn't fire, so you have to retain your variable yourself otherwise it'll live just until your method finishes;

If so - use property setters, eg.:

self.stringForDisplay = anotherString;

that way ARC will do all the memory management.

Thursday, September 2, 2021
 
Juriy
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :