Routing System request syntax
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-&gt;getRoute($req);
        call_user_func($fn, $req, $res);
    }

    public function setRoutes(array $routes)
    {
        $this-&gt;routes = $routes;
    }

    public function addRoute($method, $path, $fn) {
        $this-&gt;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-&gt;setRoutes([
            'GET /' =&gt; [self::class, 'RootRoute']
            'GET /:page/' =&gt; [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).