Easily and reliably allow your classes to be hooked into without allowing people to touch your core code. Some inspiration came from Aspect Oriented Programming.
Authors of packages or developers extending existing packages and anyone that needs to allow hooks for core code they can modify.
- Logging, triggering events, analytics
- When you need to do something consistently before or after a method call: custom authenticating of an HTTP call, transforming data after it's been received
- Whatever you make of it
- Hooking to specific instances
- Global hooks for all instances of a specific class
- Hooking to constructors
- Dynamic analysis to catch bugs before they happen: method name checks, mismatched parameters, restricted methods
- PSR2 and PSR4
- 99% code coverage thanks to TDD
- Perfectly-spaced, hand-crafted PHPDocs
- Semantic Versioning (currently in initial development phase)
The recommended way to install Hooky is through Composer.
# Install Composer
curl -sS https://getcomposer.org/installer | php
Next, run the Composer command to install the latest stable version of Hooky:
composer require 2upmedia/hooky
After installing, you need to require Composer's autoloader:
require 'vendor/autoload.php';
require 'vendor/autoload.php';
class HelloWorld
{
use _2UpMedia\Hooky\HooksTrait;
public function sayIt()
{
$this->callBeforeHooks($this, __METHOD__);
}
}
HelloWorld::globalBeforeAllHook(function() {
echo 'hello world';
});
(new HelloWorld())->sayIt(); // echos hello world
https://github.com/2upmedia/hooky-demos
There's two core actions: before and after. You choose what if want to allow one, both, or either/or.
$this->callBeforeHooks()
allows ALL before-type hooks: beforeAll, onceBeforeAll, before{Method}, and onceBefore{Method}
$this->callAfterHooks()
does the same but for after-type hooks
You can control the types of hooks you will allow per method by using the specific call method for the action you want to allow.
For instance, if you don't want to allow beforeAll hooks for your method, but you want to allow people to hook before your method you can call $this->callAfterMethodHooks()
.
Early returns are possible using the following format right before your core method code:
public function foo($methodParameter){
if (($hookReturn = $this->callBeforeHooks($this, __METHOD__, [$methodParameter])) !== null) {
return $hookReturn;
}
Note: The first applicable registered hook that returns a value will cancel all successively registered hooks for that method.
NOTE: use with care
Just send parameters as references
// method hook
public function foo($methodParameter){
if (($hookReturn = $this->callBeforeHooks($this, __METHOD__, [&$methodParameter])) !== null) {
return $hookReturn;
}
// in your callable you also mark the parameter as a reference
$bar->afterFooHook(function (&$methodParameter) {
$methodParameter = 'Some other value';
});
Return the special NULL constant and also make sure to use hookReturn()
when you set up your hooks.
// method hook
public function foo($methodParameter){
if (($hookReturn = $this->callBeforeHooks($this, __METHOD__, [&$methodParameter])) !== null) {
return $this->hookReturn($hookReturn);
}
// in your callable
use _2UpMedia\Hooky\Constants;
$bar->afterFooHook(function ($methodParameter) {
return Constants::NULL;
});
NOTE: use with care
// method hook
public function foo($methodParameter){
// do some awesome stuff with $methodParameter;
$return = $this->wowwow($methodParameter); // split core return value into a variable
if (($hookReturn = $this->callAfterHooks($this, __METHOD__, [$methodParameter, $return])) !== null) {
return $this->hookReturn($hookReturn);
}
return $return; // very important in case you don't have any hooks returning values
// in your callable
use _2UpMedia\Hooky\CancelPropagationException;
$bar->afterFooHook(function ($methodParameter) {
throw new CancelPropagationException('buahahahaha!');
});
$bar->afterFooHook(function ($methodParameter) {
// this one never gets called
});
To reduce the risk of hooks breaking because of the underlying libraries being refactored, the decision was made to restrict hooking to concrete public methods of interface methods or abstract methods as the default. Interfaces and abstracts are usually well thought out and usually don't change as often as they're supposed to be contracts, and thus any hooks relying on those methods would break much less frequently, if not at all.
This default behavior can be changed. See the Changing Default Behavior section.
You can restrict hooking to the following types of methods using setDefaultAccessibility()
: public, protected, private, and/or abstract/interface method.
use _2UpMedia\Hooky\Constants;
// allow hooking to public methods
$this->setDefaultAccessibility(Constants::PUBLIC_ACCESSIBLE);
// allow hooking to public and protected methods
$this->setDefaultAccessibility(Constants::PUBLIC_ACCESSIBLE | Constants::PROTECTED_ACCESSIBLE);
// allow hooking ONLY to public and protected abstract methods
$this->setDefaultAccessibility(Constants::PUBLIC_ACCESSIBLE | Constants::PROTECTED_ACCESSIBLE | Constants::ABSTRACT_ONLY );
Global hooks are called statically by the name of the class that's using the HooksTrait. Global hooks are called for all instances.
The format is: {ClassName}::global{Action}[ {Method} ]Hook
{ClassName}::globalBeforeAllHook
{ClassName}::globalOnceBeforeAllHook
{ClassName}::globalBefore{Method}Hook
{ClassName}::globalOnceBefore{Method}Hook
{ClassName}::globalAfterAllHook
{ClassName}::globalOnceAfterAllHook
{ClassName}::globalAfter{Method}Hook
{ClassName}::globalOnceAfter{Method}Hook
Bar::globalBeforeAllHook(function ($) {
var_dump(
});
$bar->foo();
Instance-specific hooks are called as methods of an instance and these hooks are not called globally.
The format is: {$instanceVariable}->{action}[ {Method} ]Hook
{$instanceVariable}->beforeAllHook
{$instanceVariable}->onceBeforeAllHook
{$instanceVariable}->before{Method}Hook
{$instanceVariable}->onceBefore{Method}Hook
{$instanceVariable}->afterAllHook
{$instanceVariable}->onceAfterAllHook
{$instanceVariable}->after{Method}Hook
{$instanceVariable}->onceAfter{Method}Hook
The {ClassName}::$checkCallableParameters
flag triggers PHP Notices on different issues where method parameters don't match up with the callables set up through hooks. This is handy during development to catch potential bugs. By default it is turned on to reduce bugs immediately. Turn it off in a production environment.
In the __construct()
of your concrete class call $this->setHookableMethods(['methodName1', 'methodName2'])
.
Because of the nature of Traits, __destruct()
will be overridden if the implemented class already has them. The workaround is to use an alias.
use \_2UpMedia\Hooky\HooksTrait { __destruct as traitDestruct; }
public function __destruct()
{
$this->traitDestruct();
keywords: aop, hook, event, pub, sub