42

The example in the php documentation on Closure::bind include static on the anonymous function declaration. why? I can't find the difference if it is removed.

with:

class A {
    private static $sfoo = 1;
}
$cl1 = static function() { // notice the "static"
    return self::$sfoo;
};
$bcl1 = Closure::bind($cl1, null, 'A');
echo $bcl1(); // output: 1

without:

class A {
    private static $sfoo = 1;
}
$cl1 = function() {
    return self::$sfoo;
};
$bcl1 = Closure::bind($cl1, null, 'A');
echo $bcl1(); // output: 1
Burhan Ali
  • 2,258
  • 1
  • 28
  • 38
learning php
  • 1,134
  • 2
  • 10
  • 14

4 Answers4

50

I beg to differ. It's true that often it won't matter. But sometimes it matters a whole lot.

The closure holding a reference to $this might prevent garbage collection of that object, which in turn may also impact performance significantly. Here's an example where it really makes a huge difference:

class LargeObject {
    protected $array;

    public function __construct() {
        $this->array = array_fill(0, 2000, 17);
    }

    public function getItemProcessor(): Closure {
        // Try with and without 'static' here
        return static function () {
            // do some processing unrelated to $this
        };
    }
}

$start = microtime(true);

$processors = [];
for ($i = 0; $i < 2000; $i++) {
    $lo = new LargeObject();
    $processors[] = $lo->getItemProcessor();
}

$memory = memory_get_usage() >> 20;
$time = (microtime(true) - $start) * 1000;
printf("This took %dms and %dMB of memory\n", $time, $memory);

Here's the output with a normal closure:

This took 55ms and 134MB of memory

Here's the output with a static closure:

This took 22ms and 1MB of memory

I tested this with PHP 7.3.19 on Debian Buster, so YMMV. Obviously this is a specially constructed example to demonstrate the difference. But things like this may happen in real applications as well. I started using Slevomat's SlevomatCodingStandard.Functions.StaticClosure sniff to remind me to always use static closures.

jlh
  • 4,349
  • 40
  • 45
  • 3
    20000 iterations: With static: This took 107ms and 7MB of memory / Without static: This took 545ms and 1338MB of memory At PHP 7.4.16 / linux mint (ubuntu beaver) – Karel Wintersky Apr 19 '21 at 09:05
  • 4
    try it across various php version: https://3v4l.org/QnDuA, and it's still valid in php 8.1 – n0099 Dec 07 '21 at 21:03
40

found the difference: you can't bind static closures to object, only change the object scope.

class foo { }

$cl = static function() { };

Closure::bind($cl, new foo); // PHP Warning:  Cannot bind an instance to a static closure
Closure::bind($cl, null, 'foo') // you can change the closure scope
learning php
  • 1,134
  • 2
  • 10
  • 14
  • 1
    +1 Good find; I had noticed that `$this` is unbound, but the fact that it *cannot* be bound is interesting. – Dan Lugg May 08 '14 at 23:30
21

Static Closures, like any other static method, cannot access $this.

Like any other method, a non-static Closure that does not access $this will generally work in a static context.

Hendrik Rombach
  • 320
  • 2
  • 7
  • 3
    can you provide a source of static closure's performance gain? – Danon Apr 30 '20 at 19:29
  • I'd also like to see the source of the performance imporvement and how measurable it might be in a codebase. For most of use the extra characters neeing parsed might not outweigh any performance gain. – William Patton Jun 24 '20 at 20:21
  • I based the performance argument on hearsay, I since benchmarked it and it's negligible – Hendrik Rombach Jun 26 '20 at 09:26
8

As you've noticed, it doesn't really matter.

It's like using the static keyword on a class method. You don't necessarily need it if you don't reference $this within the method (though this does violate strict standards).

I suppose PHP can work out you mean the Closure to access A statically due to the null 2nd argument to bind()

reformed
  • 4,505
  • 11
  • 62
  • 88
Phil
  • 157,677
  • 23
  • 242
  • 245