1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Conia\Chuck; |
6
|
|
|
|
7
|
|
|
use Closure; |
8
|
|
|
use Conia\Chuck\Di\Entry; |
9
|
|
|
use Conia\Chuck\Error\ErrorRenderer; |
10
|
|
|
use Conia\Chuck\Error\Handler; |
11
|
|
|
use Conia\Chuck\Factory; |
12
|
|
|
use Conia\Chuck\Http\Emitter; |
13
|
|
|
use Conia\Chuck\Middleware; |
14
|
|
|
use Conia\Chuck\Registry; |
15
|
|
|
use Conia\Chuck\Renderer\HtmlErrorRenderer; |
16
|
|
|
use Conia\Chuck\Renderer\HtmlRenderer; |
17
|
|
|
use Conia\Chuck\Renderer\JsonErrorRenderer; |
18
|
|
|
use Conia\Chuck\Renderer\JsonRenderer; |
19
|
|
|
use Conia\Chuck\Renderer\Renderer; |
20
|
|
|
use Conia\Chuck\Renderer\TextErrorRenderer; |
21
|
|
|
use Conia\Chuck\Renderer\TextRenderer; |
22
|
|
|
use Conia\Chuck\Routing\AddsRoutes; |
23
|
|
|
use Conia\Chuck\Routing\RouteAdder; |
24
|
|
|
use Psr\Container\ContainerInterface as PsrContainer; |
25
|
|
|
use Psr\Http\Message\ResponseInterface as PsrResponse; |
26
|
|
|
use Psr\Http\Message\ServerRequestInterface as PsrServerRequest; |
27
|
|
|
use Psr\Http\Server\MiddlewareInterface as PsrMiddleware; |
28
|
|
|
use Psr\Log\LoggerInterface as PsrLogger; |
29
|
|
|
|
30
|
|
|
/** @psalm-api */ |
31
|
|
|
class App implements RouteAdder |
32
|
|
|
{ |
33
|
|
|
use AddsRoutes; |
34
|
|
|
|
35
|
32 |
|
public function __construct( |
36
|
|
|
protected Router $router, |
37
|
|
|
protected Registry $registry, |
38
|
|
|
protected Middleware|PsrMiddleware|null $errorHandler = null, |
39
|
|
|
) { |
40
|
32 |
|
self::initializeRegistry($registry, $router); |
41
|
|
|
|
42
|
32 |
|
if (!is_null($errorHandler)) { |
43
|
|
|
// The error handler should be the first middleware |
44
|
29 |
|
$router->middleware($errorHandler); |
45
|
|
|
} |
46
|
|
|
} |
47
|
|
|
|
48
|
29 |
|
public static function create(?PsrContainer $container = null): self |
49
|
|
|
{ |
50
|
29 |
|
$registry = new Registry($container); |
51
|
29 |
|
$router = new Router(); |
52
|
|
|
|
53
|
29 |
|
return new self($router, $registry, new Handler($registry)); |
54
|
|
|
} |
55
|
|
|
|
56
|
14 |
|
public function router(): Router |
57
|
|
|
{ |
58
|
14 |
|
return $this->router; |
59
|
|
|
} |
60
|
|
|
|
61
|
9 |
|
public function registry(): Registry |
62
|
|
|
{ |
63
|
9 |
|
return $this->registry; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** @psalm-param Closure(Router $router):void $creator */ |
67
|
1 |
|
public function routes(Closure $creator, string $cacheFile = '', bool $shouldCache = true): void |
68
|
|
|
{ |
69
|
1 |
|
$this->router->routes($creator, $cacheFile, $shouldCache); |
70
|
|
|
} |
71
|
|
|
|
72
|
20 |
|
public function addRoute(Route $route): Route |
73
|
|
|
{ |
74
|
20 |
|
return $this->router->addRoute($route); |
75
|
|
|
} |
76
|
|
|
|
77
|
1 |
|
public function addGroup(Group $group): void |
78
|
|
|
{ |
79
|
1 |
|
$this->router->addGroup($group); |
80
|
|
|
} |
81
|
|
|
|
82
|
1 |
|
public function group( |
83
|
|
|
string $patternPrefix, |
84
|
|
|
Closure $createClosure, |
85
|
|
|
string $namePrefix = '', |
86
|
|
|
): Group { |
87
|
1 |
|
$group = new Group($patternPrefix, $createClosure, $namePrefix); |
88
|
1 |
|
$this->router->addGroup($group); |
89
|
|
|
|
90
|
1 |
|
return $group; |
91
|
|
|
} |
92
|
|
|
|
93
|
1 |
|
public function staticRoute( |
94
|
|
|
string $prefix, |
95
|
|
|
string $path, |
96
|
|
|
string $name = '', |
97
|
|
|
): void { |
98
|
1 |
|
$this->router->addStatic($prefix, $path, $name); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** @psalm-param non-falsy-string|list{non-falsy-string, ...}|Closure|Middleware|PsrMiddleware ...$middleware */ |
102
|
5 |
|
public function middleware(string|array|Closure|Middleware|PsrMiddleware ...$middleware): void |
103
|
|
|
{ |
104
|
5 |
|
$this->router->middleware(...$middleware); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @psalm-param non-empty-string $name |
109
|
|
|
* @psalm-param non-empty-string $class |
110
|
|
|
*/ |
111
|
1 |
|
public function renderer(string $name, string $class): Entry |
112
|
|
|
{ |
113
|
1 |
|
return $this->registry->tag(Renderer::class)->add($name, $class); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @psalm-param non-empty-string $contentType |
118
|
|
|
* @psalm-param non-empty-string $renderer |
119
|
|
|
*/ |
120
|
1 |
|
public function errorRenderer(string $contentType, string $renderer, mixed ...$args): Entry |
121
|
|
|
{ |
122
|
1 |
|
return $this->registry->tag(Handler::class) |
123
|
1 |
|
->add($contentType, ErrorRenderer::class)->args(renderer: $renderer, args: $args); |
124
|
|
|
} |
125
|
|
|
|
126
|
1 |
|
public function logger(callable $callback): void |
127
|
|
|
{ |
128
|
1 |
|
$this->registry->add(PsrLogger::class, Closure::fromCallable($callback)); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @psalm-param non-empty-string $key |
133
|
|
|
* @psalm-param class-string|object $value |
134
|
|
|
*/ |
135
|
4 |
|
public function register(string $key, object|string $value): Entry |
136
|
|
|
{ |
137
|
4 |
|
return $this->registry->add($key, $value); |
138
|
|
|
} |
139
|
|
|
|
140
|
11 |
|
public function run(): PsrResponse|false |
141
|
|
|
{ |
142
|
11 |
|
$factory = $this->registry->get(Factory::class); |
143
|
11 |
|
assert($factory instanceof Factory); |
144
|
11 |
|
$serverRequest = $factory->request(); |
145
|
11 |
|
$request = new Request($serverRequest); |
146
|
|
|
|
147
|
11 |
|
$this->registry->add(PsrServerRequest::class, $serverRequest); |
148
|
11 |
|
$this->registry->add($serverRequest::class, $serverRequest); |
149
|
11 |
|
$this->registry->add(Request::class, $request); |
150
|
|
|
|
151
|
11 |
|
$response = $this->router->dispatch($request, $this->registry); |
152
|
|
|
|
153
|
11 |
|
return (new Emitter())->emit($response) ? $response : false; |
154
|
|
|
} |
155
|
|
|
|
156
|
120 |
|
public static function initializeRegistry( |
157
|
|
|
Registry $registry, |
158
|
|
|
Router $router, |
159
|
|
|
): void { |
160
|
120 |
|
$registry->add(Router::class, $router); |
161
|
120 |
|
$registry->add($router::class, $router); |
162
|
|
|
|
163
|
120 |
|
$registry->add(Factory::class, \Conia\Chuck\Psr\Nyholm::class); |
164
|
120 |
|
$registry->add(Response::class)->constructor('fromFactory'); |
165
|
|
|
|
166
|
|
|
// Add default renderers |
167
|
120 |
|
$rendererTag = $registry->tag(Renderer::class); |
168
|
120 |
|
$rendererTag->add('text', TextRenderer::class); |
169
|
120 |
|
$rendererTag->add('json', JsonRenderer::class); |
170
|
120 |
|
$rendererTag->add('html', HtmlRenderer::class); |
171
|
120 |
|
$rendererTag->add('textError', TextErrorRenderer::class); |
172
|
120 |
|
$rendererTag->add('jsonError', JsonErrorRenderer::class); |
173
|
120 |
|
$rendererTag->add('htmlError', HtmlErrorRenderer::class); |
174
|
|
|
|
175
|
|
|
// Register mimetypes which are compared to the Accept header on error. |
176
|
|
|
// If the header matches a registered Renderer |
177
|
120 |
|
$handlerTag = $registry->tag(Handler::class); |
178
|
120 |
|
$handlerTag->add('text/plain', ErrorRenderer::class)->args(renderer: 'textError', args: []); |
179
|
120 |
|
$handlerTag->add('text/html', ErrorRenderer::class)->args(renderer: 'htmlError', args: []); |
180
|
120 |
|
$handlerTag->add('application/json', ErrorRenderer::class)->args(renderer: 'jsonError', args: []); |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
|