ContainerAware Considered Harmful
First published at Monday, 7 October 2013
Warning: This blog post is more then 10 years old – read and use with care.
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).
ContainerAware interface (actually
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 an object so that it can directly retrieve services from it.
Accessing the DIC in your classes can seriously harm maintainability and code re-usability in the long run.
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:
Mock the container and make it return the mocked service mocks,
Use the container in your test cases and make it return the mocks
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).
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.
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.