Menu

Trait trong PHP: use, dùng nhiều trait và insteadof

Trait trong PHP là một khối phương thức và thuộc tính dùng lại được, mà bạn chép vào một lớp bằng use TraitName;. Tìm hiểu cách dùng nhiều trait, giải quyết xung đột tên bằng insteadof và as, thành viên abstract và static trong trait, phương thức nào được ưu tiên, và so sánh trait, interface với lớp trừu tượng.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Trait trong PHP là một khối phương thức và thuộc tính dùng lại được. Bạn khai báo nó bằng trait Name { ... } và đưa nó vào bất kỳ lớp nào bằng use Name; bên trong thân lớp. PHP chép các thành viên của trait vào lớp, nên chúng chạy y hệt như khi bạn viết chúng ngay tại đó.

User và Robot không có lớp cha chung, vậy mà giờ cả hai đều có hello(). Trait sinh ra cho đúng trường hợp này: lớp PHP chỉ kế thừa được từ một lớp, còn trait chia sẻ code theo chiều ngang, giữa các lớp không liên quan với nhau.

Trait có thuộc tính

Trait có thể mang theo thuộc tính cùng với phương thức. Các phương thức dùng $this như mọi phương thức khác của lớp, vì một khi trait đã được chép vào, chúng chính là phương thức của lớp:

Lớp không được khai báo thuộc tính trùng tên với thuộc tính của trait, trừ khi hai khai báo giống hệt nhau (cùng phạm vi truy cập, kiểu và giá trị mặc định); nếu không sẽ là lỗi nghiêm trọng (fatal error).

Dùng nhiều trait

Liệt kê nhiều trait trong một câu lệnh use, cách nhau bởi dấu phẩy, hoặc viết nhiều dòng use. Lớp sẽ nhận thành viên của tất cả các trait đó:

Giải quyết xung đột bằng insteadof và as

Khi hai trait định nghĩa phương thức trùng tên, PHP không đoán mà dừng lại với một lỗi nghiêm trọng. Bên trong khối use, insteadof chọn phương thức được dùng, còn as giữ phương thức kia dưới một tên khác:

Nếu chỉ viết use EnglishGreeting, FrenchGreeting; mà không có khối, script dừng lại trước khi in ra bất cứ thứ gì:

PHP Fatal error:  Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12

as cũng đổi được phạm vi truy cập. use Loggable { log as protected; } biến log() của trait thành protected chỉ trong lớp này, còn use Loggable { log as private writeLog; } thêm một bí danh private.

Thành viên abstract và static trong trait

Trait có thể đòi hỏi lớp dùng nó phải cung cấp một thứ gì đó bằng cách khai báo phương thức abstract. Trait cũng có thể có phương thức static và thuộc tính static. Mỗi lớp dùng trait nhận một bản sao riêng của thuộc tính static:

Car và Bike đếm riêng, vì thuộc tính static được chép vào từng lớp. Điều này khác với thuộc tính static khai báo trong một lớp cha chung, vốn được mọi lớp con dùng chung; trang static trình bày trường hợp đó.

Phương thức nào được dùng: lớp, trait hay lớp cha

Nếu cùng một tên phương thức có trong chính lớp, trong trait mà lớp dùng và trong lớp cha, PHP chọn theo thứ tự: phương thức của chính lớp, rồi của trait, rồi của lớp cha. Bên trong trait, parent:: vẫn gọi được phiên bản của lớp cha:

Trait không phải là một kiểu

instanceof không bao giờ khớp với trait, và một tham số khai báo kiểu bằng tên trait sẽ từ chối mọi đối tượng với lỗi TypeError. Để hỏi "lớp này có hành vi đó không", hãy ghép trait với một interface, hoặc kiểm tra lớp bằng class_uses():

Mẫu ở trên là cách làm thường gặp: interface Exportable là thứ các hàm khai báo kiểu và instanceof kiểm tra, còn trait ExportsProperties là phần cài đặt làm sẵn mà các lớp có thể dùng hoặc thay thế. Bản thân trait chỉ là code dùng chung, không có ràng buộc nào; khi bạn thấy mình kiểm tra class_uses() trong logic nghiệp vụ, đó là lúc trait cần một interface đi kèm. Với code dùng chung cho một nhóm lớp có liên quan, lớp trừu tượng có thể là lựa chọn đơn giản hơn.

Câu hỏi thường gặp

Trait trong PHP là gì?

Trait là một nhóm phương thức (và thuộc tính) có tên, được khai báo bằng trait Name { }, và một lớp lấy nó vào bằng use Name;. PHP chép các thành viên của trait vào lớp, nên chúng hoạt động như thể bạn viết chúng ngay trong lớp đó. Đây là cách chia sẻ code giữa các lớp không có lớp cha chung.

Một lớp PHP có dùng được nhiều trait không?

Được: use Timestamps, SoftDeletes; hoặc nhiều dòng use. Nếu hai trait định nghĩa phương thức trùng tên, bạn phải chọn một bằng insteadof, ví dụ use A, B { A::hello insteadof B; }, và có thể giữ phương thức còn lại dưới tên mới bằng B::hello as helloB;.

Trait và interface trong PHP khác nhau thế nào?

Interface là một kiểu không có code: nó quy định lớp phải có những phương thức nào, và instanceof dùng được với nó. Trait là code không có kiểu: nó dán phần thân phương thức vào lớp, nhưng $obj instanceof SomeTrait luôn là false. Hai thứ này thường được dùng cùng nhau: interface cho kiểu, trait cho phần cài đặt mặc định.

Trait có hàm khởi tạo hoặc phương thức abstract được không?

Được cả hai. Trait có thể khai báo abstract public function name(): string; để bắt lớp dùng nó phải cung cấp phương thức đó, và có thể định nghĩa __construct, dù hàm khởi tạo trong trait rất dễ đụng với hàm khởi tạo của chính lớp và hiếm khi là ý hay.

Nếu lớp và trait định nghĩa cùng một phương thức thì phương thức nào được dùng?

Phương thức của chính lớp thắng phương thức của trait, và phương thức của trait thắng phương thức kế thừa từ lớp cha. Vậy thứ tự là: lớp, rồi trait, rồi lớp cha.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu