Completed
Push — master ( 066784...351105 )
by Joao
02:29
created

src/RouteHandler.php (2 issues)

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 Exception;
6
use FastRoute\Dispatcher;
7
use FastRoute\RouteCollector;
8
use InvalidArgumentException;
9
use ByJG\RestServer\Exception\Error404Exception;
10
use ByJG\RestServer\Exception\Error405Exception;
11
use ByJG\RestServer\ServiceHandler;
12
13
class RouteHandler
14
{
15
16
    use \ByJG\DesignPattern\Singleton;
17
18
    const OK = "OK";
19
    const METHOD_NOT_ALLOWED = "NOT_ALLOWED";
20
    const NOT_FOUND = "NOT FOUND";
21
22
    protected $_defaultMethods = [
23
        // Service
24
        [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}/{secondid}.{output}', "handler" => 'service'],
25
        [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}.{output}', "handler" => 'service'],
26
        [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{id:[0-9]+}/{action}.{output}', "handler" => 'service'],
27
        [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{id:[0-9]+}.{output}', "handler" => 'service'],
28
        [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}.{output}', "handler" => 'service'],
29
        [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}.{output}', "handler" => 'service']
30
    ];
31
    protected $_moduleAlias = [];
32
    protected $_defaultVersion = '1.0';
33
34
    public function getDefaultMethods()
35
    {
36
        return $this->_defaultMethods;
37
    }
38
39
    public function setDefaultMethods($methods)
40
    {
41
        if (!is_array($methods)) {
42
            throw new InvalidArgumentException('You need pass an array');
43
        }
44
45
        foreach ($methods as $value) {
46
            if (!isset($value['method']) || !isset($value['pattern'])) {
47
                throw new InvalidArgumentException('Array has not the valid format');
48
            }
49
        }
50
51
        $this->_defaultMethods = $methods;
52
    }
53
54
    public function getDefaultRestVersion()
55
    {
56
        return $this->_defaultVersion;
57
    }
58
59
    public function setDefaultRestVersion($version)
60
    {
61
        $this->_defaultVersion = $version;
62
    }
63
64
    public function getModuleAlias()
65
    {
66
        return $this->_moduleAlias;
67
    }
68
69
    public function addModuleAlias($alias, $module)
70
    {
71
        $this->_moduleAlias[$alias] = $module;
72
    }
73
74
    public function process()
75
    {
76
        // Initialize ErrorHandler with default error handler
77
        ErrorHandler::getInstance()->register();
78
79
        // Get the URL parameters
80
        $httpMethod = $_SERVER['REQUEST_METHOD'];
81
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
82
        parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $queryStr);
83
84
        // Generic Dispatcher for XMLNuke
85
        $dispatcher = \FastRoute\simpleDispatcher(function(RouteCollector $r) {
86
87
            foreach ($this->getDefaultMethods() as $route) {
88
                $r->addRoute(
89
                    $route['method'], str_replace('{version}', $this->getDefaultRestVersion(), $route['pattern']),
90
                    isset($route['handler']) ? $route['handler'] : 'default'
91
                );
92
            }
93
        });
94
95
        $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 81 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...
96
97
        switch ($routeInfo[0]) {
98
            case Dispatcher::NOT_FOUND:
99
100
                throw new Error404Exception('404 Not found');
101
102
            case Dispatcher::METHOD_NOT_ALLOWED:
103
104
                throw new Error405Exception('405 Method Not Allowed');
105
106
            case Dispatcher::FOUND:
107
108
                // ... 200 Process:
109
                $vars = array_merge($routeInfo[2], $queryStr);
110
111
                // Check Alias
112
                $moduleAlias = $this->getModuleAlias();
113
                if (isset($moduleAlias[$vars['module']])) {
114
                    $vars['module'] = $moduleAlias[$vars['module']];
115
                }
116
                $vars['module'] = '\\' . str_replace('.', '\\', $vars['module']);
117
118
                // Define output
119
                if (!isset($vars['output'])) {
120
                    $vars['output'] = Output::JSON;
121
                }
122
                ErrorHandler::getInstance()->setHandler($vars['output']);
123
124
                // Check if output is set
125
                if ($vars['output'] != Output::JSON && $vars['output'] != Output::XML && $vars['output'] != Output::CSV && $vars['output']
126
                    != Output::RDF) {
127
                    throw new Exception('Invalid output format. Valid are XML, JSON or CSV');
128
                }
129
130
                // Set all default values
131
                foreach ($vars as $key => $value) {
132
                    $_REQUEST[$key] = $_GET[$key] = $vars[$key];
133
                }
134
135
                return [ $vars['module'], $vars['output']];
136
137
            default:
138
                throw new \Exception('Unknown');
139
        }
140
    }
141
142
    /**
143
     * Process the ROUTE (see httpdocs/route-dist.php)
144
     *
145
     * ModuleAlias needs to be an array like:
146
     *  [ 'alias' => 'Full.Namespace.To.Class' ]
147
     *
148
     * RoutePattern needs to be an array like:
149
     * [
150
     * 		[ "method" => ['GET'], "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}/{secondid}.{output}', "handler" => 'service' ],
151
     * ]
152
     *
153
     * @param array $moduleAlias
154
     * @param array $routePattern
155
     * @param string $version
156
     * @param bool $cors
157
     */
158
    public static function processRoute($moduleAlias = [], $routePattern = null, $version = '1.0', $cors = false, $routeIndex = "index.php")
0 ignored issues
show
processRoute uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
159
    {
160
        ob_start();
161
        session_start();
162
163
        /**
164
         * @var RouteHandler
165
         */
166
        $route = RouteHandler::getInstance();
167
168
        /**
169
         * Module Alias contains the alias for full namespace class.
170
         *
171
         * For example, instead to request:
172
         * http://somehost/module/Full.NameSpace.To.Module
173
         *
174
         * you can request only:
175
         * http://somehost/module/somealias
176
         */
177
        foreach ((array) $moduleAlias as $alias => $module) {
178
            $route->addModuleAlias($alias, $module);
179
        }
180
181
        /**
182
         * You can create RESTFul compliant URL by adding the version.
183
         *
184
         * In the route pattern:
185
         * /{version}/someurl
186
         *
187
         * Setting the value here XMLNuke route will automatically replace it.
188
         *
189
         * The default value is "1.0"
190
         */
191
        $route->setDefaultRestVersion($version);
192
193
        /**
194
         * There are a couple of basic routes pattern for the default parameters
195
         *
196
         * e.g.
197
         *
198
         * /1.0/command/1.json
199
         * /1.0/command/1.xml
200
         *
201
         * You can create your own route pattern by define the methods here
202
         */
203
        if (!empty($routePattern)) {
204
            $route->setDefaultMethods($routePattern);
205
        }
206
207
        // --------------------------------------------------------------------------
208
        // You do not need change from this point
209
        // --------------------------------------------------------------------------
210
211
        if (!empty($_SERVER['SCRIPT_FILENAME'])
212
            && file_exists($_SERVER['SCRIPT_FILENAME'])
213
            && basename($_SERVER['SCRIPT_FILENAME']) !== "route.php" 
214
            && basename($_SERVER['SCRIPT_FILENAME']) !== $routeIndex
215
        )  {
216
            $file = $_SERVER['SCRIPT_FILENAME'];
217
            if (strpos($file, '.php') !== false) {
218
                require_once($file);
219
            } else {
220
                header("Content-Type: " . RouteHandler::mimeContentType($file));
221
222
                echo file_get_contents($file);
223
            }
224
            return;
225
        }
226
227
        list($class, $output) = $route->process();
228
229
        $handler = new ServiceHandler($output);
230
        $handler->setHeader();
231
        if (!$cors || ($cors && $handler->setHeaderCors())) {
232
            echo $handler->execute($class);
233
        }
234
    }
235
236
    protected static function mimeContentType($filename)
237
    {
238
239
        $mime_types = array(
240
            'txt' => 'text/plain',
241
            'htm' => 'text/html',
242
            'html' => 'text/html',
243
            'php' => 'text/html',
244
            'css' => 'text/css',
245
            'js' => 'application/javascript',
246
            'json' => 'application/json',
247
            'xml' => 'application/xml',
248
            'swf' => 'application/x-shockwave-flash',
249
            'flv' => 'video/x-flv',
250
            // images
251
            'png' => 'image/png',
252
            'jpe' => 'image/jpeg',
253
            'jpeg' => 'image/jpeg',
254
            'jpg' => 'image/jpeg',
255
            'gif' => 'image/gif',
256
            'bmp' => 'image/bmp',
257
            'ico' => 'image/vnd.microsoft.icon',
258
            'tiff' => 'image/tiff',
259
            'tif' => 'image/tiff',
260
            'svg' => 'image/svg+xml',
261
            'svgz' => 'image/svg+xml',
262
            // archives
263
            'zip' => 'application/zip',
264
            'rar' => 'application/x-rar-compressed',
265
            'exe' => 'application/x-msdownload',
266
            'msi' => 'application/x-msdownload',
267
            'cab' => 'application/vnd.ms-cab-compressed',
268
            // audio/video
269
            'mp3' => 'audio/mpeg',
270
            'qt' => 'video/quicktime',
271
            'mov' => 'video/quicktime',
272
            // adobe
273
            'pdf' => 'application/pdf',
274
            'psd' => 'image/vnd.adobe.photoshop',
275
            'ai' => 'application/postscript',
276
            'eps' => 'application/postscript',
277
            'ps' => 'application/postscript',
278
            // ms office
279
            'doc' => 'application/msword',
280
            'rtf' => 'application/rtf',
281
            'xls' => 'application/vnd.ms-excel',
282
            'ppt' => 'application/vnd.ms-powerpoint',
283
            // open office
284
            'odt' => 'application/vnd.oasis.opendocument.text',
285
            'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
286
        );
287
288
        $ext = strtolower(array_pop(explode('.', $filename)));
289
        if (array_key_exists($ext, $mime_types)) {
290
            return $mime_types[$ext];
291
        } elseif (function_exists('finfo_open')) {
292
            $finfo = finfo_open(FILEINFO_MIME);
293
            $mimetype = finfo_file($finfo, $filename);
294
            finfo_close($finfo);
295
            return $mimetype;
296
        } else {
297
            return 'application/octet-stream';
298
        }
299
    }
300
}
301