Completed
Push — master ( 91a6b3...01a195 )
by Neomerx
04:40
created

Application::setUpExceptionHandler()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
ccs 0
cts 0
cp 0
c 0
b 0
f 0
nc 1
1
<?php namespace Limoncello\Core\Application;
2
3
/**
4
 * Copyright 2015-2016 [email protected] (www.neomerx.com)
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Closure;
20
use Interop\Container\ContainerInterface;
21
use Limoncello\Core\Contracts\Application\ApplicationInterface;
22
use Limoncello\Core\Contracts\Application\SapiInterface;
23
use Limoncello\Core\Contracts\Routing\RouterConfigInterface;
24
use Limoncello\Core\Contracts\Routing\RouterInterface;
25
use Limoncello\Core\Routing\Router;
26
use Limoncello\Core\Routing\RouterConfig;
27
use LogicException;
28
use Psr\Http\Message\RequestInterface;
29
use Psr\Http\Message\ResponseInterface;
30
use Psr\Http\Message\ServerRequestInterface;
31
use Zend\Diactoros\Response;
32
use Zend\Diactoros\Response\EmptyResponse;
33
use Zend\Diactoros\ServerRequest;
34
35
/**
36
 * @package Limoncello\Core
37
 *
38
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
39
 */
40
abstract class Application implements ApplicationInterface
41
{
42
    /** Method name for default request factory. */
43
    const FACTORY_METHOD = 'defaultRequestFactory';
44
45
    /**
46
     * @var SapiInterface|null
47
     */
48
    private $sapi;
49
50
    /**
51
     * @var RouterInterface
52
     */
53
    private $router;
54
55
    /**
56
     * @return ContainerInterface
57
     */
58
    abstract protected function createContainer();
59
60
    /**
61
     * @return array
62
     */
63
    abstract protected function getRoutesData();
64
65
    /**
66
     * @return callable[]
67
     */
68
    abstract protected function getGlobalMiddleware();
69
70
    /**
71
     * @param SapiInterface      $sapi
72
     * @param ContainerInterface $container
73
     *
74
     * @return void
75
     */
76
    abstract protected function setUpExceptionHandler(SapiInterface $sapi, ContainerInterface $container);
77
78
    /**
79
     * @inheritdoc
80
     */
81 5
    public function setSapi(SapiInterface $sapi)
82
    {
83 5
        $this->sapi = $sapi;
84
85 5
        return $this;
86
    }
87
88
    /**
89
     * @inheritdoc
90
     */
91 6
    public function run()
92
    {
93 6
        if ($this->sapi === null) {
94 1
            throw new LogicException('SAPI not set.');
95
        }
96
97 5
        $userContainer = $this->createContainer();
98
99 5
        $this->setUpExceptionHandler($this->sapi, $userContainer);
100
101
        list($matchCode, $allowedMethods, $handlerParams, $handler, $routeMiddleware, $configurators, $requestFactory) =
102 5
            $this->getRouter()->match($this->sapi->getMethod(), $this->sapi->getUri()->getPath());
103
104 5
        if (empty($configurators) === false) {
105 2
            $this->configureUserContainer($userContainer, $configurators);
106
        }
107
108
        switch ($matchCode) {
109 5
            case RouterInterface::MATCH_FOUND:
110 3
                $handler = $this->createOrdinaryTerminalHandler($handler, $handlerParams, $userContainer);
111 3
                break;
112 2
            case RouterInterface::MATCH_METHOD_NOT_ALLOWED:
113 1
                $handler = $this->createMethodNotAllowedTerminalHandler($allowedMethods);
114 1
                break;
115
            default:
116 1
                $handler = $this->createNotFoundTerminalHandler();
117 1
                break;
118
        }
119
120 5
        $globalMiddleware = $this->getGlobalMiddleware();
121 5
        $handler = $this->createMiddlewareChain($handler, $userContainer, $globalMiddleware, $routeMiddleware);
122
123
        // if handler consists only from Controller and user didn't asked for Request creation
124 5
        $requestNotNeeded = $requestFactory === null && empty($globalMiddleware) === true && empty($routeMiddleware) &&
125 5
            $matchCode === RouterInterface::MATCH_FOUND;
126 5
        $request = $requestNotNeeded === true ?
127 5
            null : $this->createRequest($this->sapi, $userContainer, $requestFactory);
128
129
        // send `Request` down all middleware (global then route's then terminal handler in `Controller` and back) and
130
        // then send `Response` to SAPI
131 5
        $this->sapi->handleResponse($this->handleRequest($handler, $request));
132 5
    }
133
134
    /**
135
     * @return callable
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
136
     */
137 5
    public static function getDefaultRequestFactory()
138
    {
139 5
        return [static::class, static::FACTORY_METHOD];
140
    }
141
142
    /**
143
     * @param SapiInterface $sapi
144
     *
145
     * @return ServerRequestInterface
146
     */
147 3
    public static function defaultRequestFactory(SapiInterface $sapi)
148
    {
149 3
        return new ServerRequest(
150 3
            $sapi->getServer(),
151 3
            $sapi->getFiles(),
152 3
            $sapi->getUri(),
153 3
            $sapi->getMethod(),
154 3
            $sapi->getRequestBody(),
155 3
            $sapi->getHeaders(),
156 3
            $sapi->getCookies(),
157 3
            $sapi->getQueryParams(),
158 3
            $sapi->getParsedBody()
159
        );
160
    }
161
162
    /**
163
     * @param Closure               $handler
164
     * @param RequestInterface|null $request
165
     *
166
     * @return ResponseInterface
167
     */
168 5
    protected function handleRequest(Closure $handler, RequestInterface $request = null)
169
    {
170
        /** @var ResponseInterface $response */
171 5
        $response = $handler($request);
172
173 5
        return $response;
174
    }
175
176
    /**
177
     * @param int   $status
178
     * @param array $headers
179
     *
180
     * @return ResponseInterface
181
     */
182 2
    protected function createEmptyResponse($status = 204, array $headers = [])
183
    {
184 2
        $response = new EmptyResponse($status, $headers);
185
186 2
        return $response;
187
    }
188
189
    /**
190
     * @return RouterInterface
191
     */
192 5
    protected function getRouter()
193
    {
194 5
        if ($this->router === null) {
195 5
            $this->router = $this->createRouter();
196 5
            $this->router->loadCachedRoutes($this->getRoutesData());
197
        }
198
199 5
        return $this->router;
200
    }
201
202
    /**
203
     * @return array
204
     */
205 5
    protected function getRouterConfig()
206
    {
207 5
        return RouterConfig::DEFAULTS;
208
    }
209
210
    /**
211
     * @param ContainerInterface $container
212
     * @param callable[]         $configurators
213
     *
214
     * @return void
215
     */
216 2
    protected function configureUserContainer(ContainerInterface $container, array $configurators)
217
    {
218 2
        foreach ($configurators as $configurator) {
219 2
            call_user_func($configurator, $container);
220
        }
221 2
    }
222
223
    /**
224
     * @param Closure            $handler
225
     * @param ContainerInterface $userContainer
226
     * @param array|null         $globalMiddleware
227
     * @param array|null         $routeMiddleware
228
     *
229
     * @return Closure
230
     */
231 5
    protected function createMiddlewareChain(
232
        Closure $handler,
233
        ContainerInterface $userContainer,
234
        array $globalMiddleware,
235
        array $routeMiddleware = null
236
    ) {
237 5
        $handler = $this->createMiddlewareChainImpl($handler, $userContainer, $routeMiddleware);
238 5
        $handler = $this->createMiddlewareChainImpl($handler, $userContainer, $globalMiddleware);
239
240 5
        return $handler;
241
    }
242
243
    /**
244
     * @return RouterInterface
245
     */
246 5
    private function createRouter()
247
    {
248 5
        $config = $this->getRouterConfig();
249
250 5
        $generatorClass  = $config[RouterConfigInterface::KEY_GENERATOR];
251 5
        $dispatcherClass = $config[RouterConfigInterface::KEY_DISPATCHER];
252 5
        $router          = new Router($generatorClass, $dispatcherClass);
253
254 5
        return $router;
255
    }
256
257
    /**
258
     * @param SapiInterface      $sapi
259
     * @param ContainerInterface $userContainer
260
     * @param callable|null      $requestFactory
261
     *
262
     * @return ServerRequestInterface
263
     */
264 4
    private function createRequest(
265
        SapiInterface $sapi,
266
        ContainerInterface $userContainer,
267
        callable $requestFactory = null
268
    ) {
269 4
        $request = call_user_func(
270 4
            $requestFactory === null ? self::getDefaultRequestFactory() : $requestFactory,
271
            $sapi,
272
            $userContainer
273
        );
274
275 4
        return $request;
276
    }
277
278
    /**
279
     * @param callable           $handler
280
     * @param array              $handlerParams
281
     * @param ContainerInterface $container
282
     *
283
     * @return Closure
284
     */
285 3
    private function createOrdinaryTerminalHandler(
286
        callable $handler,
287
        array $handlerParams,
288
        ContainerInterface $container
289
    ) {
290
        return function (ServerRequestInterface $request = null) use ($handler, $handlerParams, $container) {
291 3
            return call_user_func($handler, $handlerParams, $container, $request);
292 3
        };
293
    }
294
295
    /**
296
     * @param array $allowedMethods
297
     *
298
     * @return Closure
299
     */
300 1
    private function createMethodNotAllowedTerminalHandler(array $allowedMethods)
301
    {
302
        // 405 Method Not Allowed
303
        return function () use ($allowedMethods) {
304 1
            return $this->createEmptyResponse(405, ['Accept' => implode(',', $allowedMethods)]);
305 1
        };
306
    }
307
308
    /**
309
     * @return Closure
310
     */
311 1
    private function createNotFoundTerminalHandler()
312
    {
313
        // 404 Not Found
314
        return function () {
315 1
            return $this->createEmptyResponse(404);
316 1
        };
317
    }
318
319
    /**
320
     * @param Closure            $handler
321
     * @param ContainerInterface $userContainer
322
     * @param array|null         $middleware
323
     *
324
     * @return Closure
325
     */
326 5
    private function createMiddlewareChainImpl(
327
        Closure $handler,
328
        ContainerInterface $userContainer,
329
        array $middleware = null
330
    ) {
331 5
        $start = count($middleware) - 1;
332 5
        for ($index = $start; $index >= 0; $index--) {
333 4
            $handler = $this->createMiddlewareChainLink($handler, $middleware[$index], $userContainer);
334
        }
335
336 5
        return $handler;
337
    }
338
339
    /**
340
     * @param Closure            $next
341
     * @param callable           $middleware
342
     * @param ContainerInterface $userContainer
343
     *
344
     * @return Closure
345
     */
346
    private function createMiddlewareChainLink(Closure $next, callable $middleware, ContainerInterface $userContainer)
347
    {
348 4
        return function (ServerRequestInterface $request) use ($next, $middleware, $userContainer) {
349 4
            return call_user_func($middleware, $request, $next, $userContainer);
350 4
        };
351
    }
352
}
353