This article is part of a series on design patterns for framework independent code, especially to write extensions for Magento 1 and Magento 2 alike.


All Parts:


So we want to decouple business logic (our library) from the framework, making the Magento module depend on the library, not the other way around.

But somehow we still need to be able to interact with existing Magento models.

The Bridge design pattern (a simplified variation of it, to be exact) addresses this problem:

Intent: Decouple an abstraction from its implementation so that the two can vary independently

Just like our Configuration Value Objects, this is again an implementation of the Dependency Inversion principle.

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.

  2. Abstractions should not depend on details. Details should depend on abstractions.

Here, „Details“ are the concrete classes in the library as well as in the Magento module. And as an abstraction we create a set of interfaces, the Implementors. These tell the module, what exactly we need from Magento (or, generally speaking, from the application) and constitute a well-defined interface between the two.

Remember that the end goal is a structure like this:
Diagram: Dependencies

So we cannot use any class of the modules directly from the library. The modules have to implement interfaces instead, which were defined in the library, and pass these implementations to the library, wherever it expects the interfaces.

The UML diagram below shows the participants of the bridge pattern in its original sense. What’s important for us, is that the library provides interfaces (“Implementor”), which are implemented by the module (“ConcreteImplementor”). I am going to call these concrete implementations Bridge Classes. The client code in the library only depends on the interfaces, never on the concrete implementations. The “RefinedAbstraction” allows us to have different variations on the client as well because the bridge pattern is meant to solve parallel inheritance hierarchies.

Bridge UML class diagram

In my examples I don’t use the additonal abstraction within the library (“Abstraction” and “RefinedAbstraction”), but use the interfaces directly. Why? Because it solves a problem that I don’t have: being able to have different implementations on the client side (the library). I will not introduce another layer of abstraction if I don’t need it.

Example

Let’s say we need to be able to get name and URL for one or several products by SKU. Then these are our implementor interfaces:

Implementor Interfaces

Our library then requires a ProductRepository, but leaves the implementation up to the Magento module. For unit tests we will create a stub implementation or mock the interface.

The implementation then can act as an adapter for the Magento models:

Magento 1 Bridge Classes

I used Mage::getModel('integernet_example/bridge_product', $product) here, which follows the common best practice in Magento module development, not to use „new“, but unfortunately getModel() is limited to one constructor parameter.

So I ended up intentionally breaking the rule for bridge classes. To keep the flexibility of the Magento rewrite system and follow the same principles as in the library, I introduce a factory. This can be a single helper (which can be rewritten using the Magento rewrite system):

Now the findBySku method in the ProductRepository implementation above looks like this:

Library Code

A class in the library that uses these interfaces could look like this (just a CSV export of selected products, using a writer such as League\Csv):

It receives a ProductRepository instance, eventually created by the Magento module. As promised in the last part, Dependency Injection, I’ll give an example how to connect the parts as well:

To create our super complex product URL export class, we’ll have a ProductExport factory:

This also instantiates the internal dependencies (here: the CSV writer), which we don’t need to expose to the Magento module to keep the interface between library and module small and keep as much program logic as possible in the library.

Using the Library in the Module

This is how you could use the code from above in an admin controller action:

I would move the instantiation of the ProductExportFactory to our existing factory helper, to be able to reuse or rewrite it:

… which leaves us with a controller action like this:

Depending on your actual use case, you also might want to create the product exporter immediately from the helper to avoid factories that create factories (I know, it’s a common joke about Java and overengineered class design).

Pattern Definition

As stated above, this is not following the Bridge pattern definition to the point. You could also say, it’s a variation of the Adapter pattern, but where the “Adaptor” implements an interface and is exchangable:

Adapter UML class diagram

  • Bridge class => Adaptor
  • Magento models => Adaptee

Defining Interfaces

The interfaces should only contain the methods we really need. If an interface still ends up with lots of methods, consider splitting it into multiple interfaces, because it’s unlikely that they are used all at once in a class. The concrete implementation can still be one class, implementing multiple interfaces, like this:

Interface Segregation Principle – Clients should not be forced to depend upon interfaces that they don’t use.

This makes it not only easier to mock the dependencies for unit tests, but also we are more flexible when it comes to different implementations (i.e. Magento 1 vs. Magento 2 vs. OroCommerce …)

But what about the presentation layer? In the next part, we will discuss, which parts of the layout you can decouple, and introduce view models.

Fabian Schmengler

Author: Fabian Schmengler

Fabian Schmengler is Magento developer and trainer at integer_net. His focus lies in backend development, conceptual design and test automation.

Fabian was repeatedly selected as a Magento Master in 2017 and 2018 based on his engagements, active participation on StackExchange and contributions to the Magento 2 core.

More Information · Twitter · GitHub