Magento 2 Solid Principle

As soon as you start working with OOPs we came to know about sevral design pattern and SOLID principle is the first thing that we can across. As per magento technical guidelines (under class design)

Object decomposition MUST follow the SOLID principles

If you check the Magento code than you can clearly see that Magento code is following SOLID principles. We will look into it these principle one by one:-

SOLID stands for:

  • Single Responsibility
  • Open-Closed
  • Liskov Substitution
  • Interface Segregation
  • Dependency Inversion

Single Responsibility Principle

A class should have only and only one reason to change.

As its name suggest, it states that every object should have single responsibility i.e, single role to perform and that responsibility should be entirely encapsulated by the class. It means that when we are adding some unrelated feature they should not affect same class.
For example, In Magento 2 every controller has just one responsibility.

namespace Magento\Cms\Controller\Page;

class View extends \Magento\Framework\App\Action\Action {

    public function execute() {

        $pageId = $this->getRequest()->getParam('page_id', $this->getRequest()->getParam('id', false));
        $resultPage = $this->_objectManager->get(\Magento\Cms\Helper\Page::class)->prepareResultPage($this, $pageId);
        if (!$resultPage) {
            $resultForward = $this->resultForwardFactory->create();
            return $resultForward->forward('noroute');
        }
    return $resultPage;
    }
}

Open-Closed Principle

Objects or entities should be open for extension, but closed for modification.

The above statement itself explains everything that it should be always easy to make behaviour change of particular implementation but we should make changes directly into the original source code. We should make changes by extending the class.

For example, In Magento 2 if you check module-catalog/Controller/Adminhtml/Category.php it is just initiating category object and rest of the actions (such as:- move, refresh page) are using it by extending the \Magento\Catalog\Controller\Adminhtml\Category class and making behaviour changes as per the requirement.

namespace Magento\Catalog\Controller\Adminhtml\Category;

class Move extends \Magento\Catalog\Controller\Adminhtml\Category {

    public function execute() {
        
        $parentNodeId = $this->getRequest()->getPost('pid', false);
        $prevNodeId = $this->getRequest()->getPost('aid', false);
        ............
    }
}

Liskov substitution principle

This principle states that any substitute/child class (implemented by either extension or dependency injection) should adhere the same rules as its predecessor class i.e, it should not break the functionality that is already being used by keeping method signature consistent.

For example, in below example you can see that toOptionArray() in both class return an array


namespace Magento\Ups\Model\Config\Source;

use Magento\Shipping\Model\Carrier\Source\GenericInterface;

class Generic implements GenericInterface {

    public function toOptionArray()
    {
        $configData = $this->carrierConfig->getCode($this->_code);
        $arr = [];
        foreach ($configData as $code => $title) {
            $arr[] = ['value' => $code, 'label' => __($title)];
        }
        return $arr;
    }
}
namespace Magento\Ups\Model\Config\Source;

class OriginShipment extends \Magento\Ups\Model\Config\Source\Generic

{
    protected $_code = 'originShipment';
    public function toOptionArray()
    {
        $orShipArr = $this->carrierConfig->getCode($this->_code);
        $returnArr = [];
        foreach ($orShipArr as $key => $val) {
            $returnArr[] = ['value' => $key, 'label' => $key];
        }
        return $returnArr;
    }
    return $returnArr;
    }
}

Interface Segregation

This states that we should not use those method or properties in the interface that the client may not use. Rather than having all method which are related to responsibility it’s good to break it into multiple interfaces and let a single class extends multiple interface. For example see the below image:-

Dependency Inversion principle

High level module should not depend on low level moduleFirst of all keep in mind that dependency inversion is not same as dependency injection, although they go hand in hand.

Magento 2 Design Pattern

There are lots of design pattern used in Magento, here we will be discussing some of them. We all know design patterns are the solution of some commonly occurring problem during our development phase. They are the recommended way to write our code.

Service Contract Design Pattern

Magento is an extension based or modular system, which allows a third-party developer to customize and overwrite core parts of its framework. These customizations may lead to several issues, for example, it will become for developers to keep track of customization done by external extensions. Thus to overcome this Magento comes up with service contract pattern. A service contract is a set of interfaces which act as a layer between an end user and business layer. Thus rather than directly exposing business logic for customization to end user, a layer called service contract comes in between.

  • Service contracts enhances the modularity of Magento.
  • Helps merchants for easy upgrade of Magento
  • Ensure well-defined and durable API that other external and Magento module implements.
  • Provide an easy way to expose business logic via REST or SOAP interfaces.

Object Manager

It itself consist of various pattern such as:- Dependency injection, Singleton, Factory, Abstract Factory, Composite, strategy, CQRS, Decorator and many more. We will discussing some most used pattern among these.
Object manager has a very big role to play, Magento prohibits the direct use of it. Object manager is responsible for implementing factory, singleton and proxy patterns. It automatically instantiates parameter in class constructors. Before moving future lets understand about injectable and non-injectable objects:-

Injectable objects

They does not have their own identity such as EventManager, CustomerAccountManagementService.

Non-injectable objects

Such as customer, product etc. These entities usually have their identities and state, since they have their identities it is important to know on which exact instance of entity we have to work.

Dependency Injection

It is an alternative to Mage in magento 1. It is a concept of injecting the dependent object through external environment rather than creating them internally. Thus we will be asking for resource when our object is being created instead of creating resource when needed. This helps in future modification and testing becomes very easy by mocking required objects. For example:-

 

 

 

 

 

 

 

 

So in the first image, we have read() method in class A which has initialized database connection object but in second image we have injected database connection via a constructor. Now to test our database connection we can easily create mock object during object creation of class A, making our testing much easier. For large object injection Magento have introduced object manager. But using object manager is not a good practice so we use Factory classes and proxy.

Factory pattern or Factory classes:-

In Magento 2 Factory classes create a layer between the object manager and business code. Factory classes need not define explicitly as they are auto-generated. We should create factory classes for non-injectable objects.
Let see the way to get create factory classes:-

function __construct ( \Magento\Cms\Model\BlockFactory $blockFactory) {
$this->blockFactory = $blockFactory;
}

// calling create will give the object for block class
$block = $this->blockFactory->create();

Event Observer pattern

Events are a way to hook up your functionality in between of code without overriding any core class. An event is dispatched when some actions get triggered. They are very useful when we have to just modify some value of an object rather than overriding that class we can event add an observer to do that. It also prevents someone to touch our core code, anyone can use them without deep knowledge of logic running behind. You can create your own event also by writing following line of code:-

$this->eventManager->dispatch('my_module_event_after',['myEventData'=>$eventData]);

An observer is certain type of Magento classes that contains the logic which you want to implement in any event. Below is an example of observer class:-

namespace MyCompany\MyModule\Observer;

use Magento\Framework\Event\ObserverInterface;

class MyObserver implements ObserverInterface
{
  public function __construct()
  {
    //Observer initialization code...
    //You can use dependency injection to get any class this observer may need.
  }

  public function execute(\Magento\Framework\Event\Observer $observer)
  {
    //Observer execution code...
  }
}

Proxy Pattern

Proxy classes are used to work in place of another class and in Magento 2 they are sometimes used in place of resource hungry classes. To understand what proxy classes do let’s see the reason which leads to the occurrence of proxy classes. As we know Magento uses constructor injection for object creation and when we instantiate an object all the classes in its constructor will also instantiate thus leading to a chain of instantiation via a constructor, this can really slow down the process and impact the performance of an application, so to stop chain instantiation Magento uses proxy classes.

Lets see following code:-

Magento\Catalog\Model\Product\Attribute\Source\Status\Proxy

Magento\Catalog\Model\Product\Link\Proxy

So in above code, we are using proxy classes for catalogProductStatus and productLink. When we run

 php bin/magento setup:di:compile 

Magento creates proxy classes on the fly using di.xml with some fixed conventions, thus replacing the original object with a proxy class object. Now let us look at our proxy class to understand how it is working

namespace Magento\Catalog\Model\Product\Attribute\Source\Status;
 
class Proxy extends \Magento\Catalog\Model\Product\Attribute\Source\Status implements \Magento\Framework\ObjectManager\NoninterceptableInterface
 
{
 
protected $_objectManager = null;
protected $_instanceName = null;
protected $_subject = null;
 
protected $_isShared = null;
 
/**
 
* Proxy constructor
 
*
 
* @param \Magento\Framework\ObjectManagerInterface $objectManager
 
* @param string $instanceName
 
* @param bool $shared
 
*/
 
public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Magento\\Catalog\\Model\\Product\\Attribute\\Source\\Status', $shared = true)
 
{
 
$this->_objectManager = $objectManager;
 
$this->_instanceName = $instanceName;
 
$this->_isShared = $shared;
 
}
 
/**
 
* @return array
 
*/
 
public function __sleep()
 
{
 
return ['_subject', '_isShared', '_instanceName'];
 
}
 
/**
 
* Retrieve ObjectManager from global scope
 
*/
 
public function __wakeup()
 
{
 
$this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance();
 
}
 
public function __clone()
 
$this->_subject = clone $this->_getSubject();

}
 
…..........

Some common convention Magento follow while creation of proxy:-

  • Namespace of proxy class will be same as original (Magento\Catalog\Model\Product\Attribute\Source\Status)
  • Proxy class only extends one object i.e, object manager
  • Has magic functions such as __sleep, __wake which are invoked only on certain action and function such as __clone will make an object of original class and will provide the object only when it is needed (making use of lazy loading design pattern), thus improving the performance of application