Hướng dẫn dùng examples traits trong PHP

Hi vọng với bài viết này, bạn đã hiểu rõ cách sử dụng hàm get_declared_traits() trong PHP. Nếu thấy bài viết hay và ý nghĩa, hãy like và chia sẻ với bạn bè để mọi người cùng nhau học tập nhé. Cảm ơn bạn đã ghé thăm codetutam.com        

Trong bài này, chúng ta sẽ tìm hiểu về Trait trong PHP. Để học tốt bài này, các bạn cần đọc lại bài để biết cách chạy ứng dụng web PHP với XAMPP.

1. Trait trong PHP là gì?

PHP chỉ hỗ trợ đơn kế thừa (single inheritance). Tức là một lớp con (child class) chỉ có thể kế thừa từ một lớp cha (parent class). Nếu một lớp (class) cần kế thừa từ nhiều lớp cha thì phải làm sao? Trait trong PHP giúp giải quyết vấn đề này.

Trait có những phương thức bình thường và những phương thức abstract. Những phương thức này có thể có bất kỳ access modifier nào (public, private hoặc protected). Trong Trait cũng có thể có các thuộc tính (properties). Những thuộc tính và phương thức của Trait có thể được sử dụng trong nhiều lớp (class).

Trait trong PHP được khai báo với từ khóa trait.


Để sử dụng một Trait trong một lớp, chúng ta sử dụng từ khóa use:


Ví dụ sử dụng Trait cho lớp Welcome như bên dưới:

var1 = 9999;
    echo "Welcome to gochocit.com. ";
  }
}

class Welcome {
  //using trait in Welcome class
  use message1;
}

$obj = new Welcome();
$obj->msg1();
echo "
"; echo $obj->var1; ?>
Kết quả
Welcome to gochocit.com.
9999

Trong ví dụ trên, chúng ta khai báo 2 Trait message1message2. Sau đó, chúng ta tạo ra 2 class Welcome và Welcome2. Lớp Welcome sử dụng Trait message1.  Lớp Welcome2 sử dụng cả 2 Trait message1message2.

Vậy, điều gì sẽ xảy ra nếu một lớp cần kế thừa nhiều hành vi? Traits trong PHP  sẽ giải quyết vấn đề này.

Traits (đặc điểm) trong PHP được sử dụng để khai báo các phương thức có thể được sử dụng trong nhiều lớp.

Traits trong PHP có thể có các phương thức và phương thức trừu tượng để có thể sử dụng trong nhiều lớp và các phương thức có thể có bất kỳ chỉ thị truy cập nào (public, private hoặc protected).

Traits trong PHP được khai báo với từ khóa trait như ví dụ sau:

Để sử dụng traits trong một lớp, chúng ta sẽ sử dụng từ khóa use như ví dụ sau:

Ví dụ dưới đây minh họa sử dụng traits trong PHP:

msg1();
?>

Đây là kết quả:

OOP is fun!

Ở đây, chúng tôi khai báo một traits tên là message1. Sau đó, chúng tôi tạo ra một lớp tên là Welcome.

Lớp Welcome sử dụng traits message1 do đó tất cả các phương thức trong message1 sẽ có sẵn trong lớp này.

Nếu các lớp khác cũng cần sử dụng hàm msg1(), chỉ cần sử dụng traits message1 trong các lớp đó là xong. Điều này làm giảm sự trùng lặp của mã, bởi vì không cần phải lặp đi lặp lại một đoạn mã nào đó.

Sử dụng nhiều traits trong PHP

PHP hỗ trợ một lớp có thể sử dụng nhiều trait.

Ví dụ sau minh họa sử dụng nhiều traits trong PHP:

msg1();
    echo "
"; $obj2 = new Welcome2(); $obj2->msg1(); echo "
"; $obj2->msg2(); ?>

Đây là kết quả:

OOP is fun!
OOP is fun!
OOP reduces code duplication!

Ở đây, chúng tôi khai báo hai traits là: message1 và message2. Sau đó, chúng tôi tạo hai lớp là: Welcome1 và Welcome2.

Lớp Welcome1 sử dụng traits message1 và lớp Welcome2 sử dụng cả hai trait là message1 và message2 (các traits được phân tách bằng dấu phẩy).

Do đó đối tượng của lớp Welcome2 có thể truy cập các phương thức được định nghĩa trong cả hai traits là message1 và message2.

Giảm thiểu sao chép mã thông qua tổ chức tốt hơn và tái sử dụng mã là mục tiêu quan trọng của lập trình hướng đối tượng.

Nhưng trong PHP đôi khi bạn có thể gặp khó khăn vì những hạn chế của mô hình đơn kế thừa mà nó sử dụng; bạn có một số phương thức mà bạn muốn sử dụng trong nhiều lớp nhưng chúng không thể kế thừa từ nhiều lớp.

Các ngôn ngữ như C++ và Python cho phép chúng ta kế thừa từ nhiều lớp để giải quyết vấn đề này ở một mức độ nào đó và mixins trong Ruby cho phép chúng ta trộn chức năng của một hoặc nhiều lớp mà không cần sử dụng kế thừa.

Nhưng đa kế thừa có nhiều vấn đề chẳng hạn như Diamond Problem và mixins có thể là một cơ chế quá phức tạp để giải quyết vấn đề.

Trong bài viết này chúng ta sẽ thảo luận về traits, một tính năng mới được giới thiệu trong PHP 5.4 để khắc phục các vấn đề như vậy. Bạn có thể xem giới thiệu về traits trong PHP ở bài viết này:

Khái niệm về traits tự nó không có gì mới đối với lập trình và được sử dụng trong các ngôn ngữ khác như Scala và Perl. Chúng cho phép chúng ta sử dụng lại mã theo chiều ngang trên các lớp độc lập trong các phân cấp lớp khác nhau.

Traits là gì?

Traits tương tự như một lớp trừu tượng không thể tự khởi tạo (mặc dù nó thường được so sánh với một interface). Tài liệu PHP định nghĩa traits như sau:

Traits là một cơ chế để tái sử dụng mã trong các ngôn ngữ đơn kế thừa như PHP. Traits nhằm giảm một số hạn chế của đơn kế thừa bằng cách cho phép nhà phát triển sử dụng lại các tập phương thức một cách tự do trong một số lớp độc lập ở trong các hệ thống phân cấp lớp khác nhau.

Hãy xem xét ví dụ này:

Sẽ có vấn đề nếu cả hai lớp trên đều cần một số chức năng chung, ví dụ như làm cho cả hai đều là Singleton.

Vì PHP không hỗ trợ đa kế thừa, mỗi lớp sẽ phải triển khai mã cần thiết để hỗ trợ mẫu Singleton hoặc sẽ có một hệ thống phân cấp thừa kế không có ý nghĩa.

Traits cung cấp một giải pháp chính xác cho loại vấn đề này.

Traits

0 này có một triển khai rõ ràng của mẫu Singleton với một phương thức tĩnh 
1 tạo ra một đối tượng của lớp bằng cách sử dụng trait này (nếu nó chưa được tạo) và trả về nó.

Hãy thử tạo các đối tượng của các lớp này bằng phương thức

1 này.

Đây là kết quả:

object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}

Chúng ta có thể thấy

3 là một đối tượng của 
4 và 
5 là một đối tượng của 
6, nhưng cả hai hiện đang hành xử như là những Singleton. Phương thức từ traits 
0 đã được inject theo chiều ngang cho các lớp sử dụng nó.

Traits không áp đặt bất kỳ ngữ nghĩa bổ sung nào trên lớp. Theo một cách nào đó, bạn có thể nghĩ về nó như là một cơ chế sao chép và dán hỗ trợ trình biên dịch trong đó các phương thức của traits được sao chép vào lớp chứa nó.

Nếu chúng ta chỉ đơn giản là tạo lớp con

4 từ lớp cha có thuộc tính private 
9, thuộc tính đó sẽ không được hiển thị trong kết xuất của 
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
0. Nhưng với traits thì có!

Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}

Nhiều traits

Cho đến nay chúng ta chỉ sử dụng một traits trong một lớp, nhưng trong một vài trường hợp, chúng ta có thể cần kết hợp chức năng của nhiều traits.

sayHello() . " " . $world->sayWorld();
?>

Đây là kết quả:

Hello World

Ở đây chúng tôi có hai traits là

object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
1 và
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
2. Traits
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
1 chỉ có thể nói "Hello" và traits
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
2 chỉ có thể nói "World".

Trong lớp

object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
5 chúng tôi đã sử dụng cả hai traits
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
1 và
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
2, do đó đối tượng của
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
5 sẽ có các phương thức từ cả hai traits này và có thể nói "Hello World".

Traits lồng nhau

Khi ứng dụng lớn lên, rất có thể chúng ta sẽ có một tập hợp các traits được sử dụng trên các lớp khác nhau.

PHP 5.4 cho phép chúng ta có các traits lồng nhau để chúng ta có thể thêm chỉ một traits thay vì nhiều traits vào trong tất cả các lớp này.

Điều này cho phép chúng ta viết lại ví dụ trước như sau:

sayHello() . " " . $world->sayWorld();
?>

Đây là kết quả:

Hello World

Ở đây chúng tôi đã tạo ra traits

object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
9 sử dụng các traits Hello và World, và đưa nó vào lớp
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
5.

Vì traits

object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
9 có các phương thức từ hai traits khác, nên nó giống hệt như chúng ta sử dụng hai traits trong lớp.

Thứ tự ưu tiên

Như tôi đã đề cập, traits hoạt động như thể các phương thức của chúng đã được sao chép và dán vào các lớp sử dụng chúng và chúng hoàn toàn được làm phẳng theo định nghĩa của các lớp.

Có thể có các phương thức có cùng tên trong các traits khác nhau hoặc trong chính lớp đó. Bạn có thể tự hỏi cái nào sẽ có hiệu lực trong đối tượng của lớp con.

Thứ tự ưu tiên là:

  1. Các phương thức của traits ghi đè các phương thức được kế thừa từ lớp cha.
  2. Các phương thức được định nghĩa trong lớp hiện tại ghi đè các phương thức từ traits.

Điều này được làm rõ trong ví dụ sau:

sayHello() . " " . $this->sayWorld();
    }

    function sayBaseWorld() {
        echo $this->sayHello() . " " . parent::sayWorld();
    }
}

class Base
{
    function sayWorld(){
        return "Base World";
    }
}

class HelloWorld extends Base
{
    use Hello;
    function sayWorld() {
        return "World";
    }
}

$h =  new HelloWorld();
$h->sayHelloWorld();
echo "
"; $h->sayBaseWorld(); ?>

Đây là kết quả:

0

Chúng ta có lớp

object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
9 kế thừa từ lớp
Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}
3 và cả hai lớp có một phương thức tên là
Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}
4 nhưng với các cách triển khai khác nhau. Ngoài ra, chúng tôi cũng đã thêm trait
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
1 vào trong lớp
object(DbReader)#1 (0) {
}
object(FileReader)#2 (0) {
}
9.

Chúng tôi có hai phương thức là

Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}
7 và
Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}
8 cùng gọi phương thức
Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}
4 tồn tại trong cả hai lớp cũng như trong traits.

Nhưng trong đầu ra, chúng ta có thể thấy phương thức

Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}
4 của lớp con đã được gọi. Nếu muốn tham chiếu tới phương thức của lớp cha, chúng ta có thể sử dụng từ khóa
sayHello() . " " . $world->sayWorld();
?>
1 như được trình bày trong phương thức
Class [  class FileReader ] {
  @@ /home/comdy/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/comdy/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}
8.

Giải quyết xung đột và bí danh

Khi sử dụng nhiều traits có thể xảy ra tình huống trong đó các traits khác nhau sử dụng cùng tên phương thức.

Ví dụ: PHP sẽ đưa ra một lỗi nghiêm trọng nếu bạn cố chạy mã sau đây vì xung đột tên các phương thức:

1

Những xung đột như vậy không được giải quyết tự động cho bạn. Thay vào đó, bạn phải chọn phương thức nào sẽ được sử dụng bằng từ khóa

sayHello() . " " . $world->sayWorld();
?>
3.

1

Đây là kết quả:

3

Ở đây chúng tôi đã chọn sử dụng phương thức

sayHello() . " " . $world->sayWorld();
?>
4 của traits
sayHello() . " " . $world->sayWorld();
?>
5 để lớp
sayHello() . " " . $world->sayWorld();
?>
6 sẽ phát nhạc chứ không phải trò chơi.

Trong ví dụ trên, một trong hai phương thức của hai traits đã được chọn.

Nhưng có một số trường hợp bạn muốn giữ cả hai phương thức mà vẫn tránh được xung đột. Trong trường hợp này bạn có thể sử dụng một tên mới (bí danh) cho một phương thức của traits.

Một bí danh không làm thay đổi tên của phương thức, nhưng nó cung cấp một tên thay thế để gọi phương thức đó. Bí danh được tạo bằng cách sử dụng từ khóa

sayHello() . " " . $world->sayWorld();
?>
7.

4

Đây là kết quả:

5

Bây giờ bất kỳ đối tượng nào của lớp

sayHello() . " " . $world->sayWorld();
?>
6 cũng sẽ có một phương thức
sayHello() . " " . $world->sayWorld();
?>
9, nó trương tự như
Hello World
0.

Reflection

Reflection API là một trong những tính năng mạnh mẽ của PHP để phân tích cấu trúc bên trong của các interface, lớp và phương thức và dịch ngược chúng.

Và vì chúng ta đang nói về traits, bạn có thể muốn biết về các hỗ trợ của Reflection API  cho traits. Trong PHP 5.4, bốn phương thức đã được thêm vào

Hello World
1 để lấy thông tin traits trong một lớp.

Chúng ta có thể sử dụng

Hello World
2 để có được một mảng của tất cả các traits được sử dụng trong một lớp.

Phương thức

Hello World
3 trả về một mảng các tên traits trong lớp đó.

Phương thức

Hello World
4 được sử dụng để kiểm tra một cái gì đó có phải là traits hay không.

Trong phần trước chúng ta đã thảo luận về việc sử dụng bí danh cho các traits để tránh xung đột do các traits có cùng tên.

Phương thức

Hello World
5 sẽ trả về một mảng các bí danh của traits được ánh xạ tới tên ban đầu của nó.

Các tính năng khác

Ngoài những điều đã đề cập ở trên, còn có những đặc điểm khác khiến traits trở nên thú vị hơn.

Chúng ta biết rằng trong kế thừa cổ điển, các thuộc tính riêng của một lớp không thể được truy cập bởi các lớp con.

Tuy nhiên traits có thể truy cập các thuộc tính hoặc phương thức riêng của các lớp sử dụng nó và ngược lại! Đây là một ví dụ:

6

Đây là kết quả:

7

Vì traits được làm phẳng hoàn toàn vào lớp sử dụng chúng, bất kỳ thuộc tính hoặc phương thức nào của traits sẽ trở thành một phần của lớp đó và chúng tôi truy cập chúng giống như bất kỳ thuộc tính hoặc phương thức khác của lớp.

Chúng ta thậm chí có thể có các phương thức trừu tượng trong traits để bắt các lớp sử dụng nó phải ghi đè các phương thức này. Ví dụ:

8

Đây là kết quả:

9

Ở đây chúng tôi có một traits tên là

Hello World
6 với một phương thức trừu tượng tên là
Hello World
7. Nó yêu cầu tất cả các lớp sử dụng traits này phải ghi đè phương thức này.

Mặt khác, PHP sẽ đưa ra một lỗi cho biết có một phương thức trừu tượng chưa được ghi đè.

Không giống như traits trong Scala, traits trong PHP có thể có một hàm khởi tạo nhưng nó phải được khai báo public (một lỗi sẽ được ném nếu là private hoặc protected).

Dù sao đi nữa, bạn hãy thận trọng khi sử dụng các hàm khởi tạo trong traits, bởi vì nó có thể dẫn đến các xung đột ngoài ý muốn trong các lớp sử dụng nó.

Tóm lược

Traits là một trong những tính năng mạnh nhất được giới thiệu trong PHP 5.4 và tôi đã thảo luận gần như tất cả các tính năng của chúng trong bài viết này.

Chúng cho phép các lập trình viên sử dụng lại các đoạn mã theo chiều ngang trên nhiều lớp mà không phải nằm trong cùng một hệ thống phân cấp thừa kế.

Thay vì có ngữ nghĩa phức tạp, chúng cung cấp cho chúng ta cơ chế đơn giản để tái sử dụng mã.

Mặc dù traits có một số nhược điểm nhưng chắc chắn chúng có thể giúp cải thiện thiết kế ứng dụng của bạn, loại bỏ sao chép mã và làm cho nó trở nên DRY (Don't Repeat Yourself) hơn.