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
introduced
by
![]() |
|||||
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'); |
|||
0 ignored issues
–
show
The function
Doctrine\Common\Annotati...istry::registerLoader() has been deprecated: This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
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 |