Subscribers
Last updated
Last updated
One characteristic from the WordPress code is that anything starts with a hook callback registration.
This makes the code heavily relying on the WordPress hook API and the logic to register hook is repeated over and over leading to these block of code to be present anywhere inside the plugin:
For filters:
For actions:
This code repetition creates multiples issues.
First it is coupling the business logic from the application to the WordPress API which can be an issue if there is any change in that API.
The second issue is while unit testing as it makes testing more complex. The WordPress API is not defined in that context, and it forces to mock that logic for all hooks from the class each time a new test for the business logic is created.
To solve that it is possible to separate the actual registration from the callback function to the hook from the mapping from callback to the hook declaration.
In the same shape that solution would have two entities.
A subscriber holding the business logic and the mapping from callbacks to the hooks declarations.
An event manager that would be registering hooks to the WordPress API from a list of mapping from callbacks to the hooks.
Now that the overall picture is given it is now time to go into the implementation details.
For the mapping an array will be using following that format.
Each key from the array will the name of the hook to register.
Each value from the array will be an array for each callback linked to that hook. Each callback being defined by three elements:
the name from the callback method.
the priority of that callback.
the number of parameters from the callback.
To summarize it will have the following shape.
This is this data that will the interface for callback and hooks mapping between the subscribers and the event manager.
Note: It is not necessary to indicate if the hook is a filter or an action inside the mapping.
This is due to the fact that for WordPress actions are filters that return nothing. Due to that it is possible to register an action callback as a filter callback, and it will be still called while using do_action
method.
The event manager system translates to that classes.
With the following code for SubscriberInterface
interface.
For the Subscriber
it will have to implement the get_subscribed_events
from the SubscriberInterface
interface to return the mapping from callbacks to their respective hooks.
Finally, the last step is to implement the event manager.
First the method add_callback
will have to implemented to register a callback. To register hooks it is possible to use only the function add_filter
as for WordPress actions are in fact filters that returns nothing.
Once the method add_callback
is created then the wiring between the add_subscriber
method and the callback registration needs to be created to register all callbacks for each hook when a subscriber is passed.
Finally, the last step is to wire everything inside the main plugin PHP file.
This way the business logic is now decoupled from the WordPress API and in case future changes are needed to be related to that it would impact only the event manager and not anymore the business logic.
In the same way it is also possible to unit test the business logic without having to mock add_filter
and add_action
functions.
However, the definition from the subscriber is verbose and needs to be simplified to make it easier for the developer and less error-prone.
A good interface would be using a docblock as following and abstract the hook registration.
To achieve that two steps would have to be added to the previous process.
First a detection from the hooks inside the doc-blocks from each method from subscribers.
Second an adapter to make the new subscriber type able to work with the event manager which requires an SubscriberInterface
type to register the callbacks.
With the following code for the SubscriberAdapter
.
And the following logic for SubscriberAdapterFactory
.
Once the two parts are implemented then it is time to add it to the previous logic to load the new subscribers.
With that new logic it is possible to load subscribers using doc-blocks for registering hooks.
However, even now subscribers are more pleasant to work with a major issue is still present. Currently, all subscribers will be loaded on the initial load from the plugin even they are not used.
For a small plugin that is not an issue but when it is going to grow then this will become a performance issue that needs to be tackled.
For that a lazy load from the subscribers will have to be implements, so they will be loaded just in time when they are needed.
To achieve this the SubscriberAdapter
class will have to become a proxy class which will handle the lazy load logic from the subscriber the following way.
Which is going to translate to the following implementation.
And the following modification inside SubscriberAdapterFactory
which going to be renamed SubscriberProxyFactory
.
Now the performance issue is finally addressed the subscribers are finally completed and allow clean code while not making the developer experience or performance any worse.