Tuesday 6 January 2015

PHP: help me understand calling a callback in PHP

G'day:
This flummoxed me yesterday, and I wonder if anyone can cast any light on the scene for me.

I've got this sort of situation:

class TestCallback {

    private $callback;

    function __construct($callback){
        $this->callback = $callback;
    }

    // [...]

}



Where I pass a callback into an object as a constructor argument. A method will subsequently need to call that callback. That's fine.

But here's my first attempt at calling the callback:

class TestCallback {

    private $callback;

    function __construct($callback){
        $this->callback = $callback;
    }

    function callDirect(){
        // this single step does not
        $this->callback("direct");
    }

    // [...]

}

$o = new TestCallback(function($type){
    printf("called via %s<br>", $type);
});

$o->callDirect();


This errors when I run it, thus:

Fatal error: Call to undefined method TestCallback::callback()


OK, I was doing something wrong. This does not surprise me with PHP one bit (given I'm such a n00b).

Whilst troubleshooting it, I landed on this approach:

    function callViaIntermediary(){
        $callback = $this->callback; 
        $callback("intermediary");
    }

This works:

called via intermediary

But I don't get it. What is it about assigning the property to an intermediary variable suddenly makes stuff work?

Talking to one of my colleagues, he gave me this alternative, too:

    function callViaProxy(){
        call_user_func($this->callback, "proxy");
    }

This also works fine.

I tried making the property public, and calling it directly:

class TestCallback {

    public $callback;

    function __construct($callback){
        $this->callback = $callback;
    }


}

$o = new TestCallback(function($type){
    printf("called via %s<br>", $type);
});
$o->callback("public");

This yields the same fatal error as before.

From the error, one can infer that the parser is looking for an actual defined function, callback(), eg:

function callback(){
    // etc
}

And, indeed, if I have that in my class, the code "works" in that the callback() method is run.

So it seems to me that whilst $this->something refers to a property something, if if that property value is a function, the one cannot then call that function simply by adding the parentheses: $this->something(). Because the PHP parser doesn't "get" that. It assumes that something has to be a declared function. Which doesn't strike me as being correct. Does it?

If I switch back to CFML world, I have this analogous code:

component accessors=true {

    property callback;

    function init(callback){
        setCallBack(callback);
    }

    function callDirect(){
        callback("direct");
    }

}

o = new TestCallback(function(type){
    writeOutput("called via #type#<br>");
});

o.callDirect();


This works fine, and this is how my expectations are set. I'mnot saying PHP should do what CFML does, just that this is where my expectations lie. I'm a CFML dev, after all.

I'm really bemused in PHP why this doesn't work:

$this->callback("direct");

But this does:

$callback = $this->callback; 
$callback("intermediary");

How does simply assigning $this->callback to a different variable change how the contents of the variable are treated?

Or is it simply a shortfall of the parser? The combination of -> and () don't make sense to it?

Any thoughts?

Cheers.

--
Adam