Search code examples
phpfunctionclosuresslimphp-closures

Slim protect callables


In Slim Framework, there is a protect function that wraps callables within a function (i.e. another callable). The description in the manual says:

What if you want to literally store a closure as the raw value and not have it invoked? You can do that like this:

$app->myClosure = $app->container->protect(function () {});

Looking into the source code, I see this:

/**
 * Protect closure from being directly invoked
 * @param  Closure $callable A closure to keep from being invoked and evaluated
 * @return Closure
 */
public function protect(\Closure $callable)
{
    return function () use ($callable) {
        return $callable;
    };
}

I'm wondering what the point of that is. Here I did my own test:

$foo = function() {
    echo "Hello";
};

$bar = function () use ($foo) {
    return $foo;
};

var_dump($foo);
var_dump($bar);

This is what I got:

object(Closure)#1 (0) {
}
object(Closure)#2 (1) {
  ["static"]=>
  array(1) {
    ["foo"]=>
    object(Closure)#1 (0) {
    }
  }
}

I can call $bar() to return the callable but why would I do that if I can just use $foo? Can someone please explain the purpose of this?


Solution

  • It is all about closures being invoked on runtime. The point of it becomes apparent when you need to use a callback (pass a closure as callable \Closure to another function) without immediately invoking it.

    Let's look what happens inside our Slim run.

    So if we simply assign a closure to a resource, like so

    $app->foo = function () {
        return 'invoked foo returns this string';
    };
    

    or as a Slims singleton resource

    $app->container->singleton('foo', function () {
        return 'invoked foo returns this string';
    });
    

    it will be invoked on each call or on our first call to it, respectively ... so

    $app->foo;
    

    will return the string invoked foo returns this string.

    Lets say that we want another function to use our callable (as some kind of middle layer) and wants to invoke it with call_user_function(). So we don't want to pass in the invoked function (which will be passing the returned value), but rather an uninvoked closure, which we achieve by assigning the closure to a variable/resource using the protect() method

    $app->bar = $app->container->protect(function () {
        return 'bar returns a callable closure';
    });
    

    And for a demonstration let's pass our $app->foo and $app->bar to call_user_function():

    call_user_func($app->foo);
    

    will throw an error

    "call_user_func() expects parameter 1 to be a valid callback,
     function 'invoked foo returns this string' not found or invalid function name"
    

    because it tries to invoke the returned string, where

    call_user_func($app->bar);
    

    calls the closure saved in $app->bar and returns its return string, like so:

    "bar returns a callable closure"
    

    I hope this example illustrates the usefulness of Slim's protect() method.