Traits

So far, you have learned that extending from classes allows you to inherit code (properties and method implementations), but it has the limitation of extending only from one class each time. On the other hand, you can use interfaces to implement multiple behaviors from the same class, but you cannot inherit code in this way. To fill this gap, that is, to be able to inherit code from multiple places, you have traits.

Traits are mechanisms that allow you to reuse code, "inheriting", or rather copy-pasting code, from multiple sources at the same time. Traits, as abstract classes or interfaces, cannot be instantiated; they are just containers of functionality that can be used from other classes.

If you remember, we have some code in the Person class that manages the assignment of IDs. This code is not really part of a person, but rather part of an ID system that could be used by some other entity that has to be identified with IDs too. One way to extract this functionality from Person—and we are not saying that it is the best way to do so, but for the sake of seeing traits in action, we choose this one—is to move it to a trait.

To define a trait, do as if you were defining a class, just use the keyword trait instead of class. Define its namespace, add the use statements needed, declare its properties and implement its methods, and place everything in a file that follows the same conventions. Add the following code to the src/Utils/Unique.php file:

<?php

namespace BookstoreUtils;

trait Unique {
    private static $lastId = 0;
    protected $id;

    public function setId(int $id) {
        if (empty($id)) {
            $this->id = ++self::$lastId;
        } else {
            $this->id = $id;
            if ($id > self::$lastId) {
                self::$lastId = $id;
            }
        }
    }

    public static function getLastId(): int {
        return self::$lastId;
    }
    public function getId(): int {
        return $this->id;
    }
}

Observe that the namespace is not the same as usual, since we are storing this code in a different file. This is a matter of conventions, but you are entirely free to use the file structure that you consider better for each case. In this case, we do not think that this trait represents "business logic" like customers and books do; instead, it represents a utility for managing the assignment of IDs.

We include all the code related to IDs from Person. That includes the properties, the getters, and the code inside the constructor. As the trait cannot be instantiated, we cannot add a constructor. Instead, we added a setId method that contains the code. When constructing a new instance that uses this trait, we can invoke this setId method to set the ID based on what the user sends as an argument.

The class Person will have to change too. We have to remove all references to IDs and we will have to define somehow that the class is using the trait. To do that, we use the keyword use, like in namespaces, but inside the class. Let's see what it would look like:

<?php

namespace BookstoreDomain;

use BookstoreUtilsUnique;

class Person {
    use Unique;

    protected $firstname;
    protected $surname;
    protected $email;

    public function __construct(
        int $id,
        string $firstname,
        string $surname,
        string $email
    ) {
        $this->firstname = $firstname;
        $this->surname = $surname;
        $this->email = $email;
       $this->setId($id);
    }

    public function getFirstname(): string {
        return $this->firstname;
    }
    public function getSurname(): string {
        return $this->surname;
    }
    public function getEmail(): string {
        return $this->email;
    }
    public function setEmail(string $email) {
        $this->email = $email;
    }
}

We add the use Unique; statement to let the class know that it is using the trait. We remove everything related to IDs, even inside the constructor. We still get an ID as the first argument of the constructor, but we ask the method setId from the trait to do everything for us. Note that we refer to that method with $this, as if the method was inside the class. The updated hierarchic tree would look like the following (note that we are not adding all the methods for all the classes or interfaces that are not involved in the recent changes in order to keep the diagram as small and readable as possible):

Traits

Let's see how it works, even though it does so in the way that you probably expect. Add this code into your init.php file, include the necessary use statements, and execute it in your browser:

$basic1 = new Basic(1, "name", "surname", "email");
$basic2 = new Basic(null, "name", "surname", "email");
var_dump($basic1->getId()); // 1
var_dump($basic2->getId()); // 2

The preceding code instantiates two customers. The first of them has a specific ID, whereas the second one lets the system choose an ID for it. The result is that the second basic customer has the ID 2. That is to be expected, as both customers are basic. But what would happen if the customers are of different types?

$basic = new Basic(1, "name", "surname", "email");
$premium = new Premium(null, "name", "surname", "email");
var_dump($basic->getId()); // 1
var_dump($premium->getId()); // 2

The IDs are still the same. That is to be expected, as the trait is included in the Person class, so the static property $lastId will be shared across all the instances of the class Person, including Basic and Premium customers. If you used the trait from Basic and Premium customer instead of Person (but you should not), you would have the following result:

var_dump($basic->getId()); // 1
var_dump($premium->getId()); // 1

Each class will have its own static property. All Basic instances will share the same $lastId, different from the $lastId of Premium instances. This should make clear that the static members in traits are linked to whichever class uses them, rather than the trait itself. That could also be reflected on testing the following code which uses our original scenario where the trait is used from Person:

$basic = new Basic(1, "name", "surname", "email");
$premium = new Premium(null, "name", "surname", "email");
var_dump(Person::getLastId()); // 2
var_dump(Unique::getLastId()); // 0
var_dump(Basic::getLastId()); // 2
var_dump(Premium::getLastId()); // 2

If you have a good eye for problems, you might start thinking about some potential issues around the usage of traits. What happens if we use two traits that contain the same method? Or what happens if you use a trait that contains a method that is already implemented in that class?

Ideally, you should avoid running into these kinds of situations; they are warning lights for possible bad design. But as there will always be extraordinary cases, let's see some isolated examples on how they would behave.

The scenario where the trait and the class implement the same method is easy. The method implemented explicitly in the class is the one with more precedence, followed by the method implemented in the trait, and finally, the method inherited from the parent class. Let's see how it works. Take for example the following trait and class definitions:

<?php

trait Contract {
    public function sign() {
        echo "Signing the contract.";
    }
}

class Manager {
    use Contract;

    public function sign() {
        echo "Signing a new player.";
    }
}

Both implement the sign method, which means that we have to apply the precedence rules defined previously. The method defined in the class takes precedence over the one from the trait, so in this case, the executed method will be the one from the class:

$manager = new Manager();
$manager->sign(); // Signing a new player.

The most complicated scenario would be one where a class uses two traits with the same method. There are no rules that solve the conflict automatically, so you have to solve it explicitly. Check the following code:

<?php

trait Contract {
    public function sign() {
        echo "Signing the contract.";
    }
}

trait Communicator {
    public function sign() {
        echo "Signing to the waitress.";
    }
}

class Manager {
    use Contract, Communicator;
}

$manager = new Manager();
$manager->sign();

The preceding code throws a fatal error, as both traits implement the same method. To choose the one you want to use, you have to use the operator insteadof. To use it, state the trait name and the method that you want to use, followed by insteadof and the trait that you are rejecting for use. Optionally, use the keyword as to add an alias like we did with namespaces so that you can use both the methods:

class Manager {
    use Contract, Communicator {
        Contract::sign insteadof Communicator;
        Communicator::sign as makeASign;
    }
}

$manager = new Manager();
$manager->sign(); // Signing the contract.
$manager->makeASign(); // Signing to the waitress.

You can see how we decided to use the method of Contract instead of Communicator, but added the alias so that both methods are available. Hopefully, you can see that even the conflicts can be solved, and there are specific cases where there is nothing to do but deal with them; in general, they look like a bad sign—no pun intended.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.137.67