PHP: Traits, And When To Use Them
About using PHP traits to re-use code and follow the DRY principle.
By. Jacob
Edited: 2020-05-30 00:38
A PHP trait allows you to store a collection of properties methods for easy re-use in the classes that might need them. The "trait" term can be taken quite literally, as it is used to define a 'trait' that is shared among multiple classes.
This does not mean that everything is a good fit inside a trait. I have been thinking about this subject for several months, and I am still not completely decided as to how they are best used. But, it does seem that, at least, one fitting use is for defining magic methods.
For example, it may be you are defining magic methods such as __get or __set for multiple classes; instead of repeating the same code in all the classes that needs it, you can create a trait and load it in each class. This is good DRY (Don't Repeat Yourself)
Below is a simple example trait I created to prevent adding properties to class instances:
namespace doorkeeper\lib\class_traits;
trait no_set {
public function __set($name, $value) {
throw new \Exception("Adding new properties is not allowed on " . __CLASS__);
}
}
To use the trait in other classes:
class some_class {
public $message = 'hallo';
public function show_message() {
echo $this->$message;exit();
}
use \doorkeeper\lib\class_traits\no_set;
}
Attempting to create a new property via $some_class->some_new_property = 'value';, after instantiating the class, will now trigger an exception; but you can still change the value of existing properties. I.e.: $some_class->message = 'goodbye';
Inheritance vs Traits
I personally tend to use Dependency Injection to share code between classes and avoid using inheritance and traits; In fact, extending classes is probably best reserved for when you work on other peoples code, and traits seem to have few good use cases.
If you do not own the code, you can consider use extends, at least for a few of reasons:
- It is easier to extend a class than it is to, possibly, modify the entire implementation.
- It keeps your own code clearly separated from the code in class you are extending.
- If the class is updated, your code will rarely stop working, and if it does, fixing it will be easier.
- It rarely makes sense to extend your own code when you could just work on it directly.
You should not use traits to change other peoples code, as it will require making modifications to the original class—use extends to "extend" their code instead.
Do not use traits as a general-purpose way of copy-pasting code that is often re-used. Consider if the code fits better in a dedicated class that you can re-use and independently instantiate—then provide this through DI as needed.
Dependency Injection vs Traits
I do not really think there is a competition. Whenever I have considered using traits for something, I have always ended up refactoring it to DI later. My main reason is that there is often a huge benefit in being able to independently instantiate code.
All those use declarations is also a refactoring problem waiting to happen. If a trait is moved, you will need to update all classes that use it. With DI, you just pass the object from a central location, such as your composition root—moving the original class file will not break your application elsewhere.
A lot of people do not like DI because they need to pass on long lists of dependencies; as an alternative you could create a container-object to keep commonly used objects. If your project grows large enough, then you may find that some objects are practically used "globally" throughout your project; keeping those in a container is a decent approach that still permit independent instantiation when the code is needed elsewhere.
Another option is to create factory classes to create your objects. Inside these, you may create dependencies directly using the new keyword. As said, generally it is not recommended to create dependencies inside the classes that depend on them, since if something changes, you might need to update all of your classes that uses those dependencies—when done inside a factory class it is a different matter, however.
Note. "Factory" is just a term used to describe the function of a ordinary PHP class. For example, there is no "factory" keyword in PHP. A factory is simply an object used to create other objects.
Traits might still have some uses, I have just not really found them yet. There is only one situation where I am using traits at the moment, and that is when changing behavior of certain magic methods implemented by PHP. I think it is good practice to prevent users from modifying our instantiated objects, and for this purpose, I have created a trait that I use in classes I do not want changed.
trait no_set {
public function __set($name, $value) {
throw new Exception("Adding new properties is not allowed on " . __CLASS__);
}
}
In this way, traits can be a very useful way to avoid repetition when coding. But, in most cases, you will probably benefit more from using DI (Dependency Injection) in the longer term.
Tell us what you think: