Magento 2

Magento fundamentals: helper decomposition

What's an helper

According to Wikipedia's definition, "in object-oriented programming, a helper class is used to assist in providing some functionality, which isn't the main goal of the application or class in which it is used."

Helpers used to be first-class citizens in Magento 1, which left many tracks into Magento 2 codebase.

That's the reason why, I guess, a lot of Magento developers kept on implementing their helpers, violating the new Magento 2 guidelines.

Why helpers don't respect the new Magento 2 guidelines

Since, by definition, a helper is a class with many responsibilities, it doesn't adhere to the class design principles defined in the Magento technical guidelines.

To be more specific, according to the single responsibility principle, "there should never be more than one reason for a class to change".

In Magento 2, many helpers extend the \Magento\Framework\App\Helper\AbstractHelper base class.

In Magento 2.4.2, this class alone depends on:

  • \Magento\Framework\Module\Manager

  • \Psr\Log\LoggerInterface

  • \Magento\Framework\App\RequestInterface

  • \Magento\Framework\UrlInterface

  • \Magento\Framework\HTTP\Header

  • \Magento\Framework\Event\ManagerInterface

  • \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress

  • \Magento\Framework\Cache\ConfigInterface

  • \Magento\Framework\Url\EncoderInterface

  • \Magento\Framework\Url\DecoderInterface

  • \Magento\Framework\App\Config\ScopeConfigInterface

This class is a perfect example of a Swiss Army knife with more than one reason to change.

What does it mean to decompose a helper?

Let's start with the fact that helpers don't provide data (more on this later) but behaviors.

In Magento, behaviors should be implemented as service classes, adhering as much as possible to the following principles:

  • they don't have a mutable state;

  • they are usually instantiated as singletons, using constructor-based dependency injection;

  • they have a name that reveals their intent, generally starting with a verb at the imperative form;

  • they have a single public execute() method which fulfills the purpose.

💡 Note that there are many service classes in Magento that don't follow all the above rules: repositories, resource models, and many others. The most important of the rules that we should try to apply is related to the immutable state, that protects from unpredictable side effects. The adherence to the others is not always mandatory, as we will see in an example below.

If we look at our helpers, we can see that, after all, they are nothing but a set of behaviors packed together in a single class.

Decomposing them won't be a difficult task.

But helpers provide data!

When we read that a service class doesn't provide data, it doesn't mean that it can't provide data taken from a persistence layer.

It means that a service class is not an entity; it doesn't even have an identity and a state and doesn't persist and retrieve its data.

It can provide other entities' data. For example, a repository is a service class that provides data access (a.k.a. CRUD) methods for a given entity.

How to decompose a helper

At this point, we are ready to decompose our helpers into different service classes.

It's just a matter of identifying the different behaviors or reasons to change our helper class and split them into several service classes.

💡 When doing this, we are recommended to follow the Service Contracts guidelines.

An example is worth more than a thousand words.

Let's say we have the following helper class (methods' implementation details don't matter at this point):

The above class exposes two main behaviors:

  • give access to some configuration values;

  • build an action URL given a customer id.

Thus, we will define two service contracts and their corresponding implementation:

  • a service class that gives access to the configuration values; this class won't have a single execute() method but a method for each configuration we want to retrieve.<br/>This is an acceptable compromise that allows us to avoid defining a service class for each configuration value, which would be overwhelming;

  • a service class that allows us to get an action URL given a specific customer; maybe the logic may differ depending on the fact the customer is a guest or a registered user; we encapsulate this behavior inside the class implementation and represents the only reason for future changes.

The resulting code structure is shown below.

Service contracts declarations in MyCoolVendor_MyCoolModuleApi

Service contracts implementations in MyCoolVendor_MyCoolModule

Dependency injection bindings

Conclusion

Adhering to others' coding guidelines may be tedious because it forces us to abandon our habits and our comfort zone.

But as soon as we realize the benefits, it pays back. Everyone's code follows the same structure and rules, becomes more readable, improving stability and maintainability in the long term.

Give it a try!

Articolo scritto da

COO | Reggio Emilia

Alessandro lavora in Bitbull come team leader, esperto di software design e sviluppo di soluzioni ecommerce.
Membro attivo della community Magento come contributor e maintainer, ha ricevuto per tre volte il riconoscimento di Magento Master e Top 50 Contributor.
Dal 2020 è membro della Magento Association content committee.

☝ Ti piace quello che facciamo?  Unisciti a noi!