Skip to content

Instantly share code, notes, and snippets.

@westonruter
Last active April 2, 2016 17:55
Show Gist options
  • 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'] );
}
}
}
@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