///
/// 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 .
/**
* 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__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]);
}