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 BadMethodCallException; |
||
6 | use ByJG\RestServer\ErrorHandler; |
||
7 | use ByJG\RestServer\Exception\ClassNotFoundException; |
||
8 | use ByJG\RestServer\Exception\Error404Exception; |
||
9 | use ByJG\RestServer\Exception\Error405Exception; |
||
10 | use ByJG\RestServer\Exception\InvalidClassException; |
||
11 | use ByJG\RestServer\HandlerInterface; |
||
12 | use ByJG\RestServer\Output; |
||
13 | use ByJG\RestServer\RouteHandler; |
||
14 | use ByJG\RestServer\ServiceAbstract; |
||
15 | use FastRoute\Dispatcher; |
||
16 | use FastRoute\RouteCollector; |
||
17 | use InvalidArgumentException; |
||
18 | |||
19 | class RouteHandler |
||
20 | { |
||
21 | |||
22 | use \ByJG\DesignPattern\Singleton; |
||
23 | |||
24 | const OK = "OK"; |
||
25 | const METHOD_NOT_ALLOWED = "NOT_ALLOWED"; |
||
26 | const NOT_FOUND = "NOT FOUND"; |
||
27 | |||
28 | protected $_defaultMethods = [ |
||
29 | // Service |
||
30 | [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}/{secondid}.{output}' ], |
||
31 | [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}.{output}' ], |
||
32 | [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{id:[0-9]+}/{action}.{output}' ], |
||
33 | [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{id:[0-9]+}.{output}' ], |
||
34 | [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}/{action}.{output}' ], |
||
35 | [ "method" => ['GET', 'POST', 'PUT', 'DELETE'], "pattern" => '/{version}/{module}.{output}' ] |
||
36 | ]; |
||
37 | protected $_moduleAlias = []; |
||
38 | protected $_defaultRestVersion = '1.0'; |
||
39 | protected $_defaultHandler = '\ByJG\RestServer\ServiceHandler'; |
||
40 | protected $_defaultOutput = null; |
||
41 | |||
42 | public function getDefaultMethods() |
||
43 | { |
||
44 | return $this->_defaultMethods; |
||
45 | } |
||
46 | |||
47 | public function setDefaultMethods($methods) |
||
48 | { |
||
49 | if (!is_array($methods)) { |
||
50 | throw new InvalidArgumentException('You need pass an array'); |
||
51 | } |
||
52 | |||
53 | foreach ($methods as $value) { |
||
54 | if (!isset($value['method']) || !isset($value['pattern'])) { |
||
55 | throw new InvalidArgumentException('Array has not the valid format'); |
||
56 | } |
||
57 | } |
||
58 | |||
59 | $this->_defaultMethods = $methods; |
||
60 | } |
||
61 | |||
62 | public function getDefaultRestVersion() |
||
63 | { |
||
64 | return $this->_defaultRestVersion; |
||
65 | } |
||
66 | |||
67 | public function setDefaultRestVersion($version) |
||
68 | { |
||
69 | $this->_defaultRestVersion = $version; |
||
70 | } |
||
71 | |||
72 | public function getDefaultHandler() |
||
73 | { |
||
74 | return $this->_defaultHandler; |
||
75 | } |
||
76 | |||
77 | public function setDefaultHandler($value) |
||
78 | { |
||
79 | $this->_defaultHandler = $value; |
||
80 | } |
||
81 | |||
82 | public function getDefaultOutput() |
||
83 | { |
||
84 | return empty($this->_defaultOutput) ? Output::JSON : $this->_defaultOutput; |
||
85 | } |
||
86 | |||
87 | public function setDefaultOutput($defaultOutput) |
||
88 | { |
||
89 | $this->_defaultOutput = $defaultOutput; |
||
90 | } |
||
91 | |||
92 | public function getModuleAlias() |
||
93 | { |
||
94 | return $this->_moduleAlias; |
||
95 | } |
||
96 | |||
97 | public function addModuleAlias($alias, $module) |
||
98 | { |
||
99 | $this->_moduleAlias[$alias] = $module; |
||
100 | } |
||
101 | |||
102 | public function process() |
||
103 | { |
||
104 | // Initialize ErrorHandler with default error handler |
||
105 | ErrorHandler::getInstance()->register(); |
||
106 | |||
107 | // Get the URL parameters |
||
108 | $httpMethod = $_SERVER['REQUEST_METHOD']; |
||
109 | $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); |
||
110 | parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $queryStr); |
||
111 | |||
112 | // Generic Dispatcher for RestServer |
||
113 | $dispatcher = \FastRoute\simpleDispatcher(function(RouteCollector $r) { |
||
114 | |||
115 | foreach ($this->getDefaultMethods() as $route) { |
||
116 | $r->addRoute( |
||
117 | $route['method'], |
||
118 | str_replace('{version}', $this->getDefaultRestVersion(), $route['pattern']), |
||
119 | isset($route['handler']) ? $route['handler'] : $this->getDefaultHandler() |
||
120 | ); |
||
121 | } |
||
122 | }); |
||
123 | |||
124 | $routeInfo = $dispatcher->dispatch($httpMethod, $uri); |
||
0 ignored issues
–
show
|
|||
125 | |||
126 | switch ($routeInfo[0]) { |
||
127 | case Dispatcher::NOT_FOUND: |
||
128 | |||
129 | throw new Error404Exception('404 Not found'); |
||
130 | |||
131 | case Dispatcher::METHOD_NOT_ALLOWED: |
||
132 | |||
133 | throw new Error405Exception('405 Method Not Allowed'); |
||
134 | |||
135 | case Dispatcher::FOUND: |
||
136 | |||
137 | // ... 200 Process: |
||
138 | $vars = array_merge($routeInfo[2], $queryStr); |
||
139 | |||
140 | // Check Alias |
||
141 | $moduleAlias = $this->getModuleAlias(); |
||
142 | if (isset($moduleAlias[$vars['module']])) { |
||
143 | $vars['module'] = $moduleAlias[$vars['module']]; |
||
144 | } |
||
145 | $vars['module'] = '\\' . str_replace('.', '\\', $vars['module']); |
||
146 | |||
147 | // Define output |
||
148 | if (!isset($vars['output'])) { |
||
149 | $vars['output'] = $this->getDefaultOutput(); |
||
150 | } |
||
151 | ErrorHandler::getInstance()->setHandler($vars['output']); |
||
152 | |||
153 | // Set all default values |
||
154 | foreach ($vars as $key => $value) { |
||
155 | $_REQUEST[$key] = $_GET[$key] = $vars[$key]; |
||
156 | } |
||
157 | |||
158 | // Instantiate the Service Handler |
||
159 | $handlerInstance = $this->getHandler($routeInfo[1], $vars['output']); |
||
160 | $instance = $this->executeAction($vars['module']); |
||
161 | |||
162 | echo $handlerInstance->execute($instance); |
||
163 | break; |
||
164 | |||
165 | default: |
||
166 | throw new Exception('Unknown'); |
||
167 | } |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Get the Handler based on the string |
||
172 | * |
||
173 | * @param string $handler |
||
174 | * @param string $output |
||
175 | * @throws ClassNotFoundException |
||
176 | * @throws InvalidClassException |
||
177 | * @return HandlerInterface Return the Handler Interface |
||
178 | */ |
||
179 | public function getHandler($handler, $output) |
||
180 | { |
||
181 | if (!class_exists($handler)) { |
||
182 | throw new ClassNotFoundException("Handler $handler not found"); |
||
183 | } |
||
184 | $handlerInstance = new $handler(); |
||
185 | if (!($handlerInstance instanceof HandlerInterface)) { |
||
186 | throw new InvalidClassException("Handler $handler is not a HandlerInterface"); |
||
187 | } |
||
188 | $handlerInstance->setOutput($output); |
||
189 | $handlerInstance->setHeader(); |
||
190 | |||
191 | return $handlerInstance; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Instantiate the class found in the route |
||
196 | * |
||
197 | * @param string $class |
||
198 | * @return ServiceAbstract |
||
199 | * @throws ClassNotFoundException |
||
200 | * @throws InvalidClassException |
||
201 | * @throws BadMethodCallException |
||
202 | */ |
||
203 | public function executeAction($class) |
||
204 | { |
||
205 | // Instantiate a new class |
||
206 | if (!class_exists($class)) { |
||
207 | throw new ClassNotFoundException("Class $class not found"); |
||
208 | } |
||
209 | $instance = new $class(); |
||
210 | |||
211 | if (!($instance instanceof ServiceAbstract)) { |
||
212 | throw new InvalidClassException("Class $class is not an instance of ServiceAbstract"); |
||
213 | } |
||
214 | |||
215 | // Execute the method |
||
216 | $method = strtolower($instance->getRequest()->server("REQUEST_METHOD")); // get, post, put, delete |
||
217 | $customAction = $method . ($instance->getRequest()->get('action')); |
||
218 | if (method_exists($instance, $customAction)) { |
||
219 | $instance->$customAction(); |
||
220 | } else { |
||
221 | throw new BadMethodCallException("The method '$customAction' does not exists."); |
||
222 | } |
||
223 | |||
224 | return $instance; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Process the ROUTE (see httpdocs/route-dist.php) |
||
229 | * |
||
230 | * ModuleAlias needs to be an array like: |
||
231 | * [ 'alias' => 'Full.Namespace.To.Class' ] |
||
232 | * |
||
233 | * RoutePattern needs to be an array like: |
||
234 | * [ |
||
235 | * [ |
||
236 | * "method" => ['GET'], |
||
237 | * "pattern" => '/{version}/{module}/{action}/{id:[0-9]+}/{secondid}.{output}', |
||
238 | * "handler" => '\ByJG\RestServer\ServiceHandler' |
||
239 | * ], |
||
240 | * ] |
||
241 | * |
||
242 | * @param array $moduleAlias |
||
243 | * @param array $routePattern |
||
244 | * @param string $version |
||
245 | * @param string $routeIndex |
||
246 | */ |
||
247 | public static function handleRoute($moduleAlias = [], $routePattern = null, $version = '1.0', $routeIndex = "index.php") |
||
0 ignored issues
–
show
handleRoute 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...
|
|||
248 | { |
||
249 | ob_start(); |
||
250 | session_start(); |
||
251 | |||
252 | /** |
||
253 | * @var RouteHandler |
||
254 | */ |
||
255 | $route = RouteHandler::getInstance(); |
||
256 | |||
257 | /** |
||
258 | * Module Alias contains the alias for full namespace class. |
||
259 | * |
||
260 | * For example, instead to request: |
||
261 | * http://somehost/module/Full.NameSpace.To.Module |
||
262 | * |
||
263 | * you can request only: |
||
264 | * http://somehost/module/somealias |
||
265 | */ |
||
266 | foreach ((array) $moduleAlias as $alias => $module) { |
||
267 | $route->addModuleAlias($alias, $module); |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * You can create RESTFul compliant URL by adding the version. |
||
272 | * |
||
273 | * In the route pattern: |
||
274 | * /{version}/someurl |
||
275 | * |
||
276 | * Setting the value here XMLNuke route will automatically replace it. |
||
277 | * |
||
278 | * The default value is "1.0" |
||
279 | */ |
||
280 | $route->setDefaultRestVersion($version); |
||
281 | |||
282 | /** |
||
283 | * There are a couple of basic routes pattern for the default parameters |
||
284 | * |
||
285 | * e.g. |
||
286 | * |
||
287 | * /1.0/command/1.json |
||
288 | * /1.0/command/1.xml |
||
289 | * |
||
290 | * You can create your own route pattern by define the methods here |
||
291 | */ |
||
292 | if (!empty($routePattern)) { |
||
293 | $route->setDefaultMethods($routePattern); |
||
294 | } |
||
295 | |||
296 | // -------------------------------------------------------------------------- |
||
297 | // You do not need change from this point |
||
298 | // -------------------------------------------------------------------------- |
||
299 | |||
300 | if (!empty($_SERVER['SCRIPT_FILENAME']) |
||
301 | && file_exists($_SERVER['SCRIPT_FILENAME']) |
||
302 | && basename($_SERVER['SCRIPT_FILENAME']) !== "route.php" |
||
303 | && basename($_SERVER['SCRIPT_FILENAME']) !== $routeIndex |
||
304 | ) { |
||
305 | $file = $_SERVER['SCRIPT_FILENAME']; |
||
306 | if (strpos($file, '.php') !== false) { |
||
307 | require_once($file); |
||
308 | } else { |
||
309 | header("Content-Type: " . RouteHandler::mimeContentType($file)); |
||
310 | |||
311 | echo file_get_contents($file); |
||
312 | } |
||
313 | return; |
||
314 | } |
||
315 | |||
316 | $route->process(); |
||
317 | } |
||
318 | |||
319 | /** |
||
320 | * Get the Mime Type based on the filename |
||
321 | * |
||
322 | * @param string $filename |
||
323 | * @return string |
||
324 | */ |
||
325 | protected static function mimeContentType($filename) |
||
326 | { |
||
327 | |||
328 | $mime_types = array( |
||
329 | 'txt' => 'text/plain', |
||
330 | 'htm' => 'text/html', |
||
331 | 'html' => 'text/html', |
||
332 | 'php' => 'text/html', |
||
333 | 'css' => 'text/css', |
||
334 | 'js' => 'application/javascript', |
||
335 | 'json' => 'application/json', |
||
336 | 'xml' => 'application/xml', |
||
337 | 'swf' => 'application/x-shockwave-flash', |
||
338 | 'flv' => 'video/x-flv', |
||
339 | // images |
||
340 | 'png' => 'image/png', |
||
341 | 'jpe' => 'image/jpeg', |
||
342 | 'jpeg' => 'image/jpeg', |
||
343 | 'jpg' => 'image/jpeg', |
||
344 | 'gif' => 'image/gif', |
||
345 | 'bmp' => 'image/bmp', |
||
346 | 'ico' => 'image/vnd.microsoft.icon', |
||
347 | 'tiff' => 'image/tiff', |
||
348 | 'tif' => 'image/tiff', |
||
349 | 'svg' => 'image/svg+xml', |
||
350 | 'svgz' => 'image/svg+xml', |
||
351 | // archives |
||
352 | 'zip' => 'application/zip', |
||
353 | 'rar' => 'application/x-rar-compressed', |
||
354 | 'exe' => 'application/x-msdownload', |
||
355 | 'msi' => 'application/x-msdownload', |
||
356 | 'cab' => 'application/vnd.ms-cab-compressed', |
||
357 | // audio/video |
||
358 | 'mp3' => 'audio/mpeg', |
||
359 | 'qt' => 'video/quicktime', |
||
360 | 'mov' => 'video/quicktime', |
||
361 | // adobe |
||
362 | 'pdf' => 'application/pdf', |
||
363 | 'psd' => 'image/vnd.adobe.photoshop', |
||
364 | 'ai' => 'application/postscript', |
||
365 | 'eps' => 'application/postscript', |
||
366 | 'ps' => 'application/postscript', |
||
367 | // ms office |
||
368 | 'doc' => 'application/msword', |
||
369 | 'rtf' => 'application/rtf', |
||
370 | 'xls' => 'application/vnd.ms-excel', |
||
371 | 'ppt' => 'application/vnd.ms-powerpoint', |
||
372 | // open office |
||
373 | 'odt' => 'application/vnd.oasis.opendocument.text', |
||
374 | 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', |
||
375 | ); |
||
376 | |||
377 | $ext = strtolower(array_pop(explode('.', $filename))); |
||
378 | if (array_key_exists($ext, $mime_types)) { |
||
379 | return $mime_types[$ext]; |
||
380 | } elseif (function_exists('finfo_open')) { |
||
381 | $finfo = finfo_open(FILEINFO_MIME); |
||
382 | $mimetype = finfo_file($finfo, $filename); |
||
383 | finfo_close($finfo); |
||
384 | return $mimetype; |
||
385 | } else { |
||
386 | return 'application/octet-stream'; |
||
387 | } |
||
388 | } |
||
389 | } |
||
390 |
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
.