Chúng ta có thể tưởng tượng rằng vũ trụ được tạo từ các đối tượng khác nhau như mặt trời, mặt trăng, trái đất, … Theo cách tương tự, bạn có thể tưởng tượng chiếc xe hơi được tạo từ các đối tượng khác nhau như bánh xe, bánh lái, cần sang số, … Theo cách như vậy, các khái niệm trong lập trình hướng đối tượng giả sử rằng mọi thứ như một đối tượng và triển khai một phần mềm bởi sử dụng các đối tượng khác nhau.

Các khái niệm hướng đối tượng trong PHP

Trước khi đi vào chi tiết, chúng ta nhắc lại một số khái niệm liên quan tới Lập trình hướng đối tượng.

  • Class :

    • Class là một khuôn mẫu (template) để tạo ra các đối tượng (objects).
    • Nó định nghĩa các thuộc tính (biến thành viên) và phương thức (hàm thành viên) mà các đối tượng của Class đó sẽ có.
    • Class là một cấu trúc dữ liệu tự định nghĩa, giúp bạn tổ chức mã nguồn một cách có cấu trúc và linh hoạt.
  • Object − Một instance (sự thể hiện) riêng biệt của cấu trúc dữ liệu được định nghĩa bởi một Class. Một khi bạn định nghĩa một Class, và sau đó tạo nhiều Object của Class đó. Các Object cũng còn được biết như là Instance.

    • Đối tượng là một phiên bản cụ thể của một Class.
    • Khi bạn tạo một đối tượng từ một Class, bạn đang tạo một thể hiện riêng biệt của Class đó.
    • Mỗi đối tượng có các thuộc tính và phương thức được xác định bởi Class mà nó được tạo ra từ.
    • Ví dụ:
      • Ví dụ, nếu bạn có một Class "Car" (Xe hơi), bạn có thể định nghĩa các thuộc tính như "màu sắc", "năm sản xuất", "giá tiền" và các phương thức như "khởi động", và "dừng lại". Một khi bạn đã định nghĩa Class "Car", bạn có thể tạo ra nhiều đối tượng "Car" khác nhau từ Class đó. Ví dụ:

        class Car {
            // Thuộc tính
            public $color;
            public $year;
            public $price;
            
            // Phương thức
            public function start() {
                // Code để khởi động xe
            }
        
            public function stop() {
                // Code để dừng lại
            }
        }
        
        // Tạo một đối tượng "Car"
        $car1 = new Car();
        $car1->color = "Red";
        $car1->year = 2022;
        $car1->price = 25000;​
        
        // Tạo một object "Car" 2
        $car2 = new Car();
        $car2->color = "Green";
        $car2->year = 2023;
        $car2->price = 55000;​

        Trong ví dụ trên, $car1 là một đối tượng của Class "Car", và nó có các thuộc tính "color", "year", "price" và các phương thức "start", "accelerate", "brake", "stop" được xác định bởi Class "Car".

  • Biến thành viên(Properties) − Là các biến được định nghĩa bên trong một Class và chứa dữ liệu của Object. Biến thành viên được truy cập và thao tác thông qua các phương thức của Class

  • Hàm thành viên(Methos) − Đây là hàm được định nghĩa bên trong một Class và được sử dụng để truy cập dữ liệu đối tượng.

  • Tính kế thừa(Inheritance) − Là khả năng một Class có thể kế thừa các thuộc tính và phương thức từ một Class khác. Điều này cho phép tái sử dụng mã nguồn và tạo ra các Class con (subclasses) dựa trên Class cha (parent class).

  • Class cha − Một Class mà được kế thừa từ Class khác. Nó cũng được gọi là một Class cơ sở (base class) hoặc super class.

  • Class con − Một Class mà kế thừa từ Class khác. Nó cũng được gọi là một Class phụ hoặc một Class kế thừa.

  • Tính đa hình − Đây là một khái niệm hướng đối tượng mà cùng một hàm có thể được sử dụng cho các mục đích khác nhau. Ví dụ, tên hàm sẽ vẫn giống như vậy, nhưng nó nhận số tham số khác nhau và có thể thực hiện các tác vụ khác nhau.

  • Nạp chồng (Overloading) − Một kiểu đa hình, trong đó một số hoặc tất cả toán tử có trình triển khai khác nhau phụ thuộc vào kiểu các tham số của chúng. Tương tự, các hàm cũng có thể được nạp chồng với trình triển khai khác nhau.

  • Trừu tượng hóa dữ liệu − Là quá trình ẩn các chi tiết về cách dữ liệu được lưu trữ và xử lý trong một class, giúp tạo ra các class và đối tượng có tính mở rộng và tái sử dụng cao hơn.

  • Tính bao đóng − Liên quan tới một khái niệm mà chúng ta có thể bao đóng tất cả dữ liệu và hàm thành viên với nhau để tạo thành một Object.

  • Constructor − Liên quan tới một kiểu hàm đặc biệt mà sẽ được gọi tự động bất cứ khi nào có một sự tạo thành đối tượng từ một Class.

  • Destructor − Liên quan tới một kiểu hàm đặc biệt mà sẽ được gọi tự động bất cứ khi nào một đối tượng bị xóa hoặc ra khỏi phạm vi.

Định nghĩa Class trong PHP

Form chung để định nghĩa một Class mới trong PHP như sau:

<?php
   class phpClass{
      var $var1;
      var $var2 = "một giá trị hằng số";
      
      function myfunc ($arg1, $arg2) {
         [..]
      }
      [..]
   }
?>

Dưới đây là phần giải thích mỗi dòng trên:

  • Thủ tục class đặc biệt, được theo sau bởi tên của Class mà bạn muốn định nghĩa.

  • Một tập hợp dấu ngoặc móc bao quanh bất kỳ số lượng khai báo biến hoặc định nghĩa hàm nào.

  • Các khai báo biến bắt đầu với thủ tục đặc biệt var, được theo sau bởi một tên biến theo qui ước $; chúng cũng có thể có một phép gán khởi tạo tới một giá trị hằng số.

  • Định nghĩa hàm ở đây khá giống với các hàm standalone trong PHP, nhưng đây là hàm cục bộ cho Class và sẽ được sử dụng để thiết lập và truy cập dữ liệu đối tượng.

Ví dụ

Ví dụ sau định nghĩa một Class tên là Books.

Phần 1:

<?php
   class  Books{
      /* các biến thành viên */
      var $price;
      var $title;
      
      /* các hàm thành viên */
      function setPrice($par){
         $this->price = $par;
      }
      
      function getPrice(){
         echo $this->price ."<br/>";
      }
      
      function setTitle($par){
         $this->title = $par;
      }
      
      function getTitle(){
         echo $this->title ." <br/>";
      }
   }
?>

Biến $this là một biến đặc biệt và nó tham chiếu tới cùng đối tượng (ví dụ: chính nó).

Tạo Object trong PHP

Một khi bạn đã định nghĩa Class cho mình, thì bạn có thể tạo bao nhiêu đối tượng của kiểu Class đó tùy bạn thích. Ví dụ sau là cách tạo đối tượng bởi sử dụng từ khóa new trong PHP.

Phần 2:

$tiengAnh = new Books;
$toanCaoCap = new Books;
$tuTuongHCM = new Books;

Ở đây, chúng ta đã tạo 3 đối tượng và những đối tượng này là độc lập với nhau và chúng sẽ có sự tồn tại riêng rẽ nhau. Phần tiếp theo, chúng ta xem cách truy cập hàm thành viên và xử lý các biến thành viên trong PHP.

Gọi hàm thành viên trong PHP

Sau khi tạo các đối tượng, bạn sẽ có thể gọi các hàm thành viên liên quan tới đối tượng đó. Một hàm thành viên sẽ chỉ có thể xử lý biến thành viên liên quan tới đối tượng đó.

Ví dụ sau minh họa cách thiết lập title và price cho 3 book bằng cách gọi các hàm thành viên.

Phần 3:

$tiengAnh->setTitle( "English Grammar in Use" );
$tuTuongHCM->setTitle( "Toán cao cấp 1" );
$toanCaoCap->setTitle( "Tư tưởng Hồ Chí Minh" );

$tiengAnh->setPrice( 10 );
$tuTuongHCM->setPrice( 15 );
$toanCaoCap->setPrice( 7 );

Bây giờ bạn gọi các hàm thành viên khác để lấy giá trị đã được thiết lập trong ví dụ trên:

Phần 4:

$tiengAnh->getTitle();
$tuTuongHCM->getTitle();
$toanCaoCap->getTitle();
$tiengAnh->getPrice();
$tuTuongHCM->getPrice();
$toanCaoCap->getPrice();

Lưu 4 phần code trên (theo thứ tự như trên) trong một file có tên là test.php trong htdocs, sau đó mở trình duyệt và gõ địa chỉ http://localhost:8080/test.php sẽ cho kết quả:

Hướng đối tượng trong PHP

Hàm constructor trong PHP

Hàm constructor là kiểu hàm đặc biệt mà sẽ được gọi tự động bất cứ khi nào có một sự tạo thành đối tượng từ một Class. Vì thế, chúng ta lợi dụng cách vận hành này, bằng việc khởi tạo nhiều thứ thông qua các hàm constructor trong PHP.

PHP cung cấp một hàm đặc biệt được gọi là __construct() để định nghĩa một constructor. Bạn có thể truyền bao nhiêu tham số tùy bạn vào trong hàm constructor này.(Nếu bạn để là private một construct bạn sẽ k tạo được một đối tượng)

Ví dụ sau sẽ tạo một constructor cho Class Books và nó sẽ khởi tạo price và title cho book tại thời điểm tạo đối tượng này.

Phần 1.1:

function __construct( $par1, $par2 ){
   $this->price = $par1;
   $this->title = $par2;
}

Bây giờ, chúng ta không cần gọi tập hợp hàm riêng rẽ để thiết lập price và title. Chúng ta chỉ có thể khởi tạo hai biến thành viên của chúng tại thời điểm tạo đối tượng. Bạn kiểm tra ví dụ sau:

Phần 2':

$tiengAnh = new Books( "English Grammar in Use", 10 );
$toanCaoCap = new Books ( "Toán cao cấp 1", 15 );
$tuTuongHCM = new Books ("Tư tưởng Hồ Chí Minh", 7 );

/* lấy các giá trị đã được thiết lập */
$tiengAnh->getTitle();
$tuTuongHCM->getTitle();
$toanCaoCap->getTitle();

$tiengAnh->getPrice();
$tuTuongHCM->getPrice();
$toanCaoCap->getPrice();

Đặt contructor (phần 1.1) vào class (phần 1) và sau đó thay các phần 2, 3 và 4 bằng phần 2' (theo thứ tự như trên) trong một file có tên là test.php trong htdocs, sau đó mở trình duyệt và gõ địa chỉ http://localhost:8080/test.php sẽ cho kết quả tương tự như trên:

Hướng đối tượng trong PHP

Trong PHP, khi một lớp con kế thừa từ một lớp cha, nếu lớp con không định nghĩa constructor (hàm khởi tạo) riêng của mình, constructor của lớp cha sẽ được tự động gọi khi một đối tượng của lớp con được khởi tạo.

Tuy nhiên, nếu lớp con định nghĩa constructor riêng, constructor này sẽ ghi đè (override) constructor của lớp cha. Trong trường hợp này, để gọi constructor của lớp cha từ constructor của lớp con, bạn cần sử dụng từ khóa parent::__construct().

Dưới đây là một ví dụ minh họa:

class Animal {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
        echo "Animal constructor\n";
    }
}

class Dog extends Animal {
    public function __construct($name) {
        parent::__construct($name);
        echo "Dog constructor\n";
    }
}

// Khởi tạo đối tượng của lớp con
$dog = new Dog("Buddy");​

Kết quả khi chạy mã này sẽ là:

Animal constructor
Dog constructor​

Như bạn thấy, constructor của lớp cha (Animal) được gọi trước, sau đó mới đến constructor của lớp con (Dog). Điều này cho thấy rằng constructor của lớp cha được ưu tiên gọi trước khi constructor của lớp con được gọi.

Destructor trong PHP

Giống một hàm constructor trong PHP, phương thức __destruct thường được sử dụng để giải phóng tài nguyên và thực hiện các hành động cuối cùng trước khi một đối tượng bị hủy trong PHP.

  • Giải phóng tài nguyên: Khi một đối tượng không còn được sử dụng nữa, phương thức __destruct có thể được sử dụng để giải phóng bất kỳ tài nguyên nào mà đối tượng đó đã cấp phát. Điều này bao gồm đóng các kết nối cơ sở dữ liệu, đóng tệp tin, hoặc giải phóng bộ nhớ.

  • Ghi log hoặc thông báo: Bạn có thể sử dụng phương thức __destruct để ghi log hoặc gửi thông báo khi một đối tượng được hủy. Điều này có thể hữu ích để theo dõi vấn đề hoặc hành vi trong ứng dụng của bạn.

Tính kế thừa trong PHP

Các định nghĩa Class trong PHP có thể kế thừa từ một định nghĩa Class cha bởi sử dụng mệnh đề extends trong PHP. Cú pháp của nó như sau:

class Child extends Parent {
   <phần định nghĩa của lớp con>
}

Hiệu quả của tính kế thừa là Class con (Class phụ hoặc Class kế thừa) có các đặc trưng sau:

  • Tự động có tất cả khai báo biến thành viên của Class cha.

  • Tự động có tất cả các hàm thành viên giống như trong Class cha, mà (theo mặc định) sẽ làm việc theo cùng phương thức như khi hàm đó làm trong Class cha.

Ví dụ sau kế thừa Class Books và thêm một số tính năng tùy theo yêu cầu.

class Novel extends Books{
   var publisher;
   
   function setPublisher($par){
      $this->publisher = $par;
   }
   
   function getPublisher(){
      echo $this->publisher. "<br />";
   }
}

Bây giờ, ngoài những hàm đã kế thừa, Class Novel bổ sung thêm hai hàm thành viên.

Một class con trong PHP có thể kế thừa từ một Class cha trực tiếp và chỉ một Class cha. Tuy nhiên, các Class có thể được kế thừa qua nhiều cấp độ, nghĩa là một Class con có thể kế thừa từ một Class cha, và Class cha có thể kế thừa từ một Class cha khác, và tiếp tục như vậy.

Ví dụ:

class C {
    public function foo() {
        echo "Class C";
    }
}

class B extends C {
    // Lớp B kế thừa từ lớp C
}

class A extends B {
    // Lớp A kế thừa từ lớp B
}

$a = new A();
$a->foo(); // Kết quả: "Class C"​

Trong ví dụ trên, Class A kế thừa từ Class B, và Class B kế thừa từ Class C. Do đó, Class A có thể truy cập các phương thức và thuộc tính từ cả Class B và Class C.

Nếu một Class con kế thừa từ một Class cha, nó có thể sử dụng tất cả các thuộc tính và phương thức public hoặc protected của Class cha đó, bao gồm cả những thuộc tính và phương thức được kế thừa từ các Class cha khác trên chuỗi kế thừa. Tuy nhiên, nếu các thuộc tính hoặc phương thức trong Class cha là private, thì chúng sẽ không được truy cập từ Class con.

Note: khi một Class con kế thừa một Class cha và cả hai Class đều có một phương thức có cùng tên, thì phương thức trong Class con sẽ ưu tiên được thực thi. Điều này được gọi là "overriding" (ghi đè).

Khi bạn gọi phương thức từ một đối tượng của Class con, PHP sẽ tìm kiếm phương thức trong Class con trước. Nếu nó không tìm thấy, nó sẽ tiếp tục tìm kiếm trong Class cha. Điều này cho phép bạn ghi đè (override) phương thức trong Class con để thay đổi hoặc mở rộng hành vi của phương thức được kế thừa từ Class cha.

Ghi đè hàm (Function Overriding) trong PHP

Các định nghĩa hàm trong các Class con ghi đè các định nghĩa với cùng tên trong các Class cha. Trong một Class con, chúng ta có thể sửa đổi định nghĩa của một hàm được kế thừa từ Class cha.

Quy tắc ghi đè phương thức với tham số

  • Số lượng tham số: Phương thức ghi đè trong Class con phải có cùng số lượng tham số như phương thức trong Class cha.
  • Tên tham số: Tên tham số có thể khác nhau, nhưng vị trí và kiểu của các tham số phải giống nhau.
  • Kiểu dữ liệu: Nếu phương thức trong Class cha có khai báo kiểu dữ liệu của tham số hoặc kiểu trả về (type hinting), thì phương thức ghi đè trong Class con cũng phải tuân theo các khai báo đó.

Ví dụ minh họa

<?php

// Định nghĩa lớp cha với phương thức có tham số
class ParentClass {
    public function greet($name) {
        echo "Hello, $name!\n";
    }
}

// Định nghĩa lớp con kế thừa từ lớp cha và ghi đè phương thức greet
class ChildClass extends ParentClass {
    public function greet($name) {
        echo "Hi, $name! Welcome!\n";
    }
}

// Sử dụng các lớp
$parent = new ParentClass();
$parent->greet("John"); // Output: Hello, John!

$child = new ChildClass();
$child->greet("John"); // Output: Hi, John! Welcome!​

Phương thức với kiểu dữ liệu của tham số

Nếu phương thức trong Class cha có kiểu dữ liệu của tham số, Class con cũng phải tuân theo kiểu dữ liệu đó:

<?php

// Định nghĩa lớp cha với phương thức có tham số có kiểu dữ liệu
class ParentClass {
    public function greet(string $name) {
        echo "Hello, $name!\n";
    }
}

// Định nghĩa lớp con kế thừa từ lớp cha và ghi đè phương thức greet
class ChildClass extends ParentClass {
    public function greet(string $name) {
        echo "Hi, $name! Welcome!\n";
    }
}

// Sử dụng các lớp
$parent = new ParentClass();
$parent->greet("John"); // Output: Hello, John!

$child = new ChildClass();
$child->greet("John"); // Output: Hi, John! Welcome!​

Phương thức với kiểu trả về

Nếu phương thức trong Class cha có khai báo kiểu trả về, Class con cũng phải tuân theo kiểu trả về đó:

<?php

// Định nghĩa lớp cha với phương thức có kiểu trả về
class ParentClass {
    public function greet(string $name): string {
        return "Hello, $name!";
    }
}

// Định nghĩa lớp con kế thừa từ lớp cha và ghi đè phương thức greet
class ChildClass extends ParentClass {
    public function greet(string $name): string {
        return "Hi, $name! Welcome!";
    }
}

// Sử dụng các lớp
$parent = new ParentClass();
echo $parent->greet("John"); // Output: Hello, John!

$child = new ChildClass();
echo $child->greet("John"); // Output: Hi, John! Welcome!​

Kết luận

Khi ghi đè một phương thức từ Class cha, các Class con phải tuân theo cấu trúc tham số của phương thức đó, bao gồm số lượng tham số, kiểu dữ liệu (nếu có) và kiểu trả về (nếu có). Điều này đảm bảo rằng phương thức ghi đè sẽ hoạt động nhất quán và đúng cách khi được gọi trên các đối tượng của Class con

Thành viên public trong PHP

Trừ khi bạn xác định, nếu không thì các thuộc tính (property) và phương thức của một Class là public. Tức là, chúng có thể được truy cập trong 3 tình huống sau:

  • Từ bên ngoài Class trong đó nó được khai báo.

  • Từ bên trong Class trong đó nó được khai báo.

  • Từ bên trong Class ngoài mà triển khai Class đó trong đó nó được khai báo.

Tới giờ, chúng ta đã thấy tất cả thành viên là các thành viên public. Nếu bạn muốn giới hạn truy cập của các thành viên của một Class, thì bạn định nghĩa thành viên Class là private hoặc protected trong PHP.

Dưới đây là các ví dụ minh họa cho từng trường hợp:

  • Từ bên ngoài Class trong đó nó được khai báo:
class MyClass {
    public $publicVar = "Public variable";

    public function publicMethod() {
        echo "Public method";
    }
}

$obj = new MyClass();
echo $obj->publicVar; // Kết quả: Public variable
$obj->publicMethod(); // Kết quả: Public method​

Trong trường hợp này, chúng ta có thể truy cập vào các biến và phương thức public từ bên ngoài Class bằng cách tạo một đối tượng của Class đó.

  • Từ bên trong Class trong đó nó được khai báo:
class MyClass {
    public $publicVar = "Public variable";

    public function publicMethod() {
        echo "Public method";
    }

    public function accessPublicMember() {
        echo $this->publicVar; // Truy cập biến public từ bên trong lớp
        $this->publicMethod(); // Truy cập phương thức public từ bên trong lớp
    }
}

$obj = new MyClass();
$obj->accessPublicMember(); // Kết quả: Public variable, Public method​

Trong trường hợp này, chúng ta có thể truy cập các biến và phương thức public từ bên trong Class bằng cách sử dụng từ khóa $this.

  • Từ bên trong Class ngoài mà triển khai Class đó trong đó nó được khai báo:
class MyClass {
    public $publicVar = "Public variable";

    public function publicMethod() {
        echo "Public method";
    }
}

class AnotherClass extends MyClass {
    public function accessParentMember() {
        echo $this->publicVar; // Truy cập biến public từ lớp con
        $this->publicMethod(); // Truy cập phương thức public từ lớp con
    }
}

$obj = new AnotherClass();
$obj->accessParentMember(); // Kết quả: Public variable, Public method​

Trong trường hợp này, Class AnotherClass kế thừa từ Class MyClass, do đó nó có thể truy cập các biến và phương thức public của Class cha từ bên trong Class con.

Thành viên private trong PHP

Nếu một property của Class được định nghĩa là private, nó chỉ có thể được truy cập bên trong Class đó. Property private không thể truy cập từ các Class con kế thừa nó hoặc từ bên ngoài của Class đó.

Một thành viên Class có thể được chỉ định là private bởi sử dụng từ khóa private ở trước thành viên đó.

class MyClass {
    public $publicVar = "Public variable";
    private $privateVar = "Private variable";

    public function publicMethod() {
        echo "Public method";
    }

    private function privateMethod() {
        echo "Private method";
    }

    public function accessPrivateMember() {
       return $this->privateMethod();
    }
}

$obj = new MyClass();
$obj->accessPrivateMember(); // Kết quả: Private variable
$obj->privateVar // wrong
$obj->privateMethod() // wrong

Khi Class MyClass được kế thừa bởi Class khác bởi sử dụng extends, hàm myPublicFunction() sẽ là nhìn thấy. Class kế thừa sẽ không nhận biết hoặc không truy cập tới hàm privateMethod và $privateVar, bởi vì chúng được khai báo là private.

Để gán giá trị cho một thuộc tính private trong một Class, bạn có thể sử dụng các phương thức setter (đặt giá trị) và getter (lấy giá trị) public. Các phương thức này cho phép bạn thực hiện các kiểm tra và xác nhận trước khi gán giá trị cho thuộc tính private.

Dưới đây là một ví dụ minh họa:

class MyClass {
    private $privateVar;

    public function setPrivateVar($value) {
        // Có thể thực hiện các kiểm tra và xác nhận ở đây
        // Ví dụ: Kiểm tra giá trị hợp lệ trước khi gán cho privateVar
        if ($value >= 0) {
            $this->privateVar = $value;
        } else {
            echo "Giá trị không hợp lệ!";
        }
    }

    public function getPrivateVar() {
        return $this->privateVar;
    }
}

$obj = new MyClass();
$obj->setPrivateVar(10); // Gọi phương thức setter để gán giá trị cho privateVar
echo $obj->getPrivateVar(); // Gọi phương thức getter để lấy giá trị của privateVar​

Trong ví dụ trên, setPrivateVar() là một phương thức public cho phép gán giá trị cho $privateVar, và getPrivateVar() là một phương thức public cho phép lấy giá trị của $privateVar. Bằng cách này, bạn có thể kiểm soát quyền truy cập vào thuộc tính private và thực hiện các xử lý cần thiết trước khi gán giá trị cho nó.

Thành viên protected trong PHP

Một thuộc tính hoặc phương thức protected có thể được truy cập trong Class mà nó được khai báo, cũng như trong các Class con kế thừa từ Class đó. Các thành viên protected không thể truy cập từ bên ngoài của hai loại Class này. Để định nghĩa một thành viên là protected trong PHP, bạn sử dụng từ khóa protected trước thành viên đó.

Dưới đây là một ví dụ:

class MyClass {
   protected $car = "Skoda";
   protected $driver = "SRK";

   function __construct($par) {
      // Các lệnh ở đây được thực thi mỗi khi
      // một instance của class
      // được tạo
   }
   
   public function myPublicFunction() {
      return "Đây là một hàm Public!";
   }
   
   protected function myProtectedFunction() {
      return "Đây là một hàm Protected!";
   }
}

class AnotherClass extends MyClass {
    public function accessProtectedMember() {
        echo $this->car; // Truy cập biến protected từ lớp con
        echo $this->driver; // Truy cập biến protected khác từ lớp con
        echo $this->myProtectedFunction(); // Truy cập phương thức protected từ lớp con
    }
}

$obj = new AnotherClass();
$obj->accessProtectedMember(); // Sẽ hiển thị giá trị và kết quả của các thành viên protected​

Trong ví dụ này, MyClass có hai thuộc tính và một phương thức, tất cả đều là protected. Class AnotherClass kế thừa từ MyClass và có thể truy cập các thành viên protected của nó.

Interface trong PHP

Interface trong hướng đối tượng là một khuôn mẫu, giúp cho chúng ta tạo ra bộ khung cho một hoặc nhiều đối tượng và nhìn vào interface thì chúng ta hoàn toàn có thể xác định được các phương thức và các thuộc tính cố định (hay còn gọi là hằng) sẽ có trong đối tượng implement nó.

  • Interface không phải là một đối tượng.
  • Trong interface chúng ta chỉ được khai báo phương thức chứ không được định nghĩa chúng.
  • Trong interface chúng ta có thể khai báo được hằng nhưng không thể khai báo biến.
  • Một interface không thể khởi tạo được (vì nó không phải là một đối tượng).
  • Các Class implement interface thì phải khai báo và định nghĩa lại các phương thức có trong interface đó.
  • Một class có thể implements nhiều interface.
  • Các interface có thể kế thừa lẫn nhau.

Dưới đây là một ví dụ về cách sử dụng Interface trong PHP:

interface Animal {
    const name = "Animal";
    public function makeSound();
}

class Dog implements Animal {
    public function makeSound() {
        echo "Woof!";
    }
}

class Cat implements Animal {
    public function makeSound() { // ghi đè lại phương thức trong interface
        echo Animal::name; // get const value trong interface
        echo "Meow!";
    }
}

$dog = new Dog();
$cat = new Cat();

$dog->makeSound(); // Kết quả: Woof!
$cat->makeSound(); // Kết quả: Meow!​

Trong ví dụ này, Interface Animal chỉ định một phương thức makeSound(). Cả hai Class DogCat đều triển khai Interface Animal, vì vậy chúng cần cung cấp một triển khai cho phương thức makeSound(). Mặc dù không có định nghĩa thuộc tính hoặc phương thức khác, Interface vẫn giúp đảm bảo rằng các Class triển khai có các phương thức cần thiết để thực hiện các hành động cụ thể.

Một interface có thể kế thừa từ một hoặc nhiều interface khác. Khi một interface kế thừa từ một interface khác, nó sẽ kế thừa tất cả các phương thức đã được định nghĩa trong các interface mà nó kế thừa.

Dưới đây là một ví dụ minh họa:

interface Flyable {
    public function fly();
}

interface Singing {
    public function sing();
}

interface BirdInterface extends Flyable, Singing {
    // Không cần định nghĩa phương thức, vì nó đã được kế thừa từ Flyable và Singing
}

class Bird implements BirdInterface {
    public function fly() {
        echo "The bird is flying.\n";
    }

    public function sing() {
        echo "The bird is singing.\n";
    }
}

$bird = new Bird();
$bird->fly(); // Output: The bird is flying.
$bird->sing(); // Output: The bird is singing.​

Trong ví dụ này, interface BirdInterface kế thừa từ hai interface khác là FlyableSinging. Tất cả các phương thức được định nghĩa trong FlyableSinging được kế thừa bởi BirdInterface. Class Bird implement từ BirdInterface, do đó nó phải triển khai tất cả các phương thức trong BirdInterface, cũng như tất cả các phương thức đã được kế thừa từ FlyableSinging.

Khi nào nên sử dụng Interface:

  • Khi bạn muốn định nghĩa một tập hợp các phương thức mà các Class khác nhau cần phải triển khai.
  • Khi bạn muốn đảm bảo rằng các Class có cùng một hành vi cơ bản.
  • Khi bạn muốn tăng tính linh hoạt và tái sử dụng mã, bởi vì bạn có thể thay đổi các Class triển khai mà không cần thay đổi mã nguồn của các phương thức.

Dưới đây là một số ví dụ cụ thể về khi nào bạn nên sử dụng Interface trong PHP:

  • Định nghĩa một tập hợp các phương thức mà các Class khác nhau cần phải triển khai: Giả sử bạn đang phát triển một ứng dụng web và bạn cần xử lý việc gửi email từ nhiều loại máy chủ email khác nhau, chẳng hạn như Gmail, Outlook, Yahoo. Bằng cách sử dụng Interface, bạn có thể định nghĩa một tập hợp các phương thức cơ bản mà mỗi máy chủ email phải triển khai, như send(), authenticate(), setRecipient(), setSubject(), v.v.

  • Đảm bảo rằng các Class có cùng một hành vi cơ bản: Giả sử bạn có một ứng dụng đang quản lý các loài động vật và bạn cần các Class cho cả chó và mèo để thực hiện hành vi cơ bản như eat(), sleep(), makeSound(). Bằng cách triển khai các Class này từ một Interface Animal, bạn đảm bảo rằng cả chó và mèo đều có các hành vi cơ bản giống nhau.

  • Tăng tính linh hoạt và tái sử dụng mã: Giả sử bạn đang phát triển một ứng dụng có thể kết nối với nhiều loại cơ sở dữ liệu khác nhau, chẳng hạn như MySQL, PostgreSQL, SQLite. Bằng cách sử dụng Interface cho Class cơ sở dữ liệu, bạn có thể thay đổi cơ sở dữ liệu mà không cần thay đổi mã nguồn của ứng dụng. Ví dụ, nếu bạn thêm một loại cơ sở dữ liệu mới, như MongoDB, bạn chỉ cần triển khai các phương thức tương ứng trong Interface mà không cần sửa đổi các Class khác trong ứng dụng của bạn.

Class trừu tượng (Abstract Class) trong PHP

Class Abstract sẽ định nghĩa các phương thức (hàm) mà từ đó các Class con sẽ kế thừa nó và Overwrite lại (tính đa hình).

  • Các phương thức ( hàm ) khi được khai báo là abstract thì chỉ được định nghĩa chứ không được phép viết code xử lý trong phương thức.
  • Trong abstract class nếu không phải là phương thức abstract thì vẫn khai báo và viết code được như bình thường.
  • Phương thức abstract chỉ có thể khai báo trong abstract class.
  • Các thuộc tính trong abstract class thì không được khai báo là abstract.
  • Không thể khởi tạo một abstract class.
  • Vì không thể khởi tạo được abstract class nên các phương thức được khai báo là abstract chỉ được khai báo ở mức độ protected và public.
  • Các Class kế thừa một abstract class phải định nghĩa lại tất cả các phương thức trong abstract class đó ( nếu không sẽ bị lỗi).
<?php
// Định nghĩa một abstract class
abstract class Animal {
    // Phương thức trừu tượng
    abstract public function makeSound();

    // Phương thức cụ thể
    public function eat() {
        echo "This animal is eating.\n";
    }
}

// Định nghĩa một lớp con của abstract class
class Dog extends Animal {
    // Thực hiện phương thức trừu tượng
    public function makeSound() {
        echo "Woof! Woof!\n";
    }
}

class Cat extends Animal {
    // Thực hiện phương thức trừu tượng
    public function makeSound() {
        echo "Meow! Meow!\n";
    }
}

// Khởi tạo các đối tượng của lớp con
$dog = new Dog();
$dog->makeSound(); // Output: Woof! Woof!
$dog->eat(); // Output: This animal is eating.

$cat = new Cat();
$cat->makeSound(); // Output: Meow! Meow!
$cat->eat(); // Output: This animal is eating.
?>

Khi nào nên sử dụng abstract class trong PHP?

Abstract class được sử dụng trong những tình huống sau:

  • Khi có các phương thức chung mà các Class con sẽ dùng chung, nhưng cần có một số phương thức phải được Class con triển khai:

    • Abstract class cho phép bạn định nghĩa các phương thức có thể chia sẻ giữa các Class con, đồng thời buộc các Class con phải thực hiện một số phương thức cụ thể.
  • Khi bạn muốn định nghĩa các phương thức mặc định nhưng vẫn muốn các Class con có thể override (ghi đè):

    • Abstract class cho phép bạn cung cấp một số hành vi mặc định, và các Class con có thể ghi đè nếu cần thiết.
    • abstract class DongVat {
          public $loai;
          public function setLoai($loai) {
            echo $loai;
          }
      }
      
      class Cat extends DongVat
      {
          public $place;
          public function setLoai($loai) {
             echo $loai. "at" . $place;
          }
      }
  • Khi bạn muốn dùng tính kế thừa mà không muốn tạo đối tượng của Class cơ sở:

    • Abstract class không thể được khởi tạo trực tiếp, điều này giúp ngăn chặn việc tạo các đối tượng không đầy đủ.

Hằng số (Constant) trong PHP

Một constant là một cái gì đó giống như một biến, trong đó nó giữ một giá trị, nhưng thực sự thì nó giống một hàm hơn, bởi vì một hằng là không thể thay đổi. Một khi bạn khai báo một hằng, nó không thay đổi.

Khai báo một hằng trong PHP là khá dễ dàng, như được thực hiện trong phiên bản MyClass này.

class MyClass {
   const requiredMargin = 1.7; // từ khóa const
   
   function __construct($incomingValue) {
      // các lệnh ở đây được thực thi mỗi khi
      // một instance của class
      // được tạo
   }
}

Trong Class này, requireMargin là một hằng. Nó được khai báo với từ khóa const trong PHP, và giá trị của nó sẽ không thay đổi trong bất kỳ tình huống nào. Ghi nhớ rằng, tên hằng không bắt đầu với $, như trong tên biến.

Từ khóa static trong PHP

Trong PHP, thuộc tính (property) và phương thức (method) có thể được khai báo là static. Điều này có nghĩa là chúng không thuộc về một đối tượng cụ thể của Class, mà thuộc về chính Class đó. Hãy cùng tìm hiểu sâu hơn về khái niệm này.

Thuộc tính và phương thức static

  • Thuộc tính static: Thuộc tính static thuộc về Class chứ không thuộc về đối tượng của Class đó. Bạn có thể truy cập thuộc tính static mà không cần tạo một đối tượng của Class. Thuộc tính static được khai báo với từ khóa static.
  • Phương thức static: Phương thức static cũng thuộc về Class và có thể được gọi mà không cần tạo một đối tượng của Class. Phương thức static cũng được khai báo với từ khóa static.

Truy cập thuộc tính và phương thức static

  • Truy cập thuộc tính static:

    • Bạn không thể truy cập thuộc tính static thông qua một đối tượng của Class. Thay vào đó, bạn phải truy cập nó thông qua tên Class.
    • Sử dụng cú pháp ClassName::$propertyName để truy cập thuộc tính static.
  • Truy cập phương thức static:

    • Bạn có thể gọi phương thức static thông qua tên Class hoặc đối tượng của Class, nhưng cách tốt nhất là gọi thông qua tên Class để rõ ràng rằng đó là một phương thức static.
    • Sử dụng cú pháp ClassName::methodName() để gọi phương thức static.

Ví dụ minh họa

<?php

class MyClass {
    // Khai báo thuộc tính static
    public static $myStaticProperty = 'Static Value';

    // Khai báo phương thức static
    public static function myStaticMethod() {
        echo "This is a static method.\n";
    }
}

// Truy cập thuộc tính static thông qua tên lớp
echo MyClass::$myStaticProperty; // Output: Static Value

// Gọi phương thức static thông qua tên lớp
MyClass::myStaticMethod(); // Output: This is a static method.

// Khởi tạo một đối tượng của lớp
$instance = new MyClass();

// Truy cập thuộc tính static thông qua đối tượng (sẽ gây lỗi)
// echo $instance->$myStaticProperty; // Lỗi: Undefined property

// Gọi phương thức static thông qua đối tượng (có thể nhưng không khuyến khích)
$instance->myStaticMethod(); // Output: This is a static method.​

Giải thích ví dụ:

  1. Khai báo thuộc tính và phương thức static:

    • Thuộc tính static myStaticProperty được khai báo với từ khóa static.
    • Phương thức static myStaticMethod được khai báo với từ khóa static.
  2. Truy cập thuộc tính static:

    • MyClass::$myStaticProperty truy cập thuộc tính static thông qua tên Class. Điều này là đúng cách và được khuyến khích.
    • Cố gắng truy cập thuộc tính static thông qua một đối tượng ($instance->$myStaticProperty) sẽ gây lỗi vì thuộc tính static không được liên kết với bất kỳ đối tượng cụ thể nào.
  3. Gọi phương thức static:

    • MyClass::myStaticMethod() gọi phương thức static thông qua tên Class. Đây là cách gọi được khuyến khích.
    • Gọi phương thức static thông qua một đối tượng ($instance->myStaticMethod()) cũng hoạt động, nhưng không khuyến khích vì nó có thể gây nhầm lẫn về bản chất của phương thức static.

Kết luận

  • Thuộc tính static phải được truy cập thông qua tên Class và không thể truy cập trực tiếp thông qua một đối tượng của Class.
  • Phương thức static cũng nên được gọi thông qua tên Class, mặc dù có thể gọi thông qua một đối tượng của Class.

Điều này giúp rõ ràng rằng thuộc tính và phương thức static không thuộc về bất kỳ đối tượng cụ thể nào, mà thuộc về chính Class đó.

Từ khóa final trong PHP

Trong PHP, từ khóa final được sử dụng để ngăn cản các Class con ghi đè một phương thức cụ thể hoặc kế thừa một Class. Nếu một phương thức được khai báo là final, các Class con không thể ghi đè phương thức đó. Nếu một Class được khai báo là final, Class đó không thể được kế thừa.

Sử dụng từ khóa final trong PHP

  1. Phương thức final: Ngăn cản các Class con ghi đè phương thức này.
  2. Class final: Ngăn cản Class đó bị kế thừa bởi bất kỳ Class nào khác.

Ví dụ về phương thức final

<?php

class BaseClass {
    public function test() {
        echo "BaseClass::test() called<br>";
    }

    final public function moreTesting() {
        echo "BaseClass::moreTesting() called<br>";
    }
}

class ChildClass extends BaseClass {
    // Gây ra lỗi Fatal Error: Cannot override final method BaseClass::moreTesting()
    public function moreTesting() {
        echo "ChildClass::moreTesting() called<br>";
    }
}

// Tạo đối tượng và gọi các phương thức
$base = new BaseClass();
$base->test(); // Output: BaseClass::test() called
$base->moreTesting(); // Output: BaseClass::moreTesting() called

$child = new ChildClass();
$child->test(); // Output: BaseClass::test() called
$child->moreTesting(); // Gây ra lỗi
?>​

Giải thích ví dụ:

  1. Định nghĩa Class BaseClass:

    • BaseClass có một phương thức test() có thể được ghi đè bởi các Class con.
    • Phương thức moreTesting() được khai báo là final, do đó các Class con không thể ghi đè phương thức này.
  2. Định nghĩa Class ChildClass kế thừa từ BaseClass:

    • ChildClass cố gắng ghi đè phương thức moreTesting(). Điều này sẽ gây ra lỗi vì moreTesting() trong BaseClass đã được khai báo là final.

Ví dụ về Class final

<?php

final class FinalClass {
    public function test() {
        echo "FinalClass::test() called<br>";
    }
}

// Cố gắng kế thừa lớp FinalClass sẽ gây ra lỗi Fatal Error
class ChildClass extends FinalClass {
    public function test() {
        echo "ChildClass::test() called<br>";
    }
}

// Tạo đối tượng và gọi phương thức
$final = new FinalClass();
$final->test(); // Output: FinalClass::test() called
?>​

Giải thích ví dụ:

  1. Định nghĩa Class FinalClass:

    • FinalClass được khai báo là final, do đó không thể bị kế thừa bởi bất kỳ Class nào khác.
    • Phương thức test() có thể được gọi từ đối tượng của FinalClass.
  2. Cố gắng kế thừa từ FinalClass:

    • ChildClass cố gắng kế thừa từ FinalClass. Điều này sẽ gây ra lỗi vì FinalClass đã được khai báo là final.

Kết luận

  • Phương thức final: Ngăn cản ghi đè phương thức trong Class con.
  • Class final: Ngăn cản Class bị kế thừa.

Việc sử dụng từ khóa final giúp bảo vệ các phương thức và Class quan trọng không bị thay đổi bởi các Class con, đảm bảo tính toàn vẹn của mã nguồn.

Trường hợp sử dụng Class final

  1. Bảo vệ sự toàn vẹn của Class:

    • Khi bạn muốn đảm bảo rằng Class của bạn không bị thay đổi hoặc mở rộng bởi các Class con, bạn có thể khai báo Class đó là final. Điều này giúp bảo vệ hành vi và trạng thái của Class, ngăn ngừa những thay đổi không mong muốn.
  2. Tối ưu hóa hiệu suất:

    • Một số hệ thống và trình biên dịch có thể tối ưu hóa các Class final tốt hơn vì biết rằng chúng sẽ không có Class con. Điều này có thể cải thiện hiệu suất của ứng dụng.
  3. Thiết kế hướng dịch vụ:

    • Trong các kiến trúc như dịch vụ web hoặc microservices, đôi khi bạn muốn đảm bảo rằng một số Class nhất định không bị thay đổi. Việc khai báo chúng là final giúp duy trì tính ổn định và nhất quán của dịch vụ.
  4. Ngăn chặn việc ghi đè các phương thức quan trọng:

    • Khi một Class chứa các phương thức quan trọng và bạn muốn đảm bảo rằng các phương thức này không bị ghi đè, bạn có thể sử dụng Class final.

Gọi constructor cha trong PHP

Trong PHP, khi bạn muốn một Class con kế thừa từ một Class cha và thực hiện một constructor riêng của nó, bạn có thể gọi constructor của Class cha một cách tường minh bằng cách sử dụng cú pháp ParentClassName::__construct(). Dưới đây là một ví dụ minh họa:

class Name {
   protected $_firstName;
   protected $_lastName;
   
   function __construct($first_name, $last_name) {
      $this->_firstName = $first_name;
      $this->_lastName = $last_name;
   }
   
   function toString() {
      return $this->_lastName . ", " . $this->_firstName;
   }
}

class NameSub1 extends Name {
   protected $_middleInitial;
   
   function __construct($first_name, $middle_initial, $last_name) {
      parent::__construct($first_name, $last_name);
      $this->_middleInitial = $middle_initial;
   }
   
   function toString() {
      return parent::toString() . " " . $this->_middleInitial;
   }
}

// Sử dụng các lớp
$name = new Name("John", "Doe");
echo $name->toString(); // Output: Doe, John

$nameSub1 = new NameSub1("John", "M", "Doe");
echo $nameSub1->toString(); // Output: Doe, John M​

Trong ví dụ này:

  • Class cha Name có một constructor nhận hai tham số và một phương thức toString() để xuất ra tên đầy đủ.
  • Class con NameSub1 kế thừa từ Class cha Name và có một constructor riêng nhận ba tham số, trong đó tham số thứ hai là chữ cái đầu tiên của tên đệm.
  • Trong constructor của NameSub1, chúng ta gọi constructor của Class cha Name bằng cách sử dụng parent::__construct() để khởi tạo phần của Class cha.
  • Class con NameSub1 cũng ghi đè phương thức toString() của Class cha để thêm chữ cái đầu tiên của tên đệm vào chuỗi tên đầy đủ.

Điều này giúp bạn tái sử dụng logic xử lý của constructor của Class cha trong Class con mà không cần phải viết lại.

Các bài học PHP phổ biến khác tại s2sontech:




Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Learning English Everyday