1 | <?php |
||
2 | /** |
||
3 | * @author Vermeulen Maxime <[email protected]> |
||
4 | * @version 2.0 |
||
5 | */ |
||
6 | |||
7 | namespace BfwApi; |
||
8 | |||
9 | use \Exception; |
||
10 | |||
11 | /** |
||
12 | * Class for API system |
||
13 | * @package bfw-api |
||
14 | */ |
||
15 | class BfwApi implements \SplObserver |
||
16 | { |
||
17 | /** |
||
18 | * @const ERR_RUN_CLASS_NOT_FOUND : Error code if the class to run has |
||
19 | * not been found |
||
20 | */ |
||
21 | const ERR_RUN_CLASS_NOT_FOUND = 2001001; |
||
22 | |||
23 | /** |
||
24 | * @const ERR_RUN_METHOD_NOT_FOUND : Error code if the method to run has |
||
25 | * not been found into the class |
||
26 | */ |
||
27 | const ERR_RUN_METHOD_NOT_FOUND = 2001002; |
||
28 | |||
29 | /** |
||
30 | * @const ERR_RUN_MODE_NOT_DECLARED : Error code if no mode (rest/graphQL) |
||
31 | * is declared into config file |
||
32 | */ |
||
33 | const ERR_RUN_MODE_NOT_DECLARED = 2001003; |
||
34 | |||
35 | /** |
||
36 | * @const ERR_CLASSNAME_NOT_DEFINE_FOR_URI : Error code if the class to use |
||
37 | * for current api route is not defined |
||
38 | */ |
||
39 | const ERR_CLASSNAME_NOT_DEFINE_FOR_URI = 2001004; |
||
40 | |||
41 | /** |
||
42 | * @const ERR_RUN_REST_NOT_IMPLEMENT_INTERFACE : The class used for the |
||
43 | * route in Rest mode not implement the interface |
||
44 | */ |
||
45 | const ERR_RUN_REST_NOT_IMPLEMENT_INTERFACE = 2001005; |
||
46 | |||
47 | /** |
||
48 | * @var \BFW\Module $module The bfw module instance for this module |
||
49 | */ |
||
50 | protected $module; |
||
51 | |||
52 | /** |
||
53 | * @var \BFW\Config $config The bfw config instance for this module |
||
54 | */ |
||
55 | protected $config; |
||
56 | |||
57 | /** |
||
58 | * @var \FastRoute\Dispatcher $dispatcher FastRoute dispatcher |
||
59 | */ |
||
60 | protected $dispatcher; |
||
61 | |||
62 | /** |
||
63 | * @var \stdClass|null $ctrlRouterInfos The context object passed to |
||
64 | * subject for the action "searchRoute". |
||
65 | */ |
||
66 | protected $ctrlRouterInfos; |
||
67 | |||
68 | /** |
||
69 | * @var string $execRouteSystemName The name of the current system. Used on |
||
70 | * event "execRoute". Allow to extends this class in another module :) |
||
71 | */ |
||
72 | protected $execRouteSystemName = 'bfw-api'; |
||
73 | |||
74 | /** |
||
75 | * Constructor |
||
76 | * |
||
77 | * @param \BFW\Module $module |
||
78 | */ |
||
79 | public function __construct(\BFW\Module $module) |
||
80 | { |
||
81 | $this->module = $module; |
||
82 | $this->config = $module->getConfig(); |
||
83 | |||
84 | $this->dispatcher = \FastRoute\simpleDispatcher([ |
||
85 | $this, |
||
86 | 'addRoutesToCollector' |
||
87 | ]); |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Getter accessor for module property |
||
92 | * |
||
93 | * @return \BFW\Module |
||
94 | */ |
||
95 | public function getModule(): \BFW\Module |
||
96 | { |
||
97 | return $this->module; |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Getter accessor for config property |
||
102 | * |
||
103 | * @return \BFW\Config |
||
104 | */ |
||
105 | public function getConfig(): \BFW\Config |
||
106 | { |
||
107 | return $this->config; |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Getter accessor for dispatcher property |
||
112 | * |
||
113 | * @return \FastRoute\Dispatcher |
||
114 | */ |
||
115 | public function getDispatcher(): \FastRoute\Dispatcher |
||
116 | { |
||
117 | return $this->dispatcher; |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Getter accessor for ctrlRouterInfos property |
||
122 | * |
||
123 | * @return object |
||
124 | */ |
||
125 | public function getCtrlRouterInfos() |
||
126 | { |
||
127 | return $this->ctrlRouterInfos; |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Getter accessor for execRouteSystemName property |
||
132 | * |
||
133 | * @return string |
||
134 | */ |
||
135 | public function getExecRouteSystemName(): string |
||
136 | { |
||
137 | return $this->execRouteSystemName; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Call by dispatcher; Add route in config to fastRoute router |
||
142 | * |
||
143 | * @param \FastRoute\RouteCollector $router FastRoute router |
||
144 | * |
||
145 | * @return void |
||
146 | */ |
||
147 | public function addRoutesToCollector(\FastRoute\RouteCollector $router) |
||
148 | { |
||
149 | $this->module->monolog->getLogger()->debug('Add all routes.'); |
||
150 | |||
151 | $urlPrefix = $this->config->getValue('urlPrefix', 'config.php'); |
||
152 | $routes = $this->config->getValue('routes', 'routes.php'); |
||
153 | |||
154 | foreach ($routes as $slug => $infos) { |
||
155 | $slug = trim($urlPrefix.$slug); |
||
156 | |||
157 | //Défault method |
||
158 | $method = ['GET', 'POST', 'PUT', 'DELETE']; |
||
159 | |||
160 | //If method is declared for the route |
||
161 | if (isset($infos['httpMethod'])) { |
||
162 | //Get the method ans remove it from httpMethod array |
||
163 | $method = $infos['httpMethod']; |
||
164 | unset($infos['httpMethod']); |
||
165 | } |
||
166 | |||
167 | $router->addRoute($method, $slug, $infos); |
||
168 | } |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Observer update method |
||
173 | * |
||
174 | * @param \SplSubject $subject |
||
175 | * |
||
176 | * @return void |
||
177 | */ |
||
178 | public function update(\SplSubject $subject) |
||
179 | { |
||
180 | if ($subject->getAction() === 'ctrlRouterLink_exec_searchRoute') { |
||
181 | $this->obtainCtrlRouterInfos($subject); |
||
182 | |||
183 | if ($this->ctrlRouterInfos->isFound === false) { |
||
184 | $this->searchRoute(); |
||
185 | } |
||
186 | } elseif ($subject->getAction() === 'ctrlRouterLink_exec_execRoute') { |
||
187 | if ( |
||
188 | $this->ctrlRouterInfos->isFound === true && |
||
189 | $this->ctrlRouterInfos->forWho === $this->execRouteSystemName |
||
190 | ) { |
||
191 | $this->execRoute(); |
||
192 | } |
||
193 | } |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * Set the property ctrlRouterInfos with the context object obtain linked |
||
198 | * to the subject. |
||
199 | * Allow override to get only some part. And used for unit test. |
||
200 | * |
||
201 | * @param \BFW\Subject $subject |
||
202 | * |
||
203 | * @return void |
||
204 | */ |
||
205 | protected function obtainCtrlRouterInfos(\BFW\Subject $subject) |
||
206 | { |
||
207 | $this->ctrlRouterInfos = $subject->getContext(); |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Obtain the classname to use for current route from fastRoute dispatcher |
||
212 | * |
||
213 | * @return void |
||
214 | * |
||
215 | * @throw \Exception If no "className" is define in config for the route. |
||
216 | */ |
||
217 | protected function searchRoute() |
||
218 | { |
||
219 | //Get current request informations |
||
220 | $bfwRequest = \BFW\Request::getInstance(); |
||
221 | $request = $bfwRequest->getRequest()->path; |
||
222 | $method = $bfwRequest->getMethod(); |
||
223 | |||
224 | //Get route information from dispatcher |
||
225 | $routeInfo = $this->dispatcher->dispatch($method, $request); |
||
226 | $routeStatus = $routeInfo[0]; |
||
227 | |||
228 | $this->module |
||
229 | ->monolog |
||
230 | ->getLogger() |
||
231 | ->debug( |
||
232 | 'Search the current route into declared routes.', |
||
233 | [ |
||
234 | 'request' => $request, |
||
235 | 'method' => $method, |
||
236 | 'status' => $routeStatus |
||
237 | ] |
||
238 | ); |
||
239 | |||
240 | //Get and send request http status to the controller/router linker |
||
241 | $httpStatus = $this->checkStatus($routeStatus); |
||
242 | |||
243 | if ($httpStatus === 404) { |
||
244 | //404 will be declared by \BFW\Application::runCtrlRouterLink() |
||
245 | return; |
||
246 | } |
||
247 | |||
248 | http_response_code($httpStatus); |
||
249 | $this->ctrlRouterInfos->isFound = true; |
||
250 | $this->ctrlRouterInfos->forWho = $this->execRouteSystemName; |
||
251 | |||
252 | if ($httpStatus !== 200) { |
||
253 | return; |
||
254 | } |
||
255 | |||
256 | global $_GET; |
||
257 | $_GET = array_merge($_GET, $routeInfo[2]); |
||
258 | |||
259 | if (!isset($routeInfo[1]['className'])) { |
||
260 | throw new Exception( |
||
261 | 'className not define for uri '.$request, |
||
262 | self::ERR_CLASSNAME_NOT_DEFINE_FOR_URI |
||
263 | ); |
||
264 | } |
||
265 | |||
266 | $this->ctrlRouterInfos->target = $routeInfo[1]['className']; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Get http status for response from dispatcher |
||
271 | * |
||
272 | * @param int $routeStatus : Route status send by dispatcher for request |
||
273 | * |
||
274 | * @return int |
||
275 | */ |
||
276 | protected function checkStatus(int $routeStatus): int |
||
277 | { |
||
278 | $httpStatus = 200; |
||
279 | |||
280 | if ($routeStatus === \FastRoute\Dispatcher::METHOD_NOT_ALLOWED) { |
||
281 | $httpStatus = 405; |
||
282 | } elseif ($routeStatus === \FastRoute\Dispatcher::NOT_FOUND) { |
||
283 | $httpStatus = 404; |
||
284 | } |
||
285 | |||
286 | return $httpStatus; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Execute the asked route |
||
291 | * |
||
292 | * @return void |
||
293 | */ |
||
294 | protected function execRoute() |
||
295 | { |
||
296 | $this->module |
||
297 | ->monolog |
||
298 | ->getLogger() |
||
299 | ->debug( |
||
300 | 'Execute current route.', |
||
301 | ['target' => $this->ctrlRouterInfos->target] |
||
302 | ); |
||
303 | |||
304 | $className = $this->ctrlRouterInfos->target; |
||
305 | if ($className === null) { |
||
306 | return; |
||
307 | } |
||
308 | |||
309 | //Get current request informations |
||
310 | $bfwRequest = \BFW\Request::getInstance(); |
||
311 | $method = strtolower($bfwRequest->getMethod()); |
||
312 | |||
313 | if (!class_exists($className)) { |
||
314 | throw new Exception( |
||
315 | 'Class '.$className.' not found.', |
||
316 | self::ERR_RUN_CLASS_NOT_FOUND |
||
317 | ); |
||
318 | } |
||
319 | if (!method_exists($className, $method.'Request')) { |
||
320 | throw new Exception( |
||
321 | 'Method '.$method.'Request not found in class '.$className.'.', |
||
322 | self::ERR_RUN_METHOD_NOT_FOUND |
||
323 | ); |
||
324 | } |
||
325 | |||
326 | $useRest = $this->config->getValue('useRest', 'config.php'); |
||
327 | $useGraphQL = $this->config->getValue('useGraphQL', 'config.php'); |
||
328 | |||
329 | if ($useRest === true) { |
||
330 | return $this->runRest($className, $method); |
||
0 ignored issues
–
show
|
|||
331 | } elseif ($useGraphQL === true) { |
||
332 | return $this->runGraphQL(); |
||
0 ignored issues
–
show
Are you sure the usage of
$this->runGraphQL() targeting BfwApi\BfwApi::runGraphQL() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
333 | } |
||
334 | |||
335 | throw new Exception( |
||
336 | 'Please choose between REST and GraphQL in config file.', |
||
337 | self::ERR_RUN_MODE_NOT_DECLARED |
||
338 | ); |
||
339 | } |
||
340 | |||
341 | /** |
||
342 | * Call the method for the current request for Rest api mode |
||
343 | * |
||
344 | * @param string $className The class name to use for the route |
||
345 | * @param string $method The method name to use (get/post/delete/put) |
||
346 | * |
||
347 | * @throws Exception If the interface is not implemented by the class |
||
348 | * |
||
349 | * @return void |
||
350 | */ |
||
351 | protected function runRest(string $className, string $method) |
||
352 | { |
||
353 | $this->module->monolog->getLogger()->debug('Use REST system.'); |
||
354 | |||
355 | $api = new $className; |
||
356 | if ($api instanceof \BfwApi\RestInterface === false) { |
||
357 | throw new Exception( |
||
358 | 'The class '.$className.' not implement \BfwApi\RestInterface', |
||
359 | self::ERR_RUN_REST_NOT_IMPLEMENT_INTERFACE |
||
360 | ); |
||
361 | } |
||
362 | |||
363 | $api->{$method.'Request'}(); |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * Call the method for the current request for GraphQL api mode |
||
368 | * |
||
369 | * Not implemented yet |
||
370 | */ |
||
371 | protected function runGraphQL() |
||
372 | { |
||
373 | $this->module->monolog->getLogger()->debug('Use GraphQL system.'); |
||
374 | |||
375 | //Not implement yet |
||
376 | http_response_code(501); |
||
377 | } |
||
378 | } |
||
379 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.