App::router()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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