Skip to content

Instantly share code, notes, and snippets.

@westonruter
Last active April 2, 2016 17:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save westonruter/bdab58392c6a6837d918 to your computer and use it in GitHub Desktop.
Save westonruter/bdab58392c6a6837d918 to your computer and use it in GitHub Desktop.
Some code we're using at XWP to make plugins easier and cleaner to write.
<?php
require_once __DIR__ . '/pluginception.php'
class ShoutPlugin {
use Pluginception;
function __construct() {
/*
* This method comes from the Pluginception trait,
* and will parse out @action/@filter tags from this
* object's methods' PhpDoc blocks, and then call
* add_action() and add_filter(). Note that the
* arg count is not passed, as the PHP_MAX_INT is
* used instead.
*/
$this->add_hooks_from_object_method_docblocks();
}
/**
* Make all post titles ¡SHOUT!
*
* @filter the_title
*
* @param string $title
* @return string
*/
function shoutify_title( $title ) {
$title = '¡' . strtoupper( $title ) . '!';
return $title;
}
/**
* This will get added to the very bottom (priority 100) of the footer.
*
* @action wp_footer, 100
*/
function show_props() {
?>
<p>Props Shady Sharaf for idea to use Reflection API to parse out the
<code>@filter</code>/<code>@action</code> tags from method PhpDoc.</p>
<?php
}
}
$shout_plugin = new ShoutPlugin();
<?php
/**
* Add improved add_filter/add_action methods to any class, and support for declaring
* filter/action callbacks via tags in PhpDoc comments. Props Shady Sharaf (@shadyvb)
* for this application of the Reflection API.
*/
trait Pluginception {
/**
* @var array
*/
protected $added_hooks = array();
/**
*
*/
function __destruct() {
$this->remove_all_hooks();
}
/**
* @param $name
* @param $callback
* @param array $args
*
* @return mixed
*/
function add_filter( $name, $callback, $args = array( 'priority' => 10, 'arg_count' => PHP_INT_MAX ) ) {
return $this->_add_hook( 'filter', $name, $callback, $args );
}
/**
* @param $name
* @param $callback
* @param array $args
*
* @return mixed
*/
function add_action( $name, $callback, $args = array( 'priority' => 10, 'arg_count' => PHP_INT_MAX ) ) {
return $this->_add_hook( 'action', $name, $callback, $args );
}
/**
* @param $type
* @param $name
* @param $callback
* @param array $args
*
* @return mixed
*/
protected function _add_hook( $type, $name, $callback, $args = array() ) {
$priority = isset( $args['priority'] ) ? $args['priority'] : 10;
$arg_count = isset( $args['arg_count'] ) ? $args['arg_count'] : PHP_INT_MAX;
$fn = sprintf( '\add_%s', $type );
$retval = \call_user_func( $fn, $name, $callback, $priority, $arg_count );
$this->added_hooks[] = compact( 'type', 'name', 'callback', 'priority' );
return $retval;
}
/**
* Add actions/filters from the methods of this class based on doc blocks
*/
function add_hooks_from_object_method_docblocks() {
$reflector = new \ReflectionObject( $this );
foreach ( $reflector->getMethods() as $method ) {
$doc = $method->getDocComment();
$arg_count = $method->getNumberOfParameters();
if ( preg_match_all( '#\* @(?P<type>filter|action)\s+(?P<name>\w+)(?:,\s+(?P<priority>\d+))?#', $doc, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
$type = $match['type'];
$name = $match['name'];
$priority = is_null( $match['priority'] ) ? 10 : intval( $match['priority'] );
$callback = array( $this, $method->getName() );
call_user_func( array( $this, "add_{$type}" ), $name, $callback, $priority, $arg_count );
}
}
}
}
/**
*
*/
function remove_all_hooks() {
foreach ( $this->added_hooks as $added_hook ) {
$fn = sprintf( 'remove_%s', $added_hook['type'] );
call_user_func( $fn, $added_hook['name'], $added_hook['callback'], $added_hook['priority'] );
}
}
}
@fjarrett
Copy link

Wow! How can multiple hooks be applied? More than one @-mention or comma separated hook names?

@westonruter
Copy link
Author

By adding multiple @filter/@action tags, yes.

@GaryJones
Copy link

Could the annotation follow the draft PSR-5 suggestion of being prefixed?

@shadyvb
Copy link

shadyvb commented Apr 2, 2016

BTW, Using the pattern [A-Za-z0-9_\/] makes it work for filters/actions with slashes in their names, unlike the current usage of \w, ie:

if ( preg_match_all( '#\* @(?P<type>filter|action)\s+(?P<name>[A-Za-z0-9_\/]+)(?:,\s+(?P<priority>\d+))?#', $doc, $matches, PREG_SET_ORDER ) ) {

This is specially helpful while working with ACF, most of their hooks employ the slash character.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment