Trait in PHP
PHP

Trait – Mixin in PHP? – PART 2

Dưới đây là phần 2 (và cũng là phần cuối) của series “Trait – Mixin trong PHP”. Ở phần này chúng ta sẽ đi sâu vào cách xử lý xung đột, mở rộng khả năng của Trait và các ví dụ thực tiễn.
Mời các bạn xem lại phần 1 tại đây: https://dangnh.cf/2018/06/01/trait-mixin-in-php/

1. Giải quyết xung đột giữa các Trait

Khi một class use nhiều trait và có phương thức trùng tên, PHP sẽ báo lỗi. Để tránh, ta dùng hai từ khóa:

1.1. insteadof

Chọn phương thức của trait này thay vì của trait kia:

trait A {
    public function foo() { echo "A::foo\n"; }
}
trait B {
    public function foo() { echo "B::foo\n"; }
}

class MyClass {
    use A, B {
        A::foo insteadof B; // ưu tiên A::foo
    }
}

(new MyClass)->foo(); // In ra "A::foo"

1.2. as

Đổi tên (alias) cho phương thức để vẫn có thể gọi cả hai:

class MyClass {
    use A, B {
        A::foo insteadof B;
        B::foo as bar;   // gọi B::foo qua tên mới bar()
    }
}

$obj = new MyClass;
$obj->foo(); // A::foo
$obj->bar(); // B::foo

2. Các tính năng nâng cao của Trait

2.1. Abstract method trong Trait

Bạn có thể khai báo phương thức abstract trong trait, buộc class kế thừa phải triển khai:

trait LoggerTrait {
    abstract protected function getLogFile(): string;

    public function log($msg) {
        file_put_contents($this->getLogFile(), $msg."\n", FILE_APPEND);
    }
}

class App {
    use LoggerTrait;

    protected function getLogFile(): string {
        return __DIR__ . '/app.log';
    }
}

2.2. Trait với thuộc tính static và const

Trait cho phép định nghĩa static property và const:

trait Configurable {
    protected static array $options = [];

    public static function setOption($k, $v) {
        static::$options[$k] = $v;
    }
    public static function getOption($k) {
        return static::$options[$k] ?? null;
    }
}

2.3. Thuộc tính và phương thức private/protected

Trait có thể khai báo method private hay protected, chỉ class chứa trait biết dùng:

trait SecretTrait {
    private function secret() {
        return "Chỉ có tôi mới biết";
    }
    public function reveal() {
        return $this->secret();
    }
}

2.4. Trait thừa kế Trait

Trait cũng có thể use trait khác:

trait A { /* ... */ }
trait B { use A; /* ... */ }
class C {
    use B; // sẽ bao gồm cả A
}

3. Ví dụ thực tế

3.1. Trait “SoftDelete” cho Model

trait SoftDelete {
    protected bool $deleted = false;

    public function delete() {
        $this->deleted = true;
    }
    public function restore() {
        $this->deleted = false;
    }
    public function isDeleted(): bool {
        return $this->deleted;
    }
}

class User {
    use SoftDelete;
    // ...
}

Giờ chỉ cần use SoftDelete, mọi class đều có tính năng xóa mềm.

3.2. Trait “Timestampable”

Tự động quản lý created_at/updated_at:

trait Timestampable {
    protected \DateTime $createdAt;
    protected \DateTime $updatedAt;

    public function touch() {
        $this->updatedAt = new \DateTime;
    }
    public function initTimestamps() {
        $this->createdAt = $this->updatedAt = new \DateTime;
    }
}

class Post {
    use Timestampable;

    public function __construct() {
        $this->initTimestamps();
    }
}

4. Best Practices khi dùng Trait

  • Giữ trait nhỏ, chuyên biệt: Mỗi trait chỉ đảm nhận một nhiệm vụ duy nhất.

  • Đặt tên rõ ràng: Ví dụ Loggable, Notifiable, SoftDeleteable

  • Tránh trait quá “nặng”: Nếu trait chứa quá nhiều logic, cân nhắc tách thành class helper hoặc service.

  • Tài liệu đầy đủ: Khai báo @method hay @property trong DocBlock nếu trait “inject” thêm method hay property cho IDE.

5. Kết luận

Trait là công cụ mạnh mẽ giúp tái sử dụng code và “mixin” behavior vào nhiều class khác nhau mà không cần đa kế thừa. Tuy nhiên, lạm dụng quá nhiều trait có thể làm code rối và khó theo dõi. Hãy thiết kế trait gọn, rõ ràng, và áp dụng cho những hàm thực sự chung nhất.

Cảm ơn bạn đã theo dõi series về Trait trong PHP. Chúc các bạn áp dụng hiệu quả và happy coding!

 


Tài liệu liên quan:

Reply