praiadeseselle/wire/core/HookEvent.php
2022-03-08 15:55:41 +01:00

253 lines
7.5 KiB
PHP

<?php namespace ProcessWire;
/**
* ProcessWire HookEvent
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* https://processwire.com
*
* Instances of HookEvent are passed to Hook handlers when their requested method has been called.
*
* #pw-summary HookEvent is a type provided to hook functions with information about the event.
* #pw-var $event
* #pw-body =
* ~~~~~~
* // Example
* $wire->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;
}
/**
* 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;
}
$value = 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();
$n = array_search($n, $names);
if($n === false) throw new WireException("Unknown argument name: $n");
}
$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;
}
}