Passed
Push — main ( 38e5fb...e27ea0 )
by Pranjal
12:24
created

App::container()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
/*
3
 * This file is part of the Scrawler package.
4
 *
5
 * (c) Pranjal Pandey <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Scrawler;
14
15
use Scrawler\Router\Router;
16
17
/**
18
 * @method \PHLAK\Config\Config    config()
19
 * @method \Scrawler\Http\Request  request()
20
 * @method \Scrawler\Http\Response response()
21
 * @method \Scrawler\Pipeline      pipeline()
22
 */
23
class App
24
{
25
    use Traits\Container;
26
    use Traits\Router;
27
28
    public static App $app;
29
30
    private Router $router;
31
32
    private \DI\Container $container;
33
34
    /**
35
     * @var array<\Closure|callable>
36
     */
37
    private array $handler = [];
38
39
    private string $version = '2.x';
40
41
    public function __construct()
42
    {
43
        self::$app = $this;
44
        $this->router = new Router();
45
        $this->container = new \DI\Container();
46
        $this->register('config', value: $this->create(\PHLAK\Config\Config::class));
47
        $this->register('pipeline', value: $this->create(Pipeline::class));
48
        $this->config()->set('debug', false);
49
        $this->config()->set('api', false);
50
        $this->config()->set('middlewares', []);
51
52
        $this->handler('404', function (): array|string {
53
            if ($this->config()->get('api')) {
54
                return ['status' => 404, 'msg' => '404 Not Found'];
55
            }
56
57
            return '404 Not Found';
58
        });
59
        $this->handler('405', function (): array|string {
60
            if ($this->config()->get('api')) {
61
                return ['status' => 405, 'msg' => '405 Method Not Allowed'];
62
            }
63
64
            return '405 Method Not Allowed';
65
        });
66
        $this->handler('500', function (): array|string {
67
            if ($this->config()->get('api')) {
68
                return ['status' => 500, 'msg' => '500 Internal Server Error'];
69
            }
70
71
            return '500 Internal Server Error';
72
        });
73
    }
74
75
    public static function engine(): self
76
    {
77
        if (null == self::$app) {
78
            self::$app = new self();
79
        }
80
81
        return self::$app;
82
    }
83
84
    /**
85
     * Register a new service in the container.
86
     */
87
    public function container(): \DI\Container
88
    {
89
        return $this->container;
90
    }
91
92
    /**
93
     * Register a new handler in scrawler
94
     * currently uselful hadlers are 404,405,500 and exception.
95
     */
96
    public function handler(string $name, \Closure|callable $callback): void
97
    {
98
        $callback = \Closure::fromCallable(callback: $callback);
99
        if ('exception' === $name) {
100
            set_error_handler($callback);
101
            set_exception_handler($callback);
102
        }
103
        $this->handler[$name] = $callback;
104
    }
105
106
    /**
107
     * Get the handler by key.
108
     */
109
    public function getHandler(string $key): \Closure|callable
110
    {
111
        return $this->handler[$key];
112
    }
113
114
    /**
115
     * Dispatch the request to the router and create response.
116
     */
117
    public function dispatch(?Http\Request $request = null): Http\Response
118
    {
119
        if (is_null($request)) {
120
            $request = $this->request();
121
        }
122
        $pipeline = new Pipeline();
123
124
        return $pipeline->middleware($this->config()->get('middlewares'))->run($request, fn ($request): \Scrawler\Http\Response => $this->dispatchRouter($request));
125
    }
126
127
    /**
128
     * Dispatch the request to the router and create response.
129
     */
130
    private function dispatchRouter(Http\Request $request): Http\Response
131
    {
132
        $httpMethod = $request->getMethod();
133
        $uri = $request->getPathInfo();
134
        $response = $this->makeResponse('', 200);
135
136
        try {
137
            [$status, $handler, $args, $debug] = $this->router->dispatch($httpMethod, $uri);
138
            switch ($status) {
139
                case Router::NOT_FOUND:
140
                    $response = $this->handleNotFound($debug);
141
                    break;
142
                case Router::METHOD_NOT_ALLOWED:
143
                    $response = $this->handleMethodNotAllowed($debug);
144
                    break;
145
                case Router::FOUND:
146
                    // call the handler
147
                    $response = $this->container->call($handler, $args);
148
                    $response = $this->makeResponse($response, 200);
149
                    // Send Response
150
            }
151
        } catch (\Exception $e) {
152
            if ($this->config()->get('debug', false)) {
153
                throw $e;
154
            } else {
155
                $response = $this->container->call($this->handler['500']);
156
                $response = $this->makeResponse($response, 500);
157
            }
158
        }
159
160
        return $response;
161
    }
162
163
    /**
164
     * Handle 404 error.
165
     *
166
     * @throws Exception\NotFoundException
167
     */
168
    private function handleNotFound(string $debug): Http\Response
169
    {
170
        if ($this->config()->get('debug')) {
171
            throw new Exception\NotFoundException($debug);
172
        }
173
        $response = $this->container->call($this->handler['404']);
174
175
        return $this->makeResponse($response, 404);
176
    }
177
178
    /**
179
     * Handle 405 error.
180
     *
181
     * @throws Exception\MethodNotAllowedException
182
     */
183
    private function handleMethodNotAllowed(string $debug): Http\Response
184
    {
185
        if ($this->config()->get('debug')) {
186
            throw new Exception\MethodNotAllowedException($debug);
187
        }
188
        $response = $this->container->call($this->handler['405']);
189
190
        return $this->makeResponse($response, 405);
191
    }
192
193
    /**
194
     * Dipatch request and send response on screen.
195
     */
196
    public function run(): void
197
    {
198
        $response = $this->dispatch();
199
        $response->send();
200
    }
201
202
    /**
203
     * Builds response object from content.
204
     *
205
     * @param array<string,mixed>|string|\Scrawler\Http\Response $content
206
     */
207
    private function makeResponse(array|string|Http\Response $content, int $status = 200): Http\Response
208
    {
209
        if (!$content instanceof Http\Response) {
0 ignored issues
show
introduced by
$content is never a sub-type of Scrawler\Http\Response.
Loading history...
210
            $response = new Http\Response();
211
            $response->setStatusCode($status);
212
213
            if (is_array($content)) {
0 ignored issues
show
introduced by
The condition is_array($content) is always true.
Loading history...
214
                $this->config()->set('api', true);
215
                $response->json($content);
216
            } elseif ($this->config()->get('api')) {
217
                $response->json($content);
218
            } else {
219
                $response->setContent($content);
220
            }
221
        } else {
222
            $response = $content;
223
        }
224
225
        return $response;
226
    }
227
228
    /**
229
     * Magic method to call container functions.
230
     *
231
     * @param array<mixed> $args
232
     */
233
    public function __call(string $function, mixed $args): mixed
234
    {
235
        try {
236
            if (!$this->container->has($function) && function_exists($function)) {
237
                return $this->container->call($function, $args);
238
            }
239
240
            return $this->container->get($function);
241
        } catch (\DI\NotFoundException $e) {
242
            throw new Exception\ContainerException($e->getMessage());
243
        }
244
    }
245
246
    /**
247
     * Add middleware(s).
248
     *
249
     * @param \Closure|callable|array<callable>|string $middlewares
250
     */
251
    public function middleware(\Closure|callable|array|string $middlewares): void
252
    {
253
        $this->config()->append('middlewares', $middlewares);
254
        $middlewares = $this->pipeline()->validateMiddleware(middlewares: $this->config()->get('middlewares'));
255
        $this->config()->set('middlewares', $middlewares);
256
    }
257
258
    /**
259
     * Get the build version of scrawler.
260
     */
261
    public function getVersion(): string
262
    {
263
        return $this->version;
264
    }
265
}
266