If you have ever done any C programming, you are perhaps familiar with macros. A macro is a predefined block of code that expands at the line indicated. In a similar manner, traits can contain blocks of code that are copied and pasted into a class at the line indicated by the PHP interpreter.
trait, and can contain properties and/or methods. You may have noticed duplication of code when examining the previous recipe featuring the CountryList and CustomerList classes. In this example, we will re-factor the two classes, and move the functionality of the list() method into a Trait. Notice that the list() method is the same in both classes.list() into a trait called ListTrait:trait ListTrait
{
public function list()
{
$list = [];
$sql = sprintf('SELECT %s, %s FROM %s',
$this->key, $this->value, $this->table);
$stmt = $this->connection->pdo->query($sql);
while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
$list[$item[$this->key]] = $item[$this->value];
}
return $list;
}
}ListTrait into a new class, CountryListUsingTrait, as shown in the following code snippet. The entire list() method can now be removed from this class:class CountryListUsingTrait implements ConnectionAwareInterface
{
use ListTrait;
protected $connection;
protected $key = 'iso3';
protected $value = 'name';
protected $table = 'iso_country_codes';
public function setConnection(Connection $connection)
{
$this->connection = $connection;
}
}Any time you have duplication of code, a potential problem arises when you need to make a change. You might find yourself having to do too many global search and replace operations, or cutting and pasting of code, often with disastrous results. Traits are a great way to avoid this maintenance nightmare.
CountryListUsingTrait class is placed into a namespace, Application\Generic, we will also need to move ListTrait into that namespace as well:namespace Application\Generic;
use PDO;
trait ListTrait
{
public function list()
{
// code as shown above
}
}setId() method differs between the Base parent class and the Test trait. The Customer class inherits from Base, but also uses Test. In this case, the method defined in the trait will override the method defined in the Base parent class:trait Test
{
public function setId($id)
{
$obj = new stdClass();
$obj->id = $id;
$this->id = $obj;
}
}
class Base
{
protected $id;
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
class Customer extends Base
{
use Test;
protected $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}Test trait defines a property $id along with the getId() methods and setId(). The trait also defines setName(), which conflicts with the same method defined in the Customer class. In this case, the directly defined setName() method from Customer will override the setName() defined in the trait:trait Test
{
protected $id;
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function setName($name)
{
$obj = new stdClass();
$obj->name = $name;
$this->name = $obj;
}
}
class Customer
{
use Test;
protected $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}insteadof keywords to resolve method name conflicts when using multiple traits. In conjunction, use the as keyword to alias method names.IdTrait and NameTrait. Both traits define a setKey() method, but express the key in different ways. The Test class uses both traits. Note the insteadof keyword, which allows us to distinguish between the conflicting methods. Thus, when setKey() is called from the Test class, the source will be drawn from NameTrait. In addition, setKey() from IdTrait will still be available, but under an alias, setKeyDate():trait IdTrait
{
protected $id;
public $key;
public function setId($id)
{
$this->id = $id;
}
public function setKey()
{
$this->key = date('YmdHis')
. sprintf('%04d', rand(0,9999));
}
}
trait NameTrait
{
protected $name;
public $key;
public function setName($name)
{
$this->name = $name;
}
public function setKey()
{
$this->key = unpack('H*', random_bytes(18))[1];
}
}
class Test
{
use IdTrait, NameTrait {
NameTrait::setKeyinsteadofIdTrait;
IdTrait::setKey as setKeyDate;
}
}From step 1, you learned that traits are used in situations where there is duplication of code. You need to gauge whether or not you could simply define a base class and extend it, or whether using a trait better serves your purposes. Traits are especially useful where the duplication of code is seen in logically unrelated classes.
To illustrate how trait methods override inherited methods, copy the block of code mentioned in step 7 into a separate file, chap_04_oop_traits_override_inherited.php. Add these lines of code:
$customer = new Customer();
$customer->setId(100);
$customer->setName('Fred');
var_dump($customer);As you can see from the output (shown next), the property $id is stored as an instance of stdClass(), which is the behavior defined in the trait:

To illustrate how directly defined class methods override trait methods, copy the block of code mentioned in step 9 into a separate file, chap_04_oop_trait_methods_do_not_override_class_methods.php. Add these lines of code:
$customer = new Customer();
$customer->setId(100);
$customer->setName('Fred');
var_dump($customer);As you can see from the following output, the $id property is stored as an integer, as defined in the Customer class, whereas the trait defines $id as an instance of stdClass:

In step 10, you learned how to resolve duplicate method name conflicts when using multiple traits. Copy the block of code shown in step 11 into a separate file, chap_04_oop_trait_multiple.php. Add the following code:
$a = new Test();
$a->setId(100);
$a->setName('Fred');
$a->setKey();
var_dump($a);
$a->setKeyDate();
var_dump($a);Notice in the following output that setKey() yields the output produced from the new PHP 7 function, random_bytes() (defined in NameTrait), whereas setKeyDate() produces a key using the date() and rand() functions (defined in IdTrait):
