АнонимныС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ

АнонимныС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ‚Π°ΠΊΠΆΠ΅ Π·Π½Π°ΡŽΡ‚ ΠΊΠ°ΠΊ замыкания (closures), β€” Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π±Π΅Π· названия. АнонимныС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π²Ρ‹Π·Ρ‹Π²Π°ΡŽΡ‚ ΠΈΠ»ΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‚ ΠΊΠ°ΠΊ значСния Π² ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ с Ρ‚ΠΈΠΏΠΎΠΌ callable.

PHP создаёт Π°Π½ΠΎΠ½ΠΈΠΌΠ½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Ρ‡Π΅Ρ€Π΅Π· класс Closure.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #1 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π°Π½ΠΎΠ½ΠΈΠΌΠ½ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ

<?php

echo preg_replace_callback(
'~-([a-z])~',
function (
$match) {
return
strtoupper($match[1]);
},
'hello-world'
);
// Π’Ρ‹Π²Π΅Π΄Π΅Ρ‚ helloWorld

Замыкания Ρ‚Π°ΠΊΠΆΠ΅ ΠΏΡ€ΠΈΡΠ²Π°ΠΈΠ²Π°ΡŽΡ‚ ΠΊΠ°ΠΊ значСния ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΌ; PHP автоматичСски ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Ρ‹Π²Π°Π΅Ρ‚ Ρ‚Π°ΠΊΠΈΠ΅ выраТСния Π² экзСмпляры Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½Π΅Π³ΠΎ класса Closure. Замыкания ΠΏΡ€ΠΈΡΠ²Π°ΠΈΠ²Π°ΡŽΡ‚ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ Ρ‚Π΅ΠΌ ΠΆΠ΅ синтаксисом, Ρ‡Ρ‚ΠΎ ΠΈ для Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ присваивания, Π²ΠΊΠ»ΡŽΡ‡Π°Ρ ΠΊΠΎΠ½Π΅Ρ‡Π½ΡƒΡŽ Ρ‚ΠΎΡ‡ΠΊΡƒ с запятой:

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #2 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ присваивания Π°Π½ΠΎΠ½ΠΈΠΌΠ½ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ ΠΊΠ°ΠΊ значСния ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ

<?php

$greet
= function($name) {
printf("ΠŸΡ€ΠΈΠ²Π΅Ρ‚, %s\r\n", $name);
};

$greet('ΠœΠΈΡ€');
$greet('PHP');

Замыкания ΡƒΠΌΠ΅ΡŽΡ‚ Π½Π°ΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ ΠΈΠ· Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΎΠΉ области видимости. Для связывания с Π°Π½ΠΎΠ½ΠΈΠΌΠ½ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠ΅ΠΉ ΠΊΠ°ΠΆΠ΄ΡƒΡŽ Ρ‚Π°ΠΊΡƒΡŽ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‚ Π² ΡΠ·Ρ‹ΠΊΠΎΠ²ΡƒΡŽ ΠΊΠΎΠ½ΡΡ‚Ρ€ΡƒΠΊΡ†ΠΈΡŽ use. Начиная с PHP 7.1 нСльзя ΡΠ²ΡΠ·Ρ‹Π²Π°Ρ‚ΡŒ с Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅ΠΌ superglobals, ΠΏΡΠ΅Π²Π΄ΠΎΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ $this ΠΈ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅, названия ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… ΡΠΎΠ²ΠΏΠ°Π΄Π°ΡŽΡ‚ с названиями ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² Π°Π½ΠΎΠ½ΠΈΠΌΠ½ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ. ОбъявлСниС Ρ‚ΠΈΠΏΠ° значСния Π²ΠΎΠ·Π²Ρ€Π°Ρ‚Π° указываСтся послС прСдлоТСния use.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #3 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ наслСдования ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… ΠΈΠ· Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΎΠΉ области видимости

<?php

$message
= 'ΠΏΡ€ΠΈΠ²Π΅Ρ‚';

// Π‘Π΅Π· конструкции use
$example = function () {
var_dump($message);
};
$example();

// НаслСдуСм ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ $message
$example = function () use ($message) {
var_dump($message);
};
$example();

// Анонимная функция наслСдуСт ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ с Ρ‚Π΅ΠΌ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠΌ, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ пСрСмСнная
// содСрТала ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ΠΌ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ, Π° Π½Π΅ Π² мСстС Π²Ρ‹Π·ΠΎΠ²Π° Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
$message = 'ΠΌΠΈΡ€';
$example();

// Ббросим message
$message = 'ΠΏΡ€ΠΈΠ²Π΅Ρ‚';

// НаслСдованиС ΠΏΠΎ ссылкС
$example = function () use (&$message) {
var_dump($message);
};
$example();

// Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ссылки, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΠΈ Π² Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΎΠΉ области видимости,
// отраТаСтся Π²Π½ΡƒΡ‚Ρ€ΠΈ Π²Ρ‹Π·ΠΎΠ²Π° Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
$message = 'ΠΌΠΈΡ€';
$example();

// Замыкания ΡƒΠΌΠ΅ΡŽΡ‚ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Ρ‚ΡŒ стандартныС Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Ρ‹
$example = function ($arg) use ($message) {
var_dump($arg . ', ' . $message);
};
$example("ΠΏΡ€ΠΈΠ²Π΅Ρ‚");

// ОбъявлСниС Ρ‚ΠΈΠΏΠ° значСния Π²ΠΎΠ·Π²Ρ€Π°Ρ‚Π° ΠΈΠ΄Ρ‘Ρ‚ послС конструкции use
$example = function () use ($message): string {
return
"ΠΏΡ€ΠΈΠ²Π΅Ρ‚, $message";
};
var_dump($example());

Π’Ρ‹Π²ΠΎΠ΄ ΠΏΡ€ΠΈΠ²Π΅Π΄Ρ‘Π½Π½ΠΎΠ³ΠΎ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π° Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠΎΡ…ΠΎΠΆ Π½Π°:

Notice: Undefined variable: message in /example.php on line 6
NULL
string(12) "ΠΏΡ€ΠΈΠ²Π΅Ρ‚"
string(12) "ΠΏΡ€ΠΈΠ²Π΅Ρ‚"
string(12) "ΠΏΡ€ΠΈΠ²Π΅Ρ‚"
string(6) "ΠΌΠΈΡ€"
string(20) "ΠΏΡ€ΠΈΠ²Π΅Ρ‚, ΠΌΠΈΡ€"
string(20) "ΠΏΡ€ΠΈΠ²Π΅Ρ‚, ΠΌΠΈΡ€"

Начиная с PHP 8.0.0 списку ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ…, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ функция наслСдуСт ΠΈΠ· области видимости, Ρ€Π°Π·Ρ€Π΅ΡˆΠ°Π΅Ρ‚ΡΡ Π²ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ ΠΊΠΎΠ½Π΅Ρ‡Π½ΡƒΡŽ Π·Π°ΠΏΡΡ‚ΡƒΡŽ, ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ парсСр ΠΏΡ€ΠΎΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΠ΅Ρ‚.

НаслСдованиС ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… ΠΈΠ· Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΎΠΉ области видимости отличаСтся ΠΎΡ‚ наслСдования Π³Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Ρ… ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ…. Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‚ Π² глобальной области видимости, которая остаётся ΠΏΡ€Π΅ΠΆΠ½Π΅ΠΉ, какая Π±Ρ‹ функция Π½ΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΠ»Π°ΡΡŒ. Π ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠ°Ρ ΠΎΠ±Π»Π°ΡΡ‚ΡŒ видимости замыкания β€” функция, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ объявили Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅; Π½Π΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ функция, ΠΈΠ· ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅ Π²Ρ‹Π·Π²Π°Π»ΠΈ. Π‘ΠΌΠΎΡ‚Ρ€ΠΈΡ‚Π΅ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€:

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #4 Замыкания ΠΈ ΠΎΠ±Π»Π°ΡΡ‚ΡŒ видимости

<?php

// Базовая ΠΊΠΎΡ€Π·ΠΈΠ½Π° ΠΏΠΎΠΊΡƒΠΏΠΎΠΊ, которая содСрТит список
// ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚ΠΎΠ² ΠΈ количСство ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚Π°. Π’ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄,
// ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ вычисляСт ΠΎΠ±Ρ‰ΡƒΡŽ Ρ†Π΅Π½Ρƒ элСмСнтов ΠΊΠΎΡ€Π·ΠΈΠ½Ρ‹ Ρ‡Π΅Ρ€Π΅Π·
// callback-Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅
class Cart
{
const
PRICE_BUTTER = 1.00;
const
PRICE_MILK = 3.00;
const
PRICE_EGGS = 6.95;

protected
$products = array();

public function
add($product, $quantity)
{
$this->products[$product] = $quantity;
}

public function
getQuantity($product)
{
return isset(
$this->products[$product]) ? $this->products[$product] :
FALSE;
}

public function
getTotal($tax)
{
$total = 0.00;

$callback = function ($quantity, $product) use ($tax, &$total)
{
$pricePerItem = constant(
__CLASS__ . "::PRICE_" . strtoupper($product)
);

$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};

array_walk($this->products, $callback);
return
round($total, 2);
}
}

$my_cart = new Cart;

// ДобавляСм элСмСнты Π² ΠΊΠΎΡ€Π·ΠΈΠ½Ρƒ
$my_cart->add('butter', 1);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);

// Π’Ρ‹Π²ΠΎΠ΄ΠΈΠΌ ΠΎΠ±Ρ‰ΡƒΡŽ сумму с Π½Π°Π»ΠΎΠ³ΠΎΠΌ 5 % Π½Π° ΠΏΡ€ΠΎΠ΄Π°ΠΆΡƒ
print $my_cart->getTotal(0.05) . "\n";
// Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π²Π΅Π½ 54.29

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #5 АвтоматичСская привязка ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ $this

<?php

class Test
{
public function
testing()
{
return function () {
var_dump($this);
};
}
}

$object = new Test();
$function = $object->testing();
$function();

Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ выполнСния ΠΏΡ€ΠΈΠ²Π΅Π΄Ρ‘Π½Π½ΠΎΠ³ΠΎ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°:

object(Test)#1 (0) {
}

ΠŸΡ€ΠΈ объявлСнии замыкания Π² контСкстС класса Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ класс автоматичСски привязываСтся ΠΊ Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΡŽ, Π° Ρ‡Π»Π΅Π½Ρ‹ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π°ΡŽΡ‚ доступ ΠΊ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ $this Π² области видимости Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ. БтатичСскиС Π°Π½ΠΎΠ½ΠΈΠΌΠ½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡŽΡ‚, ΠΊΠΎΠ³Π΄Π° автоматичСская привязка ΠΊ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΌΡƒ классу Π½Π΅ трСбуСтся.

БтатичСскиС Π°Π½ΠΎΠ½ΠΈΠΌΠ½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ

АнонимныС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Ρ€Π°Π·Ρ€Π΅ΡˆΠ°Π΅Ρ‚ΡΡ ΠΎΠ±ΡŠΡΠ²Π»ΡΡ‚ΡŒ статичСски. Π­Ρ‚ΠΎ ΠΏΡ€Π΅Π΄ΠΎΡ‚Π²Ρ€Π°Ρ‚ΠΈΡ‚ автоматичСскоС связываниС замыкания с Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΌ классом. ΠžΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ Ρ‚ΠΎΠΆΠ΅ Π½Π΅ ΡΠ²ΡΠ·Ρ‹Π²Π°ΡŽΡ‚ΡΡ со статичСским Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅ΠΌ Π²ΠΎ врСмя выполнСния ΠΊΠΎΠ΄Π°.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #6 ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° ΠΎΠ±Ρ€Π°Ρ‚ΠΈΡ‚ΡŒΡΡ ΠΊ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ $this Π² статичСской Π°Π½ΠΎΠ½ΠΈΠΌΠ½ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ

<?php

class Foo
{
function
__construct()
{
$func = static function () {
var_dump($this);
};

$func();
}
};

new
Foo();

Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ выполнСния ΠΏΡ€ΠΈΠ²Π΅Π΄Ρ‘Π½Π½ΠΎΠ³ΠΎ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°:

Fatal error: Uncaught Error: Using $this when not in object context in script:7
Stack trace:
#0 script(9): Foo::{closure:Foo::__construct():6}()
#1 script(12): Foo->__construct()
#2 {main}
  thrown in script on line 7

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #7 ΠŸΠΎΠΏΡ‹Ρ‚ΠΊΠ° ΡΠ²ΡΠ·Π°Ρ‚ΡŒ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ со статичСской Π°Π½ΠΎΠ½ΠΈΠΌΠ½ΠΎΠΉ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠ΅ΠΉ

<?php

$func
= static function () {
// Π’Π΅Π»ΠΎ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
};

$func = $func->bindTo(new stdClass());
$func();

Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ выполнСния ΠΏΡ€ΠΈΠ²Π΅Π΄Ρ‘Π½Π½ΠΎΠ³ΠΎ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°:

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in script on line 5

Fatal error: Uncaught Error: Value of type null is not callable in script:6
Stack trace:
#0 {main}
  thrown in script on line 6

Бписок измСнСний

ВСрсия ОписаниС
8.3.0 Замыкания, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ создали ΠΈΠ· магичСских ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ², Π½Π°ΡƒΡ‡ΠΈΠ»ΠΈΡΡŒ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Ρ‚ΡŒ ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½Ρ‹Π΅ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Ρ‹.
7.1.0 Анонимным функциям нСльзя Π·Π°ΠΌΡ‹ΠΊΠ°Ρ‚ΡŒΡΡ Π²ΠΎΠΊΡ€ΡƒΠ³ superglobals, ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ $this ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΎΠΉ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ, имя ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ совпадаСт с Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°.

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅: Π’Π½ΡƒΡ‚Ρ€ΠΈ Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠΉ Ρ€Π°Π·Ρ€Π΅ΡˆΠ°Π΅Ρ‚ΡΡ Π²Ρ‹Π·Ρ‹Π²Π°Ρ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ func_num_args(), func_get_arg() ΠΈ func_get_args().

οΌ‹Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ 20 notes

up
336
orls ΒΆ
15 years ago
Watch out when 'importing' variables to a closure's scope  -- it's easy to miss / forget that they are actually being *copied* into the closure's scope, rather than just being made available.

So you will need to explicitly pass them in by reference if your closure cares about their contents over time:

<?php
$result = 0;

$one = function()
{ var_dump($result); };

$two = function() use ($result)
{ var_dump($result); };

$three = function() use (&$result)
{ var_dump($result); };

$result++;

$one();    // outputs NULL: $result is not in scope
$two();    // outputs int(0): $result was copied
$three();    // outputs int(1)
?>

Another less trivial example with objects (what I actually tripped up on):

<?php
//set up variable in advance
$myInstance = null;

$broken = function() uses ($myInstance)
{
    if(!empty($myInstance)) $myInstance->doSomething();
};

$working = function() uses (&$myInstance)
{
    if(!empty($myInstance)) $myInstance->doSomething();
}

//$myInstance might be instantiated, might not be
if(SomeBusinessLogic::worked() == true)
{
    $myInstance = new myClass();
}

$broken();    // will never do anything: $myInstance will ALWAYS be null inside this closure.
$working();    // will call doSomething if $myInstance is instantiated

?>
up
16
ayon at hyurl dot com ΒΆ
9 years ago
One way to call a anonymous function recursively is to use the USE keyword and pass a reference to the function itself:

<?php
$count = 1;
$add = function($count) use (&$add){
    $count += 1;
    if($count < 10) $count = $add($count); //recursive calling
    return $count;
};
echo $add($count); //Will output 10 as expected
?>
up
29
erolmon dot kskn at gmail dot com ΒΆ
10 years ago
<?php
    /*
    (string) $name Name of the function that you will add to class.
    Usage : $Foo->add(function(){},$name);
    This will add a public function in Foo Class.
    */
    class Foo
    {
        public function add($func,$name)
        {
            $this->{$name} = $func;
        }
        public function __call($func,$arguments){
            call_user_func_array($this->{$func}, $arguments); 
        }
    }
    $Foo = new Foo();
    $Foo->add(function(){
        echo "Hello World";
    },"helloWorldFunction");
    $Foo->add(function($parameterone){
        echo $parameterone;
    },"exampleFunction");
    $Foo->helloWorldFunction(); /*Output : Hello World*/
    $Foo->exampleFunction("Hello PHP"); /*Output : Hello PHP*/
?>
up
29
cHao ΒΆ
12 years ago
In case you were wondering (cause i was), anonymous functions can return references just like named functions can.  Simply use the & the same way you would for a named function...right after the `function` keyword (and right before the nonexistent name).

<?php
    $value = 0;
    $fn = function &() use (&$value) { return $value; };

    $x =& $fn();
    var_dump($x, $value);        // 'int(0)', 'int(0)'
    ++$x;
    var_dump($x, $value);        // 'int(1)', 'int(1)'
up
10
rob at ubrio dot us ΒΆ
16 years ago
You can always call protected members using the __call() method - similar to how you hack around this in Ruby using send.

<?php

class Fun
{
 protected function debug($message)
 {
   echo "DEBUG: $message\n";
 }

 public function yield_something($callback)
 {
   return $callback("Soemthing!!");
 }

 public function having_fun()
 {
   $self =& $this;
   return $this->yield_something(function($data) use (&$self)
   {
     $self->debug("Doing stuff to the data");
     // do something with $data
     $self->debug("Finished doing stuff with the data.");
   });
 }

 // Ah-Ha!
 public function __call($method, $args = array())
 {
   if(is_callable(array($this, $method)))
     return call_user_func_array(array($this, $method), $args);
 }
}

$fun = new Fun();
echo $fun->having_fun();

?>
up
11
mail at mkharitonov dot net ΒΆ
12 years ago
Some comparisons of PHP and JavaScript closures.

=== Example 1 (passing by value) ===
PHP code:
<?php
$aaa = 111;
$func = function() use($aaa){ print $aaa; };
$aaa = 222;
$func(); // Outputs "111"
?>

Similar JavaScript code:
<script type="text/javascript">
var aaa = 111;
var func = (function(aaa){ return function(){ alert(aaa); } })(aaa);
aaa = 222;
func(); // Outputs "111"
</script>

Be careful, following code is not similar to previous code:
<script type="text/javascript">
var aaa = 111;
var bbb = aaa;
var func = function(){ alert(bbb); };
aaa = 222;
func(); // Outputs "111", but only while "bbb" is not changed after function declaration

// And this technique is not working in loops:
var functions = [];
for (var i = 0; i < 2; i++)
{
    var i2 = i;
    functions.push(function(){ alert(i2); });
}
functions[0](); // Outputs "1", wrong!
functions[1](); // Outputs "1", ok
</script>

=== Example 2 (passing by reference) ===
PHP code:
<?php
$aaa = 111;
$func = function() use(&$aaa){ print $aaa; };
$aaa = 222;
$func(); // Outputs "222"
?>

Similar JavaScript code:
<script type="text/javascript">
var aaa = 111;
var func = function(){ alert(aaa); };
aaa = 222; // Outputs "222"
func();
</script>
up
13
a dot schaffhirt at sedna-soft dot de ΒΆ
16 years ago
When using anonymous functions as properties in Classes, note that there are three name scopes: one for constants, one for properties and one for methods. That means, you can use the same name for a constant, for a property and for a method at a time.

Since a property can be also an anonymous function as of PHP 5.3.0, an oddity arises when they share the same name, not meaning that there would be any conflict.

Consider the following example:

<?php
    class MyClass {
        const member = 1;
        
        public $member;
        
        public function member () {
            return "method 'member'";
        }
        
        public function __construct () {
            $this->member = function () {
                return "anonymous function 'member'";
            };
        }
    }
    
    header("Content-Type: text/plain");
    
    $myObj = new MyClass();

    var_dump(MyClass::member);  // int(1)
    var_dump($myObj->member);   // object(Closure)#2 (0) {}
    var_dump($myObj->member()); // string(15) "method 'member'"
    $myMember = $myObj->member;
    var_dump($myMember());      // string(27) "anonymous function 'member'"
?>

That means, regular method invocations work like expected and like before. The anonymous function instead, must be retrieved into a variable first (just like a property) and can only then be invoked.

Best regards,
up
11
simon at generalflows dot com ΒΆ
14 years ago
<?php

/* 
 * An example showing how to use closures to implement a Python-like decorator 
 * pattern.
 *
 * My goal was that you should be able to decorate a function with any
 * other function, then call the decorated function directly: 
 *
 * Define function:         $foo = function($a, $b, $c, ...) {...}
 * Define decorator:        $decorator = function($func) {...}
 * Decorate it:             $foo = $decorator($foo)
 * Call it:                 $foo($a, $b, $c, ...)
 *
 * This example show an authentication decorator for a service, using a simple
 * mock session and mock service. 
 */
 
session_start();

/* 
 * Define an example decorator. A decorator function should take the form:
 * $decorator = function($func) {
 *     return function() use $func) {
 *         // Do something, then call the decorated function when needed:
 *         $args = func_get_args($func);
 *         call_user_func_array($func, $args);
 *         // Do something else.
 *     };
 * };
 */
$authorise = function($func) {
    return function() use ($func) {
        if ($_SESSION['is_authorised'] == true) {
            $args = func_get_args($func);
            call_user_func_array($func, $args);
        }
        else {
            echo "Access Denied";
        }
    };
};

/* 
 * Define a function to be decorated, in this example a mock service that
 * need to be authorised. 
 */ 
$service = function($foo) {
    echo "Service returns: $foo";
};

/* 
 * Decorate it. Ensure you replace the origin function reference with the
 * decorated function; ie just $authorise($service) won't work, so do
 * $service = $authorise($service)
 */
$service = $authorise($service);

/* 
 * Establish mock authorisation, call the service; should get 
 * 'Service returns: test 1'. 
 */
$_SESSION['is_authorised'] = true;
$service('test 1');

/* 
 * Remove mock authorisation, call the service; should get 'Access Denied'. 
 */
$_SESSION['is_authorised'] = false;
$service('test 2');

?>
up
13
toonitw at gmail dot com ΒΆ
8 years ago
As of PHP 7.0, you can use IIFE(Immediately-invoked function expression) by wrapping your anonymous function with ().

<?php
$type = 'number';
var_dump( ...( function() use ($type) { 
    if ($type=='number') return [1,2,3]; 
    else if ($type=='alphabet') return ['a','b','c'];
} )() );
?>
up
13
john at binkmail dot com ΒΆ
9 years ago
PERFORMANCE BENCHMARK 2017!

I decided to compare a single, saved closure against constantly creating the same anonymous closure on every loop iteration. And I tried 10 million loop iterations, in PHP 7.0.14 from Dec 2016. Result:

a single saved closure kept in a variable and re-used (10000000 iterations): 1.3874590396881 seconds

new anonymous closure created each time (10000000 iterations): 2.8460240364075 seconds

In other words, over the course of 10 million iterations, creating the closure again during every iteration only added a total of "1.459 seconds" to the runtime. So that means that every creation of a new anonymous closure takes about 146 nanoseconds on my 7 years old dual-core laptop. I guess PHP keeps a cached "template" for the anonymous function and therefore doesn't need much time to create a new instance of the closure!

So you do NOT have to worry about constantly re-creating your anonymous closures over and over again in tight loops! At least not as of PHP 7! There is absolutely NO need to save an instance in a variable and re-use it. And not being restricted by that is a great thing, because it means you can feel free to use anonymous functions exactly where they matter, as opposed to defining them somewhere else in the code. :-)
up
11
dexen dot devries at gmail dot com ΒΆ
8 years ago
Every instance of a lambda has own instance of static variables. This provides for great event handlers, accumulators, etc., etc.

Creating new lambda with function() { ... }; expression creates new instance of its static variables. Assigning a lambda to a variable does not create a new instance. A lambda is object of class Closure, and assigning lambdas to variables has the same semantics as assigning object instance to variables.

Example script: $a and $b have separate instances of static variables, thus produce different output. However $b and $c share their instance of static variables - because $c is refers to the same object of class Closure as $b - thus produce the same output.

#!/usr/bin/env php
<?php

function generate_lambda() : Closure
{
        # creates new instance of lambda
    return function($v = null) {
        static $stored;
        if ($v !== null)
            $stored = $v;
        return $stored;
    };
}

$a = generate_lambda();  # creates new instance of statics
$b = generate_lambda();  # creates new instance of statics
$c = $b;                                 # uses the same instance of statics as $b

$a('test AAA');
$b('test BBB');
$c('test CCC');  # this overwrites content held by $b, because it refers to the same object

var_dump([ $a(), $b(), $c() ]);
?>

This test script outputs:
array(3) {
  [0]=>
  string(8) "test AAA"
  [1]=>
  string(8) "test CCC"
  [2]=>
  string(8) "test CCC"
}
up
8
derkontrollfreak+9hy5l at gmail dot com ΒΆ
12 years ago
Beware that since PHP 5.4 registering a Closure as an object property that has been instantiated in the same object scope will create a circular reference which prevents immediate object destruction:
<?php

class Test
{
    private $closure;

    public function __construct()
    {
        $this->closure = function () {
        };
    }

    public function __destruct()
    {
        echo "destructed\n";
    }
}

new Test;
echo "finished\n";

/*
 * Result in PHP 5.3:
 * ------------------
 * destructed
 * finished
 *
 * Result since PHP 5.4:
 * ---------------------
 * finished
 * destructed
 */

?>

To circumvent this, you can instantiate the Closure in a static method:
<?php

public function __construct()
{
    $this->closure = self::createClosure();
}

public static function createClosure()
{
    return function () {
    };
}

?>
up
3
jake dot tunaley at berkeleyit dot com ΒΆ
7 years ago
Beware of using $this in anonymous functions assigned to a static variable.

<?php
class Foo {
    public function bar() {
        static $anonymous = null;
        if ($anonymous === null) {
            // Expression is not allowed as static initializer workaround
            $anonymous = function () {
                return $this;
            };
        }
        return $anonymous();
    }
}

$a = new Foo();
$b = new Foo();
var_dump($a->bar() === $a); // True
var_dump($b->bar() === $a); // Also true
?>

In a static anonymous function, $this will be the value of whatever object instance that method was called on first.

To get the behaviour you're probably expecting, you need to pass the $this context into the function.

<?php
class Foo {
    public function bar() {
        static $anonymous = null;
        if ($anonymous === null) {
            // Expression is not allowed as static initializer workaround
            $anonymous = function (self $thisObj) {
                return $thisObj;
            };
        }
        return $anonymous($this);
    }
}

$a = new Foo();
$b = new Foo();
var_dump($a->bar() === $a); // True
var_dump($b->bar() === $a); // False
?>
up
6
kdelux at gmail dot com ΒΆ
16 years ago
Here is an example of one way to define, then use the variable ( $this ) in Closure functions.  The code below explores all uses, and shows restrictions.

The most useful tool in this snippet is the requesting_class() function that will tell you which class is responsible for executing the current Closure().  

Overview:
-----------------------
Successfully find calling object reference.
Successfully call $this(__invoke);
Successfully reference $$this->name;
Successfully call call_user_func(array($this, 'method'))

Failure: reference anything through $this->
Failure: $this->name = ''; 
Failure: $this->delfect(); 

<?php
 
    
    
    function requesting_class()
    {
        foreach(debug_backtrace(true) as $stack){
            if(isset($stack['object'])){
                return $stack['object'];
            }
        }
        
    }
    
        
    
    
    
    
    class Person
    {
        public $name = '';
        public $head = true;
        public $feet = true;
        public $deflected = false;
        
        function __invoke($p){ return $this->$p; }
        function __toString(){ return 'this'; } // test for reference
        
        function __construct($name){ $this->name = $name; }
        function deflect(){ $this->deflected = true; }
        
        public function shoot()
        { // If customAttack is defined, use that as the shoot resut.  Otherwise shoot feet
            if(is_callable($this->customAttack)){
                return call_user_func($this->customAttack);
            }
            
            $this->feet = false;
        }
    }

    $p = new Person('Bob');

    
    $p->customAttack = 
                function(){
                    
                    echo $this; // Notice: Undefined variable: this
                    
                    #$this = new Class() // FATAL ERROR
                    
                    // Trick to assign the variable '$this'
                    extract(array('this' => requesting_class())); // Determine what class is responsible for making the call to Closure
                    
                    var_dump( $this  );  // Passive reference works
                    var_dump( $$this ); // Added to class:  function __toString(){ return 'this'; }
                    
                    $name = $this('name'); // Success
                    echo $name;            // Outputs: Bob
                    echo '<br />';
                    echo $$this->name;
                    
                    call_user_func_array(array($this, 'deflect'), array()); // SUCCESSFULLY CALLED
                    
                    #$this->head = 0; //** FATAL ERROR: Using $this when not in object context
                    $$this->head = 0;  // Successfully sets value
                    
                };
 
    print_r($p);
    
    $p->shoot();
    
    print_r($p);

    
    die();

?>
up
2
Anonymous ΒΆ
16 years ago
If you want to check whether you're dealing with a closure specifically and not a string or array callback you can do this:

<?php
$isAClosure = is_callable($thing) && is_object($thing);
?>
up
1
mike at borft dot student dot utwente dot nl ΒΆ
14 years ago
Since it is possible to assign closures to class variables, it is a shame it is not possible to call them directly. ie. the following does not work:
<?php
class foo {

  public test;

  public function __construct(){
    $this->test = function($a) {
      print "$a\n";
    };
  }
}

$f = new foo();

$f->test();
?>

However, it is possible using the magic __call function:
<?php
class foo {

  public test;

  public function __construct(){
    $this->test = function($a) {
      print "$a\n";
    };
  }

  public function __call($method, $args){
    if ( $this->{$method} instanceof Closure ) {
      return call_user_func_array($this->{$method},$args);
    } else {
      return parent::__call($method, $args);
    }
  }
}
$f = new foo();
$f->test();
?>
it 
Hope it helps someone ;)
up
-2
Hayley Watson ΒΆ
2 years ago
If you have a closure (or other callable) stored in an object property and you want to call it, you can use parentheses to disambiguate between it and a method call:

<?php
class Test
{
    public $callable;

    function __construct()
    {
        $this->callable = function($a) { return $a + 2; };
    }
}

$t = new Test;

echo ($t->callable)(40);
?>
up
-4
gabriel dot totoliciu at ddsec dot net ΒΆ
15 years ago
If you want to make a recursive closure, you will need to write this:

$some_var1="1";
$some_var2="2";

function($param1, $param2) use ($some_var1, $some_var2)
{

//some code here

call_user_func(__FUNCTION__, $other_param1, $other_param2);

//some code here

}

If you need to pass values by reference you should check out

http://www.php.net/manual/en/function.call-user-func.php
http://www.php.net/manual/en/function.call-user-func-array.php

If you're wondering if $some_var1 and $some_var2 are still visible by using the call_user_func, yes, they are available.
up
-2
Hayley Watson ΒΆ
2 years ago
"If this automatic binding of the current class is not wanted, then static anonymous functions may be used instead. "

The main reason why you would not want automatic binding is that as long as the Closure object created for the anonymous function exists, it retains a reference to the object that spawned it, preventing the object from being destroyed, even if the object is no longer alive anywhere else in the program, and even if the function itself doesn't use $this.

<?php

class Foo
{
    public function __construct(private string $id)
    {
        echo "Creating Foo " . $this->id, "\n";
    }
    public function gimme_function()
    {
        return function(){};
    }
    public function gimme_static_function()
    {
        return static function(){};
    }
    public function __destruct()
    {
        echo "Destroying Foo " . $this->id, "\n";
    }
}

echo "An object is destroyed as soon as its last reference is removed.\n";
$t = new Foo('Alice');
$t = new Foo('Bob'); // Causes Alice to be destroyed.
// Now destroy Bob.
unset($t);
echo "---\n";

echo "A non-static anonymous function retains a reference to the object which created it.\n";
$u = new Foo('Carol');
$ufn = $u->gimme_function();
$u = new Foo('Daisy'); // Does not cause Carol to be destroyed,
                       // because there is still a reference to
                       // it in the function held by $ufn.
unset($u); // Causes Daisy to be destroyed.
echo "---\n"; // Note that Carol hasn't been destroyed yet.

echo "A static anonymous function does not retain a reference to the object which created it.\n";
$v = new Foo('Eve');
$vfn = $v->gimme_static_function();
$v = new Foo('Farid'); // The function held by $vfn does not
                       // hold a reference to Eve, so Eve does get destroyed here.
unset($v); // Destroy Farid
echo "---\n";
// And then the program finishes, discarding any references to any objects still alive
// (specifically, Carol).
?>

Because $ufn survived to the end of the end of the program, Carol survived as well. $vfn also survived to the end of the program, but the function it contained was declared static, so didn't retain a reference to Eve.

Anonymous functions that retain references to otherwise-dead objects are therefore a potential source of memory leaks. If the function has no use for the object that spawned it, declaring it static prevents it from causing the object to outlive its usefulness.
up
-5
petersenc at gmail dot com ΒΆ
1 year ago
As of PHP 8.3.9 PHP doesn't allow type hinting within the use statement. Consider the following Laravel route:

Route::get('/tags/{tag}', function (string $tag) use ($posts): View {
    $tagPosts = $posts->filter(
            function (Post $post) use ($tag): bool {
                return in_array($tag, $post->tags);
            }
        );
    
    return view('tags.show', [
        'posts' => $tagPosts,
        'tag' => $tag
    ]);
});

As you can see I can make the code more verbose in the closures by type hinting the parameters and the return type. use however doesn't allow type hinting.