I'm working on writing a routing system for a PHP project. I'm trying to write it in a way that it can easily be expanded later on via plugins.
I have 2 ideas for how to go about doing it. I've already created working versions of both, now it's just down to which one people find easier to use.
1:
class GeneralRouter extends Router {
public function RootRoute (Request $request, Response $response){
$request->requiresGet();
$response->html('home');
$response->send();
}
public function PageRoute (Request $request, Response $response){
$request->requiresGet();
$response->html('Hi, looking at \"' . $request->parameters["page"]. '\"');
$response->send();
}
public function routes (){
return array(
'/' => 'RootRoute',
'/:page/' => 'PageRoute'
);
}
}
2:
class GeneralRouter extends Router {
public function init (){
$this->get('/', function(Request $req, Response $res){
$response->html('<h1>home</h1>');
$response->send();
});
$this->get('/:page/', function(Request $req, Response $res){
$response->html('<pre>Hi, looking at \"' . $req->parameters["page"]. '\"</pre>');
$response->send();
});
}
}
#1 is similar to the way I've seen other PHP projects do their routing classes (CraftCMS for example), however I really like the ExpressJS-like format of #2. Which would be easier for expansion and/or is the "correct" way of doing it?
1 Answer 1
The first example is a violation of the open/closed-principle. You shouldn't need to extend the class in order to change the routes. There doesn't seem to be any other way to use the class without extending it.
class Router {
protected $routes = [];
protected function getRoute(Request $req) {
// ...
}
public function handle(Request $req, Response $res) {
$fn = $this->getRoute($req);
call_user_func($fn, $req, $res);
}
public function setRoutes(array $routes)
{
$this->routes = $routes;
}
public function addRoute($method, $path, $fn) {
$this->routes["$method $path"] = $fn;
}
}
Instead of setting method as values for the routes, use a callable. This can be a function, method, closure or invokable object.
A user may still choose to extend it and initialize in the constructor if he really wants to.
class GeneralRouter extends Router {
public function RootRoute (Request $request, Response $response){
// ...
}
public function PageRoute (Request $request, Response $response){
// ...
}
public function __construct() {
$this->setRoutes([
'GET /' => [self::class, 'RootRoute']
'GET /:page/' => [self::class, 'RootRoute']
]);
}
}
Adding a get
, post
and delete
method to the Router
class would just add micro framework syntax for those who like it, but it wouldn't fundamentally change the router.
Last, do not invent your own custom Request
and Response
objects. Use PSR-7 to ensure interoperability with libraries and test suites. There is Zend Diactorors and Jasny HTTP message (developed by me).
routes
vsinit
? you still can pass string as callable in second version and anonymous function in the first \$\endgroup\$