Because we have changed the constructor signature, all the existing instantiations of ItemsGateway are now broken. We need to find all the places in the code where the ItemsGateway class is instantiated, and change the instantiations to pass a properly-constructed Db
object and an ItemFactory.
Doing so will give us a list of all instantiations in the project. We need to review each result and change it by hand to instantiate the dependencies and pass them to the ItemsGateway.
For example, if a page script from the search results looks like this:
We need to change it to something more like this:
Do this for each instantiation of the changed class.
Now that we have changed the class and the instantiations of the class throughout the codebase, we need to make sure our legacy application works. Again, we have no formal testing process in place, so we need to run or otherwise invoke the parts of the application that use the changed class and look for errors.
Search for the next new
keyword in a class, and start the process all over again. When we find that new
keywords exist only in Factory classes, our job is complete.
In this chapter, we concentrate on removing all use of the new
keyword, except inside Factory objects. I believe there are two reasonable exceptions to this rule: Exception classes themselves, and certain built-in PHP classes, such as the SPL classes.
It would be perfectly consistent with the process described in this chapter to create an ExceptionFactory
class, inject it into objects that throw exceptions, and then use the ExceptionFactory
to create the Exception
objects to be thrown. This strikes even me as going a bit too far. I think that Exception
objects are a reasonable exception to the rule of no new
outside Factory
objects.
Sometime we will discover classes that have dependencies, and the dependencies themselves have dependencies. These intermediary dependencies are passed to the outside class, which carries them along only so that the internal objects can be instantiated with them.
How do we successfully remove the new
keyword here? The ItemsGateway needs a Db
connection. The Db
connection is never used by the Service
directly; it is used only for building the ItemsGateway.
For example, a page script might have done this when using global
variables everywhere:
And then we changed it to inject the intermediary dependency after removing globals:
But we should finally change it to inject the real dependency:
I sometimes hear the complaint that using dependency injection means a lot of extra code to do the same thing as before.
It's true. Having a call like this, where the class manages its own dependencies internally.
In the examples above, our Factory
class only creates a single newInstance()
of an object. If we regularly create collections of objects, it may be reasonable to add a newCollection()
method to our Factory
. For example, given our ItemFactory above, we may do something like the following:
We may go so far as to create an ItemCollection
class for the collection instead of using an array. If so, it would be reasonable to use a new
keyword inside our ItemFactory
to create the ItemCollection
instance. (The ItemCollection
class is omitted here).
All the dependency injection we have been doing so far has been manual injection, where we create the dependencies ourselves and then inject them as we create the objects we need. This can be a tedious process. Who wants to create a Db
object over and over again just so it can be injected into a variety of Gateway
classes? Isn't there some way to automate that?
Using a Container
brings distinct advantages:
Container
can house a Db
instance that only gets created when we ask the Container
for a database connection; the connection is created once and then reused over and over again.Container
, where objects that need multiple services for their constructor parameters can retrieve those services from the Container
inside their own creation logic.But using a Container
has disadvantages as well:
Container
used as a Service Locator replaces our global
variables with a fancy new toy that has many of the same problems as global
. The Container
hides dependencies because it is called only from inside the class that needs dependencies.