Skip to content

Instantly share code, notes, and snippets.

@tdlm
Created March 18, 2021 20:54
Show Gist options
  • Save tdlm/2fd858ad9e00300a331cd11565cd5814 to your computer and use it in GitHub Desktop.
Save tdlm/2fd858ad9e00300a331cd11565cd5814 to your computer and use it in GitHub Desktop.
Hookable Plugins with singleton action
<?php
/**
* Trait Hookable
*/
trait Hookable
{
/**
* Add doc hooks.
*/
public function addDocHooks()
{
// Get instanced class to relate the callback to.
$object = static::$instances[static::class];
// Start a reflector.
$reflector = new \ReflectionObject($object);
foreach ($reflector->getMethods() as $method) {
$phpdoc = $method->getDocComment();
$arg_count = $method->getNumberOfParameters();
if (preg_match_all(
'#\* @(?P<type>filter|action|shortcode)\s+(?P<name>[a-z0-9\/\=\-\._]+)(?:,\s+(?P<priority>\d+))?#',
$phpdoc,
$matches,
PREG_SET_ORDER
)) {
foreach ($matches as $match) {
$type = $match['type'];
$name = $match['name'];
$priority = empty($match['priority']) ? 11 : intval($match['priority']);
$callback = [$this, $method->getName()];
call_user_func(
[
self::class,
'add' . ucfirst($type),
],
$name,
$callback,
compact('priority', 'arg_count')
);
}
}
}
}
/**
* Hooks a function on to a specific action.
*
* @param string $name The hook name.
* @param array $callback The class object and method.
* @param array $args An array with priority and arg_count.
*
* @return mixed
*/
public function addAction(
$name,
$callback,
$args = []
) {
// Merge defaults.
$args = array_merge(
[
'priority' => 10,
'arg_count' => PHP_INT_MAX,
],
$args
);
return $this->addHook('action', $name, $callback, $args);
}
/**
* Hooks a function on to a specific filter.
*
* @param string $name The hook name.
* @param array $callback The class object and method.
* @param array $args An array with priority and arg_count.
*
* @return mixed
*/
public function addFilter(
$name,
$callback,
$args = []
) {
// Merge defaults.
$args = array_merge(
[
'priority' => 10,
'arg_count' => PHP_INT_MAX,
],
$args
);
return $this->addHook('filter', $name, $callback, $args);
}
/**
* Hooks a function on to a specific shortcode.
*
* @param string $name The shortcode name.
* @param array $callback The class object and method.
*
* @return mixed
*/
public function addShortcode(
$name,
$callback
) {
return $this->addHook('shortcode', $name, $callback);
}
/**
* Hooks a function on to a specific action/filter.
*
* @param string $type The hook type. Options are action/filter.
* @param string $name The hook name.
* @param array $callback The class object and method.
* @param array $args An array with priority and arg_count.
*
* @return mixed
*/
protected function addHook(
$type,
$name,
$callback,
$args = []
) {
$priority = $args['priority'] ?? 10;
$arg_count = $args['arg_count'] ?? PHP_INT_MAX;
$fn = sprintf('\add_%s', $type);
return \call_user_func($fn, $name, $callback, $priority, $arg_count);
}
}
<?php
/**
* Bootstrap class, for kicking things off.
*/
class Loader
{
/**
* Loader constructor.
*/
public function __construct()
{
\add_action('muplugins_loaded', [__CLASS__, 'bootstrap']);
}
/**
* Fire up our items.
*/
public static function bootstrap()
{
Menu::instance(); // Create instance and load up all the hooks!
}
}
// Now just create a new instance of the Loader!
new Loader();
<?php
/**
* Example Menu Class
*/
class Menu extends Plugin
{
/**
* Disable W3TC Menu Bar for non admins.
*
* @return string
*
* @see \W3TC\Generic_Plugin::admin_bar_menu
*
* @filter w3tc_capability_admin_bar
*/
public function removeW3TCMenuForNonAdmins(): string
{
return 'manage_tech';
}
/**
* Modify Admin Bar.
*
* @param \WP_Admin_Bar $wp_admin_bar
*
* @action admin_bar_menu, 999
*/
public function modifyAdminBar($wp_admin_bar)
{
// Clean up items in the WP Admin Bar.
$wp_admin_bar->remove_node('comments');
$wp_admin_bar->remove_node('new-content');
$wp_admin_bar->remove_node('wpseo-menu');
}
}
<?php
/**
* Plugin abstract class.
*/
abstract class Plugin extends Singleton
{
use Hookable;
/**
* Gets the plugin directory path.
*
* @return string Plugin directory path.
*/
public static function dir()
{
return plugin_dir_path(__FILE__);
}
/**
* Gets the plugin file path.
*
* @return string Plugin file path.
*/
public static function file()
{
return __FILE__;
}
/**
* Gets the plugin url.
*
* @return string Plugin url.
*/
public static function url()
{
return plugin_dir_url(__FILE__);
}
/**
* Gets the plugin version.
*
* @return string Plugin version.
*/
public static function version()
{
return get_file_data(self::file(), ['Version' => 'Version'], 'plugin')['Version'];
}
}
<?php
/**
* Trait Singleton
*/
abstract class Singleton
{
/**
* @var self Reference to singleton instance.
*/
protected static $instances = [];
/**
* Creates a new instance of a singleton class (via late static binding), accepting a variable-length argument list.
*
* @param mixed ...$params
*
* @return self
*/
final public static function instance(...$params): Singleton
{
if (!isset(static::$instances[static::class])) {
static::$instances[static::class] = new static();
// Call 'addDocHooks' to parse and fire object doc actions/filters.
if (method_exists(self::$instances[static::class], 'addDocHooks')) {
call_user_func_array([self::$instances[static::class], 'addDocHooks'], []);
}
// Call 'init' bootstrap method if it's defined in the inheriting class.
if (method_exists(self::$instances[static::class], 'init')) {
call_user_func_array([self::$instances[static::class], 'init'], func_get_args());
}
}
return static::$instances[static::class];
}
/**
* Prevents direct instantiation.
*
* @return void
*/
final private function __construct()
{
}
/**
* Prevents cloning the singleton instance.
*
* @return void
*/
final public function __clone()
{
}
/**
* Prevents unserializing the singleton instance.
*
* @return void
*/
final public function __wakeup()
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment