I’m facing an issue that I can’t seem to figure out.
I have a plugin where it has filters that lets you change modify some of its data.
I have a simply class method like this inside the plugin:
public static function getAllowedColors(){
$colors = array(
'green',
'blue',
);
return apply_filters('my_allowed_colors', $colors);
}
This method is used throughout the plugin like this Colors::getAllowedColors()
but it should be filterable so third party plugins or devs can change it.
The issue is that I created and activated a small custom plugin, that doesn’t have any classes or anything..just the main plugin file with the required plugin headers and in that file, I did the usual:
function add_allowed_color($colors){
$colors[] = 'black';
return $colors;
}
add_filter('my_allowed_colors', 'add_allowed_color');
For some reason the new color never gets added when getAllowedColors()
is called…
I added a dump statement inside the getAllowedColors()
method, and then a dump statement inside the small custom plugin main plugin file (where the add_filter exists) and I can see that getAllowedColors
is being called before the small custom plugin.
I’m aware that add_filter
needs to be fired before apply_filters
so that your changes can take effect…but I’m not sure how exactly to make sure that my little custom plugin always runs BEFORE the main plugin with all its classes and functionality.
I stumbled across this answer for a similar issue: https://stackoverflow.com/a/19279650/4484799 but it wouldn’t be applicable in this case since this getAllowedColors()
is a utility method that gets called when needed throughout the plugin.
In the small custom plugin I also tried adjusting the code to this:
add_action('after_setup_theme', function(){
function add_allowed_color($colors){
$colors[] = 'black';
return $colors;
}
add_filter('my_allowed_colors', 'add_allowed_color');
});
Though this is valid PHP, and if I called add_allowed_color('test')
at the bottom of its definition, it returns test
, the filter however still doesn’t work.
I want to know how I can make use of this filter? What is preventing it from working? I’ve used add_filter
several times before between several different plugins especially WooCommerce which has a ton of filters but I can’t figure out why this isn’t working
Any help is appreciated
2
Answers
@ChrisHaas has it right. In your small custom plugin, add your filter from a ‘plugins_loaded’ action handler instead of the one you used. ‘plugins_loaded’ doesn’t fire until everything is initialized with all the plugins in use.
I’m going to start by saying something you already know: WordPress is extended by hooks.
As I said, I know you know this, but I’d further clarify it by saying: WordPress should only be extended by hooks.
What I mean by that is that any code not run in a hook should also make no assumptions about the state of WordPress.
Based on comments by the OP, they have a singleton or controller or similar that boots up when their plugin entry file is executed. This is a very common pattern for plugins, and by itself isn’t necessarily right or wrong as long as this code only does PHP things such as instantiating classes, registering autoloaders or creating global functions, or registers WordPress actions/filters. If this code does anything else specific to WordPress, that’s where trouble can creep in.
The reason for the trouble is that you have no guarantee that other plugins have been loaded, and it is absolutely certain that the theme hasn’t been either. And because these haven’t been loaded, they haven’t had a chance to register their filters.
Even something as simple as
get_option
has at least six hooks that could be called in its lifecycle, and the simple translation function__
has two. Also, "simple" functions such asget_home_url
still callget_option
behind the scenes. Using any of these functions outside of a hook could lead to unexpected results.As a developer it really helps to understand the load order, at least a high level:
For each of the last four in that list, once that specific step is completed there is also a corresponding hook:
muplugins_loaded
plugins_loaded
after_setup_theme
init
If you run code that interacts with WordPress in any way (beyond filters) before the corresponding hook runs, you should assume other code at that level hasn’t run. For instance, if you are writing a plugin that executes code on entry, that means
plugins_loaded
,after_setup_theme
andinit
haven’t run, and it is totally possible that other plugins and themes haven’t registered any hooks. Similarly, if your plugin runs code in theplugins_loaded
loaded hook, then it should be assumed that the theme hasn’t been booted yet so it hasn’t registered any hooks.Sidenote: Plugins should not register the
muplugins_loaded
hook, and themes should not register theplugins_loaded
hook because by the time registration happens, the hook has already run. I’ve definitely seen this happen several times in developer’s code.tl;dr
Unless you need something more specific, the vast majority of code in a plugin can be invoked via the
init
hook because at this point the bulk of WordPress has been loaded.Further, there’s usually no advantage of creating your singleton "sooner than later". However, if your singleton is the one that does all the additional hook registration (another common pattern I see), it might be worth moving that code so that the singleton can be used for its instance methods only by the code that needs it.
Edit
I’ll amend this by saying that sometimes things appear to work depending on the plugin load order which is generally alphabetical.
If you have plugins A and B, and plugin A happens to load before B, then it will be able to register its hooks before B needs them.
This should only be considered working by coincidence however. The plugin load order can actually be changed, or you or the other plugin author might rename your plugin some day.