Friday, August 19, 2016

Understanding Namespaces in the WordPress Hook System

Hooks are a fundamental concept for WordPress developers. In previous articles on SitePoint, we've learned what hooks are and their importance, the two types of hooks: actions and filters with code examples of how they work, and an alternative way of firing actions and filters events and how to hook static and non-static class methods to actions and filters.

In this article, I will cover how to hook methods of an instantiated class (object) to actions and filters, how to integrate a namespaced class method to a hook, caveats of using namespaces in WordPress hook system and solution to them.

WordPress Hooks and Namespaces

Hooking Object Methods

Assume you were tasked by your employer to build an ad manger plugin for a large news website, to make ad insertion to news content seamless. This is how you might go about building it.

You would create an AdManager class with a number of methods that contain the various ad-networks ad-code.

class AdManager { /** * AdSense unit code. */ public function adsense() { ?> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <ins class="adsbygoogle" style="display:inline-block;width:336px;height:280px" data-ad-client="ca-pub-xxxxxxxxxxxxxxxx" data-ad-slot="6762452247"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> <?php } /** * Buysellads ad code. */ public function buysellads() { // ... } }

Say a website theme has an action called before_post_content that is fired before the post content is displayed and you want to hook the adsense method to it to display the ad before any post content. How would you go about it?

You are trying to hook the method to an action outside of the class unlike the examples we've seen in part 2 where it was done in the class constructor like so:

public function __construct() { add_action( 'before_post_content', array( $this, 'adsense' ) ); }

To hook the adsense method to the before_post_content action outside of the class (probably in the functions.php file of the active website theme) in order to display the Google AdSense ads before every post's content, you will have to replace $this with an instance of the class.

add_action( 'before_post_content', array( new AdManager(), 'adsense' ) );

And say the class includes a method that returns a singleton instance of the class.

class AdManager { // ... /** * Singleton class instance. * * @return AdManager */ public static function get_instance() { static $instance = null; if ( $instance == null ) { $instance = new self(); } return $instance; } }

Here is how the adsense method can be hooked to the before_post_content action.

add_action( 'before_post_content', array( AdManager::get_instance(), 'adsense' ) ); Namespaces

The WordPress hook system was developed at a time when there was no namespace feature in WordPress. As a result, you might find it difficult to hook a namespaced function and class method to an action and filter.

Say your AdManager class has a namespace of SitePoint\Plugin as follows.

namespace SitePoint\Plugin; class AdManager { // ... }

To hook the adsense method of the AdManager class to the before_post_content action, you must prepend the class name with the namespace like so:

add_action( 'before_post_content', array( SitePoint\Plugin\AdManager::get_instance(), 'adsense' ) );

If the class and the add_action function call is in the same PHP file namespaced by SitePoint\Plugin\, prepending the namespace to the class name is unnecessary because they are covered by the same global namespace.

Enough of class examples, let's now see a plain function.

Say you have the following namespaced function to be hooked to the wp_head action.

namespace SitePoint\Plugin; function google_site_verification() { echo '<meta name="google-site-verification" content="ytl89rlFsAzH7dWLs_U2mdlivbrr_jgV4Gq7wClHDUJ8" />'; }

Here's how it can be done with the namespace prepended to the function:

add_action( 'wp_head', 'SitePoint\Plugin\google_site_verification' ); My Namespace Horror with the Hook System

In my Admin Bar & Dashboard Access Control plugin, I registered an uninstall hook that deletes the plugin option when it's uninstalled.

Something as easy as the following lines of code shouldn't be a problem where PP_Admin_Bar_Control is the class name and on_uninstall is the method called when uninstalled.

register_uninstall_hook( __FILE__, array( 'PP_Admin_Bar_Control', 'on_uninstall' ) );

To be sure it works, I tried uninstalling the plugin to see if the plugin option will be deleted but to my surprise, I got the following error.

The plugin generated 2137 characters of unexpected output during activation.

Mind you, here is how the class and register_uninstall_hook function are defined with a namespace of ProfilePress\PP_Admin_Bar_Control.

namespace ProfilePress\PP_Admin_Bar_Control; register_uninstall_hook( __FILE__, array( 'PP_Admin_Bar_Control', 'on_uninstall' ) ); class PP_Admin_Bar_Control { // ... /** Callback to run when the uninstalled hook is called. */ public static function on_uninstall() { if ( ! current_user_can( 'activate_plugins' ) ) { return; } delete_option( 'abdc_options' ); } // ... }

Can you spot the reason why the on_uninstall class method wasn't triggered when the plugin is uninstalled?

You would think since the register_uninstall_hook function is defined under the namespace, the class should be covered by it, but that is not the case. You still need to prepend the namespace to the class as follows for it to work.

register_uninstall_hook( __FILE__, array( 'ProfilePress\PP_Admin_Bar_Control\PP_Admin_Bar_Control', 'on_uninstall' ) );

I actually had a hard time figuring out the problem. I decided I don't want to let you go through the same stress and head-banging that I went through.

Conclusion

Quirks like this make some developers cringe and stay away from WordPress. We shouldn't forget WordPress was developed at the time when PHP lacked all the language improvement and features it has now. I always try to figure out how to circumvent these quirks and then teach it to people.

I hope I have been able to demystify the hook system in WordPress. If you have any questions or contributions, please let us know in the comments.


Source: Understanding Namespaces in the WordPress Hook System

No comments:

Post a Comment