Pull to refresh

Зачем нужен static при объявлении анонимных функций?

Reading time2 min
Views19K

Буквально на днях пришел вопрос от одного из подписчиков касательно одного из постов моего telegram канала. Его смутил вот такой кусок кода

<?php

usort($firstArray, static function($first, $second) {
    return $first <=> $second;
});

Вопрос звучал так:

Зачем делать callback’и в функции сортировки (usort), статическими?

И я подумал, что это действительно хороший вопрос, на который стоит обратить внимание.

В чем проблема?

Начнем с определения из документации, чтобы засинхронизироваться:

Анонимные функции, также известные как замыкания (closures), позволяют создавать функции, не имеющие определённых имён. Они наиболее полезны в качестве значений callable-параметров, но также могут иметь и множество других применений.

Анонимные функции реализуются с использованием класса Closure.

Там-же, но это почти никто не читает :

При объявлении в контексте класса, текущий класс будет автоматически связан с ним, делая $this доступным внутри функций класса. Если вы не хотите автоматического связывания с текущим классом, используйте статические анонимные функции.

Выходит, что когда Сlosure объявляется в контексте класса, то класс автоматически привязывается к замыканию. Это означает, что $this доступен внутри области анонимной функции:

Код, чтобы протестить самостоятельно
<?php

class ExampleTest extends TestCase
{
 
    public function testBasicTest(): void
    {
        $array = [2, 1];
        usort($array, function ($first, $second) {
            var_dump($this);
            return $first <=> $second;
        });

     	  self::assertTrue(true);
    }

}

На первый взгляд "да и чёрт с ним", но стоит копнуть чуть глубже.

Замыкание, содержащее ссылку на $this, может быть не обработано сборщиком мусора, что, в свою очередь, может существенно повлиять на производительность.

Вот пример без использования static:

<?php 

class LargeObject {
    protected $array;

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

    public function getItemProcessor(): Closure {
        return function () { // Внутри функции любые вычисления
            $a = 1;
            $b = 2;
            return $a + $b;
        };
    }
}

function getPeakMemory(): string
{
    return sprintf('%.2F MiB', memory_get_peak_usage() / 1024 / 1024);
}
$start = microtime(true);

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

var_dump(getPeakMemory());

Как результат, мы получим string(10) "134.10 MiB"

Но в случае, если мы добавим static в 11 строке, то потребление памяти составит string(8) "1.19 MiB"

Всё потому, что в processors[] мы продолжаем накапливать массив, внутри которого находятся Сlosures которые связаны с классом, а значит, содержат все те данные, которые в нём хранятся.

Выводы

Если подвести короткий итог, то анонимные функции без static стоит использовать если вам необходимо привязать объект к области видимости выполнения функции. Во всех остальных случаях можно и нужно использовать static, как минимум, чтобы случайно не выстрелить себе в ногу.

P.S.

Часто для полноценного поста на Хабре мало короткой заметки. Такие выдержки я публикую в своем телеграм-канале https://t.me/beerphp. Подписывайся и сможешь получить больше интересного материала.

Tags:
Hubs:
Total votes 67: ↑65 and ↓2+63
Comments14

Articles