Hướng dẫn dùng closures definition trong PHP

Trong quá trình làm quen với các framework PHP như Laravel, Symfony... chúng ta rất hay gặp các khái niệm Lambda, Closure, chúng đều là những tính năng mới cho phép tái cấu trúc mã nguồn trở lên trực quan và dễ đọc hơn. Các khái niệm này đều rất căn bản tuy nhiên không phải ai cũng để ý và sử dụng chúng thành thạo. Bài viết này sẽ giới thiệu với các bạn Lambda là gì?, Closure là gì? và chúng được sử dụng trong lập trình PHP như thế nào?

Lambda là gì?

Nếu bạn đã từng làm việc nhiều với các ngôn ngữ như Javascript hay Ruby thì không còn xa lạ gì với hàm "nặc danh" (anonymous function), khái niệm này cũng tương tự trong PHP. Hàm nặc danh là một hàm không có tên và Lambda là một hàm nặc danh có thể được gán vào một biến hoặc truyền vào một hàm khác như một tham số. Chúng ta đã quá quen thuộc với các hàm thông thường trong PHP:

function sayHello() {
  return "Xin chào!";
}
echo sayHello();

Hàm nặc danh chỉ đơn giản là một hàm không có tên và nó có thể được định nghĩa như sau:

function () {
  return "Xin chào!";
}

Các hàm thông thường muốn thực hiện được chúng ta cần gọi đến tên của nó, vậy hàm nặc danh thì thế nào? Do hàm nặc danh không có tên nên bạn cần gán nó với một biến hoặc truyền nó vào như một tham số:

$hello = function () {
  return "Xin chào!";
} 
// Gọi hàm này
echo $hello();
// Xin chào!

Ví dụ trên đây chúng ta đã gắn hàm nặc danh với một biến, tiếp theo cùng tìm hiểu cách truyền hàm nặc danh vào như tham số.

// Truyền anonymous function vào tham số
function speak($message){
  echo $message();
}

// Gọi hàm
speak(function(){
  return "Xin chào";
});

Sử dụng hàm nặc danh hay Lambda là rất hữu ích khi các chức năng chúng ta chỉ cần sử dụng một lần duy nhất, bản chất sâu xa của vấn đề là nó sử dụng một phương thức được xây dựng sẵn trong PHP là create_function(). Ví dụ ở trên có thể viết lại như sau:

$hello = create_function('', 'echo "Xin chào";');

// Gọi hàm
$hello();

Closure là gì?

Closure là một class sử dụng anonymous function, nói cách khác Closure về cơ bản giống hàm nặc danh, ngoài ra nó có thể truy cập các biến bên ngoài phạm vi mà nó tạo ra. Closure đóng gói phạm vi của nó, nó không có quyền truy nhập vạo phạm vi mà nó được định nghĩa hoặc thực hiện. Nó có thể kế thừa các biến từ phạm vi cha (nơi closure được định nghĩa) vào closure với từ khóa use.

// Tạo một người dùng
$user = "Nguyễn Văn A";

// Tạo một Closure
$hello = function() use ($user) {
  echo "Xin chào $user";
};

// Hiển thị lời chào
$hello(); 
// "Xin chào Nguyễn Văn A"

Trong ví dụ trên bạn có thể thấy Closure có thể truy xuất biến $user vì nó được khai báo trong từ khóa use trong phần định nghĩa Closure. Nếu bạn thay đổi giá trị của $user trong Closure, nó không ảnh hưởng gì đến giá trị gốc. Để cập nhật được giá trị gốc chúng ta sử dụng con trỏ tham chiếu & trong PHP. Ví dụ:

$i = 0;
// Tăng biến $i trong phạm vi Closure
$closure = function () use ($i){ 
   $i++; 
};
// Gọi hàm
$closure();
// Kết quả $i ở ngoài không thay đổi
echo $i; 
// 0

$i = 0;
// Tăng biến $i trong phạm vi Closure nhưng sử dụng con trỏ tham chiếu &
$closure = function () use (&$i){ 
   $i++; 
};
// Gọi hàm
$closure();
// Biến $i ở ngoài đã thay đổi
echo $i;
// 1

Closure là rất hữu dụng khi sử dụng các hàm PHP mà chấp nhận một hàm callback kiểu như array_map, array_filter... Ví dụ tiếp theo chúng ta sử dụng hàm array_walk, nó nhận một mảng và chạy qua từng phần tử và cho phép gọi các hàm callback:

// Mảng tên người dùng
$users = array("Trần Thu Hà", "Nguyễn Mỹ Linh", "Hồ Quỳnh Hương", "Thu Minh");

// Truyền mảng này vào array_walk
array_walk($users, function ($name) {
  echo "Xin chào $name
"; }); // Xin chào Trần Thu Hà // Xin chào Nguyễn Mỹ Linh // ...

Closure có thể sử dụng các biến ngoài phạm vi của nó bằng cách sử dụng từ khóa use, ví dụ tiếp theo chúng ta

$multiplier = 7;

// Mảng các số
$numbers = array(1,2,3,4,5);

// Sử dụng array_walk để đi qua toàn bộ các phần tử và nhân chúng với $multiplier
array_walk($numbers, function($number) use($multiplier){
  echo $number * $multiplier;
});

Trong ví dụ trên, chúng ta thấy rằng không cần thiết phải tạo ra một hàm chỉ để nhân hai số với nhau, do đó chúng ta sử dụng Closure để thực hiện công việc như thế này và sau đó không bao giờ dùng đến nó. Bằng cách sử dụng Closure như một hàm callback, chúng ta có thể sử dụng chức năng này một lần và sau đó quên đi nó. Như ở đầu bài đã nói, Closure là một khái niệm rất hay dùng trong các framework PHP hiện nay, việc sử dụng Lambda và Closure để thực hiện các công việc nhỏ mà không làm ảnh hưởng đến namespace của dự án và đặc biệt sử dụng nó rất tốt trong callback. Ví dụ tiếp theo chúng ta sẽ thực hiện một route trong routes/web.php của Laravel nhằm in ra lời chào với định dạng đường dẫn kiểu user/tên_người_dùng.

Route::get('user/(:any)', function($name){
  return "Xin chào " . $name;
});

CÁC BÀI VIẾT KHÁC