Skip to content

Commit 47f2349

Browse files
loks0nclaude
andcommitted
Extract Hooks registry out of Http
The hook arrays (init/shutdown/options/error/start/request) lived as protected statics on Http, and the previous refactor added six public getInitHooks()/getShutdownHooks()/etc. accessors purely so Dispatcher could read them. That's a leaky API — internal process-global state promoted to public surface for the sake of one internal collaborator. Move the registries to a dedicated Hooks class (mirrors how Router owns its own static state). Http::init/shutdown/options/error/onStart/ onRequest remain as thin delegators for backwards compat; the six getXxxHooks accessors are gone. Dispatcher reads Hooks::\$init etc. directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4f5d41a commit 47f2349

3 files changed

Lines changed: 150 additions & 148 deletions

File tree

src/Http/Dispatcher.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ public function handle(): void
4444
$this->http->setRequestResource('response', fn() => $this->response);
4545

4646
try {
47-
foreach (Http::getRequestHooks() as $hook) {
47+
foreach (Hooks::$request as $hook) {
4848
$arguments = $this->http->getArguments($hook, [], []);
4949
\call_user_func_array($hook->getAction(), $arguments);
5050
}
5151
} catch (\Exception $e) {
5252
$this->http->setRequestResource('error', fn() => $e);
5353

54-
foreach (Http::getErrorHooks() as $error) {
54+
foreach (Hooks::$errors as $error) {
5555
if (\in_array('*', $error->getGroups())) {
5656
try {
5757
$arguments = $this->http->getArguments($error, [], []);
@@ -100,20 +100,20 @@ public function handle(): void
100100
if (Http::REQUEST_METHOD_OPTIONS === $method) {
101101
try {
102102
foreach ($groups as $group) {
103-
foreach (Http::getOptionsHooks() as $option) {
103+
foreach (Hooks::$options as $option) {
104104
if (\in_array($group, $option->getGroups())) {
105105
\call_user_func_array($option->getAction(), $this->http->getArguments($option, [], $this->request->getParams()));
106106
}
107107
}
108108
}
109109

110-
foreach (Http::getOptionsHooks() as $option) {
110+
foreach (Hooks::$options as $option) {
111111
if (\in_array('*', $option->getGroups())) {
112112
\call_user_func_array($option->getAction(), $this->http->getArguments($option, [], $this->request->getParams()));
113113
}
114114
}
115115
} catch (\Throwable $e) {
116-
foreach (Http::getErrorHooks() as $error) {
116+
foreach (Hooks::$errors as $error) {
117117
if (\in_array('*', $error->getGroups())) {
118118
$this->http->setRequestResource('error', fn() => $e);
119119
\call_user_func_array($error->getAction(), $this->http->getArguments($error, [], $this->request->getParams()));
@@ -130,7 +130,7 @@ public function handle(): void
130130
return;
131131
}
132132

133-
foreach (Http::getErrorHooks() as $error) {
133+
foreach (Hooks::$errors as $error) {
134134
if (\in_array('*', $error->getGroups())) {
135135
$this->http->setRequestResource('error', fn() => new Exception('Not Found', 404));
136136
\call_user_func_array($error->getAction(), $this->http->getArguments($error, [], $this->request->getParams()));
@@ -147,15 +147,15 @@ public function execute(RouteMatch $match): void
147147

148148
try {
149149
if ($route->getHook()) {
150-
foreach (Http::getInitHooks() as $hook) {
150+
foreach (Hooks::$init as $hook) {
151151
if (\in_array('*', $hook->getGroups())) {
152152
\call_user_func_array($hook->getAction(), $this->http->getArguments($hook, $pathValues, $requestParams));
153153
}
154154
}
155155
}
156156

157157
foreach ($groups as $group) {
158-
foreach (Http::getInitHooks() as $hook) {
158+
foreach (Hooks::$init as $hook) {
159159
if (\in_array($group, $hook->getGroups())) {
160160
\call_user_func_array($hook->getAction(), $this->http->getArguments($hook, $pathValues, $requestParams));
161161
}
@@ -167,15 +167,15 @@ public function execute(RouteMatch $match): void
167167
}
168168

169169
foreach ($groups as $group) {
170-
foreach (Http::getShutdownHooks() as $hook) {
170+
foreach (Hooks::$shutdown as $hook) {
171171
if (\in_array($group, $hook->getGroups())) {
172172
\call_user_func_array($hook->getAction(), $this->http->getArguments($hook, $pathValues, $requestParams));
173173
}
174174
}
175175
}
176176

177177
if ($route->getHook()) {
178-
foreach (Http::getShutdownHooks() as $hook) {
178+
foreach (Hooks::$shutdown as $hook) {
179179
if (\in_array('*', $hook->getGroups())) {
180180
\call_user_func_array($hook->getAction(), $this->http->getArguments($hook, $pathValues, $requestParams));
181181
}
@@ -185,7 +185,7 @@ public function execute(RouteMatch $match): void
185185
$this->http->setRequestResource('error', fn() => $e);
186186

187187
foreach ($groups as $group) {
188-
foreach (Http::getErrorHooks() as $error) {
188+
foreach (Hooks::$errors as $error) {
189189
if (\in_array($group, $error->getGroups())) {
190190
try {
191191
\call_user_func_array($error->getAction(), $this->http->getArguments($error, $pathValues, $requestParams));
@@ -196,7 +196,7 @@ public function execute(RouteMatch $match): void
196196
}
197197
}
198198

199-
foreach (Http::getErrorHooks() as $error) {
199+
foreach (Hooks::$errors as $error) {
200200
if (\in_array('*', $error->getGroups())) {
201201
try {
202202
\call_user_func_array($error->getAction(), $this->http->getArguments($error, $pathValues, $requestParams));

src/Http/Hooks.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Utopia\Http;
6+
7+
use Utopia\Servers\Hook;
8+
9+
/**
10+
* Process-global hook registry.
11+
*
12+
* Owns the lifecycle-hook arrays (init/shutdown/options/error/start/request)
13+
* that used to live on {@see Http} as protected statics. Keeping them here
14+
* means {@see Dispatcher} can read the registries through a dedicated
15+
* primitive instead of forcing {@see Http} to expose six `getXxxHooks()`
16+
* accessors purely for internal consumption.
17+
*
18+
* Hooks are populated at bootstrap and must not be mutated after the
19+
* server starts accepting requests; registration APIs are public static
20+
* methods, reads are public static arrays.
21+
*/
22+
final class Hooks
23+
{
24+
/** @var Hook[] */
25+
public static array $init = [];
26+
27+
/** @var Hook[] */
28+
public static array $shutdown = [];
29+
30+
/** @var Hook[] */
31+
public static array $options = [];
32+
33+
/** @var Hook[] */
34+
public static array $errors = [];
35+
36+
/** @var Hook[] */
37+
public static array $start = [];
38+
39+
/** @var Hook[] */
40+
public static array $request = [];
41+
42+
/**
43+
* Register a callback that runs before the matched route action.
44+
*/
45+
public static function init(): Hook
46+
{
47+
$hook = new Hook();
48+
$hook->groups(['*']);
49+
self::$init[] = $hook;
50+
51+
return $hook;
52+
}
53+
54+
/**
55+
* Register a callback that runs after the matched route action.
56+
*/
57+
public static function shutdown(): Hook
58+
{
59+
$hook = new Hook();
60+
$hook->groups(['*']);
61+
self::$shutdown[] = $hook;
62+
63+
return $hook;
64+
}
65+
66+
/**
67+
* Register a callback for OPTIONS method requests.
68+
*/
69+
public static function options(): Hook
70+
{
71+
$hook = new Hook();
72+
$hook->groups(['*']);
73+
self::$options[] = $hook;
74+
75+
return $hook;
76+
}
77+
78+
/**
79+
* Register an error callback.
80+
*/
81+
public static function error(): Hook
82+
{
83+
$hook = new Hook();
84+
$hook->groups(['*']);
85+
self::$errors[] = $hook;
86+
87+
return $hook;
88+
}
89+
90+
/**
91+
* Register a callback that runs once when the server starts.
92+
*/
93+
public static function onStart(): Hook
94+
{
95+
$hook = new Hook();
96+
self::$start[] = $hook;
97+
98+
return $hook;
99+
}
100+
101+
/**
102+
* Register a callback that runs at the top of every request, before
103+
* route matching.
104+
*/
105+
public static function onRequest(): Hook
106+
{
107+
$hook = new Hook();
108+
self::$request[] = $hook;
109+
110+
return $hook;
111+
}
112+
113+
/**
114+
* Clear every registered hook. Intended for test isolation.
115+
*/
116+
public static function reset(): void
117+
{
118+
self::$init = [];
119+
self::$shutdown = [];
120+
self::$options = [];
121+
self::$errors = [];
122+
self::$start = [];
123+
self::$request = [];
124+
}
125+
}

0 commit comments

Comments
 (0)