addHookAfter('Pages::saved', function(HookEvent $event) { * $page = $event->arguments(0); * $event->message("You saved page $page->path"); * }); * ~~~~~~ * #pw-body * * HookEvents have the following properties available: * * @property-read Wire|WireData|WireArray|Module $object Instance of the object where the Hook event originated. * @property-read string $method The name of the method that was called to generate the Hook event. * @property array $arguments A numerically indexed array of the arguments sent to the above mentioned method. * @property mixed $return Applicable only for 'after' or ('replace' + 'before' hooks), contains the value returned by the above mentioned method. The hook handling method may modify this return value. * @property bool $replace Set to boolean true in a 'before' hook if you want to prevent execution of the original hooked function. In such a case, your hook is replacing the function entirely. Not recommended, so be careful with this. * @property array $options An optional array of user-specified data that gets sent to the hooked function. The hook handling method may access it from $event->data. Also includes all the default hook properties. * @property-read string $id A unique identifier string that may be used with a call to `Wire::removeHook()`. * @property-read string $when In an active hook, contains either the string 'before' or 'after', indicating whether it is executing before or after the hooked method. * @property bool $cancelHooks When true, all remaining hooks will be cancelled, making this HookEvent the last one (be careful with this). * */ class HookEvent extends WireData { /** * Cached argument names indexed by "className.method" * */ static protected $argumentNames = array(); /** * Construct the HookEvent and establish default values * * @param array $eventData Optional event data to start with * */ public function __construct(array $eventData = array()) { $data = array( 'object' => null, 'method' => '', 'arguments' => array(), 'return' => null, 'replace' => false, 'options' => array(), 'id' => '', 'cancelHooks' => false ); if(!empty($eventData)) $data = array_merge($data, $eventData); $this->data = $data; parent::__construct(); } /** * Retrieve or set a hooked function argument * * ~~~~~ * // Retrieve first argument by index (0=first) * $page = $event->arguments(0); * * // Retrieve array of all arguments * $arguments = $event->arguments(); * * // Retrieve argument by name * $page = $event->arguments('page'); * * // Set first argument by index * $event->arguments(0, $page); * * // Set first argument by name * $event->arguments('page', $page); * ~~~~~ * * @param int $n Zero based number of the argument you want to retrieve, where 0 is the first. * May also be a string containing the argument name. * Omit to return array of all arguments. * @param mixed $value Value that you want to set to this argument, or omit to only return the argument. * @return array|null|mixed * */ public function arguments($n = null, $value = null) { if($n === null) return $this->data['arguments']; if($value !== null) { $this->setArgument($n, $value); return $value; } if(isset($this->data['arguments'][$n])) return $this->data['arguments'][$n]; if(is_string($n)) return $this->argumentsByName($n); return null; } /** * Returns an array of all arguments indexed by name, or the value of a single specified argument * * Note: `$event->arguments('name')` can also be used as a shorter synonym for `$event->argumentsByName('name')`. * * ~~~~~ * // Get an array of all arguments indexed by name * $arguments = $event->argumentsByName(); * * // Get a specific argument by name * $page = $event->argumentsByName('page'); * ~~~~~ * * @param string $n Optional name of argument value to return. If not specified, array of all argument values returned. * @return mixed|array Depending on whether you specify $n * */ public function argumentsByName($n = '') { $arguments = $this->data['arguments']; if(isset($arguments[$n])) return $arguments[$n]; $names = $this->getArgumentNames(); if($n) { $key = array_search($n, $names); if($key === false) return null; return array_key_exists($key, $arguments) ? $arguments[$key] : null; } $argumentsByName = array(); foreach($names as $key => $name) { $value = null; if(isset($arguments[$key])) $value = $arguments[$key]; $argumentsByName[$name] = $value; } return $argumentsByName; } /** * Sets an argument value, handles the implementation of setting for the above arguments() function * * Only useful with 'before' hooks, where the argument can be manipulated before being sent to the hooked function. * * #pw-internal * * @param int|string Argument name or key * @param mixed $value * @return $this * @throws WireException * */ public function setArgument($n, $value) { if(is_string($n) && !ctype_digit($n)) { // convert argument name to position $names = $this->getArgumentNames(); $name = $n; $n = array_search($name, $names); if($n === false) throw new WireException("Unknown argument name: $name"); } $this->data['arguments'][(int)$n] = $value; return $this; } /** * Return an array of all argument names, indexed by their position * * @return array * */ protected function getArgumentNames() { $o = $this->get('object'); $m = $this->get('method'); $key = get_class($o) . '.' . $m; if(isset(self::$argumentNames[$key])) return self::$argumentNames[$key]; $argumentNames = array(); $method = new \ReflectionMethod($o, '___' . $m); $arguments = $method->getParameters(); foreach($arguments as $a) { $pos = $a->getPosition(); $argumentNames[$pos] = $a->getName(); } self::$argumentNames[$key] = $argumentNames; return $argumentNames; } /** * Remove a hook by ID * * To remove the hook that this event is for, call it with the $hookId argument as null or blank. * * ~~~~~ * // Remove this hook event, preventing it from executing again * $event->removeHook(null); * ~~~~~ * * @param string|HookEvent|null $hookId * @return HookEvent|WireData $this * */ public function removeHook($hookId) { if(empty($hookId) || $hookId === $this) { if($this->object && $this->id) { $this->object->removeHook($this->id); } return $this; } else { return parent::removeHook($hookId); } } /** * Get * * @param object|string $key * @return mixed|null * */ public function get($key) { $value = parent::get($key); if($value === null && !ctype_digit("$key") && array_key_exists($key, $this->data['arguments'])) { // allow named arguments to be accessed from get() $value = $this->data['arguments'][$key]; } return $value; } /** * Return a string representing the HookEvent * */ public function __toString() { $s = $this->object->className() . '::' . $this->method . '('; foreach($this->arguments as $a) $s .= is_string($a) ? '"' . $a . '", ' : "$a, "; $s = rtrim($s, ", ") . ")"; return $s; } }