Search code examples
phplaravelsymfonyclosurespass-by-reference

Why is a variable used by reference to the set_error_handler function closure undefined?


While reading through the Laravel Framework source code I stumbled upon this code

set_error_handler(function ($type, $msg) use (&$error) {
    $error = $msg;
});

in the move() function of UploadedFile class of the symfony/http-foundation package that Laravel uses Here

Intelephense extension on VSCode raises a warning

Undefined variable '$error'

Deeply apologize for the screenshot of code (just in case this is a bug in the extension and you can't reproduce, please tell me) Sorry

enter image description here

While researching this, I found this answer that passes a closure use to set_error_handler

$that = $this;
set_error_handler( function() use ($that) { $that->customErrorHandler(); } );

My understanding is that customErrorHandler() is a function in the same class or context and it has to be defined outside the call

Searching for similar example on the official docs yield same results, see here

But the UploadedFile class of Symfony only define a private property $error global to the class so shouldn't it be like this?

$error = $this->error;
set_error_handler(function ($type, $msg) use (&$error) {
    $error = $msg;
});

How does this code know to get the $error variable from the property without defining it?*

*that's if it's getting it from there, otherwise...

How is $error passed here? is the undefined warning legit? given the code does actually work (just curious)


Solution

  • The &$error is making the code work. Basically, it's assigning the $error variable to null when you pass the callable, and then inside, it's setting it to the error message, and used afterwards.

    set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
    $moved = move_uploaded_file($this->getPathname(), $target);
    restore_error_handler();
    

    Then it is using the $error variable if it failed to move:

    if (!$moved) {
        throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
    }
    

    It's the same as:

    $error = null;
    set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
    

    Without the &, it will throw an undefined variable notice.

    And no the $error variable is not related to the error property.

    You can take this code for example:

    function executeCallback($callable) {
        $callable();   
    }
    
    executeCallback(function () use (&$error) {
        $error = 'This is the error message.';
    });
    
    echo $error;
    

    The $error variable is defined in the use (&$error) and used afterward. And the output of the echo would be:

    This is the error message.