2
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ what is the difference here? only name of the function routes vs init? you still can pass string as callable in second version and anonymous function in the first \$\endgroup\$ – avsej May 14 '17 at 5:01
  • \$\begingroup\$ @avsej the main difference is having a function-in-class approach that handles the different methods all in one function vs inline expressjs-style callbacks that explicitly state the request method \$\endgroup\$ – Keith M May 14 '17 at 5:29
  • 1
    \$\begingroup\$ so it is not about how to expose API, but rather how to use api to define the routes? because in both cases I can use inline functions for short actions and function names for actions with long body. Anyway it looks like it is correct that API should support both ways to leave decision to the user. \$\endgroup\$ – avsej May 14 '17 at 5:54
  • \$\begingroup\$ @avsej Correct, that's a good point about supporting both methods, I think I'll do that. Thanks for your feedback :) \$\endgroup\$ – Keith M May 14 '17 at 16:48
1
\$\begingroup\$

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).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.