Completed
Push — master ( 0b79b7...fa8b49 )
by Dawid
10s
created

Application::get()   A

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
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Igni\Http;
4
5
use Igni\Application\Application as AbstractApplication;
6
use Igni\Application\Controller\ControllerAggregate as AbstractControllerAggregate;
7
use Igni\Application\Exception\ApplicationException;
8
use Igni\Http\Controller\ControllerAggregate;
9
use Igni\Http\Exception\HttpModuleException;
10
use Igni\Http\Middleware\ErrorMiddleware;
11
use Igni\Http\Server\OnRequest;
12
use Psr\Container\ContainerInterface;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Psr\Http\Server\MiddlewareInterface;
16
use Psr\Http\Server\RequestHandlerInterface;
17
use Throwable;
18
use Zend\Diactoros\Response\SapiEmitter;
19
use Zend\Stratigility\Middleware\CallableMiddlewareDecorator;
20
use Zend\Stratigility\MiddlewarePipe;
21
22
/**
23
 * @see \Igni\Application\Application
24
 *
25
 * @package Igni\Http
26
 */
27
class Application
28
    extends AbstractApplication
29
    implements MiddlewareAggregate, MiddlewareInterface, RequestHandlerInterface, OnRequest {
30
31
    /**
32
     * @var Router
33
     */
34
    private $router;
35
36
    /**
37
     * @var ControllerAggregate
38
     */
39
    private $controllerAggregate;
40
41
    /**
42
     * @var string[]|MiddlewareInterface[]
43
     */
44
    private $middleware = [];
45
46
    /**
47
     * @var MiddlewarePipe
48
     */
49
    private $pipeline;
50
51
    /**
52
     * Application constructor.
53
     *
54
     * @param ContainerInterface|null $container
55
     */
56 13
    public function __construct(ContainerInterface $container = null)
57
    {
58 13
        parent::__construct($container);
59
60 13
        if (!$this->getContainer()->has(Router::class)) {
61 13
            $this->getContainer()->set(Router::class, new Router());
0 ignored issues
show
Bug introduced by
The method set() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as Igni\Container\ServiceLocator. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

61
            $this->getContainer()->/** @scrutinizer ignore-call */ set(Router::class, new Router());
Loading history...
62
        }
63
64 13
        $this->router = $this->getContainer()->get(Router::class);
65 13
        $this->controllerAggregate = new ControllerAggregate($this->router);
66 13
    }
67
68
    /**
69
     * While testing call this method before handle method.
70
     */
71 8
    public function startup(): void
72
    {
73 8
        $this->initialize();
74 8
        $this->handleOnBootListeners();
75 8
    }
76
77
    /**
78
     * While testing, call this method after handle method.
79
     */
80 1
    public function shutdown(): void
81
    {
82 1
        $this->handleOnShutDownListeners();
83 1
    }
84
85
    /**
86
     * Startups and run application with/or without dedicated server.
87
     * Once application is run it will listen to incoming http requests,
88
     * and takes care of the entire request flow process.
89
     *
90
     * @param Server|null $server
91
     */
92
    public function run(Server $server = null): void
93
    {
94
        $this->startup();
95
96
        if ($server) {
97
            $server->addListener($this);
98
            $server->start();
99
        } else {
100
            $response = $this->handle(ServerRequest::fromGlobals());
101
            $emitter = new SapiEmitter();
102
            $emitter->emit($response);
103
            if ($response instanceof Response) {
104
                $response->end();
105
            }
106
        }
107
108
        $this->shutdown();
109
    }
110
111
    /**
112
     * Registers PSR-15 compatible middelware.
113
     * Middleware can be either callable object which accepts PSR-7 server request interface and returns
114
     * response interface, or just class name that implements psr-15 middleware or its instance.
115
     *
116
     * @param MiddlewareInterface|callable $middleware
117
     */
118 1
    public function use($middleware): void
119
    {
120 1
        if (!is_subclass_of($middleware, MiddlewareInterface::class)) {
121 1
            if (!is_callable($middleware)) {
122
                throw new ApplicationException('Middleware must be either class or object that implements ' . MiddlewareInterface::class);
123
            }
124
125 1
            $middleware = new CallableMiddlewareDecorator($middleware);
0 ignored issues
show
Bug introduced by
It seems like $middleware can also be of type Psr\Http\Server\MiddlewareInterface; however, parameter $middleware of Zend\Stratigility\Middle...ecorator::__construct() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

125
            $middleware = new CallableMiddlewareDecorator(/** @scrutinizer ignore-type */ $middleware);
Loading history...
126
        }
127
128 1
        $this->middleware[] = $middleware;
129 1
    }
130
131
    /**
132
     * Handles request flow process.
133
     *
134
     * @see MiddlewareInterface::process()
135
     *
136
     * @param ServerRequestInterface $request
137
     * @param RequestHandlerInterface $next
138
     * @return ResponseInterface
139
     */
140 11
    public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
141
    {
142
        /** @var Route $route */
143 11
        $route = $this->router->findRoute(
144 11
            $request->getMethod(),
145 11
            $request->getUri()->getPath()
146
        );
147
148 9
        $controller = $route->getDelegator();
149
150 9
        if ($request instanceof ServerRequest) {
151 9
            $request = $request->withAttributes($route->getAttributes());
152
        }
153
154 9
        if (is_string($controller) &&
155 9
            class_exists($controller) &&
156 9
            in_array(Controller::class, class_implements($controller))
157
        ) {
158
            /** @var Controller $instance */
159
            $instance = $this->resolver->resolve($controller);
160
            return $instance($request);
161
        }
162
163 9
        if (is_callable($controller)) {
164 9
            $response = $controller($request);
165 9
            if (!$response instanceof ResponseInterface) {
166
                throw HttpModuleException::controllerMustReturnValidResponse();
167
            }
168
169 9
            return $response;
170
        }
171
172
        throw HttpModuleException::couldNotRetrieveControllerForRoute($route->getPath());
173
    }
174
175
    /**
176
     * Runs application listeners and handles request flow process.
177
     *
178
     * @param ServerRequestInterface $request
179
     * @return ResponseInterface
180
     */
181 11
    public function handle(ServerRequestInterface $request): ResponseInterface
182
    {
183 11
        $this->handleOnRunListeners();
184 11
        $response = $this->getMiddlewarePipe()->handle($request);
185 11
        return $response;
186
    }
187
188
    /**
189
     * Decorator for handle method, used by server instance.
190
     * @see Application::handle()
191
     * @see Server::addListener()
192
     *
193
     * @param ServerRequestInterface $request
194
     * @return ResponseInterface
195
     */
196
    public function onRequest(ServerRequestInterface $request): ResponseInterface
197
    {
198
        return $this->handle($request);
199
    }
200
201
    /**
202
     * Registers new controller that accepts get request
203
     * when request uri matches passed route pattern.
204
     *
205
     * @param string $route
206
     * @param callable $controller
207
     */
208 3
    public function get(string $route, callable $controller): void
209
    {
210 3
        $this->controllerAggregate->add($controller, Route::get($route));
211 3
    }
212
213
    /**
214
     * Registers new controller that accepts post request
215
     * when request uri matches passed route pattern.
216
     *
217
     * @param string $route
218
     * @param callable $controller
219
     */
220 1
    public function post(string $route, callable $controller): void
221
    {
222 1
        $this->controllerAggregate->add($controller, Route::post($route));
223 1
    }
224
225
    /**
226
     * Registers new controller that accepts put request
227
     * when request uri matches passed route pattern.
228
     *
229
     * @param string $route
230
     * @param callable $controller
231
     */
232 1
    public function put(string $route, callable $controller): void
233
    {
234 1
        $this->controllerAggregate->add($controller, Route::put($route));
235 1
    }
236
237
    /**
238
     * Registers new controller that accepts patch request
239
     * when request uri matches passed route pattern.
240
     *
241
     * @param string $route
242
     * @param callable $controller
243
     */
244 1
    public function patch(string $route, callable $controller): void
245
    {
246 1
        $this->controllerAggregate->add($controller, Route::patch($route));
247 1
    }
248
249
    /**
250
     * Registers new controller that accepts delete request
251
     * when request uri matches passed route pattern.
252
     *
253
     * @param string $route
254
     * @param callable $controller
255
     */
256 1
    public function delete(string $route, callable $controller): void
257
    {
258 1
        $this->controllerAggregate->add($controller, Route::delete($route));
259 1
    }
260
261
    /**
262
     * Registers new controller that accepts options request
263
     * when request uri matches passed route pattern.
264
     *
265
     * @param string $route
266
     * @param callable $controller
267
     */
268 1
    public function options(string $route, callable $controller): void
269
    {
270 1
        $this->controllerAggregate->add($controller, Route::options($route));
271 1
    }
272
273 1
    public function head(string $route, callable $controller): void
274
    {
275 1
        $this->controllerAggregate->add($controller, Route::head($route));
276 1
    }
277
278
    /**
279
     * Returns application's controller aggregate.
280
     *
281
     * @return AbstractControllerAggregate
282
     */
283 1
    public function getControllerAggregate(): AbstractControllerAggregate
284
    {
285 1
        return $this->controllerAggregate;
286
    }
287
288 11
    protected function getMiddlewarePipe(): MiddlewarePipe
289
    {
290 11
        if ($this->pipeline) {
291
            return $this->pipeline;
292
        }
293
294 11
        return $this->pipeline = $this->createPipeline();
295
    }
296
297 11
    private function createPipeline(): MiddlewarePipe
298
    {
299 11
        $pipe = new MiddlewarePipe();
300
        $pipe->pipe(new ErrorMiddleware(function(Throwable $exception) {
301 2
            $this->handleOnErrorListeners($exception);
302 11
        }));
303 11
        foreach ($this->middleware as $middleware) {
304 1
            if (is_string($middleware)) {
305
                $middleware = $this->resolver->resolve($middleware);
306
            }
307 1
            $pipe->pipe($middleware);
308
        }
309 11
        $pipe->pipe($this);
310
311 11
        return $pipe;
312
    }
313
}
314