A common pattern in WordPress development is to have one “Plugin” class, that implements the singleton pattern, and has an instance of each class the plugin uses sets in properties. The first time the singleton is called, all of the classes are loaded and the important hooks are set. That’s a lot of responsibilities.
Modern PHP frameworks like Symfony and Laravel use a service container, based on the inversion of control principle. Many folks, myself included, like to use a container like that for WordPress development.
I wrote a post for Torque many years ago about using Pimple, Symfony’s container package, in a WordPress plugin. These days, I prefer to use Illuminate/Container, which is Laravel’s container. If you also enjoy Laravel’s developer experience and want to experience it in a WordPress plugin, using Laravel’s container for your plugin’s main class is a great way to start.
This post is about implementing Laravel’s container as the main plugin class, and how that works in a WordPress plugin. I will also show how to use WordPress hooks to orchestrate service registration.
The example code in this post is the same as the code from my video on the same subject, from the Plugin Machine youtube channel, which you can watch here. There is also a shorter version that shows how to set up a plugin with Composer, as well as a PHP autoloader and all the testing, local dev and CI/ CD you will need, in a minute or two.
That video or this page in the Plugin Machine documentation can help you set up a plugin. You need one with Composer, and coComposer”s autoloader. I’ll be using PHP namespaces in this plugin. If you’re not familiar with PHP namespaces, please checkout this post on my blog I wrote about using PHP namespaces in WordPress.
Setting Up The Container In The Main Plugin Class
First, we need to install the container package with composer.
I chose to extend have the main plugin class extend Illuminate\Container:
You could decorate the container, and have an accessor function for the container, but I think that’s extra complexity in order to allow this class to have other responsibilities. I don’t love that, but it could work something like this:
Static Accessor Function For Main Plugin Class
The singleton pattern is considered an anti-pattern because it combines two responsibilities — managing state for class and managing other classes.
Instead of using that pattern, I like to create a static accessor function. It will work like the app() function works in Laravel — an easy way to access the service container. This gives me an easy way to access the container anywhere in the plugin. It also can help with service registration.
The way this function works is similar to how a static class property is used in the singleton. Instead a static function variable is used. The static scope persists between function calls, but is not global in scope.
This works, and I think will feel familiar to many plugin developers that are used to having a function that wraps the main singleton class.
Registering Services In The Container
In the last code snippet, you might have noticed the hook right after the class was instantiated. It may be tempting to set up your services here. Instead, we’ll use functions hooked to that action. This will keep things neater.
I want to show two ways of registering services. First, as an “instance”. This will make the same instance of an object accessible via the container. This is great for classes like API clients. Second, I will show how to bind a class as a factory. This will create a new object every time the service is requested from the container.
Registering An Instance
First, lets register one instance of the “ApiClient” class, using the “candles” hook:
The instance method’s first argument is the name of the service, we use a class reference here, which is the convention. You could use “api-client” if you want, but I think that requires remembering something associated with class name and the class name. I want to remember one thing, not two.
The second argument is the instance of the class.
Now I can use my “candle()” static accessor function to access ApiClient:
Registering A Factory
The second type of container binding will produce a different object every time its used. The first kind, which I showed in the last section, is the opposite — it returns the same instance of the object every time.
Now, let’s bind a “Product” class that needs the name of the product as the first argument to its constructor. That means we’ll need to pass arguments to the call to the container, which will look like this:
The array of options passed to the second argument of the “make” function from the container are passed as the second argument to the factory closure in the binding. Here is what that binding looks like:
I’m using spread syntax to pass the first key of the array as the first argument for the class constructor. What I like about this approach is that if I add a second argument to the constructor, I don’t have to update the binding. I could do it this, more explicit way, instead:
Containerize All Of The Things
Here is the video version of this post:
I hope you will try this out, let me know on Twitter if you do. If you have not already signed up for Plugin Machine, try it for this plugin and skip figuring out how to set up composer, testing, local dev and CI/CD, again.