As you use object-oriented programming with WordPress more and more, you’re going to encounter new sets of problems. These problems aren’t as simple as the ones that you encountered when you started. But that’s also good news. It also means that you’re making progress.
One set of problems that you start to encounter as you advance in your use of object-oriented programming deal with scaling. As your code has more and more classes, it becomes a challenge to assemble these classes together. That’s why in the past we looked at how to design a class whose job was to do that.
But there are other problems that come with having a lot of classes in your code. It’s how much your classes depend on other classes. This is what we call coupling in programming. (Not just object-oriented programming.)
To help with coupling, a famous software engineer (Robert C. Martin who’s also known as Uncle Bob) created the dependency inversion principle. It helps you decouple your classes by making dependencies external to them. (There’s more to it than that. But this is one of the core elements of the dependency inversion principle.)
This reduces coupling but creates a new problem as well. That problem is that you can’t initialize objects inside your class anymore. You have to pass them to your own object through its constructor or some other method like a setter method.
This makes it even harder to assemble your classes together. This is why software engineers created dependency injection. It’s a programming technique that helps solve this problem.
Most web application frameworks use it in some form or another. But that’s not the case with WordPress since a lot of its code isn’t object-oriented. That said, if you want to build an object-oriented plugin for WordPress, it’s pretty much mandatory. Lucky for us, it’s not that hard to use dependency injection in your own code!
A small warning about PHP versions
While it’s not recommended anymore, WordPress still has a minimum requirement of PHP 5.2.4. And, while it’s not impossible to use dependency injection with PHP 5.2, it’s a lot harder. For the sake of keeping our implementation of dependency injection simple, we’re going to use a specific feature from PHP 5.3.
That feature is anonymous functions. Anonymous functions as their name implies are functions without a name. (And thus anonymous!) We’ll look at why we want to use anonymous functions to implement dependency injection a bit later.
This was just a small warning before we get further into this article. If your code has to support PHP 5.2, you won’t be able to use the code that you’ll see in this article. That said, dependency injection is still a super important concept in modern programming. (So you’ll still learn something useful!)
Going over some concepts
Before we can begin, let’s go over some important concepts. They’ll help you make sense of what we’re doing throughout the rest of the article.
Dependency injection explained
First, there’s dependency injection. (The topic of this article!) We mentioned earlier that it’s a programming technique. But that’s a pretty vague description. It doesn’t tell us that much about it.
The story behind dependency injection begins with the dependency inversion principle. Without it, there would be no need for dependency injection. That’s because, as we explained in the introduction, the dependency inversion principle forces you to move your class’s dependencies outside of it.
You want to do that because it reduces the amount of coupling that your class has. And one way that the dependency inversion principle achieves that is by removing the instantiation of new objects from your class. Instead of doing that, you need to pass those objects to your class using a constructor or a method.
But your class still needs to receive its objects from somewhere. If you can’t do it inside your class, where can you do it? Well, as you can guess, that’s the question that dependency injection wants to answer!
Dependency injection is this idea that there should be a system that handles these objects that your class depends on. This system would inject those objects (thus the name dependency injection!) into your class. It can do either by constructing the object and passing it its dependencies. Or it can use methods that you’ve created for that purpose like setter methods. We call those constructor injection and setter injection respectively.
On service classes and singletons
But dependency injection isn’t the only concept that we need to go over. There’s another important one that you need to understand with dependency injection. It’s the idea of a service.
A service is a class that offers a specific functionality (or service!) that your application depends on. At a glance, there’s no obvious difference between a class that’s a service and one that’s not. That’s due to the fact that what makes a service special and worth discussing is how you instantiate it.
A service is often a class that you want to instantiate only once. But why would you want to instantiate a service class only once? It’s because that service object represents the service for the entire application.
If a class needs to use that service, it really just wants to use that one service object. This is why it’s common for people new to object-oriented programming to build their service classes using the singleton pattern. This helps them enforce this single object requirement for service objects.
This is also why a lot of WordPress developers end up designing all their classes as singletons. Most of the classes that they create are service classes. And they feel compelled to use the singleton pattern to ensure they get created once.
This is why we’re seeing the concept of a service class. Being able to create service objects is an important part of any dependency injection system. This means that, by implementing your own dependency injection system, you should be able to move beyond the use of singletons. (Yay!)
Using dependency injection
Now that we’ve gone over the concepts surrounding dependency injection, we can start looking at how to use it. As we mentioned earlier, dependency injection relies on a system to inject our objects. This system can be large and complex with a lot of classes. But it can also be small with a single class doing all the work.
As you can guess, we’re not interested in building a large dependency injection system. We’re going to keep this simple and build one class to handle dependency injection for us. We call this class a container.
Building a container class
Alright, so let’s begin building our MyPlugin_Container
class! As usual, we’re going to start with an empty class. You can find it below:
class MyPlugin_Container { }
Adding some storage
But a container wouldn’t be a container without some way of storing things inside of it. The easiest way to do that is with an array. So let’s add that to our MyPlugin_Container
class.
class MyPlugin_Container { /** * Values stored inside the container. * * @var array */ private $values; /** * Constructor. * * @param array $values */ public function __construct(array $values = array()) { $this->values = $values; } // ... }
Above you can see that we added the values
properties to our MyPlugin_Container
class. This is the class property that we’ll use to store everything in our container. We also need it to be an array.
That’s why we also added a constructor to our MyPlugin_Container
class. The constructor has values
as a parameter which we force to be an array using type hinting. We also give it an empty as a default value. This ensures that, when we assign it to the values
property, it’s always an array.
Accessing our storage
Now, the values
property that added is private. That means that there’s no way for other developers to access the values stored in the container. As with most things in programming, there are various ways that we can do this. (Wouldn’t it just be more convenient if there was always one good answer!?)
Most of us are familiar with arrays, so it would make a lot of sense if our MyPlugin_Container
class behaved like one. This is something that isn’t too complicated to do. We just need our class to implement a specific PHP interface.
class MyPlugin_Container implements ArrayAccess { }
The interface in question is the ArrayAccess
interface. It’s an interface that lets you access objects like you would an array. The interface requires that we implement four methods: offsetExists
, offsetGet
, offsetSet
and offsetUnset
.
Implementing the interface
Let’s look at how we’ll implement these methods in our MyPlugin_Container
class. That said, we won’t implement them all right away. We’ll start with three of the four methods that the ArrayAccess
interface wants.
class MyPlugin_Container implements ArrayAccess { // ... /** * Checks if there's a value in the container for the given key. * * @param mixed $key * * @return bool */ public function offsetExists($key) { return array_key_exists($key, $this->values); } /** * Sets a value inside of the container. * * @param mixed $key * @param mixed $value */ public function offsetSet($key, $value) { $this->values[$key] = $value; } /** * Unset the value in the container for the given key. * * @param mixed $key */ public function offsetUnset($key) { unset($this->values[$key]); } // ... }
You can see the three methods that we implemented in our MyPlugin_Container
class above. The offsetGet
method is the method that we left out. It’s a much more important component of our dependency injection container so we’re going to cover it a bit later.
The first method that we added was the offsetExists
method. PHP will call it when you do an isset
check on your object like you would on an array. The method itself returns the result of the call to the array_key_exists
function. We pass it the key
value that you used with the object and our values
array.
The second method is offsetSet
. This is the method that PHP will call when you try to assign a value to your object like you would with an array. The method has two parameters: key
and value
. We just use the key
value as the index for our values
array and value
is the value that we assign to it.
The offsetUnset
method has the opposite effect of the offsetSet
method. PHP will call this method when you want to remove a value from your object. This happens when you use the unset
function on your object like you would on an array. We just do the same call to the unset
function on our values
array.
Going back to anonymous functions and services
So why haven’t didn’t we implement the offsetGet
method with the others? Well, that’s because it’s where the magic inside of our MyPlugin_Container
class happens! But before we can look behind the scenes at how that magic works, we need to take a step back.
That’s because we need to talk more about two concepts that we talked about earlier. These two concepts are anonymous functions and services. It’s time to look how they fit into the larger picture of dependency injection.
The link between anonymous functions and dependency injection
Let’s start with anonymous functions. Why are anonymous functions so critical to dependency injection? It’s because they allow us to encapsulate the creation of an object.
$myplugin_class = function () { return new MyPlugin_Class(); };
Here’s an example of an anonymous function. The function returns a new instance of the MyPlugin_Class
class. (To keep things simple for now, we’ll say that the MyPlugin_Class
class constructor doesn’t take any arguments.)
$myplugin_object1 = $myplugin_class(); $myplugin_object2 = $myplugin_class();
We can then use the variable containing our anonymous function to create new objects. Both myplugin_object1
and myplugin_object2
are different instances of MyPlugin_Class. So, if you did $myplugin_object1 === $myplugin_object2
, it would return false
.
We call the ===
comparision operator an identical check. With objects, it will only return true
if the objects share the same reference. Since we created two different objects with our anonymous function, they aren’t identical. That’s why $myplugin_object1 === $myplugin_object2
returns false
.
Using anonymous functions for services
But, if we want to use an anonymous function to create a service object, we need our two objects to be identical. This means that we need our anonymous function to return the same service object every time. So let’s create an anonymous function that creates a service object instead of a new one each time.
$myplugin_class_service = function () { static $object; if (!$object instanceof MyPlugin_Class) { $object = new MyPlugin_Class(); } return $object; };
The key to creating a service object with an anonymous function is to use the static
keyword. If you’ve designed a singleton before, there’s a good chance that the structure of the anonymous function is familiar to you. It’s pretty much the same as the one you’d see in a singleton.
We start by defining an object
variable using the static
keyword. We then use a guard clause to check if we’ve assigned a value to our object
variable. We do this by using the instanceof
to check if object
is an instance of MyPlugin_Class
.
$myplugin_object1 = $myplugin_class_service(); $myplugin_object2 = $myplugin_class_service();
Now, let’s go back to code that we used earlier to create to create myplugin_object1
and myplugin_object2
. We changed it to use our new myplugin_class_service
anonymous function to create the objects. In this scenario, both myplugin_object1
and myplugin_object2
will be identical. If you did $myplugin_object1 === $myplugin_object2
, it would return true
.
Getting objects from our container
Alright, so now we can go back to our missing offsetGet
method! The goal of our MyPlugin_Container
class is to be able to store anonymous functions like the ones that we just saw. So the code that we had earlier would become something like this:
$container['myplugin_class'] = function () { return new MyPlugin_Class(); }; $myplugin_object = $container['myplugin_class'];
The issue with this example is that our dependency injection container can also store other things. It doesn’t just store anonymous functions. So we need a way for it to know if we’re trying to create an object with an anonymous function.
$container['myplugin_class'] = function () { return new MyPlugin_Class(); }; $container['myplugin_variable'] = 'variable'; $myplugin_object = $container['myplugin_class']; $myplugin_variable = $container['myplugin_variable'];
Our offsetGet
method needs to work for both myplugin_object
and myplugin_variable
. For myplugin_object
, would return a new instance of the MyPlugin_Class
class. And, for myplugin_variable
, it needs to just return the value that we stored.
Anonymous functions are objects
To do that, we’re going to use a lesser known fact about anonymous functions. In PHP, anonymous functions are actually instances of the Closure
class. This means that we can detect if we stored anonymous function by using the instanceof
operator.
class MyPlugin_Container implements ArrayAccess { // ... public function offsetGet($key) { if (!array_key_exists($key, $this->values)) { return new WP_Error('no_value_found', sprintf('Container doesn\'t have a value stored for the "%s" key.', $key)); } return $this->values[$key] instanceof Closure ? $this->values[$key]() : $this->values[$key]; } // ... }
So here’s our offsetGet
method at last! For all the explanations that we went through, it’s still a small method. That said, there are still a few more things going on that are worth explaining.
First, we have a guard clause. It checks if we even have the given key
in our values
array. We perform the check using the array_key_exists
function like we did in the offsetExists
method.
If the key
isn’t present in our values
array, we return a WP_Error
object. (You could also throw a InvalidArgumentException
if you prefer!) Otherwise, we return the value stored in our dependency injection container for the given key
.
We use the ternary operator to do that. We use it to determine if we stored an anonymous function or not. If we have an instance of a Closure
, we call our stored value as a function. (Like we did earlier!) Otherwise, we just return it as is.
Storing a service object
At this point, our MyPlugin_Container
is pretty well rounded. But there’s still one more element that we haven’t covered yet. It’s how to create service objects using our dependency injection container.
We saw earlier that we can create service objects using an anonymous function and the static
keyword. But, in our example, we also created manually by doing new MyPlugin_Class()
. This doesn’t work with the way that we create objects with our dependency injection container.
With our dependency injection container, we use anonymous functions to create our objects. This means that our anonymous function for creating service objects should work with them as well. We can do this by making the anonymous function for creating service objects reusable.
class MyPlugin_Container implements ArrayAccess { // ... public function service(Closure $closure) { return function () use ($closure) { static $object; if (null === $object) { $object = $closure(); } return $object; }; } // ... }
This is the goal of the service
method above. The method has a single parameter: closure
. We also enforce that closure
is a Closure
object using a type hint.
The method itself just returns an anonymous function similar to the one we saw earlier for service objects. We pass this closure
to another anonymous function using the use
language construct. (We’re going full closure inception here!) The anonymous function then uses that closure
if we didn’t create that object already.
$container['service'] = $container->service(function () { return new MyPlugin_Class(); });
And here is how we’d use our service
method with our container
. We store the anonymous function returned by the service
method inside our dependency injection container. And we pass the service
method the anonymous function that creates our MyPlugin_Class
object.
Injecting dependencies with our container
Alright, so, at this point, our MyPlugin_Container
class is pretty much done. That said, to keep things simple, we worked under an important assumption. It’s that the constructor of our MyPlugin_Class
(or any other class) doesn’t take any arguments.
But that’s not how things work in practice. Most classes have constructors that require that you pass them one or more arguments. That’s the point of dependency injection after all! So we need to tweak our MyPlugin_Container
class so that we can inject dependencies when we create our objects.
The way we do this is pretty simple. All that we have to do is pass our MyPlugin_Container
object to our anonymous functions. If we did that, we could create anonymous functions that look like this:
$container['myplugin_class'] = function (MyPlugin_Container $container) { return new MyPlugin_Class($container['service']); };
You’ll notice that we added our MyPlugin_Container
as a parameter of our anonymous function. This lets us use our dependency injection container inside the anonymous function. And that way we can use it to inject a dependency into our MyPlugin_Class
.
This is the trick to doing dependency injection with this type of dependency injection container. We pass the dependency injection container to the anonymous functions creating our objects. And they use it to inject the dependencies that they need to create the object in question.
Modifying our container class
Alright! So the only thing left to do is to modify our MyPlugin_Container
class. We need it to pass itself to our anonymous functions when it calls them. This isn’t too hard to do because there are only two places where this happens in our MyPlugin_Container
class.
class MyPlugin_Container implements ArrayAccess { // ... public function offsetGet($key) { if (!array_key_exists($key, $this->values)) { return new WP_Error('no_value_found', sprintf('Container doesn\'t have a value stored for the "%s" key.', $key)); } return $this->values[$key] instanceof Closure ? $this->values[$key]($this) : $this->values[$key]; } // ... public function service(Closure $closure) { return function (MyPlugin_Container $container) use ($closure) { static $object; if (null === $object) { $object = $closure($container); } return $object; }; } }
Above is our modified MyPlugin_Container
class. We made two small changes to it. Those were at the two locations where we interacted with anonymous functions.
The first change is in the offsetGet
method. We now pass the anonymous function a copy of the object when we have the values
array stores a Closure
. We do this by passing the this
pseudo-variable representing our object to the anonymous function.
The other change that we did is to the service
method. Here, we changed the anonymous function that the method returns. It now has a container
parameter like the anonymous function that we saw earlier.
Organizing the configuration of our container
So, at this point, our MyPlugin_Container
class is fully functional. You can use it in your plugin to create that objects that it needs. That said, there’s still a small improvement that we can make to it.
This improvement relates to how we configure our dependency injection container. So far, our dependency injection container configuration examples have been quite simple. We were always configuring a single service or object.
But this isn’t how things will play out in practice. In reality, you’ll have dozens of classes, parameters and services. And you’ll want to configure them all inside your dependency injection container. This can get messy really fast if you don’t have a way to organize things. (You always want to keep your files organized!)
This is the final improvement that we’ll make to our MyPlugin_Container
class. We’ll create a way to organize the configuration of the container. That way you won’t be stuck with a single large file with all the container configurations in it.
Creating a configuration contract
The easiest way to do this is with an interface. Why an interface? Because we’re not interested in creating an object for configuring our dependency injection container.
What we really want is to create a contract that tells other developers how they can configure it. This gives them more flexibility when trying to organize how they want to configure the container. It also makes sense because, as you’ll see soon, these configuration class don’t have any code that we want to reuse.
interface MyPlugin_ContainerConfigurationInterface { /** * Modifies the given dependency injection container. * * @param MyPlugin_Container $container */ public function modify(MyPlugin_Container $container); }
But first, let’s look at the interface itself! Above is the code for our MyPlugin_ContainerConfigurationInterface
interface. The interface has a single method: modify
.
The role of the modify
method in our MyPlugin_ContainerConfigurationInterface
interface contract is pretty explicit. It expects to receive the MyPlugin_Container
dependency injection container that we’re configuring. And it’ll make modifications to it.
Implementing our configuration contract
Now that we have our MyPlugin_ContainerConfigurationInterface
interface, we can look at how to use it. Let’s imagine that our plugin has a specific component or system like an event manager or a router. A configuration class would look something like this:
class MyPlugin_ComponentConfiguration implements MyPlugin_ContainerConfigurationInterface { public function modify(MyPlugin_Container $container) { $container['component_parameter'] = get_option('myplugin_component_parameter', 'default_value'); $container['component_service'] = $container->service(function (MyPlugin_Container $container) { return new MyPlugin_ComponentClass($container['component_parameter']); }); } }
We have the MyPlugin_ComponentConfiguration
class for our imaginary component. It implements the MyPlugin_ContainerConfigurationInterface
interface that we created in the previous section. This means that the class also has the modify
method required by the interface contract.
The modify
method itself adds two things to our dependency injection container. First, it adds the component_parameter
which is an option stored in the WordPress database. We fetch it using the get_option
function.
We then use that component_parameter
parameter when creating our component_service
. The component_service
uses the service
method that we created earlier. We pass it an anonymous function that creates a MyPlugin_ComponentClass
object using the component_parameter
parameter.
On creating a WordPress configuration class
You might be wondering about the use of the get_option
function in the modify
method. Is it a good idea to use WordPress function like that when configuring a dependency injection container? The answer is yes! It’s actually a great use of a dependency injection container.
That’s because it’s a real challenge to design classes that don’t use WordPress functions. In fact, it’s the biggest struggle that WordPress developers have when they’re trying to learn to use object-oriented programming. But with a dependency injection container, we can decouple your code from WordPress code.
class MyPlugin_WordPressConfiguration implements MyPlugin_ContainerConfigurationInterface { public function modify(MyPlugin_Container $container) { $container['wordpress_database'] = $container->service(function (MyPlugin_Container $container) { global $wpdb; return $wpdb; }); $container['wordpress_query'] = function (MyPlugin_Container $container) { return new WP_Query(); }; $container['wordpress_user'] = wp_get_current_user(); } }
The MyPlugin_WordPressConfiguration
class above is an example of what we mean by that. We use it to modify our dependency injection container with things that would normally couple us to WordPress code. You can add WordPress related classes, parameters and services.
The MyPlugin_WordPressConfiguration
class has an example of each. First, we added the wpdb
class as a service. Next, we added an anonymous function which creates WP_Query
object each time that you ask for it. The last one stores the current user as returned by the wp_get_current_user
function.
There’s really no limit to what you can store inside your dependency injection container. Any time that you need something from WordPress, you should just add it to your dependency injection container. That way you won’t need to use WordPress functions inside your classes anymore! (Well, at least, not as much!)
Configuring a container using configuration objects
So, at this point, we broke down the configuration of our dependency injection container into configuration classes. But we still can’t use these configuration objects with our MyPlugin_Container
class. We need to add a way to pass to objects to it.
class MyPlugin_Container implements ArrayAccess { // ... /** * Configure the container using the given container configuration objects. * * @param array $configurations */ public function configure(array $configurations) { foreach ($configurations as $configuration) { $configuration->modify($this); } } // ... }
That’s the goal of the configure
method shown above. The method has a single parameter: configurations
. configurations
is an array of configuration objects implementing the MyPlugin_ContainerConfigurationInterface
interface.
We use a foreach
loop to go through each configuration
object. The loop itself just calls the configuration
object’s modify
method. And we pass it the this
pseudo-variable representing our container as the container
argument.
It’s worth noting that we’re not doing any validation on the content of the configurations
array. We assume that the array will only contain objects implementing the MyPlugin_ContainerConfigurationInterface
. But it’s possible that you won’t feel comfortable with that. If that’s the case, you can add some validation inside the foreach
loop to check if configuration
variable implements the interface. (The easiest way to do that is with the instanceof
operator.)
$container = new MyPlugin_Container(); $container->configure(array( new MyPlugin_ComponentConfiguration(), new MyPlugin_WordPressConfiguration(), ));
This brings us to how we’d use the configure
method in practice. This is what this last code sample shows. We initialized our MyPlugin_Container
class and then called the configure
method. As an argument, we passed it an array with the two configuration objects that we created earlier.
A requirement with object-oriented programming
And this is how you can use dependency injection with WordPress! The whole thing revolves around the MyPlugin_Container
class that we created. You can find the complete code for it below:
class MyPlugin_Container implements ArrayAccess { /** * Values stored inside the container. * * @var array */ private $values; /** * Constructor. * * @param array $values */ public function __construct(array $values = array()) { $this->values = $values; } /** * Configure the container using the given container configuration objects. * * @param array $configurations */ public function configure(array $configurations) { foreach ($configurations as $configuration) { $configuration->modify($this); } } /** * Checks if there's a value in the container for the given key. * * @param mixed $key * * @return bool */ public function offsetExists($key) { return array_key_exists($key, $this->values); } /** * Get a value from the container. * * @param mixed $key * * @return mixed|WP_Error */ public function offsetGet($key) { if (!array_key_exists($key, $this->values)) { return new WP_Error('no_value_found', sprintf('Container doesn\'t have a value stored for the "%s" key.', $key)); } return $this->values[$key] instanceof Closure ? $this->values[$key]($this) : $this->values[$key]; } /** * Sets a value inside of the container. * * @param mixed $key * @param mixed $value */ public function offsetSet($key, $value) { $this->values[$key] = $value; } /** * Unset the value in the container for the given key. * * @param mixed $key */ public function offsetUnset($key) { unset($this->values[$key]); } /** * Creates a closure used for creating a service using the given callable. * * @param Closure $closure * * @return Closure */ public function service(Closure $closure) { return function (MyPlugin_Container $container) use ($closure) { static $object; if (null === $object) { $object = $closure($container); } return $object; }; } }
This class might not seem like much to you. Most of it is just documentation anyways. (Because documentation is important!) But, as you use it, you’ll come to realize how important it is to your success with object-oriented programming.
That’s because, as we mentioned in the introduction, you’re going to create more and more classes as time goes on. It’s the nature of object-oriented programming. (And a legitimate complaint that some developers have with it.) Managing all these classes in a sane way will become a real issue.
This is when using a dependency injection container will go from something that’s nice-to-have to a must-have. It’s a good thing that using one isn’t that complicated. You can just reuse this class and start building some configuration classes of your own.