Domanda

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?

È stato utile?

Soluzione

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top