TL;DR: I find the using a registry, subscribers, and services very useful when building backend-centric plugins and utilities for WordPress. This post walks through how to do it.
After working in with design patterns, object-oriented programming, and WordPress for years, common ways of solving problems are bound to arise.
This is how we got object-oriented design patterns to begin with, so maybe this is a WordPress-centric variation of that.
Though I’ve written about things such as registries in previous articles (and ones that are not that old even), it’s never a bad idea to revisit the same topic especially when there’s something to continue to add to the previous take.
A Registry, Subscribers, and Services
Everything described below is to be understood within the context of the WordPress plugin. That is, this isn’t meant to be read as a way to work with any other frameworks, languages, applications, or when using it with any other patterns.
Remember that when reading this.
Anyway, the general idea behind the combination of these object types if the following:
- The registry handles all of the subscribers,
- The subscribers listen for hooks within WordPress (those that exist or even custom hooks),
- The services do the actual work whenever the subscribe dispatches them.
The purpose being there’s a single place to register the classes responsible for dispatching the work. That’s it.
Further, this also makes it easy to keep things separate so that if you want to test your services in isolation, it’s much easier because they aren’t necessarily coupled tightly to WordPress. And if they are, then you can mock the data that needs to be passed into a given function and then evaluate the result.
This isn’t an article about testing, though, so back to the actual classes.
Registry
By definition, the purpose of a registry is to keep track of things. When it comes to implementing this pattern in WordPress, the idea is that the registry can keep track of subscribers (which I’ll define later in this article).
Further, the idea is that when the time comes, which will likely different for however your plugin is structured, all of the subscribers will be instantiates. To that point, though, you’re likely going to want to do it early in the WordPress lifecycle.
That said, here’s an example of how to the code for registering the subscribers:
private $subscribers = [ AssetSubscriber::class, // ... DeletedUserSubscriber::class, ];
Next, here’s a function for instantiating the subscribers.
public function run() { array_map( function ($subscriber) { (new $subscriber())->subscribe(); }, $this->subscribers ); }
These blocks can be part of the same function or they can be separate depending on your needs.
Subscribers
As mentioned, subscribers are the way to:
- Listen for a certain hook in WordPress
- Dispatch a Service to do whatever work is intended for the given hook.
So assume for a moment you want to do something whenever a user is deleted. You want to instantiate a service via the subscriber whenever this hook happens.
As an example:
class DeletedUserSubscriber { public function subscribe() { (new DeletedUserService())->add('delete_user'); } }
Note the subscriber is aware of the service (though it maintains no dependency on it as its simply an intermediary between WordPress and the service) and specifies the hook on the service that its instantiating.
Services
Finally, services are the objects who do all of the heavy lifting in a plugin. This means that if they need to read or write to the database, the file system, the network, process data, etc., it all happens within their context.
They may be aware of other classes, they may not be. They may implement an interface or an abstract class or not. That’s really beyond the scope of this post. But the point is that, using the hook from above as an example, if you want to do something when a user is deleted, you do it within the service.
For example:
class DeletedUserService { public function add(string $hook) { add_action($hook, [$this, 'deletedUser'], 99, 1); } public function deletedUser(int $userId) { $user = get_userdata($userId); if (false === $user) { return; } // Do work with the user that's being deleted. } }
And that’s the end of it. Once the service runs, control will be returned to WordPress and the application will continue execution as normal.
All Together Now
Assuming you have a bootstrap file for your plugin, which most do as this is where the required plugin is defined, an autoloader is required, and instantiation of the plugin itself occurs.
If you’re interesting in seeing a more complete solution that demonstrates how to use the above code in a practical setting, let me know on Twitter. That way, I’ll know to go ahead and draft up another article. 🙂