Cấu trúc Traits

Traits

Từ phiên bản PHP 5.4, ngôn ngữ PHP đã thêm một tính năng sử dụng lại mã nguồn tên là traits. Một trait tương tự như một class, nó có chứa một nhóm các hàm để nhúng vào bên trong class. Nhưng không thể tạo một object từ trait.

Một class có thể sử dụng lại nhiều trait khác nhau với từ khóa use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

class Base {
  public function sayHello() {
    echo 'Hello ';
  }
}

trait SayWorld {
  public function sayHello() {
    // trong trait vẫn có thể sử dụng parent, self, $this
    parent::sayHello();
    echo 'World.';
  }
}

class MyHelloWorld extends Base {
  // sử dụng trait SayWorld
  // tất cả các hàm trong trait SayWorld
  // được sử dụng trong class này 
  // mà không phải viết lại
  use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello(); // -> Hello World.

Trong trường hợp class có tên hàm trùng với tên hàm của trail thì class sử dụng hàm của class mà bỏ qua hàm của trait.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
trait SayWorld {
  public function sayHello() {
    echo 'Trait xin chào';
  }
}

class MyHelloWorld {
  use SayWorld;
  public function sayHello() {
    echo 'Class Hello xin chào';
  }
}

$o = new MyHelloWorld();
$o->sayHello(); // -> Class Hello xin chào.

Xung đột khi sử dụng trait

Khi một class sử dụng hai trait khác nhau tuy nhiên hai trail có ít nhất một hàm trùng tên thì xảy ra xung đột. Lúc này chúng ta xử lý bằng cách class chỉ chọn một hàm của một trong hai trait.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
trait A {
  public function smallTalk() {
    echo "a\n";
  }
}

trait B {
  public function smallTalk() {
    echo "b\n";
  }
}

class Talker {
  use A, B {
    B::smallTalk insteadof A; // sử dụng hàm smallTalk của B
    A::smallTalk as talk; // tạo tên một alias tới hàm của A
  }
}

$talker = new Talker();
$talker->smallTalk(); // -> b
$talker->talk(); // -> a

Thay đổi phạm vi truy cập của hàm

Phạm vi truy cập (Visibility) của hàm bao gồm public, protectedprivate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
trait HelloWorld {
  public function sayHello() {
    echo 'Hello World!';
  }
}

// thay đổi phạm vi truy cập thành protected
class MyClass1 {
  use HelloWorld { sayHello as protected; }
}

// Alias myPrivateHello thì thay đổi thành private
// hàm sayHello vẫn không thay đổi
class MyClass2 {
  use HelloWorld { sayHello as private myPrivateHello; }
}

Trait sử dụng trait khác

Một trait có thể tái sử dụng mã nguồn của nhiều trait khác.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
trait Hello {
  public function sayHello() {
    echo 'Hello ';
  }
}

trait World {
  public function sayWorld() {
    echo 'World!';
  }
}

trait HelloWorld {
  use Hello, World;
}

class MyHelloWorld {
  use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();

Hàm trừu tượng trong trait

Một trait cũng có thể định nghĩa hàm trừu tượng. Những class sử dụng lại trait này phải cài đặt hàm trừu tượng của trait.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
trait Hello {
  abstract public function getHello();
}
class MyHelloWorld {
  use Hello;
  public function getHello() {
    return 'hello world';
  }
}
$o = new MyHelloWorld();
$o->MyHelloWorld();

Hàm static trong trait

Trait cũng có thể định nghĩa hàm static và thuộc tính static.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
trait Counter {
  public function inc() {
    static $c = 0;
    $c = $c + 1;
    echo "$c\n";
  }
}

class C1 {
  use Counter;
}

class C2 {
  use Counter;
}

$o = new C1();
$o->inc(); // -> 1

$p = new C2();
$p->inc(); // -> 1

Thuộc tính trong trait

Một khi tạo thuộc tính trong trait, không cần tạo lại trong class nữa. Không giống với thừa kế class, nếu chúng ta cố tình tạo thuộc tính trong class cùng tên với thuộc tính trong trait thì sẽ báo lỗi.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
trait PropertiesTrait {
  public $x = 1;
}

class PropertiesExample {
  use PropertiesTrait;
}

$example = new PropertiesExample;
echo $example->x; // -> 1