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
|
|||
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 |
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
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 returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.