Hàm ẩn danh (anonymous function), còn gọi là closure, là một hàm không có tên. Bạn viết function ($param) { ... } ở chỗ cần một giá trị: gán nó vào biến, truyền cho một hàm khác hoặc trả về nó. Hàm này có thể mang theo biến từ đoạn code xung quanh bằng use ($var).
Mọi hàm ẩn danh đều là đối tượng của lớp có sẵn Closure, vì vậy nó có thể được lưu lại, truyền đi khắp nơi và gọi sau bằng $greet(...).
Từ khóa use bắt biến theo giá trị
Tự bản thân closure không nhìn thấy các biến bên ngoài. use ($x) sao chép giá trị của $x vào closure vào lúc closure được tạo, không phải lúc nó chạy:
use theo tham chiếu: &$var
use (&$x) đưa cho closure chính biến đó thay vì một bản sao. Thay đổi bên trong closure được thấy ở bên ngoài, và thay đổi bên ngoài cũng được thấy ở bên trong. Đây là cách một callback cộng dồn vào tổng hoặc giữ một bộ đếm.
Mỗi lần gọi makeCounter() tạo ra một $count mới, và closure được trả về giữ cho biến đó tiếp tục tồn tại sau khi hàm đã kết thúc. Đó chính là ý nghĩa của từ "closure": hàm "bao lấy" những biến mà nó cần.
Truyền closure làm callback
Các hàm như array_map(), array_filter(), usort() và array_walk() nhận một hàm làm đối số. Closure cho phép bạn viết hàm đó ngay tại chỗ, với bao nhiêu câu lệnh tùy công việc cần:
Khi thân hàm chỉ là một biểu thức, một arrow function như fn($p) => $p['price'] ngắn hơn và không cần use.
Callable: tên hàm, phương thức và strlen(...)
Một tham số có kiểu callable nhận nhiều thứ hơn closure. Một chuỗi chứa tên hàm, một mảng gồm đối tượng và tên phương thức, một mảng gồm tên lớp và tên phương thức tĩnh đều được tính. Từ PHP 8.1, cú pháp first-class callable strlen(...) biến bất kỳ thứ nào trong số đó thành Closure một cách trực tiếp.
Dạng (...) được kiểm tra ngay khi dòng đó chạy: gõ nhầm sẽ ném ra Error ngay tại chỗ. Một chuỗi như 'strtoupr' chỉ là một chuỗi cho đến khi có ai đó thử gọi nó.
Closure bên trong lớp: $this và static
Một closure được tạo bên trong phương thức sẽ tự động được gắn $this, nên nó có thể dùng các thuộc tính của đối tượng. Hãy đánh dấu nó là static function khi không cần $this, việc này cũng giúp closure không giữ đối tượng tồn tại lâu hơn cần thiết.
Quên use
Bug phổ biến nhất với closure là dùng một biến bên ngoài mà không liệt kê nó trong use. Bên trong closure, biến đó không tồn tại:
$minAge = 18;
$adults = array_filter($people, function ($p) {
return $p['age'] >= $minAge; // $minAge is not in use (...)
});
PHP prints: Warning: Undefined variable $minAge
$minAge là null bên trong closure, mọi độ tuổi so sánh >= null đều cho true, và mọi người đều lọt qua bộ lọc. Trên một máy chủ ẩn cảnh báo, chẳng có gì báo cho bạn biết. Hãy thêm biến đó vào use:
Câu hỏi thường gặp
Closure trong PHP là gì?
Là một hàm không có tên, được tạo bằng function (...) { ... } rồi lưu vào biến hoặc truyền làm đối số. PHP biểu diễn nó dưới dạng đối tượng của lớp Closure. Closure có thể mang theo các biến từ phạm vi nơi nó được tạo, được liệt kê trong use (...).
Từ khóa use làm gì trong closure PHP?
use ($x) sao chép biến bên ngoài $x vào closure tại thời điểm closure được tạo. Những thay đổi của $x bên ngoài sau đó sẽ không được thấy. Viết use (&$x) để dùng chung biến đó, khi ấy thay đổi có tác dụng theo cả hai chiều.
Callable trong PHP là gì?
Là bất cứ thứ gì PHP có thể gọi: một closure, tên hàm dạng chuỗi ('strtoupper'), một mảng [$object, 'method'] hoặc [ClassName::class, 'staticMethod'], hoặc một đối tượng có __invoke(). Dùng kiểu callable để nhận bất kỳ thứ nào trong số đó và is_callable() để kiểm tra.
strlen(...) trong PHP có nghĩa là gì?
Đó là cú pháp first-class callable có từ PHP 8.1: strlen(...) tạo một Closure từ hàm mà không gọi nó. Cú pháp này cũng dùng được với phương thức, $obj->method(...), và phương thức tĩnh, Foo::bar(...), và được kiểm tra ngay khi dòng đó chạy, khác với tên hàm dạng chuỗi.
Có cần dấu chấm phẩy sau hàm ẩn danh không?
Có, khi bạn gán nó vào biến: $f = function () { ... }; là một câu lệnh gán, nên kết thúc bằng ;. Bỏ dấu này sẽ gây lỗi parse. Một closure được truyền trực tiếp làm đối số thì không cần dấu chấm phẩy riêng.