views:

74

answers:

3

Hello, my code is :

class myClass {
    $myVariable = 'myCallback';

    function myFunction() {
        $body = false;
        $callback = $this->myVariable;

        function test($handle, $line) {
            global $body, $callback;

            if ($body) {
                call_user_func($callback, $line);
            }

            if ($line === "\r\n") {
                $body = true;
            }

            return strlen($line);
        }

        ...
        curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'test');
        ...
    }
}

function myCallback($data) {
    print $data;
}

$myCls = new myClass();
$myCls->myFunction();

Warning: call_user_func() [function.call-user-func]: First argument is expected to be a valid callback !

My $callback value is empty, how can I resolve this issue ? Restriction : myCallback function cannot be changed !

+8  A: 

Important: This is only possible in PHP >= 5.3. The OP does not use PHP 5.3 but I will leave the answer here in case someone has a similar problem and uses PHP 5.3.


$callback is not a global variable, it is local in the methods scope. From the documentation:

Closures may also inherit variables from the parent scope. Any such variables must be declared in the function header. Inheriting variables from the parent scope is not the same as using global variables. Global variables exist in the global scope, which is the same no matter what function is executing. The parent scope of a closure is the function in which the closure was declared (not necessarily the function it was called from).

Make use of use (and assign the function to a variable):

$test = function($handle, $line) use ($callback, $body){

    if ($body) {
        call_user_func($callback, $line);
    }

    if ($line === "\r\n") {
        $body = true;
    }

    return strlen($line);
};

and later:

curl_setopt($ch, CURLOPT_WRITEFUNCTION, $test);
Felix Kling
I don't like that **x.9** figure, +1 good answer :)
Sarfraz
You cannot define Closures without assigning them to a variable
Gordon
@Gordon: Good point, you are right, at least when you want to use `use`, you have to assign it to a variable. I get a syntax error otherwise (or I am doing something stupid ;))
Felix Kling
@Felix Closures and Lambdas are objects. That's why you have to assign them. They are supposed to be passed around. From docs: *Anonymous functions are currently implemented using the Closure class.* - you should also note that they are only available since 5.3
Gordon
Do you have a solution for PHP 5.2 ?
Fabien Bernede
@Fabien: Make your test function a method in MyClass
Dennis Haarbrink
@Fabien Bernede: Please see Gordon's answer.
Felix Kling
@Fabien The Pre5.3 way to create lambdas ( [not closures](http://stackoverflow.com/questions/2209327/is-it-possible-to-simulate-closures-in-php-5-2-x-not-using-globals) ) is to use `create_function`, but this is slow and kinda ugly and wont let you close over variables (e.g. no `use` keyword).
Gordon
+2  A: 

With the exception of the missing keyword when declaring $myVariable, this should not throw an error at all, because inside the test() function $body is NULL unless you defined it in the global scope. In other words, your myCallback should never get called. Apparently, you did define $body in the global scope though and made it a great example of why using globals will lead to all sorts of unexpected behavior. If you would define $callback in the global scope to hold 'myCallback' it should work, but you dont want globals.

And do yourself a favor and get rid of the function in the method:

class myClass {

    protected $_myVariable = 'myCallback';

    public function myFunction()
    {
        // calling callback that calls callback (should make you think ;))
        // that would be your curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'test');
        echo call_user_func_array(
            array($this, '_test'),
            array('body', 'foo', 'bar'));
    }

    protected function _test($body, $handle, $line)
    {
        if ($body) {
            call_user_func($this->_myVariable, $line);
        }
        if ($line === "\r\n") {
            $body = true;
        }
        return strlen($line);
    }
}

The test() function is now a method inside your class. This is much clearer and more maintainable than putting the code flow into some function inside a method. Note that I pass in $body as the first argument. I dont know how cURL accepts callbacks or what it passes to them. If you cannot make $body the first argument, make it a class member check for it's state with $this->body instead (as now shown by Philippe below)

Gordon
@Gordon: Ha, I should have had a look at that ;) Anyway, I tried it too and it works indeed +1 (deleted my other nonsense comments)
Felix Kling
+2  A: 

Just for the fun of it another one ... (not tested though).

class myClass
{
    protected $callback = 'myCallback';
    protected $body = false;

    public function myFunction()
    {
        ...
        curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'test'));
        ...
    }


    public function test($handle, $line)
    {
        if ($this->body) {
            call_user_func($this->callback, $line);
        }

        if ($line === "\r\n") {
            $body = true;
        }

        return strlen($line);
    }
}

function myCallback($data) {
    print $data;
}

$myCls = new myClass();
$myCls->myFunction();
Philippe Gerber