> * @since 1.0.0 */ private array $groups = []; /** * Callbacks. * * @var array * @since 1.0.0 */ private array $callbacks = []; /** * Dispatcher. * * @var DispatcherInterface * @since 1.0.0 */ private DispatcherInterface $dispatcher; /** * Constructor. * * @param Dispatcher $dispatcher Dispatcher. If no dispatcher is provided a simple general purpose dispatcher is used. * * @since 1.0.0 */ public function __construct(Dispatcher $dispatcher = null) { $this->dispatcher = $dispatcher ?? new class() implements DispatcherInterface { /** * {@inheritdoc} */ public function dispatch(array | string | \Closure $func, mixed ...$data) : array { if (!($func instanceof \Closure)) { return []; } $func(...$data); return []; } }; } /** * Add events from file. * * Files need to return a php array of the following structure: * return [ * '{EVENT_ID}' => [ * 'callback' => [ * '{DESTINATION_NAMESPACE:method}', // can also be static by using :: between namespace and functio name * // more callbacks here * ], * ], * ]; * * @param string $path Hook file path * * @return bool * * @since 1.0.0 */ public function importFromFile(string $path) : bool { if (!\is_file($path)) { return false; } /** @noinspection PhpIncludeInspection */ $hooks = include $path; foreach ($hooks as $group => $hook) { foreach ($hook['callback'] as $callback) { $this->attach($group, $callback, $hook['remove'] ?? false, $hook['reset'] ?? true); } } return true; } /** * Clear all events * * @return void * @since 1.0.0 */ public function clear() : void { $this->groups = []; $this->callbacks = []; } /** * Attach new event * * @param string $group Name of the event (unique) * @param string|\Closure $callback Callback or route for the event * @param bool $remove Remove event after triggering it? * @param bool $reset Reset event after triggering it? Remove must be false! * * @return bool * * @since 1.0.0 */ public function attach(string $group, string | \Closure $callback, bool $remove = false, bool $reset = false) : bool { if (!isset($this->callbacks[$group])) { $this->callbacks[$group] = ['remove' => $remove, 'reset' => $reset, 'callbacks' => []]; } $this->callbacks[$group]['callbacks'][] = $callback; $this->addGroup($group, ''); return true; } /** * Trigger event based on regex for group and/or id * * @param string $group Name of the event (can be regex) * @param string $id Sub-requirement for event (can be regex) * @param mixed $data Data to pass to the callback * * @return bool returns true on successfully triggering ANY event, false if NO event could be triggered which also includes sub-requirements missing * * @since 1.0.0 */ public function triggerSimilar(string $group, string $id = '', mixed $data = null) : bool { $groupIsRegex = \stripos($group, '/') === 0; $idIsRegex = \stripos($id, '/') === 0; $groups = []; foreach ($this->groups as $groupName => $value) { $groupNameIsRegex = \stripos($groupName, '/') === 0; if ($groupIsRegex) { if (\preg_match($group, $groupName) === 1) { $groups[$groupName] = []; } } elseif ($groupNameIsRegex && \preg_match($groupName, $group) === 1) { $groups[$groupName] = []; } elseif ($groupName === $group) { $groups[$groupName] = []; } } foreach ($groups as $groupName => $groupValues) { foreach ($this->groups[$groupName] as $idName => $value) { $idNameIsRegex = \stripos($idName, '/') === 0; if ($idIsRegex) { if (\preg_match($id, $idName) === 1) { $groups[$groupName][] = $idName; } } elseif ($idNameIsRegex && \preg_match($idName, $id) === 1) { $groups[$groupName][] = $id; } elseif ($idName === $id) { $groups[$groupName] = []; } } if (empty($groups[$groupName])) { $groups[$groupName][] = $id; } } if (!\is_array($data)) { $data = [$data]; } $data['@triggerGroup'] ??= $group; $triggerValue = false; foreach ($groups as $groupName => $ids) { foreach ($ids as $id) { $triggerValue = $this->trigger($groupName, $id, $data) || $triggerValue; } } return $triggerValue; } /** * Trigger event * * @param string $group Name of the event * @param string $id Sub-requirement for event * @param mixed $data Data to pass to the callback * * @return bool returns true on successfully triggering the event, false if the event couldn't be triggered which also includes sub-requirements missing * * @since 1.0.0 */ public function trigger(string $group, string $id = '', mixed $data = null) : bool { if (!isset($this->callbacks[$group])) { return false; } if (isset($this->groups[$group])) { $this->groups[$group][$id] = true; } if ($this->hasOutstanding($group)) { return false; } foreach ($this->callbacks[$group]['callbacks'] as $func) { if (\is_array($data)) { $data['@triggerGroup'] ??= $group; $data['@triggerId'] = $id; } else { $data = [ $data, ]; $data['@triggerGroup'] = $group; $data['@triggerId'] = $id; } $this->dispatcher->dispatch($func, ...\array_values($data)); } if ($this->callbacks[$group]['remove']) { $this->detach($group); } elseif ($this->callbacks[$group]['reset']) { $this->reset($group); } return true; } /** * Reset group * * @param string $group Name of the event * * @return void * * @since 1.0.0 */ private function reset(string $group) : void { if (!isset($this->groups[$group])) { return; // @codeCoverageIgnore } foreach ($this->groups[$group] as $id => $ok) { $this->groups[$group][$id] = false; } } /** * Check if a group has missing sub-requirements * * @param string $group Name of the event * * @return bool * * @since 1.0.0 */ private function hasOutstanding(string $group) : bool { if (!isset($this->groups[$group])) { return false; // @codeCoverageIgnore } foreach ($this->groups[$group] as $id => $ok) { if (!$ok) { return true; } } return false; } /** * Detach an event * * @param string $group Name of the event * * @return bool * * @since 1.0.0 */ public function detach(string $group) : bool { $result1 = $this->detachCallback($group); $result2 = $this->detachGroup($group); return $result1 || $result2; } /** * Detach an event * * @param string $group Name of the event * * @return bool * * @since 1.0.0 */ private function detachCallback(string $group) : bool { if (isset($this->callbacks[$group])) { unset($this->callbacks[$group]); return true; } return false; } /** * Detach an event * * @param string $group Name of the event * * @return bool * * @since 1.0.0 */ private function detachGroup(string $group) : bool { if (isset($this->groups[$group])) { unset($this->groups[$group]); return true; } return false; } /** * Add sub-requirement for event * * @param string $group Name of the event * @param string $id ID of the sub-requirement * * @return void * * @since 1.0.0 */ public function addGroup(string $group, string $id) : void { if (!isset($this->groups[$group])) { $this->groups[$group] = []; } if (isset($this->groups[$group][''])) { unset($this->groups[$group]['']); } $this->groups[$group][$id] = false; } /** * {@inheritdoc} */ public function count() : int { return \count($this->callbacks); } }