Our 19th door in our Advent Calendar!
Hooks have been a pillar of WordPress development pretty much since WordPress exists. Even if actions and filters have quite a different scope, their internal implementation is almost identical. In this blogpost I want to give an introduction into how to deal with WordPress hooks with a focus on how to remove WordPress Hooks that use objects.
What makes a Hook
Every action or filter is made by 4 elements:
- a “hook name”, sometimes referred as “hook tag” or just “action” / “filter” or even “hook”
- a callback
- a priority
- an optional set of arguments
Internally WordPress generates also a callback identifier and its value is predictable only for named functions and static class methods. For the other three kinds of callbacks the value is based on
spl_object_hash() and so it is not predictable.
The non-predictability of the identifier causes a well-known issue with hooks that uses objects: they are hard to remove.
A deeper Look at the Issue
remove_filter are called, WordPress calculates the identifier of the callback to remove based on the arguments passed to those functions, then removes from the registered hooks for the specific tag and priority, the one that uses the callback identified with the calculated identifier.
For this reason, to be sure to succeed in removing, the best thing to do is to pass to
remove_filter the exact arguments (tag, callback and priority) that were used to add the hook.
When callbacks involves objects “exact” means “exact instance”.
The actions added in constructor can’t be removed from outside
SomeClass like this:
$this is only accessible inside
In this example:
the action added with the closure and the action added with a just-created
SomeClass instance, can’t be removed, because there’s no way to access the instance. In fact, doing something like this:
will not work, because the instance of
SomeClass passed to
remove_action is not the same instance passed to
add_action, which means
spl_objec_hash will return a different value for them. The consequence is that their identifier will be different.
The lost opportunity
For this reasons, many developers consider a bad practice to use callbacks involving objects and they suggest to always use named functions or static methods.
I personally strongly disagree with such statement when plugins are written in object oriented way.
Moreover, I think that the fact some of the functions of plugins API work well with some callback types and not with other is a flaw of the plugin API. Something that was totally understandable in 2004 when PHP was transitioning from version 4 to 5, closures and invokable objects did not existed and OOP PHP was at its first steps. But 14 years later, in my opinion, that can surely be considered a defect that had been better to address long time ago, especially considering that a big rewriting of the plugin API internals have been released just one year ago (with WP 4.7), without even discussing the possibility to address the issue in some way.
Strategies to get around the Issue “Remove WordPress Hooks”
Design plugins that make use of dynamic methods and closures and still allow third party code to remove them is possible by using a few strategies.
Strategy #1: The “enabler” filter
When there’s an object that adds hooks, it is possible to provide a filter that acts as a flag to enable or disable the hooks addiction:
With a class like this, third party code can prevent hooks to be added, instead of removing them:
This one liner not only is easy and short, it is also very readable and more explicit than multiple calls to
remove_actions , being at any affect an API to disable class functionalities.
Moreover, it is better for backward compatibility. If the next release of
SomeClass will add different hooks, by just keeping the
'some_class_enable' in place it is possible to keep compatibility with third parties that used to remove hooks: third parties don’t have to care what happen inside the
Strategy #2: Expose the object via action
When objects add hooks, it is possible to equip them with methods that enable and disable functionalities, encapsulating the related hook operations inside those methods.
After that, the same methods can trigger actions passing
$this as argument, so exposing the object to third parties which can make use of enable / disable methods.
When hooks are added in such way, third parties can do things like this:
This strategy, just like the previous, is explicit, readable, and very useful for backward compatibility. It brings major benefits when there are more methods to enable/disable features, allowing third parties to pick the exact features they need.
A variation of this strategy, make use of an external initializer that creates the class, add hooks and then expose the used instance:
In this variant enable / disable methods are not necessary because thirds party could directly remove hooks by calling
This approach favorites a cleaner design of the class, that does not have to deal with flags and related conditionals, but enforce all the methods used in hooks to be public (which might or might not be desirable) and is less flexible in term of backward compatibility: every time
initialize_some_class function changes, third parties that make use of
some_class_init hook very likely needs to change as well.
Strategy #3: Shared instance / registry pattern
The difficulty to remove hook callbacks that uses objects is to access the exact instance that is used. Pattern like shared instances and registry pattern allow to access specific object instances, and so third parties are enabled to remove hooks.
A very trivial example:
third parties can do:
This approach can be even more powerful and backward compatible if the shared instance has a dedicated API for enabling / disabling functionalities, as seen in the strategy #2.
Please note that:
- shared instances are not singletons
- even if storing an object in a global variable is at any effect a shared instance pattern implementation, it is the worst implementation one can think of.
When everything seems lost…
The strategies shown above work well for new plugins you are going to work on, but many time developers need to deal with existent plugins they did not write.
In those cases, when in the need to remove a hook, the last-resort consist in:
- access the global
$wp_filtervariable, where WordPress stores both all actions and filters
- for every callback added using an object, check the class and the method used to find the one that matches the target
- remove it when found
even if this could work, it has 2 main issues:
- it does not really work with closures: all closures are instance of the same class and have no methods, so when more closures are added to same hook at same priority, there’s no way to distinguish one closure form another
- it requires some code that needs to be done again and again for every hooks the one wants to target (it does not scale!)
How to remove WordPress Hooks with “Object Hooks Remover”
Inpsyde just released a package named “Object Hooks Remover” that provides a last-resort solution to the issue of removing hooks which uses object methods or closures.
The package provides 5 functions:
Each one has different use cases.
This function is used to remove hook callbacks that use dynamic object methods.
The object methods to remove are identified by the class and method name. An hook added like this:
can be removed like this:
When called like this, the function removes the callback no matter the priority (unlike
remove_filter) but it is possible to limit the priority to target passing it as third parameter.
Remove hooks using closures has always been incredibly tricky, because as previously said, it is hard to programmatically distinguish one closure from another.
The new Inpsyde package attempt to solve this issue, distinguishing a closure from another thanks to two characteristics:
- the object the closure is bound to (the bound object is the object that resolves as
$thisinside the closure. It is set automatically to the object the closure is declared from but can be set explicitly, see
- the closure signature (i.e. parameter names and types)
For example, a hook added like this:
can be removed like this:
The bound object to target has been identified by its class name in the example above, but having access to the exact instance of the object that declared the function, it is possible to pass the object instance as second parameter limiting the match of closures to those added by that specific instance.
This function, just like
remove_closure_hook (and unlike
remove_filter), removes matching closure(s) added to any priority, but it is possible to limit the priority to target passing it as third parameter.
remove_object_hook this function only targets static methods. Even if static class methods could be removed via
remove_filter, this function can be still useful because can remove callbacks from any priority and even without specifying a method name).
Remove hook callbacks added with a specific object instance that must be provided.
When having access to the exact instance used by some hooks, it would be possible to remove those hooks via
remove_filter, but this function can still be useful because in a single call can remove all the hooks that use the instance, no matter the method or the priority used.
No more than a shortcut for using
__invoke as method name (second parameter).
More about the package
“Object Hooks Remover” is a Composer package, which means that any plugin, MU-plugin or theme which can require the package via
inpsyde/object-hooks-remover can use (and reuse) it.
It requires PHP 7+ and it is licensed under MIT license.
And tomorrow there will be another great contribution to our Inpsyde WordPress Advent Calendar!