9. Traits
Traits are structures analogous to classes. However, they cannot be instantiated. They are intended to be included in classes. Including a Trait in a class has the same effect as if the Trait’s code had been copied into the class. We put code in a Trait that is likely to be reused in multiple classes.
9.1. The Script Tree

9.2. Including a trait in a class
The script [traits-01.php] demonstrates a basic use of a trait in a class:
<?php
class Class04 {
// attribute
private $name;
// constructor
public function __construct(string $name) {
$this->name = $name;
}
// getters and setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
trait Trait04 {
// attribute
private $name;
// getters and setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
class Class05 {
// Include the Trait
use Trait04;
// constructor
public function __construct(string $name) {
$this->name = $name;
}
}
// test --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// Displaying both classes
print_r($class04);
print_r($class05);
Code comments
- lines 3–21: definition of the [Class04] class with an attribute, its get/set methods, and a constructor;
- lines 23–36: We take the code from [Class04] without its constructor and transfer it to the trait [Trait04] as-is. We do not include the constructor since a trait cannot be instantiated;
- line 23: the keyword [trait] makes [Trait04] a trait rather than a class;
- lines 38–45: we define a class [Class05] that takes the code from the trait [Trait04] (line 40) and adds a constructor (lines 43–45), identical to that of the class [Class04], to make the class instantiable;
- line 40: the keyword [use] allows a trait to be included in a class;
- lines 50–56: tests show that the classes [Class04] and [Class05] function in the same way;
Results
The results in lines 3–10 show that the classes [Class04] and [Class05] have the same content;
Conclusion
Using the [use Trait] statement in a class is equivalent to including the code from [Trait] in the class.
9.3. Using the Same Trait in Different Classes
One of the main benefits of a trait seems to be the reuse of the same code (attributes + methods) across different classes. We will see, however, that we can achieve the same goal using simple classes.
Sharing a trait between classes is illustrated by the following [trait-02.php] script:
<?php
trait Trait01 {
// attribute
private $id = 0;
// method
public function doSomething() {
print "Trait01::doSomething… ($this->id)\n";
}
}
class Class02 {
// include Trait01
use Trait01 {
// The method [Trait01::doSomething] is accessible
// in the class under the name [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
// class-specific method
public function doSomething(): void {
// id attribute
$this->id += 10;
// use of Trait01 method
$this->doSomethingInTrait();
// local output
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusion of Trait01
use Trait01;
// method local to the class
public function doSomethingElse(): void {
// id attribute
$this->id += 10;
// using a method from Trait01
$this->doSomething();
// local output
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
$class02 = new Class02();
$class03 = new Class03();
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
Comments
- Lines 3–10: a trait defining an attribute (line 5) and a method (lines 8–10).
- The trait [Trait01] is injected into two classes: [Class02] (lines 14–32) and [Class03] (lines 34–46).
- Lines 16–20: Injection of [Trait01] into [Class02];
- Line 19 resolves a conflict: [Trait01] and [Class02] both have a method named [doSomething]. There are two cases to consider:
- the method [Class02::doSomething] is called from outside the class. In this case, the method [Class02::doSomething] takes precedence over the method [Trait01::doSomething] and is the one that is called;
- the [Class02::doSomething] method is called from within the class. In this case, there is a conflict: the PHP interpreter does not know which method to call;
Line 19 renames the [Trait01::doSomething] method to [doSomethingInTrait]. Thus, within [Class02], we will use the following notation:
- [doSomethingInTrait] to call the [Trait01::doSomething] method;
- [doSomething] to call the [Class02::doSomething] method;
- Lines 25, 27: The [Class02] class uses the attribute and method of [Trait01] as if they were its own;
- lines 34–48: the class [Class03] is identical to the class [Class02]. The inclusion of [Trait01] is simpler here because there is no conflict between the methods of [Trait01] and [Class03];
Results
Note that the trait [Trait01] is not shared between classes [Class02] and [Class03]. Thus, by including [Trait01] in classes [Class02] and [Class03], the attribute [Trait01::i] becomes two distinct attributes: [Class02::i] and [Class03::i]. This is shown in lines 2 and 4 of the results. If the attribute [Trait01::i] had been shared between the classes [Class02] and [Class03], we would have had 20 on line 4 instead of 10.
The script [trait-03.php] shows that we can achieve the same result by using a class instead of a trait:
<?php
// class that replaces the trait
class Class01 {
// attribute
private $id = 0;
// setter
public function setId(int $id) {
$this->id = $id;
}
// getter
public function getId(): int {
return $this->id;
}
// method
public function doSomething(): void {
print "Class01::doSomething… ($this->id)\n";
}
}
class Class02 {
// inclusion of Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// class-specific method
public function doSomething(): void {
// change Class01 attribute
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// use Class01 method
$this->class01->doSomething();
// local display
print "Class02->doSomething\n";
}
}
class Class03 {
// include Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// class-local method
public function doSomethingElse(): void {
// change Class01 attribute
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// use Class01 method
$this->class01->doSomething();
// local output
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
// two objects
$class02 = new Class02();
$class03 = new Class03();
// will access two different instances of [Class01]
$class02->setClass01(new Class01());
$class03->setClass01(new Class01());
// verification
$class02->doSomething();
$class03->doSomethingElse();
}
// test02 ----------------
function test02(): void {
// shared instance of [Class01]
$class01 = new Class01();
// two objects
$class02 = new Class02();
$class03 = new Class03();
// will access the same instance of [Class01]
$class02->setClass01($class01);
$class03->setClass01($class01);
// verification
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
// test02
print "test02-----------------\n";
test02();
Comments
- lines 4-23: the [Class01] class replaces the [Trait01] trait. The code for [Trait01] was embedded in the code for the [Class02] and [Class03] classes. Here, that will not be the case. Instead, a reference to the code in the [Class01] class will be injected into the [Class02] and [Class03]. This means that the attributes of class [Class01] will be external to the code of [Class02] and [Class03]. Since the [id] attribute is private here (line 6), a getter (lines 14–16) and a setter (lines 9–11) must be provided. This is the first difference from a trait: you must create the code to access the class’s private attributes. We could have changed the visibility of the [id] attribute to [public], but this is never recommended. Using a setter to set an attribute’s value allows us to validate it;
- line 27: inclusion in the [Class02] code of a reference to the [Class01] class. Because this attribute is private, we need to create a setter (lines 30–32) to initialize it;
- lines 37–41: for any use of the code from [Class01], we must use the attribute [$this→class01];
- lines 48–69: the class [Class03] is a clone of the class [Class02], except that its method has a different name;
- lines 72–82: the first test. This test involves injecting two different instances of the [Class01] class into the [Class02] and [Class03] classes (lines 77 and 78);
- lines 85–97: The second test injects the same instance of the [Class01] class into the [Class02] and [Class03] classes (lines 97, 92, 93);
- lines 100–101: execution of test [test01];
- lines 103–104: execution of test [test02];
Results
Comments on the results
- lines 2–5: we get the same results as with the trait [Trait01]. We can conclude that using the trait is not essential here, but it does reduce the code because there is no need for methods to access the trait’s attributes: these are an integral part of the code in which the trait has been incorporated;
- lines 7–10: Since the same reference to [Class01] was injected into classes [Class02] and [Class03], the attribute [Class01::id] was shared between the two classes. This is why line 9 of the results displays 20 instead of 10 when using the trait. We can conclude that if traits’ attributes need to be shared between classes, then the trait is not usable and a class must be used instead;
9.4. Grouping Methods in a Trait
In the previous example, the trait contained attributes and methods. Here, we consider the case where it contains only methods. In this case, the trait resembles a factorization of methods that can then be used in different classes. Since the trait has no attributes here, we will consider the case where the methods it groups work solely on the parameters passed to them. In fact, this is not mandatory: a trait can operate on an attribute [$this→attribute1] without having that attribute itself. It is then up to the classes that use this trait to provide the attribute [$this→attribute1].
In the case where the trait has only methods that operate solely on parameters passed to them, we will show that the trait can then be replaced by a class having the same methods as the trait and declared static.
The use of the trait is illustrated by the script [trait-04.php], which reuses the trait from the previous example while removing all attributes:
<?php
trait Trait01 {
// method to share
public function doSomething() {
print "Trait01::doSomething ….\n";
}
}
class Class02 {
// include Trait01
use Trait01 {
// The method [Trait01::doSomething] is accessible
// in the class under the name [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
public function doSomething(): void {
// call the Trait01 method
$this->doSomethingInTrait();
// local output
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusion of Trait01
use Trait01;
// method local to the class
public function doSomethingElse(): void {
// call method from Trait01
$this->doSomething();
// local output
print "Class03->doSomethingElse\n";
}
}
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Comments
- lines 3-10: the trait [Trait01] no longer has any attributes;
- lines 14–18: inclusion of [Trait01] in [Class02]. The method of [Trait01] is used on line 22;
- line 31: inclusion of [Trait01] in [Class03]. The method of [Trait01] is used on line 36;
Results
In this use case, the trait [Trait01] can easily be replaced by a class. This is demonstrated by the following script [trait-05.php]:
<?php
abstract class Class01 {
// static method to be shared
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// call Class01 method
Class01::doSomething();
// local output
print "Class02->doSomething\n";
}
}
class Class03 {
// method local to the class
public function doSomethingElse(): void {
// call method from Class01
Class01::doSomething();
// local output
print "Class03->doSomethingElse\n";
}
}
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Comments
- lines 3-10: the trait [Trait01] is replaced by an abstract class [Class01] in which all methods are declared static. The class is declared abstract solely to prevent its instantiation. We would have liked to also write [final] to prevent its derivation, but PHP 7 does not accept the [final abstract] prefix for a class. It’s one or the other, but not both;
- line 16: instead of writing [$this→doSomethingInTrait], we now write [Class01::doSomething], i.e., we call the static method [doSomething] of the class [Class01];
- line 28: we repeat the same process in [Class03];
Results
We get the same result as with the trait [Trait01], which shows that using it can be avoided. We noted that a trait’s methods can operate on an attribute [$this→attribut1] even if the trait itself does not have that attribute. It is therefore up to the classes that use this trait to provide the attribute [$this→attribut1]. This is an exotic case: we might as well “pass up” the attribute [$this→attribut1]—which the classes using the trait must have—into the trait itself. That way, it will necessarily be part of the attributes of the class using the trait.
9.5. Multiple inheritance with a trait
It is common to read in PHP literature that traits enable multiple inheritance: the ability for a class to inherit from multiple classes. The C++ language supports this feature, but Java and C# do not, as they only support single inheritance. We will show that while using a trait in a derived class does allow for the implementation of something resembling multiple inheritance, this use case can also be implemented using simple classes.
The script [trait-06.php] implements a derived class and a trait:
<?php
trait Trait01 {
// attribute
private $i;
// method to share
public function doSomethingInTrait01() {
// Modify Trait01::$i
$this->i++;
// display
print "Trait01::doSomethingInTrait01… i=$this->i\n";
}
}
class Class02 {
// attribute
protected $j = 0;
// method
public function doSomethingInClass02(): void {
// Modify Class02::j
$this->j += 10;
// display
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
// derived class
class Class03 extends Class02 {
// inherits from Class02:j and Trait01::i
// includes Trait01
use Trait01;
// method
public function doSomethingInClass03(): void {
// Using the method from Trait01
$this->doSomethingInTrait01();
// Modify Trait01::i
$this->i += 100;
// Modify Class03::j (== Class02::j)
$this->j += 1000;
// display
print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
}
}
// test ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();
Comments
- lines 3-15: we return to a trait [Trait01] with an attribute and a method that manipulates it;
- lines 17–29: a class [Class02] that has nothing to do with the trait [Trait01]. It does not use it;
- line 19: we have declared the single attribute of [Class02] with [protected] visibility so that it is accessible in derived classes;
- line 32: the class [Class03] extends the class [Class02]. Furthermore, it incorporates the trait [Trait01] (line 35). Finally, it inherits the attributes and methods of [Class02] and incorporates the attributes and methods of [Trait01]. We therefore have something analogous to multiple inheritance;
Results
Just as we did in a previous example, we will show that:
- the trait can be replaced by a class;
- instead of incorporating the trait into the derived class, we incorporate a reference to an instance of the class;
The script [trait-07.php] is as follows:
<?php
class Class01 {
// attribute
protected $i;
// getter and setter
public function getI(): int {
return $this->i;
}
public function setI(int $i): void {
$this->i = $i;
}
// method
public function doSomethingInClass01(): void {
// Modify Class01::$i
$this->i++;
// display
print "Class01::doSomething in Class01… i=$this->i\n";
}
}
class Class02 {
// attribute
protected $j = 0;
// class-specific method
public function doSomethingInClass02(): void {
// Modify Class02::j
$this->j += 10;
// display
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
class Class03 extends Class02 {
// inclusion of Class01
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// method local to the class
public function doSomethingInClass03(): void {
// using a method from Class01
$this->class01->doSomethingInClass01();
// Modify Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// Modify Class03::j
$this->j += 1000;
// display
print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
}
}
// test ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();
Comments
- lines 3–24: the class [Class01] replaces the trait [Trait01]. Since the class [Class01] will be incorporated into the classes via a reference, get/set methods have been provided for the attribute [$i];
- lines 26–38: the class [Class02] remains unchanged;
- line 40: the class [Class03] extends the class [Class02];
- line 42: the class [Class01] is incorporated into [Class03] via a reference;
- lines 45–47: a setter is provided to initialize the reference to the [Class01] class;
- lines 50–61: the method [doSomethingInClass03] does the same thing as before, but with more complex code;
Results
From this example, we can conclude that, once again, the trait is not essential, but it must be acknowledged that it allows for shorter code in the derived class.
9.6. Using a trait instead of an abstract class
We often encounter the following use case: we create a fairly general interface I that can give rise to several implementations. These share common code but differ in other methods. We can implement this use case in two ways:
- We create an abstract class C that contains the code common to the derived classes. Class C implements interface I, but certain methods that must be declared in the derived classes are declared abstract in class C, and therefore class C itself is abstract. We then create classes C1 and C2 derived from C, each of which implements the undefined (abstract) methods of their parent class C in their own way;
- we create a trait T that is nearly identical to the abstract class C from the previous solution. This trait does not implement the interface I because, syntactically, it cannot. We then create classes C1 and C2 that implement the interface I and use the trait T. All that remains for these classes is to implement the methods of the interface I that are not implemented by the trait T they import;
Here is an example that illustrates how similar these two solutions are.
Application 1 implements Solution 1 described above [trait-08.php]:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
abstract class AbstractClass implements Interface1 {
// attributes
protected $attr1 = 11;
protected $attr2 = 12;
// getters and setters
public function getAttr1() {
return $this->attr1;
}
public function getAttr2() {
return $this->attr2;
}
public function setAttr1($attr1) {
$this->attr1 = $attr1;
return $this;
}
public function setAttr2($attr2) {
$this->attr2 = $attr2;
return $this;
}
// implemented method
public function doSomething(): void {
print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
}
// unimplemented method
abstract public function doSomethingElse(): void;
}
// derived class 1
class Class1 extends AbstractClass {
// attribute
private $attr3 = 13;
// getter and setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementation of doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// derived class 2
class Class2 extends AbstractClass {
// attribute
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementation of doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// external function
function useInterfaceWith(Interface1 $interface):void{
$interface->doSomething();
$interface->doSomethingElse();
}
// tests
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Comments
- lines 3–8: the interface [Interface1] has two methods;
- Lines 10–41: The abstract class [AbstractClass] implements the interface [Interface1] (line 10). It has two attributes with their getters and setters (lines 12–32), implements the [doSomething] method of the [Interface1] interface (lines 35–37), but does not implement the [doSomethingElse] method. This method is therefore declared abstract (line 40). The abstract class [AbstractClass] cannot be instantiated and, to be useful, must be derived from;
- lines 44–63: the Class1 class extends the abstract class [AbstractClass] and thus implements the interface [Interface1] (line 14). It provides a body for the [doSomethingElse] method that its parent class had not defined (lines 59–61). It also adds an attribute to those of its parent class (lines 46–56);
- lines 66–82: The Class2 class extends the abstract class [AbstractClass] and therefore implements the interface [Interface1] (line 66). It provides a body for the [doSomethingElse] method, which its parent class had not defined (lines 80–82). It also adds an attribute to those of its parent class (lines 68–77);
- lines 87–90: The function [useInterfaceWith] takes a type [Interface1] as a parameter and calls the two methods of that interface;
- lines 93–94: the [useInterfaceWith] function is called the first time with a [Class1] type and the second time with a [Class2] type. This is correct since both types implement the [Interface1] interface;
Results
AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Now we implement Solution 2 using the script [trait-09.php]. This involves replacing the abstract class with a trait:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
trait Trait1 {
// attributes
private $attr1 = 11;
private $attr2 = 12;
// getters and setters
public function getAttr1() {
return $this->attr1;
}
public function getAttr2() {
return $this->attr2;
}
public function setAttr1($attr1) {
$this->attr1 = $attr1;
return $this;
}
public function setAttr2($attr2) {
$this->attr2 = $attr2;
return $this;
}
// implemented method
public function doSomething(): void {
print "Trait::doSomething [$this->attr1,$this->attr2]\n";
}
}
// derived class 1
class Class1 implements Interface1 {
// using the trait
use Trait1;
// attribute
private $attr3 = 13;
// getter and setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementation of doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// derived class 2
class Class2 implements Interface1 {
// using the trait
use Trait1;
// attribute
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementation of doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// external function using the interface
function useInterfaceWith(Interface1 $interface): void {
$interface->doSomething();
$interface->doSomethingElse();
}
// tests
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Comments
- lines 3–8: the interface [Interface1] has not changed;
- lines 10–41: the trait [Trait1] replaces the abstract class [AbstractClass] from Solution 1. The code is the same except for the following details:
- line 10: the trait [Trait1] does not implement the interface [Interface1]. This is syntactically impossible;
- lines 12–13: the visibility attribute [protected] of the abstract class [AbstractClass]’s attributes becomes [private] here. These two attributes are intended to give derived classes direct access to the parent class’s (protected) or trait’s (private) attributes without having to go through getters and setters;
- the trait [Trait1] does not declare the abstract method [doSomethingElse];
- lines 41-62: the class [Class1] in Solution 2 is identical to the class [Class1] in Solution 1, with the following minor differences:
- line 41: the class [Class1] implements the interface [Interface1], whereas in Solution 1, it extended the abstract class [AbstractClass];
- line 43: it uses the trait [Trait1] to implement part of the interface;
- lines 65–85: the same comments apply as for [Class1];
- lines 87–95: the rest of the code remains unchanged;
Results
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
We do indeed get the same results.
9.7. Conclusion
From the previous examples, it is clear that the use cases where using a trait would provide a net benefit are not clear. In our examples, we can always do without it by replacing it with a class. However, it seems that using a trait is practical for factoring out code between different derived classes, as if that code belonged to a parent class. This is what we will do in the following example.