Viblo CTF
+2

Traits và cách sử dụng Traits trong PHP

Lời mở đầu

Xin chào các bạn 😄 trong bài viết lần này chúng ta cùng nhau tìm hiểu về Traits trong PHP, sử dụng Traits như thế nào và các dụng của Traits nha!

1. Khái niệm về Traits

Traits là một cơ chế cho việc tái sử dụng mã nguồn (source code của các bạn) trong những ngôn ngữ hướng đối tượng chỉ có đơn kế thừa (single inheritance languages) như là PHP hoặc Ruby.

Khi học về OOP, chắc hẳn các bạn cũng biết kế thừa (extends) từ nhiều class trong có lợi ích rất lớn. Chúng ta có thể tái sử dụng lại các phương thức ở các class khác nhau để tránh việc lặp code. Ví dụ mình có một ClassA với phương thức methodA(), một ClassB với phương thức methodB(), bây giờ yêu cầu là thiết kế một ClassC có thể sử dụng cả 2 methodA() lẫn methodB() ở 2 class trên. Đối với ngôn ngữ đa kế thừa như PHP, chúng ta có một giải pháp cứng đầu đó là: cho ClassB kế thừa ClassA (lúc này ClassB có thể sửa dụng các phương thức của ClassA), sau đó cho ClassC kế thừa ClassB (ClassC mới vừa có thể dùng các phương thức của ClassA lẫn ClassB), giống kiểu như bắc cầu vậy 😄. Tuy nhiên ví dụ của mình chỉ là một ví dụ đơn giản, các bạn biết đó, đời không như mơ mà 😄 thực tế nếu áp dụng cách trên thì đó sẽ trở nên rất tệ (ví dụ như sử dụng nhiều phương thức ở nhiều class khác nhau hơn).

Từ PHP 5.4 trở đi, PHP đã hỗ trợ Traits để khắc phục những giới hạn của đơn kế thừa. (yeah). Traits có thể được hiểu như là một class, giúp tập hợp một nhóm phương thức mà chúng ta muốn sử dụng trong một class khác., nó không thể được khởi tạo, thay vào đó chúng ta sử dụng từ khóa use. Đây là ví dụ về cách khai báo một Trait trong PHP:

<?php

trait TraitName
{
    // code cua ban
}

Trong đó:

  • TraitName: là tên của trait mà bạn muốn đặt.

Tiếp theo là để sử dụng Trait trong một class, ta làm như sau:

class ClassName
{
    use TraitName; // goi den trait

    // code cua ban :D 
}

Trong đó:

  • ClassName: là tên của class mà bạn sử dụng traits.
  • TraitName: là tên của trait mà bạn muốn dùng.

2. Các đặc điểm của Traits

  • Traits được sử dụng để gom lại các phương thức và thuộc tính mà chúng ta muốn sử dụng lại nhiều lần.
  • Traits giống như một Abstract Class (đều không thể khởi tạo được), nhưng cũng không hoàn toàn là giống nhau.
  • Các phương thức (methods) trong Traits có thể override lại trong class sử dụng nó.

3. Ưu điểm của Traits

  • Giảm việc lặp code (đáp ứng được nguyên tắc DRY, các bạn có thể xem thêm tại đây) 😄 .
  • Khắc phục được các điểm yếu của đơn kế thừa.
  • Tránh việc kế thừa nhiều tầng, gây phức tạp cho việc đọc code, khó bảo trì.
  • Định nghĩa ngắn gọn, có thể sử dụng dễ dàng ở những nơi cần thiết (giống như việc copy nguyên đoạn code viết Trait vào class bạn cần vậy).

4. Nhược điểm của Traits

  • Sử dụng Traits gây ra khó khăn trong việc đọc các phương thức từ một class có sử dụng Traits.

5. Một số ví dụ về Traits

Dưới đây là một ví dụ về cách sử dụng Traits trong PHP, mời các bạn cùng theo dõi nha:

<?php

trait TraitA
{
    public function methodA() 
    {
        echo 'Day la methodA cua TraitA';
    }
}

trait TraitB
{
    public function methodB() 
    {
        echo 'Day la methodB cua TraitB';
    }
}
  • Đầu tiên mình khai báo 2 file traits đó là traitA.php để khai báo TraitA, traitB.php để khai báo TraitB, trong 2 traits này mình chỉ khai báo 2 methods đơn giản để làm ví dụ 😄.
<?php
// nhúng 2 file chứa traits
include 'traitA.php';
include 'traitB.php';

class ClassA
{
    use TraitA; // gọi trait của TraitA
    use TraitB; // gọi trait của TraitB
}

class ClassB
{
    use TraitA; // gọi trait của TraitA
    use TraitB; // gọi trait của TraitB
}

$a = new ClassA; // khởi tạo đối tượng ClassA
$a->methodA(); // 'Day la methodA cua TraitA'
echo '<br>';
$a->methodB(); // 'Day la methodB cuar TraitB'
echo '<br>';
$b = new ClassB; // khởi tạo đối tượng ClassB
$b->methodA(); // 'Day la methodA cua TraitA'
echo '<br>';
$b->methodB(); // 'Day la methodB cua TraitB'
  • Tiếp theo mình khai báo 2 class ClassA, ClassB, cả 2 class này đều use 2 traits TraitA và TraitB. kết quả trả về chính là kết quả mà 2 methodA() và methodB() thực hiện.

5.1 Traits lồng nhau

Giống như cấu trúc điều khiển hay vòng lặp thì bạn cũng có thể sử dụng traits lồng nhau, cách sử dụng như sau:

trait TraitA
{
        public function methodA()
        {
            echo 'Day la methodA cua TraitA';
        }
}

trait TraitB
{
    use TraitA; // gọi TraitA giống như trong một class vậy
}

5.2 Ưu tiên phương thức trong traits

Giả sử các bạn có 2 TraitA và TraitB, và trong cả 2 traits này đều có chung một method tên là methodC():

<?php

trait TraitA
{
    public function methodA() 
    {
        echo 'Day la methodA cua TraitA';
    }

    public function methodC()
    {
        echo 'Day la methodC cua TraitA';
    }
}
<?php

trait TraitB
{
    public function methodB() 
    {
        echo 'Day la methodB cua TraitB';
    }

    public function methodC() 
    {
        echo 'Day la methodC cua TraitB';
    }
}
  • Tiếp theo là định nghĩa ClassA use hai Traits trên
<?php

include 'traitA.php';
include 'traitB.php';

class ClassA
{
    use TraitA, TraitB;
}

$a = new ClassA;
$a->methodA(); // 'Day la methodA cua TraitA'
echo '<br>';
$a->methodB(); // 'Day la methodB cuar TraitB'
echo '<br>';
$a->methodC(); // Không có kết quả trả về.

Để xử lý tình huống trên, các bạn có thể làm như sau:

  • Sử dụng insteadof để xét độ ưu tiên cho phương thức bạn muốn sử dụng:
<?php

include 'traitA.php';
include 'traitB.php';

class ClassA
{
    use TraitA, TraitB {
        TraitB::methodC insteadOf TraitA;
    }
}
  • Hoặc có thể override lại methodC() ở trong ClassA:
<?php

include 'traitA.php';
include 'traitB.php';

class ClassA
{
    use TraitA, TraitB;

    public function methodC()
    {
        return $this->methodB();
    }   
}

$a = new ClassA;
$a->methodA(); // 'Day la methodA cua TraitA'
echo '<br>';
$a->methodB(); // 'Day la methodB cuar TraitB'
echo '<br>';
$a->methodC(); // 'Day la methodB cuar TraitB'

6. Sự khác biệt giữa Traits và Abstract Class

Traits khác với Abstract Class vì nó không dựa trên sự kế thừa. Việc sử dụng Abstract Class khiến chúng ta phải xây dựng một mô hình phức tạp. Còn việc sử dụng Trait giống như là bạn copy lại những đoạn code có cùng một cách xử lý vào trong những class bạn cần dùng vậy.

7. Sự khác biệt giữa Traits và Interface

Có thể nói Traits và Interface khá giống nhau về tính chất sử dụng. Cả hai đều không thể sử dụng nếu không có một class được implements cụ thể. Tuy nhiên không phải bao giờ bạn cũng có thời gian để implement lại tất cả method có trong interface nếu chúng xử lý giống nhau phải không? Khi có nhiều chức năng xử lý giống nhau, và được ứng dụng trong nhiều class khác nhau (có thể khác nhau về bản chất) thì chúng ta nên sử dụng traits để giúp code ngắn gọn (một phần vì PHP không hỗ trợ đa kế thừa).

Tổng kết

Việc sử dụng Traits mang lại cho chúng ta giải pháp cho việc tránh kế thừa phức tạp, tạo ra nhiều tầng lớp lồng lên nhau trong những ngôn ngữ đa kế thừa (PHP). Tuy nhiên việc lạm dụng quá cũng không phải là một quyết định đúng đắn, điều này có thể khiển class của bạn vi phạm nguyên tắc SRP (1 trong các Design Principles). Mong rằng thông qua bài chia sẻ này có thể giúp ích ít nhiều cho các bạn trong quá trình sử dụng Traits và học PHP. Hẹn gặp lại các bạn trong các bài viết sau 😄

Tài liệu tham khảo


All Rights Reserved