1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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]);
}
|