public static function execute(string $hook, $params)
    {
        if (!($params instanceof Dictionary))
            $params = TypedDictionary::wrap($params);

        if (isset(self::$in_progress[$hook]))
            throw new RecursionException("Recursion in hooks is not supported");

        self::$in_progress[$hook] = true;

        // Count invoked hooks
        if (!isset(self::$counters[$hook]))
            self::$counters[$hook] = 1;
        else
            ++self::$counters[$hook];

        // Add the name of the hook to the parameters
        if ($params instanceof TypedDictionary)
            $params->setType('hook', Type::STRING);
        $params['hook'] = $hook;

        // Check if the hook has any subscribers and if it hasn't been paused
        if (isset(self::$hooks[$hook]) && empty(self::$paused[$hook]))
        {
            // Call hooks and collect responses
            foreach (self::$hooks[$hook] as $subscriber)
            {
                $cb = $subscriber['callback'];

                try
                {
                    $cb($params);
                }
                catch (HookInterrupted $e)
                {
                    break;
                }
                catch (\Throwable $e)
                {
                    $logger = self::getLogger();
                    if (!($logger instanceof NullLogger))
                    { 
                        // Log the error when a logger is available
                        self::getLogger()->error("Callback to {0} throw an exception: {1}", [$hook, $e]);
                    }
                    else
                    { 
                        // Otherwise debug. Doing this because otherwise exceptions go completely unnoticed
                        Functions::debug($e);
                    }
                }
            }
        }

        unset(self::$in_progress[$hook]);

        return $params;
    }