Zamknięcia w PHP… czym dokładnie są i kiedy trzeba ich użyć?

82

Więc programuję w ładny, aktualny, obiektowy sposób. Regularnie korzystam z różnych aspektów OOP, które implementuje PHP, ale zastanawiam się, kiedy może być konieczne użycie domknięć. Czy są jacyś eksperci, którzy mogą rzucić trochę światła na to, kiedy warto byłoby wprowadzić zamknięcia?

rg88
źródło

Odpowiedzi:

82

PHP będzie obsługiwać zamknięcia natywnie w wersji 5.3. Zamknięcie jest dobre, gdy potrzebujesz funkcji lokalnej, która jest używana tylko do jakiegoś małego, konkretnego celu. Dokument RFC dla domknięć daje dobry przykład:

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

Pozwala to zdefiniować replacementfunkcję lokalnie wewnątrz replace_spaces(), aby nie była:
1) Zaśmiecanie globalnej przestrzeni nazw
2 ) Sprawianie, że ludzie trzy lata później zastanawiają się, dlaczego istnieje funkcja zdefiniowana globalnie, która jest używana tylko w jednej innej funkcji

Utrzymuje porządek. Zwróć uwagę, że sama funkcja nie ma nazwy, po prostu jest zdefiniowana i przypisana jako odniesienie $replacement.

Ale pamiętaj, na PHP 5.3 trzeba poczekać :)

Możesz również uzyskać dostęp do zmiennych spoza jego zakresu w zamknięciu za pomocą słowa kluczowego use. Rozważmy ten przykład.

// 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;  
 }); 

Doskonałe wyjaśnienie podano tutaj. Co to są lambdy i domknięcia php

dirtside
źródło
1
To niesamowite wyjaśnienie. +1
David J Eddy
4
Podobało mi się wyjaśnienie, dlaczego używasz zamknięć. Większość ludzi tak naprawdę tego nie rozumie. +1
Carrie Kendall
10
To jest wyjaśnienie funkcji anonimowych, a nie wyjaśnienie zamknięć. Funkcje anonimowe są, jak mówisz, takie same, jak nazwane funkcje, z wyjątkiem tego, że nie są globalne. Z drugiej strony, domknięcia są funkcjami zawierającymi zmienne swobodne o zasięgu leksykalnym (zadeklarowane za pomocą „use”); to znaczy. mogą kopiować i odwoływać się do wartości z zakresu, w którym są zadeklarowane, nawet po tym, jak wszystko inne zostało wyrzucone.
Warbo
@Warbo To prawda; wtedy tak naprawdę nie rozumiałem różnicy między funkcją anonimową a zamknięciem. Zamknięcia mają sens tylko wtedy, gdy poznasz anonimowe funkcje, ale do dziś wciąż znajduję „wyjaśnienia” tego, czym jest zamknięcie (tak jak moje, sprzed 7 lat ;-)), które nie wyjaśniają jego zakresu.
dirtside
Dlatego właśnie mówiłem, sprawdź JavaScript, w którym zamknięcia są często używane - ale pamiętaj, że reguły zakresu zmiennych są różne w PHP.
Rolf
17

Kiedy w przyszłości będziesz potrzebować funkcji, która będzie wykonywać zadanie, o którym teraz zdecydowałeś.

Na przykład, jeśli czytasz plik konfiguracyjny i jeden z parametrów mówi ci, że hash_methoddla twojego algorytmu jest multiplyraczej niż square, możesz utworzyć zamknięcie, które będzie używane wszędzie tam, gdzie będziesz musiał coś zaszyfrować.

Zamknięcie można utworzyć w (na przykład) config_parser(); tworzy funkcję o nazwie do_hash_method()using variable local to config_parser()(z pliku konfiguracyjnego). Za każdym razem, gdy do_hash_method()jest wywoływana, ma dostęp do zmiennych w zakresie lokalnym, config_parser()nawet jeśli nie jest wywoływana w tym zakresie.

Miejmy nadzieję, że dobry hipotetyczny przykład:

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)
}
Dan Udey
źródło
Nie mogę po prostu skopiować, wkleić tego przykładu i go uruchomić. Preferowany przykład, który mogę po prostu uruchomić.
Kim Stacks
3
Ta odpowiedź jest słaba. To bezsensowne stwierdzenie: „Kiedy będziesz potrzebować funkcji w przyszłości, która wykonuje zadanie, o którym teraz zdecydowałeś”.
Relaks na Cyprze
15

Oprócz szczegółów technicznych, zamknięcia są podstawowym warunkiem wstępnym stylu programowania znanego jako programowanie zorientowane na funkcje. Zamknięcie jest z grubsza używane do tego samego, co obiekt w programowaniu obiektowym; Wiąże dane (zmienne) razem z jakimś kodem (funkcją), który można następnie przekazać gdzie indziej. W związku z tym wpływają na sposób, w jaki piszesz programy lub - jeśli nie zmienisz sposobu pisania programów - nie mają żadnego wpływu.

W kontekście PHP są one trochę dziwne, ponieważ PHP jest już mocno oparte na klasowym, obiektowym paradygmacie, jak również na starszym paradygmacie proceduralnym. Zazwyczaj języki, które mają domknięcia, mają pełny zakres leksykalny. Aby zachować kompatybilność wsteczną, PHP tego nie otrzyma, więc oznacza to, że zamknięcia będą tutaj trochę inne niż w innych językach. Myślę, że jeszcze nie widzieliśmy dokładnie, jak będą używane.

troelskn
źródło
10

Podoba mi się kontekst dostarczony przez post troelskn. Kiedy chcę zrobić coś takiego jak przykład Dana Udeya w PHP, używam wzorca strategii OO. Moim zdaniem jest to znacznie lepsze niż wprowadzenie nowej funkcji globalnej, której zachowanie jest określane w czasie wykonywania.

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

Możesz także wywoływać funkcje i metody używając zmiennej przechowującej nazwę metody w PHP, co jest świetne. więc innym podejściem do przykładu Dana byłoby coś takiego:

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

oczywiście, jeśli chcesz, aby był dostępny wszędzie, możesz po prostu ustawić wszystko statycznie ...

Grossvogel
źródło
2

Zamknięcie to w zasadzie funkcja, dla której piszesz definicję w jednym kontekście, ale uruchamiasz w innym kontekście. Javascript bardzo mi pomógł w ich zrozumieniu, ponieważ są wszędzie używane w JavaScript.

W PHP są mniej efektywne niż w JavaScript ze względu na różnice w zakresie i dostępności zmiennych „globalnych” (lub „zewnętrznych”) z poziomu funkcji. Jednak począwszy od PHP 5.4, zamknięcia mogą uzyskać dostęp do obiektu $ this, gdy są uruchamiane wewnątrz obiektu, co czyni je o wiele bardziej efektywnymi.

Na tym polega zamknięcie i powinno wystarczyć, aby zrozumieć, co jest napisane powyżej.

Oznacza to, że powinno być możliwe zapisanie gdzieś definicji funkcji i użycie zmiennej $ this wewnątrz definicji funkcji, następnie przypisanie definicji funkcji do zmiennej (inni podali przykłady składni), a następnie przekazanie tej zmiennej do obiektu i wywołać go w kontekście obiektu, funkcja może wtedy uzyskać dostęp do obiektu i manipulować nim za pomocą $ this tak, jakby była to tylko kolejna z jej metod, podczas gdy w rzeczywistości nie jest zdefiniowana w definicji klasy tego obiektu, ale gdzie indziej.

Jeśli nie jest to zbyt jasne, nie martw się, stanie się jasne, gdy zaczniesz ich używać.

Rolf
źródło
Szczerze mówiąc, wcale nie jest to jasne, nawet dla mnie, autora. Zasadniczo mówię: aby dowiedzieć się, jakie są domknięcia, sprawdź je w JavaScript, ale pamiętaj, że zakres zmiennej różni się między JavaScript a PHP.
Rolf
1

Zasadniczo Closure to funkcje wewnętrzne, które mają dostęp do zmiennych zewnętrznych i są używane jako funkcja zwrotna do anonimowej funkcji (funkcji, które nie mają żadnej nazwy).

 <?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;
Harsh Gehlot
źródło
$param='captain'in func sayhello()jest lokalną zmienną func sayhello(). $param='ironman'powyżej sayhello()jest zmienną globalną. Jeśli chcesz zrobić tylko jedną zmienną $ param w swoim skrypcie, powinieneś zadzwonić: global $param;within sayhello()func
vlakov
0

Oto przykłady zamknięć w php

// Author: [email protected]
// 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);

Wynik:

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

A B C D E F 

c
Hisham Dalal
źródło
0

Domknięcia:

MDN ma najlepsze wyjaśnienie IMO:

Zamknięcie jest połączeniem funkcji połączonej razem (zamkniętej) z odniesieniami do jej stanu otoczenia (środowisko leksykalne). Innymi słowy, zamknięcie daje dostęp do zakresu funkcji zewnętrznej z funkcji wewnętrznej.

tj. zamknięcie to funkcja z dostępem do zmiennych znajdujących się w zakresie nadrzędnym. Zamknięcie pozwala nam wygodnie tworzyć funkcje w locie, ponieważ w niektórych sytuacjach funkcje są potrzebne tylko w jednym miejscu (wywołania zwrotne, argumenty wywoływalne).

Przykład:

$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
Willem van der Veen
źródło