·

Magento 2 is packed with a lot of cool design patterns. Most of them you might already know, like dependency injection or interceptors, because they are well-known and well- documented. There are however much more design patterns in Magento 2 that are also worth using. One of these patterns that I use regulary in my own modules are Pools.

What is a pool?

You might not be aware, but chances are that you already used pools more than once in Magento 2. Take the CLI for example. If you want to register a command for this, you add something like the following to di.xml:

There is nothing exotic about the above example. What it states is that: “If an instance of the CommandList is created, add the following entry to the $commands array”. Let’s take a look at the constructor of Magento\Framework\Console\CommandList:

That’s all of it. Actually, if you take a look at the entire class, you’ll note that it’s a very small class that does nothing more than store the $commands array.
What makes it powerfull is that the $commands array can be filled by external modules and due to dependency injection we can use these commands anywhere!

A more practical example

Let’s say we want to create a module that can export all orders, and any other module is allowed to add a column to this export. This might look like a complex task at first, but it’s actually very simple to set up by using a pool.

The setup

First we setup our interface for a single column in our export. Since we are going to allow other extensions (possibly made by 3rd party developers) to add columns to our export, we must provide an interface so we know what to expect:

Api/Data/ColumnInterface.php

Next up, we create an interface for our pool, so we can use it properly with dependency injection:

Api/ColumnPoolInterface.php

And last but not least, we must add the dependency preferences to our di.xml, so we can use our interface in our constructors:

etc/di.xml

The code

Now that our setup is done, it’s time to actually write some code. Since we’ve mapped our interface to a model in di.xml, let’s there:

Model/ColumnPool.php

Now, let’s break this class down. First look at the constructor:

The only thing the constructor does, is iterate through all the columns to make sure that all entries are implementations of ColumnInterface.
The getColumnTypes()-method returns an array with all the used codes of the columns. The
getColumns()-method simply returns the entire array.
And last but not least, we have a getColumn()-method that returns a column with a given name, and throws an exception if it’s not found.

Responsibility

Please note that this class throws exceptions and not fails silently or returns false. This is intended behaviour. If you ask “why?” you should think about responsibility. Ask yourself the question: “Is it the responsibility of the column pool to handle errors?”. The answer is: No. It’s the responsibility of the pool to throw the error, but not to deal with it.
Thinking about responsibility during development leads to cleaner, more SOLID code. But that’s a whole different story…

The implementation

Now we have our setup and our code, we can put the pool to use. Let’s start with adding a column to the pool. Let’s add the following to our di.xml-file:

With the above code we added our first column to the pool: IncrementId. So let’s just look at what this class would look like:

Model/Column/IncrementId.php

This is our first column that we added to our column pool. It simply takes an integer $orderId and returns the increment ID that matches the order.
So how can we put this to practical use? Well, up to so far we’ve only created a pool and added a single column to it. Let’s add a manager to handle our order exports. The first thing we need to do for this, is create an interface for our manager:

Api/OrderExportManagementInterface.php

Not much special here. Just a single method. Let’s add the dependency preference to our di.xml:

And now for the interesting part: The manager itself:

Once again, let’s break this class a bit down. The most important part in this class is in the constructor:

Notice how we use the column pool in our constructor. This is where it all comes together. The ColumnPoolInterface will be mapped to the ColumnPool-model due to di.xml. The same
di.xml that filled the $columns argument of the constructor of ColumnPool.
So what happens in the export()-method on our ColumnPoolManagement:

As you can see, we iterate through the orders, and for each order we iterate through all the columns provided by the columnpool, effectively allowing the values of our export to be determined purely by configuration and external modules.

The beauty of it

The beauty of this method, by using the pool-design pattern, is that we completely separated the various responsibilities of our order export, and made it modular, flexible and easy to extend as well. Just think about it:

  • The only responsibility of the pool is to keep track of what columns to export.
  • The only responsibility of the actual exporter is to load a bunch of orders and iterate through them.
  • The responsibility of fetching and validating data and such is not in this module, but in each individual column.
  • We’re not even saving to CSV of XLS or whatever, since that’s not the responsibility of the exporter.

Compare this to an export method that works like this:

The above example is very sensitive for errors and makes it very hard, or even almost impossible to add, remove or mutate specific columns in the order export. You’re most likely have to create a complete rewrite if you want to make adjustments to the above.

In conclusion

I hope this article have shown you some insight in what pools are in Magento 2, but most importantly: how you can utilize them to the fullest in your own extensions.

Visitors give this article an average rating of 4.4 out of 5.

How would you rate this article?

Leave a Reply