Lambda và Closures trong PHP

Vừa qua mình gặp 1 tình huống là phải dùng hàm call back để xử lý vấn đề, thế là tìm hiểu luôn cái lý thuyết về php closure xem nó là như thế nào. trước hết là tìm hiểu Lambda.

Lambda là gì?

Các hàm lambda là các hàm ẩn danh (anonymous function), sử dụng một lần, có thể được định nghĩa vào bất cứ lúc nào, và thường gắn với một biến hoặc gán vào 1 hàm khác như một tham số . Các hàm này chỉ tồn tại trong phạm vi của biến mà nó được định nghĩa, vì vậy khi biến đó vượt ra ngoài phạm vi, thì hàm này cũng không còn nữa.

Hàm ẩn danh

Một hàm ẩn danh chỉ đơn giản là một hàm không có tên.

Ví dụ

// Hàm thường
function helloWorld()
{
  return "Hello world";
}

// Hàm ẩn danh
function ()
{
  return "Hello world";
}

Sử dụng hàm ẩn danh

Bởi vì các hàm này không có tên, chúng ta không thể gọi nó như một chức năng thường xuyên. Thay vào đó ta phải gán nó vào một biến hoặc cho vào 1 hàm khác khác như là một tham số.

Ví dụ

// Hàm bình thường
echo helloWorld();
// "Hello world"

// Hàm ẩn danh
// gán cho 1 biến
$hello = function () {
  return "Hello world";
}

// gọi hàm ẩn danh
echo $hello();
// "Hello world"

Để sử dụng các hàm ẩn danh, chúng ta gán nó vào một biến và sau đó gọi là biến như là một function như trên. Hoặc sử dụng Lambda như thế này:

// Pass Lambda to function
function shout ($message)
{
  echo $message();
}

// Call function
shout(function() {
  return "Hello world";
});

Tại sao dùng Lambda

Lambdas rất hữu dụng, bởi vì chúng ta không cần phải tạo hẳn 1 hàm cho 1 lần sử dụng duy nhất.

Thông thường, chúng ta sẽ cần một hàm để làm một công việc, nhưng nó không có nghĩa là chúng ta sẽ dùng nó trong phạm vi global. Thay vì có một hàm sử dụng một lần và sau đó bỏ đi để nó ở đó, chúng ta có thể sử dụng một Lambda để thay thế.

Tất nhiên, chúng ta có thể sử dụng chức năng create_function trong PHP. Điều này về cơ bản là giống nhau:

// Use create_function
$hello = create_function('', 'echo "Hello World!";');

// Call function
$hello();

Closure là gì?

Một Closure (bao đóng :-s) về cơ bản giống như một Lambda, ngoài ra nó có thể truy cập các biến bên ngoài phạm vi mà nó được tạo ra.

Ví dụ:

// Create a user
$user = "Thỏ 7 màu";

// Create a Closure
$hello = function() use ($user) {
  echo "Hello $user";
};

// Greet the user
$hello(); // Returns "Hello Thỏ 7 màu"

Như chúng ta có thể thấy ở trên, Closure có thể truy cập biến $user. Bởi vì nó đã được khai báo trong các điều khoản sử dụng (use ($user)) của định nghĩa hàm Closure.

Nếu chúng ta thay đổi biến $user ban đầu trong Closure, nó sẽ không ảnh hưởng đến các biến ban đầu. Để cập nhật các biến ban đầu, chúng ta có thể thêm một dấu &. Một dấu & trước một biến có nghĩa đây là một reference và vì vậy các biến ban đầu cũng được cập nhật.

// Set counter
$i = 0;
// Increase counter within the scope
// of the function
$closure = function () use ($i)
{
    $i++;
};
// Run the function
$closure();
// The global count hasn't changed
echo $i; // Returns 0

// Reset count
$i = 0;
// Increase counter within the scope
// of the function but pass it as a reference
$closure = function () use (&$i)
{
    $i++;
};
// Run the function
$closure();
// The global count has increased
echo $i; // Returns 1

Closure cũng rất hữu ích khi sử dụng các hàm PHP mà chấp nhận hàm call back như array_map, array_filter, array_reduce hoặc array_walk.

Ví dụ:

// An array of names
$users = ['Thỏ 7 màu', 'Đậu đỏ', 'Gấu AK', 'Bé đội xô'];

// Pass the array to array_walk
array_walk($users, function ($name) {
  echo "Hello $name<br>";
});
// Returns
// -> Hello Thỏ 7 màu
// -> Hello Đậu đỏ
// -> Hello Gấu AK
// -> ..

Ngoài ra, chúng ta có thể truy cập các biến bên ngoài phạm vi của Closure bằng cách sử dụng use

// Set a multiplier
$multiplier = 3;

// Create a list of numbers
$numbers = [1, 2, 3, 4];

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

Sử dụng Closure trong trường hợp cụ thể

Ví dụ trong Laravel

Route::get('user/(:any)', function ($name) {
  return "Hello " . $name;
});

Như vậy là có 1 link user/Tho7mau thì sẽ có "Hello Tho7mau" :v

Đó là ví dụ, còn tình huống của mình là thế này

class A {
    public static function f($query, $callback = null)
    {
        $query->where(...);
        //code
        $query->where(function ($currentQuery) use ($keyword, $callback) {
                $currentQuery->orWhere(...);

                if ($callback && is_callable($callback)) {
                    $currentQuery = $callback($currentQuery);
                }
         });

         $query->someWhere(...);

         return $query->paginate($xxx);
    }
}

class B extends A {
    public static function f1($yyy)
    {
        //code
        $query->where(...);
        //code

        return parent::f($query, __CLASS__ . '::f2');
    }

    public static function f2($query)
    {
        return $query->orWhere(...);
    }
}

Kết luận

Tài liệu: