docs/hook.md
:::{php:namespace} Atk4\Core
:::
# Hook Trait
:::{php:trait} HookTrait
:::
## Introduction
HookTrait adds some methods into your class to registering call-backs that would
be executed by triggering a hook. All hooks are local to the object, so if you
want to have application-wide hook then use `app` property.
## Hook Spots
Hook is described by a string identifier which we call hook-spot, which would
normally be expressing desired action with prefixes "before" or "after" if
necessary.
Some good examples for hook spots are:
- beforeSave
- afterDelete
- validation
The framework or application would typically execute hooks like this:
```
$obj->hook('spot');
```
You can register multiple call-backs to be executed for the requested `spot`:
```
$obj->onHook('spot', function ($obj) {
echo "Hook 'spot' is called!";
});
```
## Adding callbacks
:::{php:method} onHook($spot, $fx = null, array $args = [], int $priority = 5)
:::
Register a call-back method. Calling several times will register multiple
callbacks which will be execute in the order that they were added.
## Short way to describe callback method
There is a concise syntax for using $fx by specifying object only.
In case $fx is omitted then $this object is used as $fx.
In this case a method with same name as $spot will be used as callback:
```
protected function init(): void
{
parent::init();
$this->onHookShort($spot, function (...$args) {
$this->beforeUpdate(...$args);
});
}
protected function beforeUpdate()
{
// will be called from the hook
}
```
## Callback execution order
$priority will make hooks execute faster. Default priority is 5, but if you add
hook with priority 1 it will always be executed before any hooks with priority
2, 3, 5 etc.
Normally hooks are executed in the same order as they are added, however if you
use negative priority, then hooks will be executed in reverse order:
```
$obj->onHook('spot', third, [], -1);
$obj->onHook('spot', second, [], -5);
$obj->onHook('spot', first, [], -5);
$obj->onHook('spot', fourth, [], 0);
$obj->onHook('spot', fifth, [], 0);
$obj->onHook('spot', ten, [], 1000);
$obj->onHook('spot', sixth, [], 2);
$obj->onHook('spot', seventh, [], 5);
$obj->onHook('spot', eight);
$obj->onHook('spot', nine, [], 5);
```
:::{php:method} hook($spot, $args = null)
:::
execute all hooks in order. Hooks can also return some values and those values
will be placed in array and returned by hook():
```
$mul = function ($obj, $a, $b) {
return $a * $b;
};
$add = function ($obj, $a, $b) {
return $a + $b;
};
$obj->onHook('test', $mul);
$obj->onHook('test', $add);
$res1 = $obj->hook('test', [2, 2]);
// res1 = [4, 4]
$res2 = $obj->hook('test', [3, 3]);
// res2 = [9, 6]
```
## Arguments
As you see in the code above, we were able to pass some arguments into those
hooks. There are actually 3 sources that are considered for the arguments:
- first argument to callbacks is always the $object
- arguments passed as 3rd argument to onHook() are included
- arguments passed as 2nd argument to hook() are included
You can also use key declarations if you wish to override arguments:
```
// continue from above example
$pow = function ($obj, $a, $b, $power) {
return pow($a, $power) + pow($b, $power);
}
$obj->onHook('test', $pow, [2]);
$obj->onHook('test', $pow, [7]);
// execute all 3 hooks
$res3 = $obj->hook('test', [2, 2]);
// res3 = [4, 4, 8, 256]
$res4 = $obj->hook('test', [2, 3]);
// res3 = [6, 5, 13, 2315]
```
## Breaking Hooks
:::{php:method} breakHook
:::
When this method is called from a call-back then it will cause all other
callbacks to be skipped.
If you pass $return argument then instead of returning all callback return
values in array the $return will be returned by hook() method.
If you do not pass $return value (or specify null) then list of the values
collected so far will be returned
Remember that adding breaking hook with a lower priority can prevent other
call-backs from being executed:
```
$obj->onHook('test', function ($obj) {
$obj->breakHook("break1");
});
$obj->onHook('test', function ($obj) {
$obj->breakHook("break2");
}, [], -5);
$res3 = $obj->hook('test', [4, 4]);
// res3 = "break2"
```
breakHook method is implemented by throwing a special exception that is then
caught inside hook() method.
## Using references in hooks
In some cases you want hook to change certain value. For example when model
value is set it may call normalization hook (methods will change $value):
```
public function set($field, $value)
{
$this->hook('normalize', [&$value]);
$this->data[$field] = $value;
}
$m->onHook('normalize', function (&$a) {
$a = trim($a);
});
```
## Checking if hook has callbacks
:::{php:method} hookHasCallbacks()
:::
This method will return true if at least one callback has been set for the hook.