PHP Traits, the Sane Way

There is a lot of discussion about the merits and drawbacks of PHP traits in general, with quite a lot of opinion as to whether or not they are good or bad. Some people take the mentality that they are completely evil. This discussion has been going back and forth since they first came to be a thing back in PHP 5.4, with no really coherent resolution. Some people have brought up that design patterns are a better answer than traits, but on the other hand, not everyone wants or needs to write a full blown library to do things basic language constructs can already achieve easily.

Rather than presenting yet-another-meaningless-op-ed on why they are good or bad, I’m just going to outline how to use them in a sane way, what usage is effective, what is not, and what is going to cause you problems. This is a pragmatic, use-case driven approach to understanding how to use a specific tool effectively, and when it should not be used. There’s more than enough theory, and practical usage of a thing is a more meaningful than opinion. Opinion can go in the comments if anyone cares to do so.


The first rule of using traits is to stop expecting them to work the same way as a class.

In the interest of understanding how to best leverage PHP traits in a sane and maintainable way, it is first important to understand how the underlying internals of traits actually work, so you do not inadvertently make dumb mistakes that will bite you later. A quick overview of key points to keep in mind when using traits to avoid doing really stupid things:

  • Traits are not classes, and they do not adhere to the same rules as classes.
  • Language constructs that are meant to be expressed in a class are meaningless in a pure trait scope.
  • Language constructs that are meant to be expressed in a class become meaningful when the trait is attached to a class, and only then.
  • Traits are inaccessible if not attached to a class.
  • Traits do not inherit anything. If two traits declare the same property, the top level trait entirely overwrites the lower level one. If there is no top level one (eg: two different traits are used by the same thing and both declare the same thing), then you get a fatal error.
  • Traits cannot declare class constants or implement interfaces.
  • Traits SHOULD NOT make assumptions about their implementing classes UNDER ANY CIRCUMSTANCES.
  • Traits have a flat scope, and do not work hierarchically like class inheritance does.
  • Visibility does not apply in traits (public, protected, private) UNTIL they are attached to a class.
  • A class extending another class that uses a trait will interpret it’s methods exactly as if they were expressed directly in the parent class (visibility and class-based language constructs apply normally at this point).

A trait is, by and large, a snippet of code that gets pasted directly into a class that uses it. Most of the pain in regards to traits occurs when you approach them from the consideration that they are going to work exactly like a class does within the scope of the trait itself. This is not the case. There are some important distinctions to wrap your head around to understand how to best leverage them and avoid bugs.

Consider the following code:

<?php

class Foo {

    final function bar()
    {
        echo 'bar';
    }
}

class Baz extends Foo {

    final function bar()
    {
        echo 'quux';
    }
}

$baz = new \Baz();
$baz->bar();

You would expect this to break, and you would be correct in thinking so. The final keyword prevents any child class from overriding function bar, so you will instantly get an error when this loads.

In contrast, you absolutely can do this:

<?php
    
trait FooTrait {

    final function bar()
    {
        echo 'bar';
    }
}

trait BazTrait {
    
    use FooTrait;
    
    function bar()
    {
        echo 'quux';
    }
}

class Foo {
    
    use BazTrait;
    
}

$baz = new Foo();
$baz->bar();

And this is perfectly valid as far as PHP is concerned.

Not only does the final keyword not prevent override, the resulting function is also not final because the topmost trait did not declare it’s function final.

Whaaaaat…!? EVIL.

Well no actually. You’ve fallen into the trap of thinking that a trait is a class. It’s not. It’s basically copypasta.

When you use a trait within a class, the trait itself is really nothing more than a chunk of text that compiles into the class. It doesn’t have to play by the same rules as the class until it is actually compiled into one. All of the typical class language constructs you would normally consider to be standard control structures like

abstract, final, private, protected, public

mean absolutely nothing within the scope of the trait. However they do mean something in the scope of the class using the trait, which means they become meaningful when the class compiles. The trait itself is only going to use the topmost declaration of anything in it, and will happily overwrite anything from other traits it uses, private, protected, public, abstract, final, or otherwise.

The topmost declaration in whatever inherited chain of use statements linking traits absolutely adheres to all of these other constructs when a class uses the trait. However, traits do not enforce these within their own inheritance, and you need to consider this when laying them out if you want your code to work or make any sense whatsoever.

So how exactly do you make sure you don’t break stuff or accidentally overwrite things? How are you going to manage a trait being able to directly access private values of another trait it uses, call inherited methods without being able to call parent::method, etc?

You are going to do this just fine if you stop acting like a trait is a class,

and instead start treating a trait like it’s just copypasta.


How to use Traits like a Sane Person

In order to make meaningful sense of this, there’s a couple of things you can do.

The easy way is to only use traits for simple things, and not try to build complex chains of them.

If you just have some quick, one-off functionality you need over and over again, this is probably what you want to do. If this suits your needs, you’re already done and don’t need to read the rest of this article at all, as long as you never, ever do it any differently than that.

We both know that’s not going to happen in practice though. Scope creep. Fun stuff.

In the event that you want to build a pretty complex chain of logic using traits (or accidentally arrive at one through no fault of your own), there’s a different approach that works pretty well.

In this case, you need to use a consistent, standardized approach to how you treat traits to maintain sanity.

Standards you say? Who needs those?

Sane people do mostly. Also people who are not utter masochists. It’s also helpful if you are also sane enough to realize that application of standards should not go so far down the rabbit hole that you over-engineer yourself into bankruptcy before ever launching anything meaningful. There’s also that. It’s about balance.

Allow me to elaborate on this a bit.

The following principles, in a nutshell, should be used explicitly if you are building complex inherited trait logic:


The TLDR; version

  • Name your class variables and private methods verbosely, preferably with a prefix that is the same name as the trait.
  • Write your trait logic under the assumption that all inherited methods regardless of visibility exist in the exact same scope, but are just not presently visible in your editor.
  • Do not name anything the same as any prior name unless you are going to entirely replace the functionality with no backwards reference to it.
  • Keep traits scoped in small related clusters, and don’t go way overboard with trait “inheritance”.
  • Create interfaces that enforce the public methods, and put a reference to the corresponding interface in your doc-block comment for the trait. The trait itself cannot implement the interface, but a class should be able to satisfy all of the requirements of the interface cleanly by implementing it and using the corresponding trait with no additional effort.
  • Do not use class constants in traits locally. If you need class constants, put them in an interface to avoid tight coupling with classes, and write the fully qualified namespace to the interface to access them. Do not assume they are local or try to enforce that they are, or you are going to have big problems. Class constant logic, by and large, belongs in classes, not traits.
  • Use at least two class levels when you use the trait to enforce visibility. Use the trait on an abstract class, and then extend the abstract class with a concrete class. The abstract class can still override any property from the trait, because at the time that it is used there, it’s just copypasta. However, once the abstract class has compiled, visibility is enforced normally for the concrete class, which then treats all of the standard language constructs exactly as if they were inherited from the abstract directly.
  • Trait initialization steps should be done incrementally, and initialization methods need to be named in a way that is scoped to the specific trait that they apply to. If one trait provides an initialization method, a trait that uses that trait needs to provide a different initialization method, and explicitly call the prior one from that method. They should not be named the same. Don’t be tempted to do use … as … to get around this, because you will write really confusing spaghetti code that you can’t effectively debug easily.

Got all that? Cool we’re done here.


The in-depth version

Still here after reading the TLDR? Cool. That means you either are interested in understanding how to do this the right way and are a sane, rational person, or you haven’t got any kind of clue what I was talking about in the TLDR version and need specific examples for reference. Fear not, they’re on their way imminently.

Name your class variables and private methods verbosely.

On the first point, name things verbosely. Always. I know I just said that, but I’m saying it again redundantly on purpose. Read it five more times and then continue.

Spending five extra seconds now to type ten extra characters is way less effort than spending several hours later, or possibly days or weeks trying to make sense of something you wrote months ago that is broken and not easy to follow.

This is good:

<?php

trait FooTrait {

private $foo_property;

}

This is bad:

<?php

trait FooTrait {

private $property;

}

There is a pretty subtle difference here, and it doesn’t really seem like it’s a big deal. However, it’s pretty likely that I’m going to have some other trait or class down the line that uses $property, and much more likely the further in my abstraction gets. It is significantly less likely that I am going to need $foo_property, unless I am writing another trait or class called Foo. In the event that I am, it is very likely that construct is explicitly intended to override functionality from this trait, which is pretty ok.


Write your trait logic under the assumption that all inherited methods exist in the exact same scope.

This is the part that is going to make most object oriented developers want to shoot themselves. Didn’t we all stop doing that silliness when we decided to use object oriented programming? Aren’t all other styles of programming hopelessly outdated? Well, no actually. The point is to do what works, not what you think ought to work.

I’d like to briefly remind you that we flew a satellite out of the entire solar system that ran on less complex code than the calculator you had in high school science class before smartphones were a thing. Do you think that is object oriented and using the most modern technology? Lol no, those things have a tendency of crashing. Who exactly is going to manually reboot a satellite that is out past Jupiter? I’d also like to remind you that most of the technology that keeps your financial well being and all of your official identity records intact was written on punch cards in the 70’s. It works. /tangent

Now that you are firmly convinced that I am the ancient relic programmer who hates all this newfangled hibbity bibbity, I’mma go the other way on this for a second. Traits. Yep, they’re definitely object oriented things, but chaining them together in some semblance of sanity requires that you temporarily pretend they aren’t. It’s ok to value both object oriented principles and other principles, provided you don’t make one an excuse to ignore what works best for the other.

Balance, padowan. That’s what we’re after here.

Enough snark, on to the meat and potatoes. When your compiler loads this:

<?php

trait Foo {

    private function A() {}

    private function B() {}

    private function C() {}
}

And this:

<?php
trait Bar {
    use Foo;

    private function D() {}

    private function E() {}

    private function F() {}
}

And this:


<?php
trait Baz {
    use Bar;

    private function G() {}

    private function H() {}

    private function I() {}
}

And this:

<?php
trait Quux {
    use Baz;

   private function J() {}

   private function K() {}

   private function L() {}
}

And also this:

<?php

class Foobar {
    use Quux;

    public function M() {}

    public function N() {}

    public function O() {}
}

It is functionally no different as far as the compiler is concerned than if you only loaded this:

<?php

class Foobar {
    
    private function A() {}

    private function B() {}

    private function C() {}

    private function D() {}

    private function E() {}

    private function F() {}

    private function G() {}

    private function H() {}

    private function I() {}

    private function J() {}

    private function K() {}

    private function L() {}

    public function M() {}

    public function N() {}

    public function O() {}
}

 

It looks different to you, but in practice, it is the exact same thing.

All of these methods can also directly access each other exactly as if I only wrote the last class and put literally all of them in there. As far as the compiler, and the Foobar class after it is compiled are concerned, that is exactly what I did. The traits have pretty much zero relevance other than the fact that calling class_uses is going to give me a list of them.

Obviously, this is a lot of text. It would be quite a lot more if I didn’t write every single method as an empty one-liner. If you’re one of those clever fellows, you’re probably considering jumping down to the comments right now to warn me of the dangers of god objects. Right there with ya. Which brings me to the next point…


Keep traits scoped in small related clusters.

Traits are not an excuse to not use classes. They are patches for classes that work as an alias to common methods that are both frequently needed and not really scoped in any sensical way that warrants a dedicated object. This is just a means of expressing very common functionality in an object oriented way, without having to resort to procedural code (gasp! the horror!), or helpers, which are very commonly regarded as a code smell, despite that pretty much everybody uses them anyways.

Traits solve that issue if you use them correctly. If you don’t use them correctly, you are in for a world of pain.

To make this simple, you basically follow the exact same best practices you would for classes, and use SOLID instead of STUPID principles. You don’t have to go way over the top and make some AbstractFooBarFizzBuzzStringableArrayStringFactoryFactory or anything like that, but you should have a pretty pragmatic, well scoped layout that can easily be indexed, and uses sensical namespacing that gives you a pretty good gist of what your traits just by looking at the name. Not really gonna beat this one to death, it’s already been gone over a billion times elsewhere. Moving on then…


Create interfaces that enforce the public methods, and put a reference to the corresponding interface in your doc-block comment for the trait.

The lack of ability to directly attach an interface to a trait is often bemoaned as a fundamental flaw…

…just like people commonly complain about how your clipboard doesn’t format text like Microsoft Word does. Wait, they don’t do that, because they know that copypasta is not a freakin’ word processor, just like a trait is not a class. Are we seeing a theme here? I hope the point has become evident by this far in.

Thankfully, actual classes have the option to implement both traits AND interfaces, which means that we can pretty easily lay out a simple means of accomplishing this, by just applying the basic PSR-4 approach to namespacing to create related constructs pretty easily (if you aren’t already doing this, you’re already causing yourself way too much pain. Fix that promptly).

Lets say for example I have the following directory structure:

  • classes/foo/
  • interfaces/foo/
  • traits/foo/

And these map to the following namespaces:

  • \classes\foo;
  • \interfaces\foo;
  • \traits\foo;

And then I have the following actual object oriented constructs at my disposal:

This trait:

<?php 

namespace traits\foo;
 
/**
 * Provides methods to satisfy FooInterface
 * @see \interfaces\foo\FooInterface
 */ 
trait FooTrait 
{ 
    public function bar() {}

    public function baz() {}

    public function quux() {}
}

And this interface:

<?php

namespace interfaces\foo;

/**
 * These methods are required to make a functional Foo.
 * You may satisfy these criteria by using the following trait:
 * @see \traits\foo\FooTrait
 */
interface FooInterface {

    public function bar();

    public function baz();

    public function quux();
}

And this class:

<?php
namespace traits\foo;

class Foo implements \interfaces\foo\FooInterface {

    use \traits\foo\FooTrait;
}

It’s probably not all that difficult to determine that they are related. In fact, you can probably tell just by looking at this that this is a super convenient way to satisfy an interface.

As an added bonus, running this through ApiGen or PhpDocumentor will spit out a wiki that directly indicates this correlation, in case for whatever reason the fact that their namespaces and doc block comments don’t give it away and you have to go RTFM.


Do not use class constants in traits locally.

I have seen no end of people trying to work around a way to hack this in. Don’t do it. Traits are not classes. if your trait has anything like:

<?php
namespace traits\foo;

trait FooTrait {

    public function bar()
    {

        if ( !defined( get_called_class() . '::THING_THAT_HAS_NO_BUSINESS_IN_A_TRAIT_EVER' ) )
        {
            throw new \Exception('You gotta put the constant in the thing!');
        }
        echo self::THING_THAT_HAS_NO_BUSINESS_IN_A_TRAIT_EVER;
    }
}

 

Then you are doing it wrong. Instead, do something like this:

//file 1
<?php
namespace interfaces\foo

interface FooInterface {
    const THE_THING = 'put things in their right home and don\'t reinvent the wheel.';
}

//file 2
<?php
namespace traits\foo;

trait FooTrait {
    public function bar()
    {
        echo \interfaces\foo\FooInterface::THE_THING;
    }
}

See? That’s probably way easier than what you were trying to do. This is also not tight coupling, because interfaces are pretty much the standard way of insuring that coupling is not tight.

A couple of caveats with this that you want to be aware of:

  • Class constants can be overridden.
  • Interface constants cannot be overridden.
  • If you have to override constants, then why are you using constants? Just because you can doesn’t mean you should. There are some merits to doing so in some rare cases, but if that is the way you want to lay it out, then either don’t rely on overriding constants (just use variables), or alternately don’t use constants in traits (again, just use variables). Variables vary. That’s kind of the point of them.

Use at least two class levels when you use the trait to enforce visibility.

This is how you insure that the desired visibility is not affected in the scope you want to work with it.

As stated before, the class directly implementing the trait treats it as copypasta, as do traits using other traits. Once that class compiles, the class language constructs are fixed and do apply further.


Trait initialization steps should be done incrementally.

So you’re with me so far, and you’ve gone out and written five nested traits, following all of the other steps, and you just can’t seem to get them all to play nice in the sandbox. Maybe you’re trying to implement a trait based expression of PSR-7 or something, and you know that

\Psr\Http\Message\ServerRequestInterface

extends \Psr\Http\Message\RequestInterface,

which extends \Psr\Http\Message\MessageInterface,

which extends \Psr\Http\Message\StreamInterface.

And you also don’t want to write a god object, so you’re trying to bust them up similarly to the interface inheritance and maximize code reuse like a good SOLID programmer. Good on you. And yet, the freakin’ initialization is ridiculous when it’s four levels deep with traits, because you can’t call anything like parent::initialize() from in a trait to reference another trait.

You might be tempted to do something like this:

<?php
trait Foo {
    function initialize() {
        //do the Foo thing
    }
}

trait Bar {
    use Foo {
        initialize as protected fooInitialize;
    }

    function initialize() {
        $this->fooInitialize();
        //do the Bar thing
    }
}

Don’t do it. It’s a trap.

Instead, you want something like this:

<?php
trait Foo {
    function initialize() {
        $this->fooInitialize();
    }
    
    protected function fooInitialize() {
        //do the Foo thing
    }
}

trait Bar {
    use Foo;

    function initialize() {
        $this->barInitialize();
    }
    
    function barInitialize() {
        $this->fooInitialize();
        //do the Bar thing
}

Why would you do that? Well, if you consider that in the first case, if Foo::initialize() breaks, I’m going to be looking for fooInitialize which isn’t even a real method in the first case.

This means that I’m wasting time poking around in Bar trying to figure out what is broken, whereas in the second case all of my methods are actual methods rather than aliases of methods.

This means that I immediately know that the broken thing is Foo::fooInitialize() and I’m not wasting redundant time backtracing and reading through the entirety of Bar to figure out where to go from there. Instead, I’m going straight to what broke and fixing it.

You may personally be a fan of Spaghetti. I’ve heard that Mom’s sphagetti in particular is the bees knees for most people. However, I am not particularly a fan of sphaghetti, and even more particularly not a fan of spaghetti code. In fact, we all pretty much collectively decided to ditch procedural programming entirely for the sake of object oriented programming specifically to get away from it.

It is better to write code that works well than code that is pretty, but impossible to follow when you run it. Really pretty spaghetti is still spaghetti.

You may be tempted to call this approach functional decomposition, and you are not entirely off base for thinking so. You’ve probably also heard that functional decomposition is an antipattern, which is correct if you are considering things in the scope of object oriented classes working in relation to one another, or a class inheriting from another class. The only problem with this line of thinking is that traits are not classes, they are copypasta. As mentioned above, they have no separation of scope until they are applied to a class. This is also why it is particularly important that you have a coherent public api, and avoid inadvertently creating god objects out of classes by overloading them with too many traits instead of keeping traits together in related clusters, and not using traits as a substitute for objects. Look at that! It’s all kinda making sense when you put them together.


What did we learn today?

Well, we learned that there’s a sane way to use traits to avoid pain. Hopefully, if you took anything from this, it would be that traits are just one tool out of many in a toolbox that solve a specific problem if used well, and cause a lot of issues if used poorly. It is important not to pound nails with a screwdriver, and it is also important to understand that not having the specific hammer you want is not an excuse not to build the house you were hired to build.

We also learned, at least hopefully implicitly if not explicitly, that there is no such thing as a magic bullet, and you should probably use better judgment about what fits for your own process instead of swinging with hype, and not to assume that something is fundamentally bad just because it’s not a thing you choose to or know how to use effectively. Who am I kidding? Nobody ever learns that.

Lemme know what you think about it in the comments if you care to.

2 thoughts on “PHP Traits, the Sane Way

  1. Just a slight correction. I get that you linked my older post as an example of “someone who considers traits to be completely evil”. I hinted at a follow up (in the comments). I’ve stopped writing PHP all together, so it’s unlikely that follow-up will ever get written.
    However, I do believe there is a sane way to use traits, and you mention the key thing to understand about them: it’s compiler-assisted copy-paste, nothing more, nothing less.

    However, you have to understand that PHP has a long history of being abused by people who don’t fully comprehend what they’re doing. I wrote that rant after seeing the official PHP documentation on traits. Back then, abstract, final, and all that were all mentioned as being “features” of traits.

    I mainly wanted to point out the inherent risks involved when you give something as weak (contract-wise) to people who are prone to over-using whatever you give them. Of course, traits can be used properly, but changing visibility (from public to private, for example, as is still documented) is pure evil.
    People will write code using class_uses to get the traits, and then assume the public methods in a given trait to be callable.

    This is all to say: I don’t think they’re pure evil, I think their implementation is sloppy, and I think they will be used in evil ways. Much like knives: they’re great in the hand of an experienced chef, not so much in the hands of someone who’s looking to mug you.

    Like

    • I can agree with that. In general, I think the problem breaks down into two main issues. First, you have programmers that like to write libraries and meta code, who care about unit tests and standards, and then you have programmers who just want to get a result accomplished and want to do everything “quick and dirty”. The second thing is usually a business constraint, where otherwise well-practiced developers get rushed by a project manager or pushy client to just ship it, and cut corners out of necessity. If that goes on for an extended period of time, you wind up with a completely unmaintainable mess cobbled together by a hundred devs who didn’t have time to comment things and none of whom are reachable for explanation. There’s also abuses that are common that use the language mostly as intended, like using regular base Exception everywhere and thereby preventing InvalidArgumentException/RuntimeException to be used appropriately because they all extend from the base Exception and trying to use a try/catch control flow becomes entirely useless when that cascades over time for example.

      There’s abuses that can happen in pretty much any language though. PHP wasn’t even intended to be a language originally and it has carried a lot of the mentality of being a “quick and dirty” utility forward over the years, but is slowly shaking that off in favor of better standards as time goes on. Javascript devs abuse prototype, python devs override things in the wrong places, bash devs override functions on the fly and break later library calls, ORMs overuse vertical key/value stores in the data layer and make manual database administration basically impossible, etc. It usually comes down to the same two problems. Sometimes its inexperience, but much more often its just rushed code getting prematurely pushed to a release date before its ready, or a healthy dose of “ain’t nobody got time for that”.

      If you make hammers, theres a point where you just have to accept that some people will use them to pound nails, and other people will use them to pound skulls. You can hope for the best that the vast majority of your customers will use them for carpentry, but at the end of the day, its really not on you if they get misused. Pretty much all tools eventually will if there is leeway to do so. As a programmer, the best you can really do is make it much harder to do wrong than to do right, like in the PHP case with weak contracts embedding your trait logic in a broader architecture that demands verification by interface to work correctly.

      Thanks for dropping your two cents though, it’s always nice to hear from people who actually care about coding responsibly.

      Like

Leave a comment