Закриття в PHP ... що, власне, це і коли вам потрібно буде ними скористатися?


82

Тож я програмую в приємній, сучасній, об’єктно-орієнтованій манері. Я регулярно використовую різні аспекти ООП, які реалізує PHP, але мені цікаво, коли, можливо, мені доведеться використовувати закриття. Будь-які експерти, які можуть пролити світло на те, коли було б корисно здійснити закриття?

Відповіді:


82

PHP автоматично підтримуватиме закриття в 5.3. Закриття - це добре, коли вам потрібна локальна функція, яка використовується лише для якихось невеликих, конкретних цілей. RFC для закриття дає хороший приклад:

function replace_spaces ($text) {
    $replacement = function ($matches) {
        return str_replace ($matches[1], ' ', ' ').' ';
    };
    return preg_replace_callback ('/( +) /', $replacement, $text);
}

Це дозволяє вам визначити replacementфункцію локально всередині replace_spaces(), щоб вона не була такою:
1) захаращення глобального простору імен;
2 ) змусити людей за три роки дивуватися, чому існує функція, визначена глобально, яка використовується лише всередині однієї іншої функції

Це тримає речі впорядковано. Зверніть увагу, як сама функція не має назви, вона просто визначається і призначається як посилання на $replacement.

Але пам’ятайте, вам доведеться почекати PHP 5.3 :)

Ви також можете отримати доступ до змінних, які не входять до сфери дії, до закриття, використовуючи ключове слово use. Розглянемо цей приклад.

// Set a multiplier  
 $multiplier = 3;

// Create a list of numbers  
 $numbers = array(1,2,3,4);

// Use array_walk to iterate  
 // through the list and multiply  
 array_walk($numbers, function($number) use($multiplier){  
 echo $number * $multiplier;  
 }); 

Чудове пояснення дано тут Що таке php лямбди та закриття


1
Це дивовижне пояснення. +1
Девід Дж Едді

4
Мені сподобалося пояснення, чому ви використовуєте закриття. Більшість людей насправді цього не розуміють. +1
Керрі Кендалл

10
Це пояснення анонімних функцій, а не пояснення закриття. Анонімні функції, як ви кажете, такі ж, як і названі функції, за винятком того, що вони не є глобальними. З іншого боку, закриття - це функції, що містять вільні змінні з лексичним масштабом (оголошені з використанням); тобто вони можуть копіювати та посилатися на значення з області, в якій вони оголошені, навіть після того, як все інше було зібрано сміття.
Warbo

@Warbo Це правда; на той час я насправді не відчував різниці між анонімною функцією та закриттям. Закриття справді має сенс лише після того, як ви поцікавитеся анонімними функціями, але до цього часу я все ще знаходжу "пояснення" того, що таке закриття (як моє, 7 років тому ;-)), яке не пояснює аспект його сфери застосування.
dirtside

Ось чому я казав, перевірте JavaScript, де інтенсивно використовуються закриття - але майте на увазі, що правила змінної області дії в PHP відрізняються.
Рольф

17

Коли вам знадобиться функція в майбутньому, яка виконує завдання, яке ви вирішили зараз.

Наприклад, якщо ви читаєте конфігураційний файл і один із параметрів повідомляє, що hash_methodдля вашого алгоритму це multiplyшвидше, ніж square, ви можете створити закриття, яке буде використовуватися там, де вам потрібно щось хешувати.

Закриття можна створити в (наприклад) config_parser(); він створює функцію, що називається do_hash_method()використанням змінних, локальних до config_parser()(з конфігураційного файлу). Кожного разу, коли do_hash_method()його викликають, він має доступ до змінних у локальній області, config_parser()навіть незважаючи на те, що вона не викликається в цій області.

Сподіваємось, хороший гіпотетичний приклад:

function config_parser()
{
    // Do some code here
    // $hash_method is in config_parser() local scope
    $hash_method = 'multiply';

    if ($hashing_enabled)
    {
        function do_hash_method($var)
        {
            // $hash_method is from the parent's local scope
            if ($hash_method == 'multiply')
                return $var * $var;
            else
                return $var ^ $var;
        }
    }
}


function hashme($val)
{
    // do_hash_method still knows about $hash_method
    // even though it's not in the local scope anymore
    $val = do_hash_method($val)
}

Я не можу просто скопіювати цей приклад і запустити його. Кращий приклад, який я можу просто запустити.
Кім Стек

3
Ця відповідь погана. Це безглузде твердження: "Коли в майбутньому вам знадобиться функція, яка виконує завдання, яке ви вирішили зараз".
Відпочинок на Кіпрі

15

Окрім технічних деталей, закриття є основною передумовою стилю програмування, відомого як функціонально-орієнтоване програмування. Закриття приблизно використовується для того самого, що і для об’єкта, в об’єктно-орієнтованому програмуванні; Він пов'язує дані (змінні) разом з деяким кодом (функцією), який потім можна передати кудись ще. Як такі, вони впливають на те, як ви пишете програми, або - якщо ви не змінюєте спосіб написання своїх програм - вони взагалі не впливають.

У контексті PHP вони трохи дивні, оскільки PHP вже важкий для об'єктно-орієнтованої парадигми на основі класу, а також для старої процедурної. Зазвичай мови, які мають закриття, мають повний лексичний обсяг. Для підтримки зворотної сумісності PHP цього не збирається отримувати, тож це означає, що закриття тут буде дещо іншим, ніж в інших мовах. Я думаю, що ми ще не побачили, як саме вони будуть використовуватися.


10

Мені подобається контекст, наданий дописом troelskn. Коли я хочу зробити щось на зразок прикладу Дана Удея в PHP, я використовую шаблон стратегії ОО. На мій погляд, це набагато краще, ніж впровадження нової глобальної функції, поведінка якої визначається під час виконання.

http://en.wikipedia.org/wiki/Strategy_pattern

Ви також можете викликати функції та методи, використовуючи змінну, що містить ім'я методу в PHP, що чудово. отже, ще один приклад на прикладі Дена буде приблизно таким:

class ConfigurableEncoder{
        private $algorithm = 'multiply';  //default is multiply

        public function encode($x){
                return call_user_func(array($this,$this->algorithm),$x);
        }

        public function multiply($x){
                return $x * 5;
        }

        public function add($x){
                return $x + 5;
        }

        public function setAlgorithm($algName){
                switch(strtolower($algName)){
                        case 'add':
                                $this->algorithm = 'add';
                                break;
                        case 'multiply':        //fall through
                        default:                //default is multiply
                                $this->algorithm = 'multiply';
                                break;
                }
        }
}

$raw = 5;
$encoder = new ConfigurableEncoder();                           // set to multiply
echo "raw: $raw\n";                                             // 5
echo "multiply: " . $encoder->encode($raw) . "\n";              // 25
$encoder->setAlgorithm('add');
echo "add: " . $encoder->encode($raw) . "\n";                   // 10

звичайно, якщо ви хочете, щоб він був доступний скрізь, ви можете просто зробити все статичним ...


2

Закриття - це в основному функція, для якої ви пишете визначення в одному контексті, але запускаєте в іншому контексті. Javascript мені дуже допоміг зрозуміти їх, оскільки вони використовуються в JavaScript всюди.

У PHP вони менш ефективні, ніж у JavaScript, через різницю в обсязі та доступності "глобальних" (або "зовнішніх") змінних всередині функцій. Проте, починаючи з PHP 5.4, закриття можуть отримати доступ до об'єкта $ this при запуску всередині об'єкта, це робить їх набагато ефективнішими.

Ось що стосується закриття, і цього повинно бути достатньо, щоб зрозуміти, що написано вище.

Це означає, що слід мати можливість десь записати визначення функції та використовувати змінну $ this всередині визначення функції, потім присвоїти визначення функції змінній (інші наводили приклади синтаксису), а потім передати цю змінну об'єкту і викликати його в контексті об'єкта, тоді функція може отримати доступ до об'єкта і маніпулювати ним за допомогою $ this, ніби це просто ще один із його методів, хоча насправді це не визначено у визначенні класу цього об'єкта, а десь ще.

Якщо це не дуже зрозуміло, то не хвилюйтеся, це стане зрозуміло, як тільки ви почнете їх використовувати.


Чесно кажучи, я це зовсім не зрозуміло, навіть для мене, автора. В основному я кажу: щоб дізнатись, що таке закриття, перевірте їх у JavaScript, але майте на увазі, що область змінних відрізняється між JavaScript і PHP.
Рольф

1

По суті, Закриття - це внутрішні функції, які мають доступ до зовнішніх змінних і використовуються як функція зворотного виклику до неприємної функції (функції, що не мають жодного імені).

 <?php
      $param='ironman';
      function sayhello(){
          $param='captain';
          $func=function () use ($param){
                $param='spiderman';
          };
       $func();
       echo  $param;
       }
      sayhello();
?>

//output captain

//and if we pass variable as a reference as(&$param) then output would be spider man;

$param='captain'in func sayhello()- це локальна змінна до func sayhello(). $param='ironman'вище sayhello()- глобальна змінна. Якщо ви хочете зробити у скрипті лише одну змінну $ param, вам слід зателефонувати: global $param;within sayhello()func
vlakov

0

Ось приклади закриття в php

// Author: HishamDalal@gamil.com
// Publish on: 2017-08-28

class users
{
    private $users = null;
    private $i = 5;

    function __construct(){
        // Get users from database
        $this->users = array('a', 'b', 'c', 'd', 'e', 'f');
    }

    function displayUsers($callback){
        for($n=0; $n<=$this->i; $n++){
            echo  $callback($this->users[$n], $n);
        }
    }

    function showUsers($callback){
        return $callback($this->users);

    }

    function getUserByID($id, $callback){
        $user = isset($this->users[$id]) ? $this->users[$id] : null;
        return $callback($user);
    }

}

$u = new users();

$u->displayUsers(function($username, $userID){
    echo "$userID -> $username<br>";
});

$u->showUsers(function($users){
    foreach($users as $user){
        echo strtoupper($user).' ';
    }

});

$x = $u->getUserByID(2, function($user){

    return "<h1>$user</h1>";
});

echo ($x);

Вихід:

0 -> a
1 -> b
2 -> c
3 -> d
4 -> e
5 -> f

A B C D E F 

c

0

Закриття:

MDN має найкраще пояснення IMO:

Закриття - це комбінація функції, об’єднаної разом (укладена) із посиланнями на оточуючий її стан (лексичне середовище). Іншими словами, закриття дає вам доступ до зони зовнішньої функції з внутрішньої функції.

тобто закриття - це функція з доступом до змінних, що знаходяться в батьківській області. Закриття дозволяє нам зручно створювати функції на льоту, оскільки в деяких ситуаціях функції потрібні лише в одному місці (зворотні виклики, аргументи, що викликаються).

Приклад:

$arr = [1,2,3,3];
$outersScopeNr = 2;

// The second arg in array_filter is a closure
// It would be inconvenient to have this function in global namespace
// The use keyword lets us access a variable in an outer scope
$newArr = array_filter($arr, function ($el) use ($outersScopeNr) {
    return $el === 3 || $el === $outersScopeNr;
});

var_dump($newArr);
// array (size=3)
//  1 => int 2
//  2 => int 3
//  3 => int 3
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.