Issues (7)

src/Application.php (1 issue)

1
<?php
2
3
namespace Phprest;
4
5
use Closure;
6
use Doctrine\Common\Annotations\AnnotationRegistry;
7
use League\Container\ContainerAwareTrait;
8
use League\Event\EmitterInterface;
9
use League\Event\EmitterTrait;
10
use League\Event\ListenerAcceptorInterface;
11
use Phprest\Middleware\ApiVersion;
12
use Phprest\Router\RouteCollection;
13
use Phprest\Service;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpKernel\HttpKernelInterface;
17
use Symfony\Component\HttpKernel\TerminableInterface;
18
use LogicException;
19
use Exception;
20
use Stack;
21
22
class Application implements
23
    HttpKernelInterface,
24
    TerminableInterface,
25
    ListenerAcceptorInterface
26
{
27
    use EmitterTrait;
28
    use ContainerAwareTrait;
29
    use Service\Hateoas\Getter;
30
    use Service\Hateoas\Util;
0 ignored issues
show
The trait Phprest\Service\Hateoas\Util requires some properties which are not provided by Phprest\Application: $headers, $apiVersion, $vendor
Loading history...
31
    use Service\Logger\Getter;
32
33
    public const CONTAINER_ID_DEBUG = 'debug';
34
    public const CONTAINER_ID_VENDOR = 'vendor';
35
    public const CONTAINER_ID_API_VERSION = 'api-version';
36
    public const CONTAINER_ID_ROUTER = 'router';
37
    public const API_VERSION_REG_EXP = '((?:[0-9](?:\.[0-9])?){1})';
38
39
    protected Config $configuration;
40
    protected Stack\Builder $stackBuilder;
41
    protected RouteCollection $router;
42
43
    /**
44
     * @var callable
45
     */
46
    protected $exceptionDecorator;
47
48 8
    public function __construct(Config $configuration)
49
    {
50 8
        $this->configuration = $configuration;
51 8
        $this->container = $configuration->getContainer();
52 8
        $this->router = $configuration->getRouter();
53 8
        $this->emitter = $configuration->getEventEmitter();
54
55 8
        AnnotationRegistry::registerLoader('class_exists');
56
57 8
        $this->registerService($configuration->getHateoasService(), $configuration->getHateoasConfig());
58 8
        $this->registerService($configuration->getLoggerService(), $configuration->getLoggerConfig());
59
60 8
        $this->setErrorHandler();
61
62 8
        $this->container->add(self::CONTAINER_ID_VENDOR, $configuration->getVendor());
63 8
        $this->container->add(self::CONTAINER_ID_API_VERSION, $configuration->getApiVersion());
64 8
        $this->container->add(self::CONTAINER_ID_DEBUG, $configuration->isDebug());
65
        $this->container->add(self::CONTAINER_ID_ROUTER, function () {
66 1
            return $this->router;
67 8
        });
68
69 8
        $this->stackBuilder = new Stack\Builder();
70 8
    }
71
72 8
    public function registerService(Service\Serviceable $service, Service\Configurable $config): void
73
    {
74 8
        $service->register($this->container, $config);
75 8
    }
76
77
    /**
78
     * @param string $class Namespaced class name
79
     */
80 1
    public function registerController(string $class): void
81
    {
82 1
        $controller = new $class($this->container);
83
84
        $this->container->add($class, static function () use ($controller) {
85 1
            return $controller;
86 1
        });
87 1
    }
88
89 2
    public function registerMiddleware(string $classPath, array $arguments = []): void
90
    {
91 2
        call_user_func_array([$this->stackBuilder, 'push'], array_merge([$classPath], $arguments));
92 2
    }
93
94
    /**
95
     * Run the application
96
     */
97 2
    public function run(Request $request = null): void
98
    {
99 2
        if (null === $request) {
100 1
            $request = Request::createFromGlobals();
101
        }
102
103 2
        $this->registerMiddleware(ApiVersion::class);
104
105 2
        $app = $this->stackBuilder->resolve($this);
106
107 2
        $response = $app->handle($request, self::MASTER_REQUEST, false);
108 1
        $response->send();
109
110 1
        $app->terminate($request, $response);
111 1
    }
112
113
    /**
114
     * Handle the request.
115
     *
116
     * @param Request $request
117
     * @param int $type
118
     * @param bool $catch
119
     *
120
     * @return Response
121
     * @throws LogicException
122
     *
123
     * @throws Exception
124
     */
125 2
    public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true): ?Response
126
    {
127
        // Passes the request to the container
128 2
        $this->getContainer()->add(Request::class, $request);
129
130
        try {
131 2
            $this->emit('request.received', $request);
132
133 2
            $dispatcher = $this->getRouter()->getDispatcher();
134 2
            $response = $dispatcher->dispatch(
135 2
                $request->getMethod(),
136 2
                $request->getPathInfo()
137
            );
138
139 1
            $this->emit('response.created', $request, $response);
140
141 1
            return $response;
142 1
        } catch (Exception $e) {
143 1
            if (!$catch) {
144 1
                throw $e;
145
            }
146
147
            $response = call_user_func($this->exceptionDecorator, $e);
148
            if (!$response instanceof Response) {
149
                throw new LogicException(
150
                    'Exception decorator did not return an instance of Symfony\Component\HttpFoundation\Response'
151
                );
152
            }
153
154
            $this->emit('response.created', $request, $response);
155
156
            return $response;
157
        }
158
    }
159
160
    /**
161
     * Add a HEAD route
162
     *
163
     * @param string|Closure $action
164
     */
165 1
    public function head(string $route, $action): self
166
    {
167 1
        $this->router->addRoute('HEAD', $route, $action);
168
169 1
        return $this;
170
    }
171
172
    /**
173
     * Add a OPTIONS route
174
     *
175
     * @param string|Closure $action
176
     */
177 1
    public function options(string $route, $action): self
178
    {
179 1
        $this->router->addRoute('OPTIONS', $route, $action);
180
181 1
        return $this;
182
    }
183
184
    /**
185
     * Add a GET route.
186
     *
187
     * @param string|Closure $action
188
     */
189 2
    public function get(string $route, $action): self
190
    {
191 2
        $this->getRouter()->addRoute('GET', $route, $action);
192
193 2
        return $this;
194
    }
195
196
    /**
197
     * Add a POST route.
198
     *
199
     * @param string|Closure $action
200
     */
201
    public function post(string $route, $action): self
202
    {
203
        $this->getRouter()->addRoute('POST', $route, $action);
204
205
        return $this;
206
    }
207
208
    /**
209
     * Add a PUT route.
210
     *
211
     * @param string|Closure $action
212
     */
213
    public function put(string $route, $action): self
214
    {
215
        $this->getRouter()->addRoute('PUT', $route, $action);
216
217
        return $this;
218
    }
219
220
    /**
221
     * Add a DELETE route.
222
     *
223
     * @param string|Closure $action
224
     */
225
    public function delete(string $route, $action): self
226
    {
227
        $this->getRouter()->addRoute('DELETE', $route, $action);
228
229
        return $this;
230
    }
231
232
    /**
233
     * Add a PATCH route.
234
     *
235
     * @param string|Closure $action
236
     */
237
    public function patch(string $route, $action): self
238
    {
239
        $this->getRouter()->addRoute('PATCH', $route, $action);
240
241
        return $this;
242
    }
243
244 3
    public function getConfiguration(): Config
245
    {
246 3
        return $this->configuration;
247
    }
248
249 6
    public function getRouter(): RouteCollection
250
    {
251 6
        return $this->router;
252
    }
253
254
    /**
255
     * Return the event emitter.
256
     */
257 1
    public function getEventEmitter(): EmitterInterface
258
    {
259 1
        return $this->getEmitter();
260
    }
261
262
    /**
263
     * Terminates a request/response cycle.
264
     */
265 1
    public function terminate(Request $request, Response $response): void
266
    {
267 1
        $this->emit('response.sent', $request, $response);
268 1
    }
269
270
    /**
271
     * Subscribe to an event.
272
     *
273
     * @param string $event
274
     * @param callable $listener
275
     * @param int $priority
276
     */
277
    public function subscribe($event, $listener, $priority = ListenerAcceptorInterface::P_NORMAL): void
278
    {
279
        $this->addListener($event, $listener, $priority);
280
    }
281
282
    /**
283
     * Set the exception decorator.
284
     */
285 8
    public function setExceptionDecorator(callable $func): void
286
    {
287 8
        $this->exceptionDecorator = $func;
288 8
    }
289
290 8
    protected function setErrorHandler(): void
291
    {
292 8
        $app = $this;
293
294 8
        $this->configuration->getLogHandler()->setLogger($this->serviceLogger());
295
296 8
        $this->configuration->getErrorHandler()->pushHandler($this->configuration->getLogHandler());
297 8
        $this->configuration->getErrorHandler()->register();
298
299
        $this->setExceptionDecorator(static function (Exception $e) use ($app) {
300
            $formatter = new ErrorHandler\Formatter\JsonXml($app->configuration);
301
302
            return new Response($formatter->format($e), http_response_code());
303 8
        });
304 8
    }
305
}
306