Passed
Push — develop ( 06817d...f99f53 )
by nguereza
02:34
created

HttpKernel   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 60
c 2
b 0
f 0
dl 0
loc 196
rs 10
wmc 15

8 Methods

Rating   Name   Duplication   Size   Complexity  
A handle() 0 11 2
A setRouting() 0 21 2
A isEmptyResponse() 0 4 2
A registerConfiguredMiddlewares() 0 10 2
A determineBasePath() 0 30 4
A use() 0 5 1
A run() 0 27 1
A __construct() 0 8 1
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant
7
 * PHP Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file HttpKernel.php
34
 *
35
 *  The HTTP Kernel class
36
 *
37
 *  @package    Platine\Framework\Kernel
38
 *  @author Platine Developers team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   http://www.iacademy.cf
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Framework\Kernel;
49
50
use Platine\Config\Config;
51
use Platine\Framework\App\Application;
52
use Platine\Framework\Http\Emitter\EmitterInterface;
53
use Platine\Framework\Http\Exception\HttpNotFoundException;
54
use Platine\Framework\Kernel\BaseKernel;
55
use Platine\Framework\Service\ServiceProvider;
56
use Platine\Http\Handler\MiddlewareInterface;
57
use Platine\Http\Handler\MiddlewareResolverInterface;
58
use Platine\Http\Handler\RequestHandlerInterface;
59
use Platine\Http\ResponseInterface;
60
use Platine\Http\ServerRequest;
61
use Platine\Http\ServerRequestInterface;
62
use Platine\Route\Router;
63
64
/**
65
 * @class HttpKernel
66
 * @package Platine\Framework\Kernel
67
 * @template T
68
 */
69
class HttpKernel extends BaseKernel implements RequestHandlerInterface
70
{
71
72
    /**
73
     * The router instance
74
     * @var Router
75
     */
76
    protected Router $router;
77
78
    /**
79
     * The middleware resolver instance
80
     * @var MiddlewareResolverInterface
81
     */
82
    protected MiddlewareResolverInterface $middlewareResolver;
83
84
    /**
85
     * The list of middlewares
86
     * @var MiddlewareInterface[]
87
     */
88
    protected array $middlewares = [];
89
90
    /**
91
     * Create new instance
92
     * @param Application $app
93
     * @param Router $router
94
     * @param MiddlewareResolverInterface $middlewareResolver
95
     */
96
    public function __construct(
97
        Application $app,
98
        Router $router,
99
        MiddlewareResolverInterface $middlewareResolver
100
    ) {
101
        parent::__construct($app);
102
        $this->router = $router;
103
        $this->middlewareResolver = $middlewareResolver;
104
    }
105
106
    /**
107
     * Add new middleware
108
     * @param  mixed $middleware
109
     * @return $this
110
     */
111
    public function use($middleware): self
112
    {
113
        $this->middlewares[] = $this->middlewareResolver->resolve($middleware);
114
115
        return $this;
116
    }
117
118
    /**
119
     * Run the kernel
120
     * @param ServerRequestInterface|null $request
121
     * @return void
122
     */
123
    public function run(?ServerRequestInterface $request = null): void
124
    {
125
        $req = $request ?? ServerRequest::createFromGlobals();
126
127
        //Share the instance to use later
128
        $this->app->instance($req, ServerRequestInterface::class);
129
130
        //bootstrap the application
131
        $this->bootstrap();
132
133
        //set the routing
134
        $this->setRouting();
135
136
        //Load configured middlewares
137
        $this->registerConfiguredMiddlewares();
138
139
140
        /** @var EmitterInterface $emitter */
141
        $emitter = $this->app->get(EmitterInterface::class);
142
        $response = $this->handle($req);
143
144
145
        $emitter->emit(
146
            $response,
147
            !$this->isEmptyResponse(
148
                $req->getMethod(),
149
                $response->getStatusCode()
150
            )
151
        );
152
    }
153
154
    /**
155
     * Handle the request and generate response
156
     * @param ServerRequestInterface $request
157
     * @return ResponseInterface
158
     */
159
    public function handle(ServerRequestInterface $request): ResponseInterface
160
    {
161
        $handler = clone $this;
162
163
        $middleware = current($handler->middlewares);
164
        if ($middleware === false) {
165
            throw new HttpNotFoundException($request);
166
        }
167
        next($handler->middlewares);
168
169
        return $middleware->process($request, $handler);
170
    }
171
172
    /**
173
     * Set routing information
174
     * @return void
175
     */
176
    public function setRouting(): void
177
    {
178
        /** @var Config<T> $config */
179
        $config = $this->app->get(Config::class);
180
181
        $basePath = $this->determineBasePath();
182
        $this->router->setBasePath($basePath);
183
184
        $routes = $config->get('routes', []);
185
        //TODO find a way to remove return of array for
186
        //routes configuration
187
        $routes[0]($this->router);
188
189
        //Load providers routes
190
        /** @var ServiceProvider[] $providers */
191
        $providers = $this->app->getProviders();
192
        foreach ($providers as $provider) {
193
            $provider->addRoutes($this->router);
194
        }
195
196
        $this->app->instance($this->router);
197
    }
198
199
    /**
200
     * Load configured middlewares
201
     * @return void
202
     */
203
    protected function registerConfiguredMiddlewares(): void
204
    {
205
        /** @var Config<T> $config */
206
        $config = $this->app->get(Config::class);
207
208
        /** @var string[] $middlewares */
209
        $middlewares = $config->get('middlewares', []);
210
211
        foreach ($middlewares as $middleware) {
212
            $this->use($middleware);
213
        }
214
    }
215
216
    /**
217
     * Will try to set application base path used somewhere in router,
218
     * Firstly will check if Application::getBasePath is not empty, if empty
219
     * check for application configuration and otherwise will try determine
220
     *  using server variable.
221
     * @return string
222
     */
223
    protected function determineBasePath(): string
224
    {
225
        $appBasePath = $this->app->getBasePath();
226
        if (!empty($appBasePath)) {
227
            return $appBasePath;
228
        }
229
230
        /** @var Config<T> $config */
231
        $config = $this->app->get(Config::class);
232
        $configBasePath = $config->get('app.base_path');
233
234
        if (!empty($configBasePath)) {
235
            $this->app->setBasePath($configBasePath);
236
            return $configBasePath;
237
        }
238
239
        //TODO
240
        /** @var ServerRequestInterface $request */
241
        $request = $this->app->get(ServerRequestInterface::class);
242
        $server = $request->getServerParams();
243
        $autoBasePath = implode('/', array_slice(explode(
244
            '/',
245
            $server['SCRIPT_NAME'] ?? ''
246
        ), 0, -1));
247
        $this->app->setBasePath($autoBasePath);
248
        if (!empty($autoBasePath)) {
249
            return $autoBasePath;
250
        }
251
252
        return '/';
253
    }
254
255
    /**
256
     * Whether is the response with no body
257
     * @param string $method
258
     * @param int $statusCode
259
     * @return bool
260
     */
261
    private function isEmptyResponse(string $method, int $statusCode): bool
262
    {
263
        return (strtoupper($method) === 'HEAD')
264
                || (in_array($statusCode, [100, 101, 102, 204, 205, 304], true));
265
    }
266
}
267