Passed
Push — master ( 09221c...35c1c3 )
by Dawid
02:49
created

Application::patch()   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\EmitterInterface;
19
use Zend\Diactoros\Response\SapiEmitter;
20
use Zend\Stratigility\Middleware\CallableMiddlewareDecorator;
21
use Zend\Stratigility\MiddlewarePipe;
22
23
/**
24
 * @see \Igni\Application\Application
25
 *
26
 * @package Igni\Http
27
 */
28
class Application
29
    extends AbstractApplication
30
    implements MiddlewareAggregate, MiddlewareInterface, RequestHandlerInterface, OnRequest {
31
32
    /**
33
     * @var Router
34
     */
35
    private $router;
36
37
    /**
38
     * @var ControllerAggregate
39
     */
40
    private $controllerAggregate;
41
42
    /**
43
     * @var string[]|MiddlewareInterface[]
44
     */
45
    private $middleware = [];
46
47
    /**
48
     * @var MiddlewarePipe
49
     */
50
    private $pipeline;
51
52
    /**
53
     * @var EmitterInterface
54
     */
55
    private $emitter;
56
57
    /**
58
     * Application constructor.
59
     *
60
     * @param ContainerInterface|null $container
61
     */
62 13
    public function __construct(ContainerInterface $container = null)
63
    {
64 13
        parent::__construct($container);
65
66 13
        if ($this->getContainer()->has(Router::class)) {
67
            $this->router = $this->getContainer()->get(Router::class);
68
        } else {
69 13
            $this->router = new Router();
70
        }
71
72 13
        if ($this->getContainer()->has(ControllerAggregate::class)) {
73
            $this->controllerAggregate = $this->getContainer()->get(ControllerAggregate::class);
74
        } else {
75 13
            $this->controllerAggregate = new ControllerAggregate($this->router);
76
        }
77
78 13
        if ($this->getContainer()->has(EmitterInterface::class)) {
79
            $this->emitter = $this->getContainer()->get(EmitterInterface::class);
80
        } else {
81 13
            $this->emitter = new SapiEmitter();
82
        }
83 13
    }
84
85
    /**
86
     * While testing call this method before handle method.
87
     */
88 8
    public function startup(): void
89
    {
90 8
        $this->handleOnBootListeners();
91 8
        $this->initialize();
92 8
        $this->handleOnRunListeners();
93 8
    }
94
95
    /**
96
     * While testing, call this method after handle method.
97
     */
98 1
    public function shutdown(): void
99
    {
100 1
        $this->handleOnShutDownListeners();
101 1
    }
102
103
    /**
104
     * Startups and run application with/or without dedicated server.
105
     * Once application is run it will listen to incoming http requests,
106
     * and takes care of the entire request flow process.
107
     *
108
     * @param Server|null $server
109
     */
110
    public function run(Server $server = null): void
111
    {
112
        $this->startup();
113
        if ($server) {
114
            $server->addListener($this);
115
            $server->start();
116
        } else {
117
            $response = $this->handle(ServerRequest::fromGlobals());
118
            $this->emitter->emit($response);
119
            if ($response instanceof Response) {
120
                $response->end();
121
            }
122
        }
123
124
        $this->shutdown();
125
    }
126
127
    /**
128
     * Registers PSR-15 compatible middelware.
129
     * Middleware can be either callable object which accepts PSR-7 server request interface and returns
130
     * response interface, or just class name that implements psr-15 middleware or its instance.
131
     *
132
     * @param MiddlewareInterface|callable $middleware
133
     */
134 1
    public function use($middleware): void
135
    {
136 1
        if (!is_subclass_of($middleware, MiddlewareInterface::class)) {
137 1
            if (!is_callable($middleware)) {
138
                throw new ApplicationException('Middleware must be either class or object that implements ' . MiddlewareInterface::class);
139
            }
140
141 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

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