Completed
Push — master ( 01a195...a1d4cc )
by Neomerx
03:10
created

Application::callControllerHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 4
crap 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 5
        if ($requestFactory === null) {
124
            // if developer removed request factory and we don't have any middleware it's OK to skip Request creation
125 3
            if (empty($globalMiddleware) === true &&
126 3
                empty($routeMiddleware) === true &&
127 3
                $matchCode === RouterInterface::MATCH_FOUND
128
            ) {
129 1
                $request = null;
130
            } else {
131 3
                $request = $this->createRequest($this->sapi, $userContainer, self::getDefaultRequestFactory());
132
            }
133
        } else {
134 2
            $request = $this->createRequest($this->sapi, $userContainer, $requestFactory);
135
        }
136
137
        // send `Request` down all middleware (global then route's then terminal handler in `Controller` and back) and
138
        // then send `Response` to SAPI
139 5
        $this->sapi->handleResponse($this->handleRequest($handler, $request));
140 5
    }
141
142
    /**
143
     * @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...
144
     */
145 5
    public static function getDefaultRequestFactory()
146
    {
147 5
        return [static::class, static::FACTORY_METHOD];
148
    }
149
150
    /**
151
     * @param SapiInterface $sapi
152
     *
153
     * @return ServerRequestInterface
154
     */
155 3
    public static function defaultRequestFactory(SapiInterface $sapi)
156
    {
157 3
        return new ServerRequest(
158 3
            $sapi->getServer(),
159 3
            $sapi->getFiles(),
160 3
            $sapi->getUri(),
161 3
            $sapi->getMethod(),
162 3
            $sapi->getRequestBody(),
163 3
            $sapi->getHeaders(),
164 3
            $sapi->getCookies(),
165 3
            $sapi->getQueryParams(),
166 3
            $sapi->getParsedBody()
167
        );
168
    }
169
170
    /**
171
     * @param Closure               $handler
172
     * @param RequestInterface|null $request
173
     *
174
     * @return ResponseInterface
175
     */
176 5
    protected function handleRequest(Closure $handler, RequestInterface $request = null)
177
    {
178
        /** @var ResponseInterface $response */
179 5
        $response = call_user_func($handler, $request);
180
181 5
        return $response;
182
    }
183
184
    /**
185
     * @param int   $status
186
     * @param array $headers
187
     *
188
     * @return ResponseInterface
189
     */
190 2
    protected function createEmptyResponse($status = 204, array $headers = [])
191
    {
192 2
        $response = new EmptyResponse($status, $headers);
193
194 2
        return $response;
195
    }
196
197
    /**
198
     * @return RouterInterface
199
     */
200 5
    protected function getRouter()
201
    {
202 5
        if ($this->router === null) {
203 5
            $this->router = $this->createRouter();
204 5
            $this->router->loadCachedRoutes($this->getRoutesData());
205
        }
206
207 5
        return $this->router;
208
    }
209
210
    /**
211
     * @return array
212
     */
213 5
    protected function getRouterConfig()
214
    {
215 5
        return RouterConfig::DEFAULTS;
216
    }
217
218
    /**
219
     * @param ContainerInterface $container
220
     * @param callable[]         $configurators
221
     *
222
     * @return void
223
     */
224 2
    protected function configureUserContainer(ContainerInterface $container, array $configurators)
225
    {
226 2
        foreach ($configurators as $configurator) {
227 2
            call_user_func($configurator, $container);
228
        }
229 2
    }
230
231
    /**
232
     * @param Closure            $handler
233
     * @param ContainerInterface $userContainer
234
     * @param array|null         $globalMiddleware
235
     * @param array|null         $routeMiddleware
236
     *
237
     * @return Closure
238
     */
239 5
    protected function createMiddlewareChain(
240
        Closure $handler,
241
        ContainerInterface $userContainer,
242
        array $globalMiddleware,
243
        array $routeMiddleware = null
244
    ) {
245 5
        $handler = $this->createMiddlewareChainImpl($handler, $userContainer, $routeMiddleware);
246 5
        $handler = $this->createMiddlewareChainImpl($handler, $userContainer, $globalMiddleware);
247
248 5
        return $handler;
249
    }
250
251
    /**
252
     * @param callable                    $handler
253
     * @param array                       $handlerParams
254
     * @param ContainerInterface          $container
255
     * @param ServerRequestInterface|null $request
256
     *
257
     * @return ResponseInterface
258
     */
259 3
    protected function callControllerHandler(
260
        callable $handler,
261
        array $handlerParams,
262
        ContainerInterface $container,
263
        ServerRequestInterface $request = null
264
    ) {
265 3
        return call_user_func($handler, $handlerParams, $container, $request);
266
    }
267
268
    /**
269
     * @return RouterInterface
270
     */
271 5
    private function createRouter()
272
    {
273 5
        $config = $this->getRouterConfig();
274
275 5
        $generatorClass  = $config[RouterConfigInterface::KEY_GENERATOR];
276 5
        $dispatcherClass = $config[RouterConfigInterface::KEY_DISPATCHER];
277 5
        $router          = new Router($generatorClass, $dispatcherClass);
278
279 5
        return $router;
280
    }
281
282
    /**
283
     * @param SapiInterface      $sapi
284
     * @param ContainerInterface $userContainer
285
     * @param callable           $requestFactory
286
     *
287
     * @return ServerRequestInterface
288
     */
289 4
    private function createRequest(
290
        SapiInterface $sapi,
291
        ContainerInterface $userContainer,
292
        callable $requestFactory
293
    ) {
294 4
        $request = call_user_func($requestFactory, $sapi, $userContainer);
295
296 4
        return $request;
297
    }
298
299
    /**
300
     * @param callable           $handler
301
     * @param array              $handlerParams
302
     * @param ContainerInterface $container
303
     *
304
     * @return Closure
305
     */
306 3
    private function createOrdinaryTerminalHandler(
307
        callable $handler,
308
        array $handlerParams,
309
        ContainerInterface $container
310
    ) {
311
        return function (ServerRequestInterface $request = null) use ($handler, $handlerParams, $container) {
312 3
            return $this->callControllerHandler($handler, $handlerParams, $container, $request);
313 3
        };
314
    }
315
316
    /**
317
     * @param array $allowedMethods
318
     *
319
     * @return Closure
320
     */
321 1
    private function createMethodNotAllowedTerminalHandler(array $allowedMethods)
322
    {
323
        // 405 Method Not Allowed
324
        return function () use ($allowedMethods) {
325 1
            return $this->createEmptyResponse(405, ['Accept' => implode(',', $allowedMethods)]);
326 1
        };
327
    }
328
329
    /**
330
     * @return Closure
331
     */
332 1
    private function createNotFoundTerminalHandler()
333
    {
334
        // 404 Not Found
335
        return function () {
336 1
            return $this->createEmptyResponse(404);
337 1
        };
338
    }
339
340
    /**
341
     * @param Closure            $handler
342
     * @param ContainerInterface $userContainer
343
     * @param array|null         $middleware
344
     *
345
     * @return Closure
346
     */
347 5
    private function createMiddlewareChainImpl(
348
        Closure $handler,
349
        ContainerInterface $userContainer,
350
        array $middleware = null
351
    ) {
352 5
        $start = count($middleware) - 1;
353 5
        for ($index = $start; $index >= 0; $index--) {
354 4
            $handler = $this->createMiddlewareChainLink($handler, $middleware[$index], $userContainer);
355
        }
356
357 5
        return $handler;
358
    }
359
360
    /**
361
     * @param Closure            $next
362
     * @param callable           $middleware
363
     * @param ContainerInterface $userContainer
364
     *
365
     * @return Closure
366
     */
367
    private function createMiddlewareChainLink(Closure $next, callable $middleware, ContainerInterface $userContainer)
368
    {
369 4
        return function (ServerRequestInterface $request) use ($next, $middleware, $userContainer) {
370 4
            return call_user_func($middleware, $request, $next, $userContainer);
371 4
        };
372
    }
373
}
374