From 2f6497d1e5c5c059ac8e028be9fd906a876d9b07 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Thu, 1 Aug 2019 11:44:33 -0400 Subject: [PATCH] Add support for hooking multiple methods to the same event handler via addHook*() calls by separating the methods to hook with commas, or by providing an array rather than a string. Also updated the corresponding removeHook() method to support removing multiple in the same call. --- wire/core/Wire.php | 34 ++++++++----- wire/core/WireData.php | 10 ++-- wire/core/WireHooks.php | 104 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 131 insertions(+), 17 deletions(-) diff --git a/wire/core/Wire.php b/wire/core/Wire.php index 712e3d83..806c5dbd 100644 --- a/wire/core/Wire.php +++ b/wire/core/Wire.php @@ -627,8 +627,9 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * * #pw-internal * - * @param string $method Method name to hook into, NOT including the three preceding underscores. + * @param string|array $method Method name to hook into, NOT including the three preceding underscores. * May also be Class::Method for same result as using the fromClass option. + * May also be array or CSV string of hook definitions to attach multiple to the same $toMethod (since 3.0.137). * @param object|null|callable $toObject Object to call $toMethod from, * Or null if $toMethod is a function outside of an object, * Or function|callable if $toObject is not applicable or function is provided as a closure. @@ -650,8 +651,10 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * must match, in order to execute hook. Default is null. * - `objMatch` (array|null): Selectors object that the current object must match in order to execute hook. * Default is null. - * @return string A special Hook ID that should be retained if you need to remove the hook later + * @return string A special Hook ID that should be retained if you need to remove the hook later. + * If multiple methods were hooked then it is a CSV string of hook IDs, accepted removeHook method (since 3.0.137). * @throws WireException + * @see https://processwire.com/docs/modules/hooks/ * */ public function addHook($method, $toObject, $toMethod = null, $options = array()) { @@ -682,9 +685,10 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * * #pw-group-hooks * - * @param string $method Method to hook in one of the following formats (please omit 3 leading underscores): + * @param string|array $method Method to hook in one of the following formats (please omit 3 leading underscores): * - `Class::method` - If hooking to *all* object instances of the class. * - `method` - If hooking to a single object instance. + * - Since 3.0.137 it may also be multiple methods to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. @@ -698,7 +702,8 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * rather than $obj->method(). The default type is 'method'. * - `priority` (int): A number determining the priority of a hook, where lower numbers are executed before * higher numbers. The default priority is 100. - * @return string A special Hook ID that should be retained if you need to remove the hook later. + * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. + * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookBefore($method, $toObject, $toMethod = null, $options = array()) { @@ -730,9 +735,10 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * * #pw-group-hooks * - * @param string $method Method to hook in one of the following formats (please omit 3 leading underscores): + * @param string|array $method Method to hook in one of the following formats (please omit 3 leading underscores): * - `Class::method` - If hooking to *all* object instances of the class. * - `method` - If hooking to a single object instance. + * - Since 3.0.137 it may also be multiple methods to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. @@ -746,7 +752,8 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * rather than $obj->method(). The default type is 'method'. * - `priority` (int): A number determining the priority of a hook, where lower numbers are executed before * higher numbers. The default priority is 100. - * @return string A special Hook ID that should be retained if you need to remove the hook later. + * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. + * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookAfter($method, $toObject, $toMethod = null, $options = array()) { @@ -778,9 +785,10 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * * #pw-group-hooks * - * @param string $property Name of property you want to add, must not collide with existing property or method names: + * @param string|array $property Name of property you want to add, must not collide with existing property or method names: * - `Class::property` to add the property to all instances of Class. - * - `property` if just adding to a single object instance. + * - `property` if just adding to a single object instance. + * - Since 3.0.137 it may also be multiple properties to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. @@ -790,7 +798,8 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * This argument can be sustituted as the 2nd argument when the 2nd argument isn’t needed, * or it can be the $options argument. * @param array $options Options typically aren't used in this context, but see Wire::addHookBefore() $options if you'd like. - * @return string A special Hook ID that should be retained if you need to remove the hook later. + * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. + * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookProperty($property, $toObject, $toMethod = null, $options = array()) { @@ -841,6 +850,7 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * @param string $method Name of method you want to add, must not collide with existing property or method names: * - `Class::method` to add the method to all instances of Class. * - `method` to just add to a single object instance. + * - Since 3.0.137 it may also be multiple methods to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. @@ -850,8 +860,9 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * This argument can be sustituted as the 2nd argument when the 2nd argument isn’t needed, * or it can be the $options argument. * @param array $options Options typically aren't used in this context, but see Wire::addHookBefore() $options if you'd like. - * @return string A special Hook ID that should be retained if you need to remove the hook later. + * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. * @since 3.0.16 Added as an alias to addHook() for syntactic clarity, previous versions can use addHook() method with same arguments. + * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookMethod($method, $toObject, $toMethod = null, $options = array()) { @@ -882,7 +893,8 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { * * #pw-group-hooks * - * @param string|null $hookId ID of hook to remove (ID is returned by the addHook() methods) + * @param string|array|null $hookId ID of hook to remove (ID is returned by the addHook() methods) + * Since 3.0.137 it may also be an array or CSV string of hook IDs to remove. * @return $this * */ diff --git a/wire/core/WireData.php b/wire/core/WireData.php index b727ac7c..5ca964a9 100644 --- a/wire/core/WireData.php +++ b/wire/core/WireData.php @@ -265,9 +265,13 @@ class WireData extends Wire implements \IteratorAggregate, \ArrayAccess { $keys = array(); } if($from->wire($key) !== null) return null; // don't allow API vars to be retrieved this way - if($from instanceof WireData) $value = $from->get($key); - else if($from instanceof WireArray) $value = $from->getProperty($key); - else $value = $from->$key; + if($from instanceof WireData) { + $value = $from->get($key); + } else if($from instanceof WireArray) { + $value = $from->getProperty($key); + } else { + $value = $from->$key; + } if(!count($keys)) return $value; // final value if(is_object($value)) { if(count($keys) > 1) { diff --git a/wire/core/WireHooks.php b/wire/core/WireHooks.php index bc8df16e..d318195d 100644 --- a/wire/core/WireHooks.php +++ b/wire/core/WireHooks.php @@ -474,19 +474,26 @@ class WireHooks { * $this->addHook($method, 'function_name'); or $this->addHook($method, 'function_name', $options); * * @param Wire $object - * @param string $method Method name to hook into, NOT including the three preceding underscores. + * @param string|array $method Method name to hook into, NOT including the three preceding underscores. * May also be Class::Method for same result as using the fromClass option. + * May also be array OR CSV string of either of the above to add multiple (since 3.0.137). * @param object|null|callable $toObject Object to call $toMethod from, * Or null if $toMethod is a function outside of an object, * Or function|callable if $toObject is not applicable or function is provided as a closure. * @param string|array $toMethod Method from $toObject, or function name to call on a hook event, or $options array. * @param array $options See $defaultHookOptions at the beginning of this class. Optional. - * @return string A special Hook ID that should be retained if you need to remove the hook later + * @return string A special Hook ID that should be retained if you need to remove the hook later. + * If the $method argument was a CSV string or array of multiple methods to hook, then CSV string of hook IDs + * will be returned, and the same CSV string can be used with removeHook() calls. (since 3.0.137). * @throws WireException * */ public function addHook(Wire $object, $method, $toObject, $toMethod = null, $options = array()) { + if(is_array($method) || strpos($method, ',') !== false) { + return $this->addHooks($object, $method, $toObject, $toMethod, $options); + } + if(is_array($toMethod)) { // $options array specified as 3rd argument if(count($options)) { @@ -673,6 +680,77 @@ class WireHooks { return $id; } + /** + * Add a hooks to multiple methods at once + * + * This is the same as addHook() except that the $method argument is an array or CSV string of hook definitions. + * See the addHook() method for more detailed info on arguments. + * + * @param Wire $object + * @param array|string $methods Array of one or more strings hook definitions, or CSV string of hook definitions + * @param object|null|callable $toObject + * @param string|array|null $toMethod + * @param array $options + * @return string CSV string of hook IDs that were added + * @throws WireException + * @since 3.0.137 + * + */ + protected function addHooks(Wire $object, $methods, $toObject, $toMethod = null, $options = array()) { + + if(!is_array($methods)) { + // potentially multiple methods defined in a CSV string + // could also be a single method with CSV arguments + + $str = (string) $methods; + $argSplit = '|'; + + // skip optional useless parenthesis in definition to avoid unnecessary iterations + if(strpos($str, '()') !== false) $str = str_replace('()', '', $str); + + if(strpos($str, '(') === false) { + // If there is a parenthesis then it is multi-method definition without arguments + // Example: "Pages::saveReady, Pages::saved" + $methods = explode(',', $str); + + } else { + // Single or multi-method definitions, at least one with arguments + // Isolate commas that are for arguments versus comments that separate multiple hook methods: + // Single method example: "Page(template=order)::changed(0:order_status, 1:name=pending)" + // Multi method example: "Page(template=order)::changed(0:order_status, 1:name=pending), Page::saved" + + while(strpos($str, $argSplit) !== false) $argSplit .= '|'; + $strs = explode('(', $str); + + foreach($strs as $key => $val) { + if(strpos($val, ')') === false) continue; + list($a, $b) = explode(')', $val, 2); + if(strpos($a, ',') !== false) $a = str_replace(array(', ', ','), $argSplit, $a); + $strs[$key] = "$a)$b"; + } + + $str = implode('(', $strs); + $methods = explode(',', $str); + + foreach($methods as $key => $method) { + if(strpos($method, $argSplit) === false) continue; + $methods[$key] = str_replace($argSplit, ', ', $method); + } + } + } + + $result = array(); + + foreach($methods as $method) { + $method = trim($method); + $hookID = $this->addHook($object, $method, $toObject, $toMethod, $options); + $result[] = $hookID; + } + + $result = implode(',', $result); + + return $result; + } /** * Provides the implementation for calling hooks in ProcessWire @@ -926,11 +1004,14 @@ class WireHooks { * } * * @param Wire $object - * @param string|null $hookID + * @param string|array|null $hookID Can be single hook ID, array of hook IDs, or CSV string of hook IDs * @return Wire * */ public function removeHook(Wire $object, $hookID) { + if(is_array($hookID) || strpos($hookID, ',')) { + return $this->removeHooks($object, $hookID); + } if(!empty($hookID) && strpos($hookID, ':')) { list($hookClass, $priority, $method) = explode(':', $hookID); if(empty($hookClass)) { @@ -947,6 +1028,23 @@ class WireHooks { return $object; } + /** + * Given a hook ID or multiple hook IDs (in array or CSV string) remove the hooks + * + * @param Wire $object + * @param array|string $hookIDs + * @return Wire + * @since 3.0.137 + * + */ + protected function removeHooks(Wire $object, $hookIDs) { + if(!is_array($hookIDs)) $hookIDs = explode(',', $hookIDs); + foreach($hookIDs as $hookID) { + $this->removeHook($object, $hookID); + } + return $object; + } + /** * Return the "all local hooks" cache *