Completed
Push — master ( 2d4d7c...173114 )
by Klaus
06:59
created

Application   C

Complexity

Total Complexity 26

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 19
dl 0
loc 209
rs 6.875
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 44 1
B getRouteCache() 0 16 6
A get() 0 4 1
A post() 0 4 1
A put() 0 4 1
A delete() 0 4 1
A patch() 0 4 1
A options() 0 4 1
A match() 0 4 1
A handle() 0 19 4
B handleErrors() 0 26 5
A terminate() 0 4 1
A run() 0 11 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Linio\Tortilla;
6
7
use FastRoute\DataGenerator\GroupCountBased as DataGenerator;
8
use FastRoute\RouteCollector;
9
use FastRoute\RouteParser\Std as RouteParser;
10
use Linio\Component\Microlog\Log;
11
use Linio\Exception\ErrorException;
12
use Linio\Exception\HttpException;
13
use Linio\Tortilla\Event\ExceptionEvent;
14
use Linio\Tortilla\Event\PostResponseEvent;
15
use Linio\Tortilla\Event\RequestEvent;
16
use Linio\Tortilla\Event\ResponseEvent;
17
use Linio\Tortilla\Listener\JsonRequestBody;
18
use Linio\Tortilla\Route\ControllerResolver\ServiceControllerResolver;
19
use Linio\Tortilla\Route\Dispatcher;
20
use Pimple\Container;
21
use Psr\Log\LogLevel;
22
use Symfony\Component\EventDispatcher\EventDispatcher;
23
use Symfony\Component\HttpFoundation\JsonResponse;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
use Symfony\Component\HttpKernel\HttpKernelInterface;
27
use Symfony\Component\HttpKernel\TerminableInterface;
28
29
class Application extends Container implements HttpKernelInterface, TerminableInterface
30
{
31
    /**
32
     * @param array $values The parameters or objects
33
     */
34
    public function __construct(array $values = [])
35
    {
36
        $this['debug'] = false;
37
        $this['config'] = [];
38
39
        $this['event.dispatcher'] = function () {
40
            return new EventDispatcher();
41
        };
42
43
        $this['application.json_request_body'] = function () {
44
            return new JsonRequestBody();
45
        };
46
47
        $this['controller.resolver'] = function () {
48
            return new ServiceControllerResolver($this);
49
        };
50
51
        $this['route.dispatcher'] = function () {
52
            $dispatcher = new Dispatcher($this->getRouteCache());
53
            $dispatcher->setControllerResolver($this['controller.resolver']);
54
55
            return $dispatcher;
56
        };
57
58
        $this['route.parser'] = function () {
59
            return new RouteParser();
60
        };
61
62
        $this['route.data_generator'] = function () {
63
            return new DataGenerator();
64
        };
65
66
        $this['route.collector'] = function () {
67
            return new RouteCollector($this['route.parser'], $this['route.data_generator']);
68
        };
69
70
        parent::__construct($values);
71
72
        $this->extend('event.dispatcher', function (EventDispatcher $eventDispatcher) {
73
            $eventDispatcher->addListener(ApplicationEvents::REQUEST, [$this['application.json_request_body'], 'onRequest']);
74
75
            return $eventDispatcher;
76
        });
77
    }
78
79
    public function getRouteCache(): array
80
    {
81
        $routeCache = $this['config']['route_cache'] ?? false;
82
83
        if (!$this['debug'] && $routeCache && file_exists($routeCache)) {
84
            return require $routeCache;
85
        }
86
87
        $routeData = $this['route.collector']->getData();
88
89
        if (!$this['debug'] && $routeCache) {
90
            file_put_contents($routeCache, '<?php return ' . var_export($routeData, true) . ';');
91
        }
92
93
        return $routeData;
94
    }
95
96
    /**
97
     * Maps a GET request to handler.
98
     *
99
     * @param string $pattern
100
     * @param mixed  $handler
101
     */
102
    public function get(string $pattern, $handler)
103
    {
104
        $this['route.collector']->addRoute('GET', $pattern, $handler);
105
    }
106
107
    /**
108
     * Maps a POST request to handler.
109
     *
110
     * @param string $pattern
111
     * @param mixed  $handler
112
     */
113
    public function post(string $pattern, $handler)
114
    {
115
        $this['route.collector']->addRoute('POST', $pattern, $handler);
116
    }
117
118
    /**
119
     * Maps a PUT request to handler.
120
     *
121
     * @param string $pattern
122
     * @param mixed  $handler
123
     */
124
    public function put(string $pattern, $handler)
125
    {
126
        $this['route.collector']->addRoute('PUT', $pattern, $handler);
127
    }
128
129
    /**
130
     * Maps a DELETE request to handler.
131
     *
132
     * @param string $pattern
133
     * @param mixed  $handler
134
     */
135
    public function delete(string $pattern, $handler)
136
    {
137
        $this['route.collector']->addRoute('DELETE', $pattern, $handler);
138
    }
139
140
    /**
141
     * Maps a PATCH request to handler.
142
     *
143
     * @param string $pattern
144
     * @param mixed  $handler
145
     */
146
    public function patch(string $pattern, $handler)
147
    {
148
        $this['route.collector']->addRoute('PATCH', $pattern, $handler);
149
    }
150
151
    /**
152
     * Maps a OPTIONS request to handler.
153
     *
154
     * @param string $pattern
155
     * @param mixed  $handler
156
     */
157
    public function options(string $pattern, $handler)
158
    {
159
        $this['route.collector']->addRoute('OPTIONS', $pattern, $handler);
160
    }
161
162
    /**
163
     * Maps various HTTP requests to handler.
164
     *
165
     * @param array  $methods
166
     * @param string $pattern
167
     * @param mixed  $handler
168
     */
169
    public function match(array $methods, string $pattern, $handler)
170
    {
171
        $this['route.collector']->addRoute($methods, $pattern, $handler);
172
    }
173
174
    public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
175
    {
176
        $event = new RequestEvent($request);
177
        $this['event.dispatcher']->dispatch(ApplicationEvents::REQUEST, $event);
178
179
        if ($event->hasResponse()) {
180
            return $event->getResponse();
181
        }
182
183
        try {
184
            return $this['route.dispatcher']->handle($request);
185
        } catch (\Throwable $exception) {
186
            if ($catch === false) {
187
                throw $exception;
188
            }
189
190
            return $this->handleErrors($exception, $request);
191
        }
192
    }
193
194
    protected function handleErrors(\Throwable $exception, Request $request): Response
195
    {
196
        $event = new ExceptionEvent($exception, $request);
197
        $this['event.dispatcher']->dispatch(ApplicationEvents::EXCEPTION, $event);
198
        Log::log(($exception instanceof ErrorException) ? $exception->getLogLevel() : LogLevel::ALERT, $exception);
199
200
        if ($event->hasResponse()) {
201
            return $event->getResponse();
202
        }
203
204
        if ($this['debug']) {
205
            return new Response((string) $exception, Response::HTTP_INTERNAL_SERVER_ERROR);
206
        }
207
208
        $response = new JsonResponse();
209
        $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
210
        $response->setData(['error' => ['message' => 'Internal error', 'code' => 500]]);
211
212
        if ($exception instanceof HttpException) {
213
            $response->setStatusCode($exception->getStatusCode());
214
            $response->headers->replace($exception->getHeaders());
215
            $response->setData(['error' => ['message' => $exception->getMessage(), 'code' => $exception->getCode()]]);
216
        }
217
218
        return $response;
219
    }
220
221
    public function terminate(Request $request, Response $response)
222
    {
223
        $this['event.dispatcher']->dispatch(ApplicationEvents::TERMINATE, new PostResponseEvent($request, $response));
224
    }
225
226
    public function run(Request $request = null)
227
    {
228
        if ($request === null) {
229
            $request = Request::createFromGlobals();
230
        }
231
232
        $response = $this->handle($request);
233
        $this['event.dispatcher']->dispatch(ApplicationEvents::RESPONSE, new ResponseEvent($request, $response));
234
        $response->send();
235
        $this->terminate($request, $response);
236
    }
237
}
238