Inversion of Control: Implementation Methods with PHP Examples

    Oh god, another Inversion of Control post


    Every more or less experienced programmer encountered the phrase Inversion of Control in his practice. But often, not everyone fully understands what it means, not to mention how to properly implement it. I hope the post will be useful to those who begin to get acquainted with the inversion of control and are somewhat confused.



    So, according to Wikipedia, Inversion of Control is an object-oriented programming principle used to reduce connectivity in computer programs, based on the following 2 principles
    • Top-level modules should not be dependent on lower-level modules. Both those and others should depend on abstraction.
    • Abstractions should not depend on the details. Details should depend on abstractions.


    In other words, we can say that all dependencies of modules should be based on abstractions of these modules, and not their specific implementations.

    Consider an example.
    Suppose we have 2 classes - OrderModel and MySQLOrderRepository. OrderModel calls MySQLOrderRepository to retrieve data from MySQL storage. Obviously, a higher-level module (OrderModel) depends on the relative low-level MySQLOrderRepository.

    An example of bad code is given below.
    load($orderID);
          return $this->prepareOrder($order);
       }
       private function prepareOrder($order)
       {
          //some order preparing
       }
    }
    class MySQLOrderRepository
    {
       public function load($orderID)
       {
          // makes query to DB to fetch order row from table	
       }
    }
    


    In general, this code will work perfectly, fulfill its responsibilities. It was possible to dwell on this. But suddenly your customer has a brilliant idea to store orders not in MySQL, but in 1C. And here you are faced with a problem - you have to change the code that worked fine, and also make changes to each method that uses MySQLOrderRepository.
    In addition, you did not write tests for OrderModel ...

    Thus, we can distinguish the following problems of the code given earlier.
    • Such code is poorly tested. We cannot test separately 2 modules when they are so tightly connected
    • Such code does not expand well. As the example above showed, in order to change the order store, we also had to change the model that processes orders


    And what to do with all this?

    1. Factory Method / Abstract Factory


    One of the easiest ways to implement control inversion is the factory method (an abstract factory can also be used)
    Its essence is that instead of directly instantiating the class object through new, we provide the client class with some interface for creating objects. Since such an interface with the right design can always be redefined, we get some flexibility when using low-level modules in high-level modules.

    Consider the above example with orders.
    Instead of directly instantiating an object of the MySQLOrderRepository class, we call the factory build method for the OrderRepositoryFactory class, which will decide which instance and which class should be created.

    Implement Inversion of Control Using Factory Method
    build();
          $order = $orderRepository->load($orderID);
          return $this->prepareOrder($order);
       }
       private function prepareOrder($order)
       {
          //some order preparing
       }
    }
    abstract class OrderRepositoryFactory
    {
      /**
       * @return IOrderRepository
       */
       abstract public function build();
    }
    class DBOrderRepositoryFactory extends OrderRepositoryFactory
    {
       public function build()
       {
          return new MySQLOrderRepository();
       }
    }
    class RemoteOrderRepositoryFactory extends OrderRepositoryFactory
    {
       public function build()
       {
          return new OneCOrderRepository();
       }
    }
    interface IOrderRepository
    {
       public function load($orderID);
    }
    class MySQLOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to DB to fetch order row from table	
       }
    }
    class OneCOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to 1C to fetch order	
       }
    }
    



    What does such an implementation give us?
    1. We are given the flexibility to create repository objects — the instantiated class can be replaced with any that we ourselves wish. For example, MySQLOrderRepository for DBOrderRepositoryfactory can be replaced with OracleOrderRepository. And it will be done in one place
    2. The code becomes more obvious as objects are created in specialized classes for this.
    3. It is also possible to add some code to execute when creating objects. Code will be added only in 1 place


    What problems does this implementation not solve?
    1. The code is no longer dependent on low-level modules, but nonetheless depends on the factory class, which still makes testing somewhat difficult


    2. Service Locator


    The basic idea behind the Service Locator pattern is to have an object that knows how to get all the services that you might need. The main difference from factories is that the Service Locator does not create objects, but knows how to get one or another object. Those. actually already contains instantiated objects.
    Objects in the Service Locator can be added directly, through the configuration file, and indeed in any convenient way for the programmer.

    Implement Inversion of Control Using Service Locator
    get('orderRepository');
          $order = $orderRepository->load($orderID);
          return $this->prepareOrder($order);
       }
       private function prepareOrder($order)
       {
          //some order preparing
       }
    }
    class ServiceLocator
    {
        private $services = array();
        private static $serviceLocatorInstance = null;
        private function __construct(){} 
        public static function getInstance()
        {
           if(is_null(self::$serviceLocatorInstance)){
              self::$serviceLocatorInstance = new ServiceLocator();
           }
           return self::$serviceLocatorInstance;        
        }
        public function loadService($name, $service)
        {
           $this->services[$name] = $service;   
        }
        public function getService($name) 
        {
           if(!isset($this->services[$name])){
              throw new InvalidArgumentException(); 
           }
           return $this->services[$name];
        }
    }
    interface IOrderRepository
    {
       public function load($orderID);
    }
    class MySQLOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to DB to fetch order row from table	
       }
    }
    class OneCOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to 1C to fetch order	
       }
    }
    // somewhere at the entry point of application
    ServiceLocator::getInstance()->loadService('orderRepository', new MySQLOrderRepository());
    



    What does such an implementation give us?
    1. We are given the flexibility to create repository objects. We can bind to a named service any class that we wish ourselves.
    2. It becomes possible to configure services through a configuration file
    3. During testing, services can be replaced with Mock-classes, which allows you to test any class using Service Locator without problems


    What problems does this implementation not solve?
    In general, the debate about whether the Service Locator is a pattern or anti-patterns is already very old and battered. In my opinion, the main problem of Service Locator
    1. Since the locator object is a global object, it can be accessed in any part of the code, which can lead to its excessive code and, accordingly, nullify all attempts to reduce the connectivity of modules


    3. Dependency Injection


    In general, Dependency Injection is the provision of an external service to some class through its implementation.
    There are 3 such ways
    • Via the class method (Setter injection)
    • Through the constructor (Constructor injection)
    • Via interface injection


    Setter injection


    With this implementation method, in the class where the dependency is introduced, the corresponding set-method is created, which sets this dependency

    Implement inversion of control with Setter injection
    repository->load($orderID);
          return $this->prepareOrder($order);
       }
       public function setRepository(IOrderRepository $repository)
       {
          $this->repository = $repository; 
       }  
       private function prepareOrder($order)
       {
          //some order preparing
       }
    }
    interface IOrderRepository
    {
       public function load($orderID);
    }
    class MySQLOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to DB to fetch order row from table	
       }
    }
    class OneCOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to 1C to fetch order	
       }
    }
    $orderModel = new OrderModel();
    $orderModel->setRepository(new MySQLOrderRepository());
    



    Constructor injection


    With this implementation method, in the constructor of the class where the dependency will be embedded, a new argument is added, which is the established dependency
    Implement inversion of control using Constructor injection
    repository = $repository; 
       }
       public function getOrder($orderID)
       {
          $order = $this->repository->load($orderID);
          return $this->prepareOrder($order);
       }
       private function prepareOrder($order)
       {
          //some order preparing
       }
    }
    interface IOrderRepository
    {
       public function load($orderID);
    }
    class MySQLOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to DB to fetch order row from table	
       }
    }
    class OneCOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to 1C to fetch order	
       }
    }
    $orderModel = new OrderModel(new MySQLOrderRepository());
    



    Interface injection


    This method of injecting dependencies is very similar to Setter Injection, then with the exception that with this method of embedding, the class where the dependency is injected is inherited from the interface that obliges the class to implement this set method.

    Implementing control inversion using Interface injection
    repository->load($orderID);
          return $this->prepareOrder($order);
       }
       public function setRepository(IOrderRepository $repository)
       {
          $this->repository = $repository; 
       }  
       private function prepareOrder($order)
       {
          //some order preparing
       }
    }
    interface IOrderRepositoryInject
    {
       public function setRepository(IOrderRepository $repository);
    }
    interface IOrderRepository
    {
       public function load($orderID);
    }
    class MySQLOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to DB to fetch order row from table	
       }
    }
    class OneCOrderRepository implements IOrderRepository
    {
       public function load($orderID)
       {
          // makes query to 1C to fetch order	
       }
    }
    $orderModel = new OrderModel();
    $orderModel->setRepository(new MySQLOrderRepository());
    



    What does the implementation with Dependency Injection give us?
    1. Class code now only depends on interfaces, not abstractions. The specific implementation is being clarified at run time.
    2. Such classes are very easy to test.


    What problems does this implementation not solve?
    In truth, I don't see any major flaws in dependency injection. This is a good way to make the class as flexible and as independent as possible from other classes. Perhaps this leads to excessive abstraction, but this is already a problem of a concrete implementation of the principle by the programmer, and not the principle itself

    4. IoC container


    An IoC container is a container that directly deals with dependency management and implementation (it actually implements Dependency Injection).

    IoC containers are present in many modern PHP frameworks - Symfony 2, Yii 2, Laravel, even in the Joomla Framework :)
    Its main purpose is the automation of the implementation of registered dependencies. Those. you just need to specify the necessary interface in the class constructor, register the specific implementation of this interface and voila - the dependency is embedded in your class The

    operation of such containers is somewhat different in different frameworks, so I provide you with links to the official resources of the frameworks, which describe how their

    Symfony 2 containers work -symfony.com/doc/current/components/dependency_injection/introduction.html
    Laravel - laravel.com/docs/4.2/ioc
    Yii 2 - www.yiiframework.com/doc-2.0/guide-concept-di-container.html

    Conclusion


    The topic of management inversion has been raised millions of times, hundreds of posts and thousands of comments on this topic. But nevertheless, I also meet people, see the code and understand that this topic is still not very popular in PHP, despite the presence of excellent frameworks, libraries that allow you to write beautiful, clean, readable, flexible code.

    I hope the article was useful to someone and someone’s code will get better thanks to this.
    Write your comments, suggestions, clarifications, questions - I will be glad

    Also popular now: