I made a simple routing class that I like to get reviews for, mostly because I do not know how to make it SOLID, since I even made this class separate from the HTTP response/request for the sake of SRP.
All this class does is route-specific, which seems like solid enough to me.
The actual library can be found at fastpress/framework for more reference.
Here is an example of how to use it separately from the framework:
require 'path/to/router.php'
use Somenamespace\Router as route;
$route = new Route;
// get request
$route->get('/', function(){
echo 'Hello';
});
// get with regex named params
$route->get('/user/{:name}/{:id}', function($name, $id){
echo "name: $name id: $id";
});
// get with vanilla regex
$route->get('/user/[a-z]+', function(){});
// Rest-like API
$route->put('/foo', function(){});
$route->delete('/foo', function(){});
$route->post('/foo', function(){});
// MVC example.
$route->get('/mvc', 'UserController@index');
NOTE: In the case of that last MVC example, where the handler is a string with "controller@index"
and since for some reason, it did not seem right to me that a routing class should .. in this case:
- check if controller file / class exists,
- check if methods exists in controller class
- instantiate the controller and inject the argument in the method
All inside a routing class, I just made the router return an array containing all these details for other class to take over and instantiate and load anything MVC-specific.
Here is the line in the router that does that:
if(is_string($routerPath) && strpos($routerPath, '@')){
list($ctrl, $method) = explode('@', $routerPath);
return ['controller' => $ctrl, 'method' => $method, 'args' => $args];
}
Aside from that, the actual router class looks like this.
<?php
class Router{
protected $routes = [
'GET' => [],
'POST' => [],
'ANY' => [],
'PUT' => [],
'DELETE' => [],
];
public $patterns = [
':any' => '.*',
':id' => '[0-9]+',
':slug' => '[a-z\-]+',
':name' => '[a-zA-Z]+',
];
const REGVAL = '/({:.+?})/';
public function any($path, $handler){
$this->addRoute('ANY', $path, $handler);
}
public function get($path, $handler){
$this->addRoute('GET', $path, $handler);
}
public function post($path, $handler){
$this->addRoute('POST', $path, $handler);
}
public function put($path, $handler){
$this->addRoute('PUT', $path, $handler);
}
public function delete($path, $handler){
$this->addRoute('DELETE', $path, $handler);
}
protected function addRoute($method, $path, $handler){
array_push($this->routes[$method], [$path => $handler]);
}
public function match(array $server = [], array $post){
$requestMethod = $server['REQUEST_METHOD'];
$requestUri = $server['REQUEST_URI'];
$restMethod = $this->getRestfullMethod($post);
#@TODO: Implement REST method.
if (!$restMethod && !in_array($requestMethod, array_keys($this->routes))) {
return FALSE;
}
$method = $restMethod ?: $requestMethod;
foreach ($this->routes[$method] as $resource) {
$args = [];
$route = key($resource);
$handler = reset($resource);
if(preg_match(self::REGVAL, $route)){
list($args, $uri, $route) = $this->parseRegexRoute($requestUri, $route);
}
if(!preg_match("#^$route$#", $requestUri)){
unset($this->routes[$method]);
continue ;
}
if(is_string($handler) && strpos($handler, '@')){
list($ctrl, $method) = explode('@', $handler);
return ['controller' => $ctrl, 'method' => $method, 'args' => $args];
}
if(empty($args)){
return $handler();
}
#TODO: pass app by func array_push($args, $this);
return call_user_func_array($handler, $args);
}
header('HTTP/1.1 404');
}
protected function getRestfullMethod($postVar){
if(array_key_exists('_method', $postVar)){
if(in_array($method, array_keys($this->routes))){
return $method;
}
}
}
protected function parseRegexRoute($requestUri, $resource){
$route = preg_replace_callback(self::REGVAL, function($matches) {
$patterns = $this->patterns;
$matches[0] = str_replace(['{', '}'], '', $matches[0]);
if(in_array($matches[0], array_keys($patterns))){
return $patterns[$matches[0]];
}
}, $resource);
$regUri = explode('/', $resource);
$args = array_diff(
array_replace($regUri,
explode('/', $requestUri)
), $regUri
);
return [array_values($args), $resource, $route];
}
}
I would like to know how to make this solid, and if you were to remove some code to do so, please state your reason, not because symfony/laravel/xyz does so. Explain (if you can) why that code belongs separately from the class.
It also goes without saying that if you see any improvement, namely on speed or performance, let me know.
$_SERVER
matches the regex pattern. The$_POST
is being passed tomatch()
so I can check if the request is RESTFul, or there arePUT
,DELETE
,UPDATE
methods in post \$\endgroup\$