📜 ⬆️ ⬇️

How I use the traits

Recently, several people asked me to talk about the use of traits in a new project that I am writing. At about the same time, Rafael Doms showed me his new speech about complex cognitive processes that we do not notice. Since our brain is a big bag that mixes everything, the result is a post that tries to cover how I use the traits, and how I decide where they are needed.

Impact vs Abstraction


The first thing you have to do is go read the “Abstraction or Leverage” post from Michael Nigard . This is a great article.

If you have little time, the main point of the post is that parts of the code (functions, classes, methods, etc.) can be intended either for abstraction or for impact. Difference in:
')

A common abstraction is the repository pattern: you do not know how the object is stored or where you do not care. Details lie outside the Repository concept.

The overall impact will be something like the base controller class in your framework. It does not hide the implementation, just adds some cool features that facilitate the work.

The aforementioned post says that both abstractions and impacts are good. But abstraction is a little better, because it always gives you the opportunity to influence, and the impact does not give you abstraction. However, I would like to add that good abstraction is more labor-intensive to create and not at all levels possible. So this is a compromise.

How is this related to traits?


Some features of a language are better than others in creating either an impact or an abstraction. Interfaces, for example, are great at helping us build and apply abstractions.

Inheritance, on the other hand, is great at providing exposure. It allows us to redefine parts of the parent code without having to copy or extract each method, use class code (but not necessarily abstract) several times. So, answering the initial question, when can I use traits?

I use traits when I want to create an impact, not an abstraction.

Sometimes.

Sometimes?


Benjamin Eberle made a good argument that the traits have basically the same problems as static access . You cannot replace or redefine them; they are frankly difficult to test.

But still static methods are useful. If you have one stateless function and you do not want to replace it with another implementation, then there is nothing wrong with making it static. Named constructors (you rarely want an empty object) or getting an array / result of mathematical operations with well-defined input / output, stateless, deterministic: all this is interesting for you. The static state, not the methods, is the real evil.

Traits have about the same limitations, plus they can only be used inside a class. They are more global than object.

This gives treits an additional feature: they can work (read and write) with the internal state of the class in which they are mixed. In some cases this makes them more suitable than static methods.

For example, I often use the generation of domain events in essence:

trait GeneratesDomainEvents { private $events = []; protected function raise(DomainEvent $event) { $this->events[] = $event; } public function releaseEvents() { $pendingEvents = $this->events; $this->events = []; return $pendingEvents; } } 

We can refactor and turn this code into an abstraction , but it will still be a good example of how traits can work with the local state of an object, in contrast to static methods. We do not want to work with an array of events blindly or move it from the object. Perhaps we don’t want to add another abstraction inside the model, and we, of course, don’t want to copy-paste this sample code everywhere. And here again treits will help us.

Other practical examples are custom logging of functions, a dump of several properties at once, or general iterative / search logic. We could solve all these tasks by the parent class, but let's talk about this a bit later.

So, traits are a good substitute in such cases, but this does not mean that static methods are useless. In fact, I still prefer to use static methods in cases where you do not need to change the internal state of the object, because it is always safer. Static methods are also much more convenient in testing, do not require a mock class.

Creating statements is a good example of those cases where I prefer static methods, despite the fact that they can usually be put into traits. I find that Assertion::positiveNumber($int) gives me the above benefits and it's easier for me to understand what the called class does.

If you have similar methods of statements, there is always a temptation to turn them into treits, but such code is already beginning to “smack”. Perhaps the method requires several parameters and you are tired of transmitting them. Perhaps checking $this->foo requires the value of $this->bar . In any of these cases, class refactoring will be the best alternative. Remember, it is always better if the impact gives way to abstraction.

So I declare: I use traits when I want an impact that requires access to the internal state of the object.

Parent classes


All that we have listed can also be implemented through inheritance. In EventGeneratingEntity perhaps this approach would be even better, since the array of events will indeed be individual . However, traits allow multiple inheritance instead of a single base class. In addition to the feature set, are there any good arguments for this approach?

Other things being equal, I would be guided by a rule like "Is-A vs. Has-A". Of course, this is not an exact rule, because traits are not a composition , but a reasonable guideline.

In other words, parent classes should be used for functions that are inherent in an object. Parent classes convey to other developers well the meaning of the code: “An employee is a person.” If we need an impact, this does not mean that the code should not be communicative.

For other non-core object functionality (logging, events, etc.), traits are a suitable tool. They do not define the nature of the class, it is ancillary functions, or better yet, an implementation detail. Everything that you get from a trait should be in the service of the main goal of the object, the traits should not be an important part of the functionality.

So, in the case of generating events, I still prefer the treit, because the creation of events is an auxiliary functionality.

Interfaces


I rarely (if at all) extend the class or create a treyte without creating an interface.

If you follow this rule, you will find that the traits well complement the principles of interface separation. It is easy to define an interface for additional functionality and to make a simple implementation in the default byte.

This allows the target class to implement its own version of the interface or use the default treit for unimportant cases. If your choice is templates and forcing a poor abstraction, traits can be a powerful ally.

And yet, if you have only one class implementing the interface in the code and you do not plan to change this, then implement the functionality directly in it, do not worry about the traits. This will make your code more readable and supported.

When I do not use traits


Honestly, I don’t use treits quite often, maybe once in a few months I create a treit with constant work on projects. All this heuristics, which I have outlined (impact requiring access to the internal state), is extremely niche. If you use them too often, you may need to take a step back and review your programming style. There is a good chance that thousands of classes are just waiting to be realized.

There are several places where I don’t like to use traits due to style preferences:


And finally, it should be remembered that treitures do not imply abstraction and they are not a composition, but still have the right to take a place among your instruments. They are useful for providing default impact with smaller implementation or duplication of code. Always be ready to reorganize them for better abstraction, as soon as you feel the signs of the code “with the smell.”

Source: https://habr.com/ru/post/258671/


All Articles