Completed
Push — 2.0 ( bddbd0...6de983 )
by Vermeulen
02:57
created

BfwApi::getModule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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()
96
    {
97
        return $this->module;
98
    }
99
100
    /**
101
     * Getter accessor for config property
102
     * 
103
     * @return \BFW\Config
104
     */
105
    public function getConfig()
106
    {
107
        return $this->config;
108
    }
109
110
    /**
111
     * Getter accessor for dispatcher property
112
     * 
113
     * @return \FastRoute\Dispatcher
114
     */
115
    public function getDispatcher()
116
    {
117
        return $this->dispatcher;
118
    }
119
120
    /**
121
     * Getter accessor for ctrlRouterInfos property
122
     * 
123
     * @return \stdClass
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()
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() === 'bfw_ctrlRouterLink_subject_added') {
181
            $this->module->monolog->getLogger()
182
                ->debug('Add observer to ctrlRouterLink subject');
183
            
184
            $app = \BFW\Application::getInstance();
185
            $app->getSubjectList()
186
                ->getSubjectForName('ctrlRouterLink')
187
                ->attach($this)
188
            ;
189
        } elseif ($subject->getAction() === 'searchRoute') {
190
            $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...
191
            
192
            if ($this->ctrlRouterInfos->isFound === false) {
193
                $this->searchRoute();
194
            }
195
        } elseif ($subject->getAction() === 'execRoute') {
196
            if (
197
                $this->ctrlRouterInfos->isFound === true &&
198
                $this->ctrlRouterInfos->forWho === $this->execRouteSystemName
199
            ) {
200
                $this->execRoute();
201
            }
202
        }
203
    }
204
    
205
    /**
206
     * Set the property ctrlRouterInfos with the context object obtain linked
207
     * to the subject.
208
     * Allow override to get only some part. And used for unit test.
209
     * 
210
     * @param \BFW\Subject $subject
211
     * 
212
     * @return void
213
     */
214
    protected function obtainCtrlRouterInfos($subject)
215
    {
216
        $this->ctrlRouterInfos = $subject->getContext();
217
    }
218
    
219
    /**
220
     * Obtain the classname to use for current route from fastRoute dispatcher
221
     * 
222
     * @return void
223
     * 
224
     * @throw \Exception If no "className" is define in config for the route.
225
     */
226
    protected function searchRoute()
227
    {
228
        //Get current request informations
229
        $bfwRequest = \BFW\Request::getInstance();
230
        $request    = $bfwRequest->getRequest()->path;
231
        $method     = $bfwRequest->getMethod();
232
233
        //Get route information from dispatcher
234
        $routeInfo   = $this->dispatcher->dispatch($method, $request);
235
        $routeStatus = $routeInfo[0];
236
        
237
        $this->module
238
            ->monolog
239
            ->getLogger()
240
            ->debug(
241
                'Search the current route into declared routes.',
242
                [
243
                    'request' => $request,
244
                    'method' => $method,
245
                    'status' => $routeStatus
246
                ]
247
            );
248
        
249
        //Get and send request http status to the controller/router linker
250
        $httpStatus = $this->checkStatus($routeStatus);
251
        
252
        if ($httpStatus === 404) {
253
            //404 will be declared by \BFW\Application::runCtrlRouterLink()
254
            return;
255
        }
256
        
257
        http_response_code($httpStatus);
258
        $this->ctrlRouterInfos->isFound = true;
259
        $this->ctrlRouterInfos->forWho  = $this->execRouteSystemName;
260
        
261
        if ($httpStatus !== 200) {
262
            return;
263
        }
264
265
        global $_GET;
266
        $_GET = array_merge($_GET, $routeInfo[2]);
267
        
268
        if (!isset($routeInfo[1]['className'])) {
269
            throw new Exception(
270
                'className not define for uri '.$request,
271
                self::ERR_CLASSNAME_NOT_DEFINE_FOR_URI
272
            );
273
        }
274
        
275
        $this->ctrlRouterInfos->target = $routeInfo[1]['className'];
276
    }
277
    
278
    /**
279
     * Get http status for response from dispatcher
280
     * 
281
     * @param int $routeStatus : Route status send by dispatcher for request
282
     * 
283
     * @return int
284
     */
285
    protected function checkStatus($routeStatus)
286
    {
287
        $httpStatus = 200;
288
        
289
        if ($routeStatus === \FastRoute\Dispatcher::METHOD_NOT_ALLOWED) {
290
            $httpStatus = 405;
291
        } elseif ($routeStatus === \FastRoute\Dispatcher::NOT_FOUND) {
292
            $httpStatus = 404;
293
        }
294
        
295
        return $httpStatus;
296
    }
297
    
298
    /**
299
     * 
300
     * 
301
     * @return void
302
     */
303
    protected function execRoute()
304
    {
305
        $this->module
306
            ->monolog
307
            ->getLogger()
308
            ->debug(
309
                'Execute current route.',
310
                ['target' => $this->ctrlRouterInfos->target]
311
            );
312
        
313
        $className = $this->ctrlRouterInfos->target;
314
        if ($className === null) {
315
            return;
316
        }
317
        
318
        //Get current request informations
319
        $bfwRequest = \BFW\Request::getInstance();
320
        $method     = strtolower($bfwRequest->getMethod());
321
        
322
        if (!class_exists($className)) {
323
            throw new Exception(
324
                'Class '.$className.' not found.',
325
                self::ERR_RUN_CLASS_NOT_FOUND
326
            );
327
        }
328
        if (!method_exists($className, $method.'Request')) {
329
            throw new Exception(
330
                'Method '.$method.'Request not found in class '.$className.'.',
331
                self::ERR_RUN_METHOD_NOT_FOUND
332
            );
333
        }
334
    
335
        $useRest    = $this->config->getValue('useRest', 'config.php');
336
        $useGraphQL = $this->config->getValue('useGraphQL', 'config.php');
337
        
338
        if ($useRest === true) {
339
            return $this->runRest($className, $method);
340
        } elseif ($useGraphQL === true) {
341
            return $this->runGraphQL();
342
        }
343
        
344
        throw new Exception(
345
            'Please choose between REST and GraphQL in config file.',
346
            self::ERR_RUN_MODE_NOT_DECLARED
347
        );
348
    }
349
    
350
    /**
351
     * Call the method for the current request for Rest api mode
352
     * 
353
     * @param string $className The class name to use for the route
354
     * @param string $method The method name to use (get/post/delete/put)
355
     * 
356
     * @throws Exception If the interface is not implemented by the class
357
     * 
358
     * @return void
359
     */
360
    protected function runRest($className, $method)
361
    {
362
        $this->module->monolog->getLogger()->debug('Use REST system.');
363
        
364
        $api = new $className;
365
        if ($api instanceof \BfwApi\RestInterface === false) {
366
            throw new Exception(
367
                'The class '.$className.' not implement \BfwApi\RestInterface',
368
                self::ERR_RUN_REST_NOT_IMPLEMENT_INTERFACE
369
            );
370
        }
371
        
372
        $api->{$method.'Request'}();
373
    }
374
    
375
    /**
376
     * Call the method for the current request for GraphQL api mode
377
     * 
378
     * Not implemented yet
379
     */
380
    protected function runGraphQL()
381
    {
382
        $this->module->monolog->getLogger()->debug('Use GraphQL system.');
383
        
384
        //Not implement yet
385
        http_response_code(501);
386
    }
387
}
388