From 9d44d776ffacbc8685aa90cdc358716f4711e2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Mon, 4 Oct 2021 14:07:48 +0200 Subject: [PATCH] Make sure same hook spot is not updated while executing --- src/HookTrait.php | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/HookTrait.php b/src/HookTrait.php index 057c7ee4..0603faea 100644 --- a/src/HookTrait.php +++ b/src/HookTrait.php @@ -6,20 +6,24 @@ trait HookTrait { - /** - * Contains information about configured hooks (callbacks). - * - * @var array}>> - */ + /** @var array}>> Configured hooks (callbacks). */ protected $hooks = []; - /** - * Next hook index counter. - * - * @var int - */ + /** @var int Next hook index counter. */ private $_hookIndexCounter = 0; + /** @var string[] */ + private $_hookActiveSpots = []; + + private function _hookRequireInactive(string $spot): void + { + if ($this->_hookActiveSpots[$spot] ?? false) { + throw (new Exception('Hook spot must be inactive for requested operation, but it is already executing')) + ->addMoreInfo('object', $this) + ->addMoreInfo('spot', $spot); + } + } + /** * @var static */ @@ -80,6 +84,7 @@ private function _rebindHooksIfCloned(): void public function onHook(string $spot, \Closure $fx, array $args = [], int $priority = 5): int { $this->_rebindHooksIfCloned(); + $this->_hookRequireInactive($spot); if (!isset($this->hooks[$spot][$priority])) { $this->hooks[$spot][$priority] = []; @@ -175,6 +180,8 @@ public function onHookDynamicShort(string $spot, \Closure $getFxThisFx, \Closure */ public function removeHook(string $spot, int $priority = null, bool $priorityIsIndex = false) { + $this->_hookRequireInactive($spot); + if ($priority !== null) { if ($priorityIsIndex) { $index = $priority; @@ -228,17 +235,18 @@ public function hookHasCallbacks(string $spot, int $priority = null, bool $prior */ public function hook(string $spot, array $args = [], HookBreaker &$brokenBy = null) { - $this->_rebindHooksIfCloned(); - $brokenBy = null; + $this->_rebindHooksIfCloned(); + $this->_hookRequireInactive($spot); $return = []; - if (isset($this->hooks[$spot])) { - krsort($this->hooks[$spot]); // lower priority is called sooner - $hooksBackup = $this->hooks[$spot]; + ksort($this->hooks[$spot]); // lower priority is called sooner + try { - while ($hooks = array_pop($this->hooks[$spot])) { + $this->_hookActiveSpots[$spot] = true; + + foreach ($this->hooks[$spot] as $hooks) { foreach ($hooks as $index => [$hookFx, $hookArgs]) { $return[$index] = $hookFx($this, ...$args, ...$hookArgs); } @@ -248,7 +256,7 @@ public function hook(string $spot, array $args = [], HookBreaker &$brokenBy = nu return $e->getReturnValue(); } finally { - $this->hooks[$spot] = $hooksBackup; + $this->_hookActiveSpots[$spot] = false; } }