Pages

Thursday, October 30, 2014

PHP Traits extends OOP posibilities

OOP if a wonderful concept in programming. People have different opinions about it, but I for one love it. First of all you have all of your work divided into groups (Classes), and adding other concepts like namespaces or packages, your classes becomes sub-groups in even larger groups. It makes it easy to keep track of your work in large code bases, and it reduces the chance of things colliding, especially in environments where 3'rd party code can be added dynamically (E.g. CMS modules for an example). It also allows classes to share code with one another. If you want to create a class that adds functionally to an existing class, you can extend from that class, and adopt the already existing code into your new class. Then all you have to do, is change and/or add whatever you had in mind, without having to copy/paste the content of one class into another. At the same time, adding to and/or fixing something the first class, will automatically be appended to the second one.

But OOP does have it's limitations. For one, you can only extend from one class. So if you want to adopt code from two classes, you are out of luck. You can of course implement as many interfaces as you'd like, but interfaces only defines the structure of a class, and does not provide any pre-done work. This has now changed in PHP with the introduction of Traits since version 5.4. A Trait is something like an abstract class, the difference being that you do not extend from it, instead you sort of includes it's content within your class. It can somewhat be compared to extending objects in JavaScript via the prototype property, and you can include as many Traits as you'd like.

trait Snipped1 {
    public static function printMessage() {
        echo "Prints a text!";
    }
}

trait Snipped2 {}

class Example {
    use Snipped1, Snipped2;
}

Example::printMessage();

The class above will contain all the content from both Traits. Also unlike when extending two classes, all static content in Traits, like static properties, will have their own instance pr. class that uses them. So changing the value of an static Trait property from one class, will not change the value in other class, using the same Trait.

One thing that Traits does not do, is parse their type to classes using them. Content that you include from a Trait, will be treated as if it belongs to the class itself. This means that you can't use something like instanceof to check whether or not a class uses a Trait. There are ways to check, but these ways are slow and not very handy. And there is nothing wrong with this, it is meant to work this way, because classes does not implement or inherit from Traits, they simply copy/paste their content.

Personally I did not find Traits very useful at the beginning, mostly because if they are used wrong, they can create a lot of chaos in your code. Much more than chaotic inheritance. But after playing around with them, I found that they can be handy if you include interfaces. By using an interface to define a class structure and then add a Trait with some partial code, you will have what can be considered an abstract class divided into two parts. One containing the partial code and one containing the details about how a class should look.

interface ISnipped {
    public function printMessage();
    public function secondMethod();
}

trait Snipped {
    public function printMessage() {
        echo "Prints a text!";
    }
}

class Example implements ISnipped {
    use Snipped;

    public function secondMethod() {
        // Do something
    }
}

$example = new Example();

if ($example instanceof ISnipped) {
    $example->printMessage();
}

The above example can be compared to the example below
public abstract class Snipped {
    public function printMessage() {
        echo "Prints a text!";
    }

    public abstract function secondMethod();
}

class Example extends Snipped {
    public function secondMethod() {
        // Do something
    }
}

$example = new Example();

if ($example instanceof Snipped) {
    $example->printMessage();
}

Both examples provide much the same structure, but using Traits we can adopt from more than one. All we have to do, is implement more interfaces and add more traits.

No comments:

Post a Comment