diff options
Diffstat (limited to 'src/lib/hooks.php')
-rw-r--r-- | src/lib/hooks.php | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/src/lib/hooks.php b/src/lib/hooks.php new file mode 100644 index 0000000..21854bd --- /dev/null +++ b/src/lib/hooks.php @@ -0,0 +1,178 @@ +<?php +/// CRIMSON --- A simple PHP framework. +/// Copyright © 2024 Freya Murphy <contact@freyacat.org> +/// +/// This file is part of CRIMSON. +/// +/// CRIMSON is free software; you can redistribute it and/or modify it +/// under the terms of the GNU General Public License as published by +/// the Free Software Foundation; either version 3 of the License, or (at +/// your option) any later version. +/// +/// CRIMSON is distributed in the hope that it will be useful, but +/// WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +/** + * CRIMSON HOOKS + * + * Crimson supports hooks to allow the user to add functionality inside + * crimson's control flow. This can be used for handling errors, or having + * custom routing functionality. + * + * The current supported hooks are: 'error', 'pre_route', 'init', and 'die'. + * + * To run a hook yourself you can call CRIMSON_HOOK($name, [$args]); + * + * To create a handler, make a function in the global scope called: + * CRIMSON_<name>_hook. i.e. if you are making a init hook handler, make a + * function called CRIMSON_init_hook(...). + * + * If a hook is called and a user hook handeler does not exist, the crimson + * builtin hook will be run instead. + * + * NOTE: It is also allowed for you to create your own hook types, but you must + * also create your own handler for it. If you dont, it will result in a error + * since crimson will not have a builtin hook handler to fall back to. + * + * NOTE: CRIMSON_HOOK does support a third argument called $builtin, but this + * is only to be used by crimson itself, so please dont use it. + * + * WARNING: If a hook cannot find a handler to run, it will call + * CRIMSON_FATAL_ERROR and NEVER return. + */ + +/** + * CRIMSON builtin ERROR hook. + * + * $req - The current request parsed by the Router. All fields are gurenteed + * to be set BESIDES 'uri' and 'uri_str', since parsing of the URI can fail + * and result to a call to this error handler. + * + * This hook is run when any Controller or the Router runs into an issue and + * fails with an HTTP Error. + * + * EXAMPLE: If the Router cannot find the requested Controller and Method + * based in the URI path, it will raise 404 Not Found. + * + * EXAMPLE: If the crimson postgres database is enabled, and db-init either + * failed or is still running (the database is not ready), crimson will raise + * 503 Service Unavaliable. + * + * EXAMPLE: If the provided URI to PHP is unable to be parsed, crimson will + * raise 400 Bad Request. + * + * This is hook is called in ROUTER->handle_error(int code) and + * Controller->error(int code). + * + * NOTE: Unlike CRIMSON's DIE hook, it is supported to re-enter crimson code. + * This error handler must never return, but it is okay to recall crimson to + * try to print an error page. + * + * WARNING: If the user tries to re-enter crimson from this handler and ends + * up raising another error, the user error handler WILL NOT be called again. + * To prevent recursion, a user error handle WILL only ever be called ONCE. + * This is due to that the handler has return type 'never', and is also not + * allowed to recurse. + */ +function CRIMSON_builtin_error_hook(array $req, int $code): never +{ + http_response_code($code); + CRIMSON_DIE("{$code} {$req['uri_str']}"); +} + +/** + * CRIMSON builtin PRE ROUTE hook. + * + * This hook does nothing by default since all required CRIMSON routing is done + * in router.php. + */ +function CRIMSON_builtin_pre_route_hook(Router $router): void +{ +} + +/** + * CRIMSON builtin INIT hook. + * + * This hook does nothing by default since all required CRIMSON init work is + * run in index.php. + * + * This hook can be overridden to run any user required initalization code + * before CRIMSON launches and runs the router. + * + * WARNING: The ROUTER is NOT YET created when this hook is run. Do not call + * ROUTER or ANY CRIMSON code in a user specified init hook. Doing so is + * UNDEFINED BEHAVIOR. + */ +function CRIMSON_builtin_init_hook(): void +{ +} + +/** + * CRIMSON builtin DIE hook. + * + * Calls die with $status, i.e. this is an alias for die(). + */ +function CRIMSON_builtin_die_hook(string|int $status = 0): never +{ + die($status); +} + +/** + * Executes a hook for crimson. + * + * $name - the name of the hook + * $args - the arguments to pass to the hook + * $builtin - force the use of the builtin hook + * + * Looks for CRIMSON_$name_hook first, which is the custom user defined hook. + * If it does not exist, it will run CRIMSON_builtin_$name_hook instead. + */ +function CRIMSON_HOOK( + string $name, + array $args = array(), + bool $builtin = FALSE, +): void { + $names = array(); + if (!$builtin) + $names[] = "CRIMSON_{$name}_hook"; + $names[] = "CRIMSON_builtin_{$name}_hook"; + + // find handler + $handler = NULL; + foreach ($names as $name) { + try { + $handler = new ReflectionFunction($name); + if ($handler) + break; + } catch (Exception $_e) {}; + } + + // error in invalid hook + if (!$handler) { + CRIMSON_ERROR("Invalid hook: {$name}"); + return; + } + + // i dont care to argument check, + // if someone screws up a hook we have bigger problems + $handler->invoke(...$args); +} + +/** + * Executes die() in php. But allows the user to add a hook to handle any + * loose resources before php kills itself. + * + * NOTE: A DIE hook should NEVER be handeled and return. A user provided hook + * must also have a return type of never and immediately die. Do NOT + * call any crimson code such as rerunning the ROUTER. Doing so will result in + * UNDEFINED BEHAVIOR and nasal demons showing up at your doorstep!!! + */ +function CRIMSON_DIE(string|int $status = 0): never +{ + CRIMSON_HOOK('die', [$status]); +} |