Closures trong PHP… chính xác thì chúng là gì và khi nào bạn cần sử dụng chúng?


82

Vì vậy, tôi đang lập trình theo hướng đối tượng tốt đẹp, cập nhật. Tôi thường xuyên sử dụng các khía cạnh khác nhau của OOP mà PHP triển khai nhưng tôi đang tự hỏi khi nào tôi có thể cần sử dụng các bao đóng. Bất kỳ chuyên gia nào có thể làm sáng tỏ khi nào thì việc thực hiện đóng cửa sẽ hữu ích?

Câu trả lời:


82

PHP sẽ hỗ trợ các lệnh đóng nguyên bản trong 5.3. Một hàm đóng là tốt khi bạn muốn một hàm cục bộ chỉ được sử dụng cho một số mục đích nhỏ, cụ thể. Các RFC cho đóng cửa cho một ví dụ điển hình:

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

Điều này cho phép bạn xác định replacementhàm cục bộ bên trong replace_spaces(), để nó không:
1) Làm xáo trộn không gian tên chung
2) Làm cho mọi người ba năm sau tự hỏi tại sao có một hàm được định nghĩa toàn cục chỉ được sử dụng bên trong một hàm khác

Nó giữ mọi thứ có tổ chức. Lưu ý rằng bản thân hàm không có tên như thế nào, nó chỉ đơn giản được định nghĩa và gán như một tham chiếu tới $replacement.

Nhưng hãy nhớ, bạn phải đợi PHP 5.3 :)

Bạn cũng có thể truy cập các biến bên ngoài phạm vi của nó vào một bao đóng bằng cách sử dụng từ khóa use. Hãy xem xét ví dụ này.

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

Một lời giải thích tuyệt vời được đưa ra ở đây Php lambdas và các bao đóng là gì


1
Đây là một lời giải thích tuyệt vời. +1
David J Eddy

4
Tôi rất thích lời giải thích tại sao bạn sẽ sử dụng các lệnh đóng. Hầu hết mọi người không thực sự nắm bắt được điều đó. +1
Carrie Kendall

10
Đây là giải thích về các hàm ẩn danh, không phải là giải thích về các bao đóng. Như bạn nói, các hàm ẩn danh giống như các hàm được đặt tên ngoại trừ chúng không phải là toàn cục. Mặt khác, Closures là các hàm chứa các biến tự do có phạm vi từ vựng (được khai báo bằng "use"); I E. chúng có thể sao chép và tham chiếu các giá trị từ phạm vi mà chúng được khai báo, ngay cả sau khi mọi thứ khác đã được thu gom.
Warbo

@Warbo Điều này đúng; vào thời điểm đó tôi không thực sự tìm hiểu sự khác biệt giữa hàm ẩn danh và hàm đóng. Đóng cửa thực sự chỉ có ý nghĩa khi bạn tìm kiếm các chức năng ẩn danh, nhưng cho đến ngày nay tôi vẫn tìm thấy "lời giải thích" về việc đóng cửa là gì (như của tôi, từ 7 năm trước ;-)) không giải thích khía cạnh phạm vi của nó.
dirtside

Đó là lý do tại sao tôi đã nói, hãy kiểm tra JavaScript nơi mà các bao đóng được sử dụng nhiều - nhưng hãy nhớ rằng các quy tắc phạm vi biến khác nhau trong PHP.
Rolf

17

Khi nào bạn sẽ cần một chức năng trong tương lai thực hiện một nhiệm vụ mà bạn đã quyết định ngay bây giờ.

Ví dụ: nếu bạn đọc một tệp cấu hình và một trong các tham số cho bạn biết rằng hash_methodthuật toán của bạn thì multiplyđúng hơn square, bạn có thể tạo một bao đóng sẽ được sử dụng ở bất cứ đâu bạn cần băm một thứ gì đó.

Đóng có thể được tạo trong (ví dụ) config_parser(); nó tạo ra một hàm được gọi là do_hash_method()sử dụng các biến cục bộ đến config_parser()(từ tệp cấu hình). Bất cứ khi nào do_hash_method()được gọi, nó có quyền truy cập vào các biến trong phạm vi cục bộ của config_parser()mặc dù nó không được gọi trong phạm vi đó.

Một ví dụ giả thuyết hy vọng tốt:

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

Tôi không thể chỉ cần sao chép, dán ví dụ này và chạy nó. Thích một ví dụ mà tôi có thể chạy đơn giản.
Kim Stacks

3
Câu trả lời này là kém. Đây là một câu nói vô nghĩa: "Khi nào bạn sẽ cần một chức năng trong tương lai thực hiện một nhiệm vụ mà bạn đã quyết định ngay bây giờ."
Thư giãn ở Síp.

15

Ngoài các chi tiết kỹ thuật, các bao đóng là điều kiện tiên quyết cơ bản cho một phong cách lập trình được gọi là lập trình hướng hàm. Một bao đóng được sử dụng gần giống như bạn sử dụng một đối tượng trong lập trình hướng đối tượng; Nó liên kết dữ liệu (các biến) cùng với một số mã (một hàm), sau đó bạn có thể chuyển đến một nơi khác. Như vậy, chúng ảnh hưởng đến cách bạn viết chương trình hoặc - nếu bạn không thay đổi cách viết chương trình của mình - chúng hoàn toàn không có tác động.

Trong bối cảnh của PHP, chúng hơi kỳ lạ, vì PHP đã nặng về mô hình hướng đối tượng dựa trên lớp, cũng như mô hình thủ tục cũ hơn. Thông thường, các ngôn ngữ có dấu đóng, có phạm vi từ vựng đầy đủ. Để duy trì khả năng tương thích ngược, PHP sẽ không có được điều này, vì vậy điều đó có nghĩa là các cách đóng sẽ hơi khác ở đây, so với các ngôn ngữ khác. Tôi nghĩ chúng ta vẫn chưa biết chính xác chúng sẽ được sử dụng như thế nào.


10

Tôi thích bối cảnh được cung cấp bởi bài đăng của troelskn. Khi tôi muốn làm điều gì đó giống như ví dụ của Dan Udey trong PHP, tôi sử dụng Mô hình chiến lược OO. Theo tôi, điều này tốt hơn nhiều so với việc giới thiệu một hàm toàn cục mới có hành vi được xác định trong thời gian chạy.

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

Bạn cũng có thể gọi các hàm và phương thức bằng cách sử dụng một biến giữ tên phương thức trong PHP, điều này thật tuyệt. vì vậy một ví dụ khác về ví dụ của Dan sẽ như thế này:

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

tất nhiên, nếu bạn muốn nó có sẵn ở mọi nơi, bạn chỉ có thể làm cho mọi thứ ở trạng thái tĩnh ...


2

Về cơ bản, một hàm đóng là một hàm mà bạn viết định nghĩa trong một ngữ cảnh nhưng chạy trong một ngữ cảnh khác. Javascript đã giúp tôi rất nhiều trong việc hiểu những điều này, bởi vì chúng được sử dụng trong JavaScript ở khắp nơi.

Trong PHP, chúng kém hiệu quả hơn trong JavaScript, do sự khác biệt về phạm vi và khả năng truy cập của các biến "toàn cầu" (hoặc "bên ngoài") từ bên trong các hàm. Tuy nhiên, bắt đầu với PHP 5.4, các bao đóng có thể truy cập đối tượng $ this khi chạy bên trong một đối tượng, điều này làm cho chúng hiệu quả hơn rất nhiều.

Đây là những gì kết thúc nói về, và nó sẽ đủ để hiểu những gì được viết ở trên.

Điều này có nghĩa là có thể viết định nghĩa hàm ở đâu đó và sử dụng biến $ this bên trong định nghĩa hàm, sau đó gán định nghĩa hàm cho một biến (những người khác đã đưa ra ví dụ về cú pháp), sau đó chuyển biến này cho một đối tượng và gọi nó trong ngữ cảnh đối tượng, sau đó hàm có thể truy cập và thao tác đối tượng thông qua $ this như thể nó chỉ là một phương thức khác của nó, trong khi trên thực tế, nó không được định nghĩa trong định nghĩa lớp của đối tượng đó mà ở một nơi khác.

Nếu nó không rõ ràng lắm, thì đừng lo lắng, nó sẽ trở nên rõ ràng khi bạn bắt đầu sử dụng chúng.


Thành thật mà nói, tôi không rõ ràng chút nào, ngay cả đối với tôi, tác giả. Về cơ bản, tôi đang nói: để tìm hiểu những gì các bao đóng, hãy kiểm tra chúng trong JavaScript, nhưng hãy nhớ rằng phạm vi biến khác nhau giữa JavaScript và PHP.
Rolf

1

Về cơ bản, Closure là các hàm bên trong có quyền truy cập vào các biến bên ngoài và được sử dụng như một hàm gọi lại hàm anonmyous (các hàm không có bất kỳ tên nào).

 <?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'trong func sayhello()là biến cục bộ thành func sayhello(). $param='ironman'ở trên sayhello()là biến toàn cục. Nếu bạn chỉ muốn thực hiện một $ param biến trong kịch bản của bạn, bạn nên gọi: global $param;trong sayhello()func
vlakov

0

Dưới đây là các ví dụ về các bao đóng trong 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);

Đầu ra:

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

A B C D E F 

c

0

Đóng cửa:

MDN có IMO giải thích tốt nhất:

Bao đóng là sự kết hợp của một hàm được đóng gói cùng nhau (kèm theo) với các tham chiếu đến trạng thái xung quanh của nó (môi trường từ vựng). Nói cách khác, một bao đóng cho phép bạn truy cập vào phạm vi của một hàm bên ngoài từ một hàm bên trong.

tức là một bao đóng là một hàm có quyền truy cập vào các biến nằm trong phạm vi cha. Một bao đóng cho phép chúng ta tạo các hàm một cách thuận tiện vì trong một số trường hợp, các hàm chỉ cần thiết ở một nơi (gọi lại, đối số có thể gọi).

Thí dụ:

$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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.