Should I use IoC Dependency Injector when developing business code?

Modern PHP frameworks (like Zend, Symfony, Phalcon) use a DI container and you just pass it in to get access to all the functionality. I am wondering if I should / could use a DI container in my business code. Let's say I need to use a database access object and a mailer object and they are already in the DI because they are being used. Can I just pass the DI container when instantiating the business class?

For example, I have a class that touches users in the database, you can call it my user model class. Right now I am just passing the DI container to the constructor of the model class when instantiating it in the controller, and it's easy and simple. Just drop everything into a DI container and be done.

But I'm going to develop an API that will also use this user model class. Since it expects a DI container, I will need to know what the model dependencies are and initialize the DI container with the correct ones. Previously, I just passed each dependency as a parameter in the constructor, but with IoC, I needed to know, without looking at the parameters, what the class dependencies are and what the name used to access each dependency is. For example, I need to know that there must be a PDO object identified by "db" in the DI container. Is this a good approach for business code?

I am probably mixing up the terms here, but I hope you get the idea.

Thank!

+3


source to share


1 answer


It doesn't really matter what kind of code you develop, whether it's business logic or structure logic (or some other kind of logic), it's all about how you deal with class dependencies.

The term business logic itself is very abstracted. You can represent business logic with a single class (aka domain object ), or you can represent business logic as a layer .

One of the main notes: Database is just a storage mechanism

When developing any application, you should keep in mind that the database is subject to change (or you may migrate to a NoSQL solution in the future). And when / if you do, it is $pdo

no longer an addiction. If you had the storage logic completely decoupled from the business system, then it would be easy enough to replace the storage engine. Otherwise, you end up rewriting a lot of things when you change it.

A well-designed architecture encourages decoupling storage logic from application logic. These templates are set as best practice: Data Mapper or Table Gateway

namespace Storage\MySQL;

use PDO;

abstract class AbstractMapper
{
     protected $pdo;

     public function __construct(PDO $pdo)
     {
            $this->pdo = $pdo;
     }
}

class UserMapper extends AbstractMapper
{
    private $table = 'cms_users';

    public function fetchById($id)
    {
       $query = sprintf('SELECT * FROM `%s` WHERE `id` =:id', $this->table);
       $stmt = $this->pdo->prepare($query);
       $stmt->execute(array(
           ':id' => $id
       ));

       return $stmt->fetch();
    }

    // the rest methods that abstract table queries
}

      

So in this case the $pdo

underlying dependency is used for the current storage engine , and it is not a framework or application dependency that you are developing. The next problem you have to solve is how to automate this $pdo

mappers dependency transfer process . There is only one solution you can use - Factory pattern

$pdo = new PDO();
$mapperFactory = new App\Storage\MySQL\Factory($pdo);

$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $pdo dependency

      

Now let's look at the obvious benefits:

First of all, it's readability - anyone who sees the code will get the key that the Mapper is used to abstractly access the table. Second, you can easily replace the storage engine (if you plan to migrate in the future and add multiple databases)

$mongo = new Mongo();
$mapperFactory = new App\Storage\Mongo\Factory($mongo);

$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $mongo dependency

      

Note. All cartographers for different storage systems must implement the same interface (API application)

Model doesn't have to be one class



When it comes to the Internet, we basically do the following:

  • Displaying a form (which might look like a contact page, login form, etc.).
  • Then by submitting the form
  • Then a comparison with our validation rules
  • On success, we insert the form data into the storage engine, on failure, error messages are displayed

So when you implement the model as a class, you end up writing the validation and storage logic in the same class, so tightly coupled and break the SRP.

You can see one of these common examples in the Yii Framework

The native model should be a class folder containing the application logic (see ZF2 or SF2).

Finally, if you actually use DiC?

Should you use a DI container when developing your code? Ok, let's take a look at this example of classic code:

class House
{
      public function __construct($diContainer)
      {
             //Let assume that door and window have their own dependencies
             // So no, it not a Service Locator in this case
             $this->door = $diContainer->getDoor();
             $this->window = $diContainer->getWindow();
             $this->floor = $diContainer->getFloor();
      }
}

$house = new House($di)

      

In this case, you will say that yours House

depends on DiC

, and not explicitly on doors, windows and floors. But wait, does ours House

really depend on DiC? This is probably not the case.

And when you start testing your class, you should also provide a prepared DiC (which is completely irrelevant)

Usually people use Di containers to avoid injections all the time. But on the other hand, it takes some RAM and some time to parse as most DI containers are configuration based.

A good architecture can be free of any Di container as it takes advantage of Factory and SRP.

Therefore, good application components should be free of any Di service locators and containers.

+1


source







All Articles