Test Failed
Branch main (c2f646)
by Pranjal
13:07
created

App   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Importance

Changes 11
Bugs 4 Features 0
Metric Value
eloc 101
c 11
b 4
f 0
dl 0
loc 267
rs 9.76
wmc 33

13 Methods

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