Completed
Push — master ( 9f0cc8...e7ce71 )
by Joao
02:39
created

src/RouteHandler.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace ByJG\RestServer;
4
5
use ByJG\DesignPattern\Singleton;
6
use ByJG\RestServer\Exception\BadActionException;
7
use ByJG\RestServer\Exception\ClassNotFoundException;
8
use ByJG\RestServer\Exception\Error404Exception;
9
use ByJG\RestServer\Exception\Error405Exception;
10
use ByJG\RestServer\Exception\Error520Exception;
11
use ByJG\RestServer\Exception\InvalidClassException;
12
use FastRoute\Dispatcher;
13
use FastRoute\RouteCollector;
14
use InvalidArgumentException;
15
16
class RouteHandler
17
{
18
19
    use Singleton;
20
21
    const OK = "OK";
22
    const METHOD_NOT_ALLOWED = "NOT_ALLOWED";
23
    const NOT_FOUND = "NOT FOUND";
24
25
    protected $_defaultMethods = [
26
        // Service
27
        ["method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}/{secondid}.{output}'],
28
        ["method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}.{output}'],
29
        ["method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{id:[0-9]+}/{action}.{output}'],
30
        ["method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{id:[0-9]+}.{output}'],
31
        ["method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}.{output}'],
32
        ["method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}.{output}']
33
    ];
34
    protected $_moduleAlias = [];
35
    protected $_defaultRestVersion = '1.0';
36
    protected $_defaultHandler = '\ByJG\RestServer\ServiceHandler';
37
    protected $_defaultOutput = null;
38
39
    public function getDefaultMethods()
40
    {
41
        return $this->_defaultMethods;
42
    }
43
44
    public function setDefaultMethods($methods)
45
    {
46
        if (!is_array($methods)) {
47
            throw new InvalidArgumentException('You need pass an array');
48
        }
49
50
        foreach ($methods as $value) {
51
            if (!isset($value['method']) || !isset($value['pattern'])) {
52
                throw new InvalidArgumentException('Array has not the valid format');
53
            }
54
        }
55
56
        $this->_defaultMethods = $methods;
57
    }
58
59
    public function getDefaultRestVersion()
60
    {
61
        return $this->_defaultRestVersion;
62
    }
63
64
    public function setDefaultRestVersion($version)
65
    {
66
        $this->_defaultRestVersion = $version;
67
    }
68
69
    public function getDefaultHandler()
70
    {
71
        return $this->_defaultHandler;
72
    }
73
74
    public function setDefaultHandler($value)
75
    {
76
        $this->_defaultHandler = $value;
77
    }
78
79
    public function getDefaultOutput()
80
    {
81
        return empty($this->_defaultOutput) ? Output::JSON : $this->_defaultOutput;
82
    }
83
84
    public function setDefaultOutput($defaultOutput)
85
    {
86
        $this->_defaultOutput = $defaultOutput;
87
    }
88
89
    public function getModuleAlias()
90
    {
91
        return $this->_moduleAlias;
92
    }
93
94
    public function addModuleAlias($alias, $module)
95
    {
96
        $this->_moduleAlias[$alias] = $module;
97
    }
98
99
    public function process()
100
    {
101
        // Initialize ErrorHandler with default error handler
102
        ErrorHandler::getInstance()->register();
103
104
        // Get the URL parameters
105
        $httpMethod = $_SERVER['REQUEST_METHOD'];
106
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
107
        parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $queryStr);
108
109
        // Generic Dispatcher for RestServer
110
        $dispatcher = \FastRoute\simpleDispatcher(function (RouteCollector $r) {
111
112
            foreach ($this->getDefaultMethods() as $route) {
113
                $r->addRoute(
114
                    $route['method'],
115
                    str_replace('{version}', $this->getDefaultRestVersion(), $route['pattern']),
116
                    isset($route['handler']) ? $route['handler'] : $this->getDefaultHandler()
117
                );
118
            }
119
        });
120
121
        $routeInfo = $dispatcher->dispatch($httpMethod, $uri);
0 ignored issues
show
It seems like $uri defined by parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) on line 106 can also be of type false; however, FastRoute\Dispatcher::dispatch() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
122
123
        switch ($routeInfo[0]) {
124
            case Dispatcher::NOT_FOUND:
125
126
                throw new Error404Exception('404 Not found');
127
128
            case Dispatcher::METHOD_NOT_ALLOWED:
129
130
                throw new Error405Exception('405 Method Not Allowed');
131
132
            case Dispatcher::FOUND:
133
134
                // ... 200 Process:
135
                $vars = array_merge($routeInfo[2], $queryStr);
136
137
                // Check Alias
138
                $moduleAlias = $this->getModuleAlias();
139
                if (isset($moduleAlias[$vars['module']])) {
140
                    $vars['module'] = $moduleAlias[$vars['module']];
141
                }
142
                $vars['module'] = '\\' . str_replace('.', '\\', $vars['module']);
143
144
                // Define output
145
                if (!isset($vars['output'])) {
146
                    $vars['output'] = $this->getDefaultOutput();
147
                }
148
                ErrorHandler::getInstance()->setHandler($vars['output']);
149
150
                // Set all default values
151
                foreach ($vars as $key => $value) {
152
                    $_REQUEST[$key] = $_GET[$key] = $vars[$key];
153
                }
154
155
                // Instantiate the Service Handler
156
                $handlerInstance = $this->getHandler($routeInfo[1], $vars['output']);
157
                $instance = $this->executeAction($vars['module']);
158
159
                echo $handlerInstance->execute($instance);
160
                break;
161
162
            default:
163
                throw new Error520Exception('Unknown');
164
        }
165
    }
166
167
    /**
168
     * Get the Handler based on the string
169
     *
170
     * @param string $handler
171
     * @param string $output
172
     * @throws ClassNotFoundException
173
     * @throws InvalidClassException
174
     * @return HandlerInterface Return the Handler Interface
175
     */
176
    public function getHandler($handler, $output)
177
    {
178
        if (!class_exists($handler)) {
179
            throw new ClassNotFoundException("Handler $handler not found");
180
        }
181
        $handlerInstance = new $handler();
182
        if (!($handlerInstance instanceof HandlerInterface)) {
183
            throw new InvalidClassException("Handler $handler is not a HandlerInterface");
184
        }
185
        $handlerInstance->setOutput($output);
186
        $handlerInstance->setHeader();
187
188
        return $handlerInstance;
189
    }
190
191
    /**
192
     * Instantiate the class found in the route
193
     *
194
     * @param string $class
195
     * @return ServiceAbstract
196
     * @throws ClassNotFoundException
197
     * @throws InvalidClassException
198
     * @throws BadActionException
199
     */
200
    public function executeAction($class)
201
    {
202
        // Instantiate a new class
203
        if (!class_exists($class)) {
204
            throw new ClassNotFoundException("Class $class not found");
205
        }
206
        $instance = new $class();
207
208
        if (!($instance instanceof ServiceAbstract)) {
209
            throw new InvalidClassException("Class $class is not an instance of ServiceAbstract");
210
        }
211
212
        // Execute the method
213
        $method = strtolower($instance->getRequest()->server("REQUEST_METHOD")); // get, post, put, delete
214
        $customAction = $method . ($instance->getRequest()->get('action'));
215
        if (method_exists($instance, $customAction)) {
216
            $instance->$customAction();
217
        } else {
218
            throw new BadActionException("The action '$customAction' does not exists.");
219
        }
220
221
        return $instance;
222
    }
223
224
    /**
225
     * Process the ROUTE (see httpdocs/route-dist.php)
226
     *
227
     * ModuleAlias needs to be an array like:
228
     *  [ 'alias' => 'Full.Namespace.To.Class' ]
229
     *
230
     * RoutePattern needs to be an array like:
231
     * [
232
     *     [
233
     *         "method" => ['GET'],
234
     *         "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}/{secondid}.{output}',
235
     *         "handler" => '\ByJG\RestServer\ServiceHandler'
236
     *    ],
237
     * ]
238
     *
239
     * @param array $moduleAlias
240
     * @param array $routePattern
241
     * @param string $version
242
     * @param string $defaultOutput
243
     * @param string $routeIndex
244
     */
245
    public static function handleRoute($moduleAlias = [], $routePattern = null, $version = '1.0', $defaultOutput = null, $routeIndex = "index.php")
246
    {
247
        ob_start();
248
        session_start();
249
250
        /**
251
         * @var RouteHandler
252
         */
253
        $route = RouteHandler::getInstance();
254
255
        /**
256
         * Module Alias contains the alias for full namespace class.
257
         *
258
         * For example, instead to request:
259
         * http://somehost/module/Full.NameSpace.To.Module
260
         *
261
         * you can request only:
262
         * http://somehost/module/somealias
263
         */
264
        foreach ((array)$moduleAlias as $alias => $module) {
265
            $route->addModuleAlias($alias, $module);
266
        }
267
268
        /**
269
         * You can create RESTFul compliant URL by adding the version.
270
         *
271
         * In the route pattern:
272
         * /{version}/someurl
273
         *
274
         * Setting the value here XMLNuke route will automatically replace it.
275
         *
276
         * The default value is "1.0"
277
         */
278
        $route->setDefaultRestVersion($version);
279
280
        /**
281
         * You can set the defaultOutput where is not necessary to set the output in the URL
282
         */
283
        $route->setDefaultOutput($defaultOutput);
284
285
        /**
286
         * There are a couple of basic routes pattern for the default parameters
287
         *
288
         * e.g.
289
         *
290
         * /1.0/command/1.json
291
         * /1.0/command/1.xml
292
         *
293
         * You can create your own route pattern by define the methods here
294
         */
295
        if (!empty($routePattern)) {
296
            $route->setDefaultMethods($routePattern);
297
        }
298
299
        // --------------------------------------------------------------------------
300
        // You do not need change from this point
301
        // --------------------------------------------------------------------------
302
303
        if (!empty($_SERVER['SCRIPT_FILENAME'])
304
            && file_exists($_SERVER['SCRIPT_FILENAME'])
305
            && basename($_SERVER['SCRIPT_FILENAME']) !== "route.php"
306
            && basename($_SERVER['SCRIPT_FILENAME']) !== $routeIndex
307
        ) {
308
            $file = $_SERVER['SCRIPT_FILENAME'];
309
            if (strpos($file, '.php') !== false) {
310
                require_once($file);
311
            } else {
312
                header("Content-Type: " . RouteHandler::mimeContentType($file));
313
314
                echo file_get_contents($file);
315
            }
316
            return;
317
        }
318
319
        $route->process();
320
    }
321
322
    /**
323
     * Get the Mime Type based on the filename
324
     *
325
     * @param string $filename
326
     * @return string
327
     */
328
    protected static function mimeContentType($filename)
329
    {
330
331
        $mime_types = array(
332
            'txt' => 'text/plain',
333
            'htm' => 'text/html',
334
            'html' => 'text/html',
335
            'php' => 'text/html',
336
            'css' => 'text/css',
337
            'js' => 'application/javascript',
338
            'json' => 'application/json',
339
            'xml' => 'application/xml',
340
            'swf' => 'application/x-shockwave-flash',
341
            'flv' => 'video/x-flv',
342
            // images
343
            'png' => 'image/png',
344
            'jpe' => 'image/jpeg',
345
            'jpeg' => 'image/jpeg',
346
            'jpg' => 'image/jpeg',
347
            'gif' => 'image/gif',
348
            'bmp' => 'image/bmp',
349
            'ico' => 'image/vnd.microsoft.icon',
350
            'tiff' => 'image/tiff',
351
            'tif' => 'image/tiff',
352
            'svg' => 'image/svg+xml',
353
            'svgz' => 'image/svg+xml',
354
            // archives
355
            'zip' => 'application/zip',
356
            'rar' => 'application/x-rar-compressed',
357
            'exe' => 'application/x-msdownload',
358
            'msi' => 'application/x-msdownload',
359
            'cab' => 'application/vnd.ms-cab-compressed',
360
            // audio/video
361
            'mp3' => 'audio/mpeg',
362
            'qt' => 'video/quicktime',
363
            'mov' => 'video/quicktime',
364
            // adobe
365
            'pdf' => 'application/pdf',
366
            'psd' => 'image/vnd.adobe.photoshop',
367
            'ai' => 'application/postscript',
368
            'eps' => 'application/postscript',
369
            'ps' => 'application/postscript',
370
            // ms office
371
            'doc' => 'application/msword',
372
            'rtf' => 'application/rtf',
373
            'xls' => 'application/vnd.ms-excel',
374
            'ppt' => 'application/vnd.ms-powerpoint',
375
            // open office
376
            'odt' => 'application/vnd.oasis.opendocument.text',
377
            'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
378
        );
379
380
        $ext = strtolower(array_pop(explode('.', $filename)));
381
        if (array_key_exists($ext, $mime_types)) {
382
            return $mime_types[$ext];
383
        } elseif (function_exists('finfo_open')) {
384
            $finfo = finfo_open(FILEINFO_MIME);
385
            $mimetype = finfo_file($finfo, $filename);
386
            finfo_close($finfo);
387
            return $mimetype;
388
        } else {
389
            return 'application/octet-stream';
390
        }
391
    }
392
}
393