>Unlock 11 Necessary Design Patterns for Every Magento 2 Developer

Unlock 11 Necessary Design Patterns for Every Magento 2 Developer

12 min read

Hi there, 

As all of you already know, design patterns are dispensable in software programming. Thanks to design patterns, code becomes more organized and easier to develop and work with. 

Today we will discuss Magento 2 design patterns in detail to explore what design pattern Magento offers and how to handle them. 

1. Object Manager

Compared with other programming languages, object-oriented programming which just appeared from PHP 5 contains a lot of limitations. 

In Magento 1, almost all objects are implemented and called via Mage class. In Magento 2, Object Manager is used to replace Mage class. This helps to solve some problems when you instantiate parameters by creating a close relationship between 3 patterns: object management, dependency injection, and plugins. (I will explain more details when it comes to the “How Magento 2 generates code” section). 

Object Manager takes the main responsibility in instantiating and configuring Objects via the 2 main methods: GET and CREATE. 

object manager - magento 2 design pattern

  • GET returns a singleton object (an instance of the class is used to share between components when running Magento, on the other hand). 
  • CREATE returns an entirely new object (a new instance of the class). 

Hence, if you call the GET method from 2 places, the same result will be generated. On the contrary, you will receive a new object if you use the CREATE method. 

So what is the purpose of Object Manager? Let’s find answers in the following: 

  • Instantiate Object Manager. Magento uses Object Manager to instantiate and insert class declared in the constructor. 
  • Implement the singleton pattern (See more HERE
  • Manage dependencies 
  • Instantiate parameters automatically

According to Magento core team, you shouldn’t use Object Manager in modules because it makes class lose its dependency. 

2. Dependency Injection

2.1. Overview 

Dependency Injection (DI) is a design pattern to solve dependencies in programming and replace for Mage class in Magento 1. 

Basically, Dependency Injection includes 4 components: 

  • Service object: is used to declare dependencies
  • Client object: depends on service object and inherits from dependencies 
  • Interface object: defines methods that client can use the service’s dependencies 
  • Injector object: implements the service’s dependencies and gives them to the client object. 

dependency injection - magento 2 design pattern

 

Dependency is also called as a function of services and injection is an action that gives dependency to its dependent object (client). Client now can use service without building a new one. 

2.2. Dependency Injection in Magento 2 

The Dependency Injection(DI) in Magento 2 is a system of object management based on Object Manager or Factory to create Objects with available configuration. 

DI is a design pattern allowing class A to declare dependencies so that class B can use those dependencies. Class A often uses Interface class and provided class B to implement things in that interface. 

Hence, there’s no need for class B to consider the dependency declaration because class A takes over, which shows a responsibility division in the structure building. This is quite important for developers to understand how Magento builds its structure. 

There are 2 ways of Dependency Injection: 

  • Via Constructor: 

constructor - magento 2 design pattern

*For example: Magento\Catalog\Controller\Adminhtml\Product\Save

  • Via Method: API often uses this method.

*For example: Magento\Catalog\Api\ProductAttributeManagementInterface

method - magento 2 design pattern

3. Factories

3.1. Overview

Factory is a non-injectable object – a design pattern that allows Magento to instantiate a representative object for an Entity. It is instantiated by Object Manager and business code. Magento automatically instantiates Factory (in the folder generated) with the type as: <class-type>Factory.

For example: Magento\Catalog\Block\Product\ImageFactory

factory - magento 2 design pattern

3.2. How to use Factory 

  • Call Factory in the Constructor: 
function __construct ( \Magento\Cms\Model\BlockFactory $blockFactory) {
    $this->blockFactory = $blockFactory;
}

Call the create() method to create a copy of the object: 

$block = $this->blockFactory->create();
  • For a few classes that require some parameters (for example: Magento\Search\Model\Autocomplete\Item).

You will directly transfer params to create(): 

$resultItem = $this->itemFactory->create([
   'title' => $item->getQueryText(),   
   'num_results' => $item->getNumResults(), 
]);

4. Proxies.

As mentioned above, Dependency Injection is a design pattern to manage object class and you can make dependency injection by 2 ways: inject via Constructor and via Method. If you inject into Constructor, class and its dependencies in that constructor are instantiated (a set of class instantiation) when you instantiate an object class. 

Also, if there are any class and its dependencies using many resources, the function’s performance is badly impacted. In reality, you may not use these dependencies taking many resources in the current class. 

To handle this problem, Magento comes up with a solution as Proxy Class – a design pattern for the class whose functions negatively affect performance. It means if class A is dependency injected into a class B, the class A is instantiated only when a function of class A is called in class B. 

You can take a look at a structure to use Proxy: \Original\Class\Name\Proxy. This class is automatically instantiated in the folder generated. 

To use Proxy class, you can transmit via the di.xml file: vendor/magento/module-backend/etc/di.xml, for example. 

proxy 1 - magento 2 design pattern

In the constructor of the class: Magento\Backend\Helper\Data   And the class Magento\Backend\Model\Auth is instantiated only when it is used. 

proxy2

⇒ Benefits: 

  • Easy to use, and you only need to declare the class/Proxy into the di.xml file 
  • Improve the program performance
  • Contain structure which is clear to understand

5. Preferences

When you desire to modify a class or a function in Magento core for the purpose of extending or changing functions, Magento 2 offers you a method as Preferences. 

Preferences are managed in the following ways: 

<moduleDir>/etc/di.xml (Global)

<moduleDir>/etc/<area>/di.xml

We take this file as an example: app/code/Bss/Custom/etc/di.xml

<preference for="Magento\Quote\Model\Quote" 
type="Bss\Custom\Model\Quote" />
  • Class A: Magento\Quote\Model\Quote
  • Class B: Bss\Custom\Model\Quote

Then running to the loadByCustomer part, class B is called, not class A (B is required to extend A).

prefrences

⇒ Strengths: 

  • Easy to use 
  • Easy to declare 
  • Modify almost class in the core, even abstract and interface
  • Modify almost functions, even protected and private 

⇒ Weakness: 

  • Conflict with modules of 3rd parties 
  • Cannot modify if class calls directly via Object Manager

6. Argument Replacement

6.1. Overview

Argument Replacement is used via the di.xml file which includes dependencies and injection. You need Argument Replacement to change some dependencies injected into constructor when instantiating that class.  The name of Argument Replacement in the xml file is corresponding to the name used in class.

For example:  In the file: vendor/magento/module-catalog/etc/di.xml

argument1 

In the constructor of Magento\Catalog\Helper\Product class: 

argument2

At this time, you will inject corresponding arguments into the Magento\Catalog\Helper\Product class when instantiating Object Manager.   

6.2. Argument Types

  • Object

Node Formats:

<argument xsi:type="object">{typeName}</argument>
<argument xsi:type="object" shared="{shared}">{typeName}</argument>

Instantiating a copy of class Name:  class name, interface name, or virtual type as typeName.

Use “shared” to define the type of copy. There are 2 types called Singleton and Transient. 

  • String
<argument xsi:type="string">{strValue}</argument> 
<argument xsi:type="string" translate="true">{strValue}</argument>
  • Boolean
<argument xsi:type="boolean">{boolValue}</argument>
  • Number 
<argument xsi:type="number">{numericValue}</argument>

Allowed types contain integers, floats, or numeric strings.

  • init_parameter
<argument xsi:type="init_parameter">{Constant::NAME}</argument> 
{Constant::NAME}

You use it to instantiate global variables for a class. For example:  

<type name="Magento\Framework\App\Arguments\ValidationState">
<argument
<argument name="appMode" 
xsi:type="init_parameter">Magento\Framework\App\State::PARAM_MODE</argument>
</argument>
</type>
  • Const
<argument xsi:type="const">{Constant::NAME}</argument> 
.......
  • Null
<argument xsi:type="null"/>

Format is Null

  • Array
<argument xsi:type="array">   
<item name="someKey" xsi:type="<type>">someVal</item> 
</argument>

It is instantiating an array for argument – similar to an array in normal PHP. 

⇒ Strengths: 

  • Various types to modify class such as object, number, array, and so on
  • High flexibility 

⇒ Weakness:

  • Complicated structure 
  • Require to declare following the right structure

7. Virtual Types

Virtual types are used via the di.xml file. They can use dependencies in the available class without changing that class. 

For example, you create a session catalog in this file: vendor/magento/module-catalog/etc/di.xml

virtual type

The class Magento\Catalog\Model\Session\Storage is the name of a virtual class (these virtual and name are set optionally) and inject dependencies as namespace into the constructor. 

8. Events and observers

When you desire to modify a class or a function in Magento core, you often think of events and observers. So, what are events and observers?

8.1. Events 

Events are activated by an action of a module. Events share data to observers and you can edit the input data. Magento also allows us to create customized events to modify data. 

  • Dispatching Events 

You are able to create a new event by using this class: Magento\Framework\Event\ManagerInterface. You can also use the dispatch function provided by this class to instantiate the event name that you desire to dispatch.   

Take a look at the code example here: https://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#dispatching-events

  • Event Areas 

It is divided into 3 types depending on the events.xml file: 

+ etc/adminhtml/events.xml – adminhtml

+ etc/frontend/events.xml – frontend

+ etc/events.xml – global

8.2. Observers 

Observers are used to catch events in order for changing the input data so that you can modify, logic, and so on.

  • Create an observer 

To create a new observer, you need to put your file in the folder: <module-root>/Observer and the inherited class: Magento\Framework\Event\ObserverInterface

Take a look at the code example here: 

https://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#creating-an-observer

  • Subscribe events 

You declare events and observers via the xml file. 

For instance: <module-root>/etc/adminhtml/events.xml   

<event name="checkout_cart_add_product_complete">
     <observer name="bss_admin_catalog_add_to_cart" 
instance="Bss\CustomEvent\Observer\AddProductObserver"/>
</event>

Once adding products to cart is completed, it will run to the execute part of the class AddProductObserver. You can modify data transmitted via the checkout_cart_add_product_complete event, including: request and response.
observer 1

observer2

To disable events, you just insert disabled=”true”  behind the observer in the xml file. 

9. Plugins

You can also consider the Plugin method to customize a function in Magento 2. It is applied to all public methods in class, interface, and framework. 

The Plugin method can be only applied for the public method, and does not cover these following cases: 

  • Final method
  • Final class
  • Non-public method
  • Static class method
  • __constructor
  • Virtual Type
  • Object that is instantiated via Magento\Framework\Interception

To declare the Plugin, you need to use the di.xml file: 

<config>
    <type name="{ObservedType}">
      <plugin name="{pluginName}" type="{PluginClassName}" 
sortOrder="10" disabled="false" />
    </type>
</config>

⇒ Required things are: 

  • Type name: The name of class or interface you want to edit 
  • Plugin name: The name of plugin (in case the names are duplicated, they will be overwritten and only the plugin running at the last is taken)
  • Plugin type: The name of plugin will be edited. 

⇒ Non-required things are: 

  • Sort order: The running order of Plugin
  • Disabled: Default value is False, and you can add the disabled attribute to disable the plugin. 

There are 3 ways to use this Plugin method: 

  • Before: used to modify the input data of a function 
  • Around: used to modify the operation process of a function 
  • After: used to modify the output data

Before method: used to change the input data. For example, using the Plugin to edit titles of custom options.

before method

Around method: used to change business logic or logic flow.

around method

After method: used to change the returned results. For example, changing the returned title.

after method

⇒ Strengths: 

  • Easy to use with a clear structure 
  • High flexibility – Few conflicts with 3rd parties 
  • Ability to modify almost functions (public method only)

⇒ Weakness: 

  • Not modify protected or private method
  • Not call other protected or private methods in the current plugin. You have to entirely rewrite. 
  • Not be modified in case class is directly called via Object Manager

10. Repositories

Basically, Repositories and Factories share a lot of common things when they are used to read, edit, or remove entity or a list of entities. However, you will need Factories when you desire to create a new entity. 

Repositories are parts of service contracts – interface, so they support Soap/Rest Api. 

Returning data is a little bit different when you use these 2 methods. 

  • ProductRepository: The returned data will be specified in the business code \Magento\Catalog\Api\Data\ProductInterface.

This will remove any excess data that you don’t use. If data exists in the cache, the disk will take it out without loading directly from the Model via the database.

  • ProductFactory: returns full data of that model entity. Hence, much data is not necessary to be used in a result. 

In a nutshell, in case Repositories is enough for your demands, you should prefer Repositories to Factories.  

11. Injectable/Non-Injectable Object

Via Dependency Injection, there are 2 types of Object such as Injectable and Non-Injectable.

  • Injectable Object: is a singleton and shareable object instantiated by Object Manager via di.xml file and injecting to the constructor. This object type can require other objects in the constructor. 
  • Non-injectable: are objects that Object Manager cannot instantiate. To have this object, you need to instantiate a new instance. These objects share some common things such as: 
  • Short life cycle
  • Require input data to instantiate from the user or from the database before it can be instantiated.

Almost all Magento models are non-injectable objects. Product is a typical example because it needs the product ID to transmit. Hence, you have to use a new instance or empty instance to call this object. To overcome this limitation, you can use Factory to instantiate a new object. 

 

< Previous Post
Next Post >