I am experimenting with PHP+WP the very first time. I intend to use WP plugin hooks. As a C++ programmer I also intend to put all my code into classes. Currently I am kind of stuck with the following snippet that should install a WP plugin hook:
class SettingsHandler
{
public function __construct()
{
add_filter('plugin_action_links', array($this, 'AddSettingsLink'), 10, 2);
}
private function AddSettingsLink($links, $file)
{
if ($file==plugin_basename(__FILE__))
{
$settings_link = '<a href="options-general.php?page=options_page">Settings</a>';
array_unshift($links, $settings_link);
}
return $links;
}
}
$settingsHandler = new SettingsHandler();
This gives me an error message: Warning: call_user_func_array() expects parameter 1 to be a valid callback, cannot access private method SettingsHandler::AddSettingsLink() in E:\xampp\apps\wordpress\htdocs\wp-includes\plugin.php on line 199
When I switch the callback to public, the error is gone. It seems I can not use a private method as a callback in PHP/WP. This would be very bad because it reveals a lot of callback methods that should not be accessed by anyone else directly. Can I make suchs callbacks private?
I also found the following snippet which runs fine:
class a
{
public function __construct()
{
$str = " test test ";
$result = preg_replace_callback('/test/', array($this, 'callback'), $str);
echo $result;
}
private function callback($m)
{
return 'replaced';
}
}
$a = new a();
Why does the second snippet work while the first fails? Where is the difference?
The second version works because preg_match_all
is called from within the class scope and will execute the callback immediately.
But the add_filter
function only adds the callback to the global $wp_filter
array. The callbacks in that array are then called at a later stage from outside the class you defined the method in. Consequently, visibility rules apply making the callback inaccessible.
You can¹ get around this by wrapping the call to the method into an anonymous function, e.g.
public function __construct()
{
add_filter(
'plugin_action_links',
function($links, $file) {
return $this->AddSettingsLink($links, $file);
},
10,
2
);
}
However, this requires at least PHP 5.4 (see changelog in Manual) due to $this
being unavailable in the anonymous function before that version.
Another option would be to have the SettingsHandler
implement __invoke
to turn it into a Functor, e.g. you add
public function __invoke($links, $file)
{
return $this->AddSettingsLink($links, $file);
}
and change the ctor code to
add_filter('plugin_action_links', $this, 10, 2);
Since the class implements __invoke
as a public entry point, the instance can be used as callback. This way, you can keep the private stuff private.
On a side note, I'd move the code adding the filter outside the class. Having it in the ctor makes the class less testable (yeah, I know no one tests WP plugins, but still). Instead, put it into your plugin file and then include everything else required from there, e.g.
// your-plugin.php
include 'SettingsHandler.php';
add_filter('plugin_action_links', new SettingsHandler, 10, 2);
But that's up to you.
¹Note: apparently this doesn't work in Wordpress because it will try to serialize the closure somewhere along the way. In general, this a working pattern to provide private methods as callbacks.