Qafoo GmbH - passion for software quality

Help you and your team benefit from new perspectives on cutting-edge quality engineering techniques and tools through the Qafoo team weblog.

By Tobias Schlitt, first published at Mon, 07 Oct 2013 11:25:46 +0200

ContainerAware Considered Harmful

A while ago I tweeted

ContainerAware is the new Singleton.

While many people agreed by retweeting and faving. I feel the need to elaborate some more on this statement and safe the explaination for the future.

TL;DR: No class of your application (except for factories) should know about the Dependency Injection Container (DIC).

Background

The ContainerAware interface (actually ContainerAwareInterface, ContainerAware is a basic implementation of it) is part of the Symfony2 API, but a similar concept is known from many other frameworks and many applications rely on it. It defines only the one method setContainer(), which allows to inject the DIC into into an object so that it can directly retrieve services from it.

It is most common to have controllers implement this interface. In fact, the Symfony2 base controllers do so and there is a base class for shell commands that does it.

Issues

Accessing the DIC in your classes can seriously harm maintainability and code re-usability in the long run.

Reduced Testability

Get your team a boost with a hands-on Qafoo workshop on object oriented design (OOD) and automated testing.

Unit testing is the most common automatic test method in the PHP world. If you have the container injected into your objects, this becomes much harder. There are three strategies to approach testing a class that gets the container injected:

  1. Mock the container and make it return the mocked service mocks,

  2. Use the container in your test cases and make it return the mocks

  3. Mock the subject of test to override the get() method that is commonly used to access services inside the class.

The first solution actually requires you to create a container mock that basically does the same thing as the real container. Except for mocking overhead you don't win much with this. So, if you are already in the situation that your classes have a dependency to the DI container, you're better of with version 2.

If you choose the second variant, you're formerly not writing a unit test, but an integration test instead. This is not a problem by default and integration tests are important, especially if you use an external framework for development. However, if you intend to write unit tests, you simply don't in this case, since another class (the DIC) is put under test.

The third variant is a simple no-go. You should never mock methods of your subject, because you cannot ensure that the mocked version of the code mimics what really happens. Your test cases lose a big amount of the safety that they should actually provide you with. Whenever you feel the need to mock a method of the test subject, that is a clear sign for the need to refactor (so-called code smell).

Hidden Dependencies

If you go for a real dependency injection approach (my favorite is constructor injection), you can easily see when a class does to much, just by looking at its dependencies. Think about a constructor like this:

<?php class UserProfileController { // ... public function __construct( UserRepository $userRepository, Search $search, MailGateway $mailGateway, Router $router, HttpClient $httpClient, PermissionService $permissions ) { // ... } // ... }

You can easily spot that there is something fishy. The class receives quite many dependencies and these do not fit very well together. Some are from the infrastructure layer, others seem to be business services and even others correlate with the MVC structure. If you see this constructor, it's eminent that there is need for refactoring. You will note that at the latest when you add another dependency to it.

Now compare that with the following code:

class UserProfileController extends ContainerAware { // ... public function __construct() { // ... } // ... }

There is no way to see what the actually dependencies of the class are. In order to see that, you need to go through the code and look for places where the Dependency Injection Container is used to retrieve objects. In real life, nobody will ever do that, especially if this is hidden behind $this->get('serviceName') calls.

Feature Sneak-In

This issue is a direct result of the previous one: If you can, you eventually will. And by this I mean access whatever service you might think you just need.

With access to the DIC a developer has the freedom to just grab any object and use it. This might be convenient to implement hacks, if you don't know where else to put a certain functionality when being in a rush. But on the other side of the coin, there is nothing which forces you to clean up the mess.

A common result is, that more and more business logic is collected in the controllers, making them become huge transaction scripts, which leads to code duplication and hidden business rules.

Conclusion

Making your classes aware of the DIC is not an option. Even if you really feel you need to, just don't do it. To be on the safe side I even recommend to make your controllers services.

If you develop Symfony2 based applications and run into the issue of having to inject too many framework services into your controllers, my co-worker Benjamin has a great post about that in his personal blog.

Comments

  • theUniC on Mon, 07 Oct 2013 11:55:46 +0200

    "No class of your application (except for factories) should know about the Dependency Injection Container (DIC)".

    I think you should clarify this sentence. Maybe factories inside the framework boundary may be coupled with the DIC. But factories inside the domain boundary would never have to be coupled with the DIC.

    Great post, BTW!

  • JamieL on Mon, 07 Oct 2013 12:38:56 +0200

    "It defines only the one method setContainer(), which allows to inject the DIC into an object"

    Sounds like firmware for a vibrator.

  • Don Gilbert on Mon, 07 Oct 2013 19:18:03 +0200

    IMO, any class that is responsible for building other classes (whether they are factories or controllers that build the models etc) can have access (and should have access) to the DiC.

    This is supported by GRASP - http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)#Controller

    "The GRASP Controller can be thought of as being a part of the Application/Service layer [2] (assuming that the application has made an explicit distinction between the application/service layer and the domain layer)"

    So this says if your controllers have access to the DiC, that makes them part of the Application/Services Layer. The problem, as you pointed out, is that developers can abuse this privilege, and many do. The answer isn't to simply ban usage of the ContainerAwareInterface, you have to be vigilant about your application structure. Just because you _can_ do something, doesn't mean that you should. Will people abuse it? Sure, but that doesn't make the thing itself harmful or evil.

  • Brandon Savage on Tue, 08 Oct 2013 22:18:53 +0200

    It would seem to me that substituting a DiC container for true IoC in an application is just plain bad design. There are a number of design patterns that help solve this problem, which too often seem ignored in PHP.

  • cordoval on Wed, 09 Oct 2013 17:58:24 +0200

    totally agree with this, is a given already in the community to go CAS, and CAS in commands as well.

  • Patrick Allaert on Mon, 14 Oct 2013 09:33:56 +0200

    Cannot agree more with that! Thanks for spotlighting this frequent issue!

    Patrick

  • Vladimir Kartaviy on Mon, 21 Oct 2013 03:47:25 +0200

    What about performance of creating dependencies when not all of them needed at a time?

  • Toby on Mon, 21 Oct 2013 06:28:48 +0200

    You can ship around that performance penalty using lazy loading proxies. Such proxies are actually another exception to the rule, because they inherently need to know the container, since their only purpose is to create the real service and therefore fetch its dependencies. Luckily, many container solutions can auto-generate such proxies for you, so you don't have to take care yourself. For example: http://symfony.com/doc/current/components/dependency_injection/lazy_services.html