Hey WordPress, How About a WP_Plugin Class?

Okay January is my month of ideas :) Let’s talk about plugins for a moment, shall we? Actions and filters are no secret to WordPress developers, right? Say, how many times do you type something like this in your plugins or theme files:

class Some_Plugin {
    function __construct() {
        add_action( 'admin_init', array( $this, 'admin_init' ) );
    }

    function admin_init() {
        // Whatever
    }
}

If you don’t, then you can stop reading now :) I’m addressing the naming technique here, the fact that admin_init is both an action tag, and a method that you assign to that action tag. So if I’m naming all my methods after actions and filters tags in WordPress, isn’t there a way to cut down the burden of having to add_action and add_filter all the time?

I’ve been thinking of an approach that could solve this problem in both, an easy to understand way, and a compatible way. Perhaps we’re lacking a WP_Plugin class in WordPress that would take care of this (along with a bunch of other things) for us, so that we can focus on writing our plugin, and not spend time matching tags to methods.

Here’s a concept that first popped into my mind, experimental and buggy, but something to get our heads thinking about it. If your class extends the WP_Plugin class, and your method’s name starts with a double-underscore prefix (or any other convention we may choose), and matches an existing action or filter, why not run the action or filter automatically without having to add_action or add_filter?

class Some_Plugin extends WP_Plugin {
    function __admin_init() {
        // Will run during admin_init
    }

    function __the_content( $content ) {
        // Will filter the_content
        return $content;
    }
}

Again, it’s just a thought and a lot of decisions should be made before implementing something like this. I went ahead and drafted a plugin file which works well to some extent. You can change the method name convention on line 18.

So my questions to you, dear reader are: is will this make things easier for the WordPress developers? Will this mess things up? What do you think of the prefix convention? How would you propose to handle the priority dilemma? What else could go into a WP_Plugin class?

Update! It seems like Reflection is a better approach at what I’m trying to do, and separating actions from filters logic too. Thanks so much for the heads up Ryan McCue, Kaiser and Daniel Dvorkin. Here’s Ryan’s improvement over my first Gist.

Thanks for stopping by and subscribing!

50 thoughts on “Hey WordPress, How About a WP_Plugin Class?

    • Kaiser, I can’t really understand what it’s doing, maybe I’m lacking some context, but I believe there should be an easier way :) Thanks for your comment!

    • Konstantin, this is part of a repo. It’s the correct way to give your Plugin the ability to do stuff in activate/deactivate/uninstall. You can take a look at the hooks in the repos init file. Notices and Errors as well as a dev mode are also included. What i wanted to say is that it would be nice if a better and easier version of this would be part of your next draft.

    • This seems like a bit too much abstraction, IMHO. register_activation_hook() and family are fairly simple, and since they’re one-offs, I don’t think there’s much point in it personally.

      Certainly willing to be convinced though.

  1. This is a great idea, but two immediate issues come to mind
    The order in which these will be called.
    The “all” action/filter is called first (if I remember correctly) where as I often need to be able to control the order actions/filters are fired in.

    Secondly, additional parameters.
    This one might be easier to solve, using func_get_args() to get all the arguments, and pass them on. The hook/filter can then just use the ones it wants (as additional parameters are not a problem if they’re not used in the method signature)

    And if we’re talking about a base plugin class, it should also include things like install/uninstall hooks, and possibly hooks for options pages, although that might be going a bit too far?

    • Hey Damian! Actions and filters are called when they are being called with do_action and apply_filters (and ref arrays too). If you’re talking about priority. The actions and filters in this case are being attached right after the “all” filter, which is run before all the rest in all actions and filters.

      So in theory, this approach attaches actions and filters right before they’re executed. Additional arguments are all passed in to the methods, regardless of whether their signature, so you can:

      function __the_shortlink( $link ) {
      return $shortlink;
      }

      As well as:

      function __the_shortlink( $link, $shortlink, $text, $title ) {
      return $shortlink;
      }

      Both will work just fine. If you noticed the WP_Plugin sample class does use func_get_args to get the name of the filter and all the available arguments. The count of the available arguments is then used with add_filter.

      I think install and uninstall hooks is a great idea but options pages are too much in my opinion. Almost every plugin will use an action or a filter, but not all plugins will render options pages :)

      Thank so much for your comment!

  2. Hey Konstantin,

    I’ve rewritten your class to take advantage of the reflection API. One major advantage to this is the ability to have custom parameters. I could also write it to handle custom priorities if I had the time, but this was just a quick example.

    https://gist.github.com/1626492

    Personally, I also prefer using static methods, but this was just a quick example.

    Let me know what you think.

    Thanks,
    Ryan.

    • Ryan, wow that looks really nice, thanks! I’m a little bit concerned though, that this adds a bunch of actions and filters during plugins_loaded when the file is being parsed. I know that it’s nothing to worry about since actions and filters are pretty damn fast, especially their attachment :)

      Oh and by the way, technically actions and filters are the same thing, but I like the separation in naming which helps maintain plugin readability. You wouldn’t be wondering whether __the_title is an action or whether you’re supposed to return something.

      Thanks so much for your comment!

  3. It is elegant and I love it. I usually have a cascade of filters that I order from longest line to least just to make the code look nicer to read in stair stepping fashion as one scrolls down. Still, that is way too many lines to think about. This is cleaner and more efficient as the filter doesn’t actually get added unless the method exists. Soooo very nice!

  4. Konstantin, I don’t seem to be able to reply directly to your comment, so I’ll reply here.

    The idea is to hook these in during `plugins_loaded`, since it’s better having it nice and early. Running on the `all` hook, however, adds overhead, since it’s called for every hook.

    I’m also aware that plugins and actions are both handled identically internally (I’ve spent a lot of time working with the internals of the plugin system), but for purposes of forward compatibility, registering them separately is a much better idea.

    I also think that it forces you to think about having to return. Too many times I’ve seen callbacks to filters not return anything, ruining the filter.

    • You’re absolutely right Ryan, it does make more sense than having to look for methods every time ‘all’ is carried out. I’m used to admin_menu inside an admin_init but it’s not really important.

      Forward compatibility sounds great too in addition to the naming hints you get. You definitely have good and valid points. Like I said, what I wrote is really really draft, just to illustrate the concept, so thanks for taking this forward!

    • Looks like I can reply to you after all, I missed the hover links (not my favourite UI).

      I agree regarding admin hooks, but I’d personally have those in a different class and only instantiate that using is_admin()

      With any luck, a more advanced version of this will be making it into a very special project of mine in the near future, so stay tuned for news regarding that. :)

  5. I tried something almost identical to this and the Gist you referenced as we build a large library of plugins we are developing (Sunrise) and backed away from it for a variety of reasons.

    First the naming convention (prefixing with “filter_” and “action_” became burdensome) and just “felt” wrong. Second, there was no good way to specify the priority and doing it as part of the function name as tosco did, while innovative, feels even worse of a burden function-naming wise. I’d really dislike seeing a new best practice emerge that would require such a naming convention.

    It also doesn’t support hooks that have names with are not valid method names, such as ones with ‘.php’ or dashes in the name. That’s the biggest deal killer.

    Plus I’m cautious about requiring a base class because there can only be one base class. Better to trigger this via a function call.

    Also, like Ryan McCue I far prefer to use static methods for actions rather than require an instance to be created and carried around for no good reason other than to allow the hooks to work. Using static methods also makes it much easier for another plugin to remove my hooks if needed.

    IF we were to do this what I’d rather see is to use reflection where all methods are just added as hooks with default values for priority (10) and parameter count (1) but give some functions that would let us modify those values and/or exclude methods from being hooks if needed.

    So I propose add_authhook_support() which is based on Ryan’s code and can be used like the following:

    class AutoHook_Example {
    static function on_load() {
    add_autohook_support( __CLASS__, array(
    ‘manage_edit_panel_set_columns’ => ‘manage_edit-panel_set_columns’, // Set hook name
    ‘manage_edit_comic_columns’ => array( ‘manage_edit-comic_columns’, 99 ), // Set hook name and priority to 99
    ‘save_post’ => 25, // Set priority to 25
    ‘init’ => false, // Don’t use as hook
    ));
    }
    static function manage_edit_panel_set_columns( … ) {

    }
    static function manage_edit_comic_columns( … ) {

    }
    static function save_post( … ) {

    }
    static function init( … ) {

    }
    }
    AutoHook_Example::on_load()

    Although it doesn’t differentiate between action and filter that’s really not functionally important. This is simplier, non-intrusive, feels more WordPress-like, handles static and instance classes, allows you to cleanly and independently set priority, allows you to disable methods from being used as hooks, and just generally works. AND it supports alternate hook names cleanly and easily.

    It’s only downside is that WordPress might add a hook in a later version which is the same as one of your internal functions that you forgot to disable.

    • It’s only downside is that WordPress might add a hook in a later version which is the same as one of your internal functions that you forgot to disable.

      This is precisely why I don’t like the nohook idea. It’s *too* magic for my liking. If I specify a method name, I want to be in control of where that’s used. If I prefix it with “action_”/”filter_”, I’m explicitly stating that I want that hooked in.

      I also don’t like stating how many parameters to take. I think that’s a duplication of data, since we already have access to the number of parameters via Reflection. (And, in fact, the names too. Another class I have for Ajax callbacks actually maps GET/POST variables to parameter names, and it has sped up my development extremely dramatically.)

      I feel like my current class is a good balance, since there’s never anything truly magical that hasn’t been explicitly stated. I’ve also got a static method based class that I’ll throw up at some point.

      (Also Mike, get in touch about your library. I’m building something sort-of similar, but very slimmed down, and only a component of something larger, so I’d be interested to hear about what you’re including in that.)

    • The “no hook” approach is just following in Konstantin’s footsteps; I can see having a class-wide PHPDoc tag that would allow implicit or explicit. I’d prefer implicit for plugins I’m not publishing and explicit for published plugins; you’d prefer explicit for everything.

      As for parameters, you misread my misread my example; I was specifying “priority” not “parameter count.”

      Anyway, for the benefit of others reading this I’ve updated my approach significantly based on your other input on the Gist.

      As for the library, the longer term goal is to gather a group of people who want to extend WordPress for use by site building professionals so I would love to collaborate with you; ping me via my about.me page. We felt we had to offer a starting point with real benefits before we took it public though, and our current client doesn’t understand the idea of sacrificing scope in exchange for budget or timeline. :)

    • Hey Mike. I mainly don’t like the use of no-hook, specially as a DocComment. It feels more WordPress-y (and clean) using the naming convention that Ryan proposes, IMHO.

      Other thing I had in mind was the # of parameters definition, but
      I see that in the last change to the code you’re using reflection.

      The only thing I’m not very convinced yet is how to manage priorities. I really, really don’t like how it looks when added as part of the method name.

    • I don’t believe it is, but PHPDoc is a fantastic way to add meta information, hence why I advocate it.

      In my own software, plugins use PHPDoc tags instead of WordPress-style plugin headers, which is essentially a reimplementation of them anyway.

    • @Daniel:

      The “nohook” and the “DocComment” parts are orthogonal so not sure which you dislike. “nohook” would be required to disable hooks when following the spirit of Konstantin’s original post to “magically” add hooks (as Ryan calls it), and “DocComment” is just a method for specifying the details. Alternate methods for specifying details could include an array of details defined in __construct() or passed to a function like add_autohook_support(), via the use of reserved named constants that I mention in another comment, or via another method that we’ve yet discovered.

      As for # of parameters, you are confusing somebody else’s proposals with mine; all my proposals used reflection to capture parameter counts.

      As for what’s more “WordPressy” I’d argue that using function name prefixes is the least WordPressy option available. No part of WordPress uses function name prefixes as a convention AFAIK whereas that is core to Drupal’s architecture. Since WordPress’ hook system came after Drupal’s it’s pretty clear that WordPress voted no on function name prefixes.

      The most “WordPressy” approach would be my first proposal which accepted an array of methods and their options which more like register_post_type(), etc.

      Ryan’s idea to use DocComments is probably the most elegant and explicit approach, and is very clean syntactically. I wouldn’t discredit just because WordPress core doesn’t use DocComments; until 3.2 WordPress didn’t support PHP5 which was a requirement for reflection. Expect to see more innovative coding approaches slowly enter WordPress as the core take advantage of feature they previously could not.

      OTOH the reason to dislike DocComments is Rarst’s concern they are not completely reliable with certain opcode caches. Ryan think’s that’s no big deal and that we should just parse the source code files but I think that might be too blunt a requirement to allow us to standardize on DocComments.

      At this point I think we are left with a collection of suboptimal solution candidates, much like the U.S.A.’s current slate of politicians running for the office of President.

    • I agree with Mike here, I’m not a fan of the prefixed name either (I implemented it since I can see some people preferring it). My current implementation has this disabled by default, but it’s super easy to enable them if you prefer them.

      I also agree that opcode caches dropping comments is a problem, but I do think that it should be treated as a bug in eAccelerator. It also breaks a lot of other things having that dropped, like Doctrine and Zend Framework: http://wildlyinaccurate.com/eaccelerator-and-doctrine-2/

      I think a workaround that makes it work (albeit maybe with some detriment to speed) is preferable to having it not work, but we should also be informing users/hosts that their version of eAccelerator will break sites because of it.

      As Mike said, there are advantages and disadvantages to each of the different ways. In my opinion though, the PHPDoc comments are the best solution even if we do have to work around a bug or two.

    • Guys, sorry for the delay. Complicated week at work.

      Re-reading my message I realize I did a really lousy job explaining my stance on this. That’s what happens when I overflow my processes stack. Let me take another crack at it.

      My main point is that I don’t like the use of phpDoc for other than documentation. And I know is mostly an irrational thing. I just don’t like it. But for the sake of argument 1) you can’t change the priority value on execution time. Sure, you can apply_filter to it, but then you can drop it from the Doc altogether. 2) I think (I’ll try to test it and confirm) even if you’re using APC, this method would parse the phpDoc each time. The op cache won’t, well, cache it. 3) Is impossible (difficult?) to assign a priority based on some calculations.

      So, based on Ryan’s code (btw, really like your add_filter and add_action implementation):

      http://pastebin.com/2MsPrpef

      It’s not tested, just as example. I’d need to change the code a bit to ensure that the _priority filters are added first. But you get the overall idea.

  6. Doesn’t that kinda just beg the question of the original purpose? Defining the name and the priority and then implementing it. I like the idea of just extend n’ go. We already know the hook name and purpose, if you need multiples then doesn’t that justify a new class anyways? Perhaps I’m too naive.

    • Have you played with the use-case yet? I think you might find that creating a new class really doesn’t address the issue.

      Anyway, I’ve updated the example significantly since I posted the above comment thanks to ideas from Ryan: Here and here.

  7. Have you played with the use-case yet? I think you might find that creating a new class really doesn’t address the issue.

    Anyway, I’ve updated the example significantly since I posted the above comment thanks to ideas from Ryan: Here and here.

  8. Explicit versus Implict Coding — Pre-Thought Listen

  9. Don’t think it was mentioned – scribu implemented something like this in his framework while back http://scribu.net/wordpress/reflection-on-filters.html

    Myself I consider adding everything inflexible (priorities/arguments mess) and fragile so I created shorthand function that simplifies syntax for the case of adding methods, but leaves me in control of what and how to add, see http://plugins.trac.wordpress.org/browser/advanced-hooks-api/tags/0.5/advanced-hooks-api.php#L78

    • By default, Zend Optimizer does not, although can be configured to be. APC does not appear to do so.

      I’m fairly certain that doing so would also cause normal comments to be stripped, including plugin headers.

    • eAccelerator does. With APC it seems to be a little vague (it stays most of the times, but I remember I had issues as well).

      As far as remember plugin headers are not read using reflection, files are simply read from disk and parsed as text (that is why there are limits in spec how far from start of file header can be).

    • As far as remember plugin headers are not read using reflection

      Oh, of course, forgot about that.

      If it does end up being a problem, it’s not too hard to parse the file ourselves using a Reflection-like API.

      Looks like eAccelerator also provides a compile-time flag to disable that “feature” (–with-eaccelerator-doc-comment-inclusion)

    • That is depressing to learn. I looked at your example you linked but it shows the code, not an example of how you would use the code.

      Clearly I’m interested in figuring out how to make this work best for Sunrise.

    • I don’t think any of my public code uses that so far. :)

      Would be something like this:

      static function on_load() {
      add_method(‘after_setup_theme’, 11);
      }

  10. I’m not a big fan of the docBlock idea. The main reason is that my IDE (Eclipse/Aptana) already doesn’t recognize @magic and drops it on function mouseover at the end of the last line, for e.g. @param bool $foo Description @magic @wp-hook (…).

    Then there’s the known fact, that reflections still have bugs. I don’t know which and on which php 5.x version, but for me it’s a no-go (personal preference as there’s no official documentation available).

    On the other hand, naming conventions are already a main part of WP. Take template names for example. Everybody’s used to it. And Konstantins first approach was dead simple, which is something I highly appreciate. I’d really love to see this going further (without reflections).

    @Mike Schinkel: Classes can be extended with more than once. If you want know how, just drop me a mail.

    • Then there’s the known fact, that reflections still have bugs. I don’t know which and on which php 5.x version, but for me it’s a no-go (personal preference as there’s no official documentation available).

      I’d love to know which versions these affect, as I’ve not heard of any of these bugs, and I have projects that would need to work around them.

    • Straight from php.net “Reflection Class » Introduction”:

      Please note that certain parts of the internal API are missing the necessary code to work with the Reflection extension. E.g., an internal PHP class might be missing reflection data for properties. These few cases are considered bugs, however, so they should be discovered and fixed.

      I honestly don’t know any bug myself, but I stay away from code that’s not documented. And the Reflection docu is a white spot on the map.

    • Those are internal classes and thankfully are not an issue for us.

      In addition, the Reflection classes are pretty well documented (I’ve written a bunch of stuff that uses it). The parts that aren’t documented are self-explanatory.

      The PHP docs site is also wrong on a few things about methods that aren’t documented. As an example, ReflectionClass::getDocComment() is marked as being not documented, when in reality it’s documented fairly well.

    • Yes, but I’m wondering if that doesn’t defeat the purpose of this use-case, to make it cleaner and easier to implement hooks. Maybe there is but it hasn’t yet occurred to me how.

  11. @Kaiser:

    naming conventions are already a main part of WP. Take template names for example. Everybody’s used to it.

    The only naming conventions I’m aware of are filename naming conventions, not function name naming conventions. I’ll argue those two are very different. Can you give an example of function name naming conventions in WP?

  12. Hey guys, sorry for the delay, as you know I’ve been quite busy, so I’m now catching up with all the comments. I’d like to thank everybody who commented and contributed ideas, I’ve actually learned a lot. Now, my thoughts on this…

    Honestly, I love the original concept I had in mind with the __ prefix. I agree with Ryan’s comments that using action/filter prefixes gives more information about the methods, then again, in the current implementation I’ll still have to find the add_action or filter calls to get that “extra info”.

    I’m a big fan of Python ( function decorators ), so the phpDoc idea sounds great, but if it’s not supported by the language itself, or stripped out at some point, or need an extra step to “compile” ( like Less CSS for ex. ) then I simply wouldn’t take the risk. Maybe in one or two internal projects, but not something for public use like a WordPress plugin.

    The idea with the array declaring the hook names and mapping them to method names, with priorities and so on, well that smells like a more confusing add_action/filter to me, but yeah, that would be something that would, err.. work like the existing model :)

    My original thought was to keep it as simple as possible, now I have to memorize even more initialization calls, function and method prefixes, method mapping and priority arrays, conventions for -page.php style hooks and more. Now it seems that add_action/filter are the easiest and the most straightforward approach. That’s only my thought, you can argue :)

    Thanks again for all the discussion!

Comments are closed.