Completed
Push — 2.0 ( 7a1ed3...660c60 )
by Vermeulen
04:06
created

BfwApi::update()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.0777
c 0
b 0
f 0
cc 6
nc 5
nop 1
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);
0 ignored issues
show
Documentation introduced by
$subject is of type object<SplSubject>, but the function expects a object<BFW\Subject>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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);
331
        } elseif ($useGraphQL === true) {
332
            return $this->runGraphQL();
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