Trong PHP, một lớp kế thừa (inheritance) lớp khác bằng extends: class Dog extends Animal cho Dog mọi thuộc tính và phương thức public, protected của Animal. Lớp con có thể thêm phương thức mới, ghi đè (override) phương thức được kế thừa bằng cách khai báo phương thức cùng tên, và gọi bản gốc bằng parent::method().
Dog không có hàm khởi tạo nên nó dùng hàm khởi tạo của Animal, vì thế new Dog('Rex') chạy được. Một lớp con chỉ có thể extends một lớp cha; PHP không có đa kế thừa lớp.
Gọi phương thức lớp cha bằng parent::
Ghi đè sẽ thay thế hoàn toàn phương thức của lớp cha. Để mở rộng thay vì thay thế, hãy gọi parent::method() từ lớp con rồi xây dựng tiếp trên kết quả của nó:
Cách này cũng dùng được với hàm khởi tạo qua parent::__construct(...), được trình bày ở trang hàm khởi tạo, kể cả chuyện gì xảy ra khi bạn quên gọi nó.
protected và private trong lớp con
Thành viên protected bị ẩn với code bên ngoài nhưng hiển thị với mọi lớp trong cùng họ. Thành viên private chỉ hiển thị trong lớp khai báo nó, nên lớp con hoàn toàn không thấy:
isset($this->pin) là false trong lớp con vì lớp con không có quyền truy cập $pin private của lớp cha, trong khi phương thức hasPin() của chính lớp cha vẫn đọc được nó. Dùng protected cho những gì lớp con được phép xây dựng tiếp, và private cho những chi tiết bạn muốn giữ quyền tự do thay đổi.
Đa hình và instanceof
Code viết theo kiểu của lớp cha chạy được với mọi lớp con. Mỗi đối tượng chạy phiên bản riêng của phương thức bị ghi đè, nên chỉ một vòng lặp là xử lý được tất cả. instanceof kiểm tra xem một đối tượng có thuộc một lớp hoặc bất kỳ lớp con nào của lớp đó không:
Khi lớp cha không bao giờ nên được dùng riêng (một Shape trơn với diện tích bằng 0 chẳng có mấy ý nghĩa), hãy biến nó thành lớp trừu tượng và khai báo area() là abstract.
Quy tắc ghi đè: chữ ký phải tương thích
Phương thức ghi đè phải nhận được ít nhất những gì lớp cha nhận và trả về thứ mà nơi gọi lớp cha dùng được. Bạn có thể thêm tham số tùy chọn, mở rộng kiểu của tham số hoặc thu hẹp kiểu trả về; bạn không thể thêm tham số bắt buộc hay biến một phương thức public thành private.
Đổi string $sep = ',' thành string $sep bắt buộc là PHP từ chối biên dịch lớp với một lỗi nghiêm trọng ("Declaration of CsvExporter::export(array sep): string must be compatible with Exporter::export(array $rows): string").
Lớp và phương thức final
final chặn việc kế thừa tại điểm đó. Phương thức final không thể bị ghi đè, còn final class không thể được kế thừa. Cả hai đều được kiểm tra khi file được biên dịch, nên script hoàn toàn không khởi chạy:
<?php
class Payment
{
final public function charge(int $cents): string
{
return "charged $cents";
}
}
class FreePayment extends Payment
{
public function charge(int $cents): string
{
return 'free';
}
}
PHP Fatal error: Cannot override final method Payment::charge() in /home/index.php on line 12
Với lớp final, thông báo là Class B cannot extend final class A. Hãy đánh dấu một phương thức là final khi lớp cha dựa vào việc nó hoạt động đúng một cách duy nhất, ví dụ một phương thức kiểm tra quyền trước khi làm bất cứ việc gì khác.
Cái bẫy phương thức private
Phương thức private không được kế thừa, nên phương thức cùng tên ở lớp con không ghi đè nó. Code trong lớp cha vẫn tiếp tục gọi phiên bản private của chính nó, điều khiến nhiều người bất ngờ khi họ tưởng phương thức của lớp con sẽ chạy:
greet() vẫn in ra Hello: $this->word() của lớp cha trỏ tới phương thức private của lớp cha. Đổi private thành protected trong Greeter rồi chạy lại; giờ phương thức của lớp con là một phép ghi đè thật sự và greet() nói Bonjour.
Câu hỏi thường gặp
extends hoạt động thế nào trong PHP?
class Dog extends Animal biến Dog thành lớp con của Animal. Một đối tượng Dog có mọi thuộc tính và phương thức public, protected của Animal, có thể thêm thuộc tính, phương thức riêng và có thể thay thế (ghi đè) các phương thức được kế thừa bằng cách khai báo phương thức cùng tên.
Làm sao gọi phương thức của lớp cha trong PHP?
Dùng parent::methodName() bên trong phương thức của lớp con, ví dụ return parent::describe() . ' and more';. Với hàm khởi tạo thì là parent::__construct(...).
PHP có hỗ trợ đa kế thừa không?
Không. Một lớp chỉ có thể extends một lớp cha. Để dùng chung hành vi từ nhiều nguồn, hãy implement nhiều interface (class A implements B, C) và dùng trait (use Loggable, Timestamps;).
final trong PHP có nghĩa là gì?
Một phương thức final không thể bị lớp con ghi đè, còn một final class thì hoàn toàn không thể được kế thừa. Vi phạm một trong hai quy tắc này là lỗi nghiêm trọng ngay khi lớp được biên dịch, ví dụ Cannot override final method A::f().
Lớp con có truy cập được thuộc tính private của lớp cha trong PHP không?
Không. Thành viên private chỉ thuộc về lớp khai báo nó, nên trong lớp con $this->pin là thuộc tính chưa được định nghĩa (một cảnh báo, rồi ra null), và phương thức cùng tên ở lớp con không ghi đè được phương thức private. Hãy khai báo thành viên đó là protected khi lớp con cần đến nó.