1
\$\begingroup\$

I'm trying to build my own PHP MVC framework. I want to implement a routing similar to how ExpressJS/NodeJS implements its routing by making use of Request Handlers.

Route.get("/", handler);
Route.get("/post/:id", handler);

In my php code, I've done something similar.

$route->get("/", "index@index");
$route->get("/blog/post/{id}/{title}", "post@index");

In my Router class, I have a method to validate the current request uri against the uris defined by the user. It also extracts the parameter key and values. ie: {id}, {title}

class Router
{
    const BASE = "/(\/?\{[a-z]+\}\/?)/";
    const VARS = "/\{([a-z]+)\}/";

    private $routes;
    private $params;
    private $action;
    private $request;

    public function __construct($request)
    {
        $this->action = "";
        $this->params = array();
        $this->routes = array();
        $this->request = $request;
    }

    public function valid(): bool
    {
        // Returns the current uri.
        // ie: "/blog/post/123/This-Is-A-Post"
        $rUri = $this->request->uri;

        // Returns the request http method
        // ie: "GET:
        $http = $this->request->http;


        /*
            $this-routes is an array of route defined by the user

            array(
                "/" => array("handler" => "index@index", "http" => "GET"),
            "/about" => array("handler" => "index@about", "http" => "GET"),
            "/blog/post/{id}/{title}" => array("handler" => "post@index", "http" => "GET"),
        )
        */
        foreach ($this->routes as $route => $data) {
            // Basically, return immediately if request uri
            // is equal to any of the uri defined by the user.
            // This also catches request uri that has no
            // defined variables. ie: {id}/{title}
            if ($route == $rUri) {
                $this->action = $data["handler"];
                return true;
            }

            $routeCount = substr_count($route, "/");
            $rUriCount = substr_count($rUri, "/");

            if ($routeCount != $rUriCount || $http != $data["http"]) {
                continue;
            }

            // This extracts the base uri from the user defined uris.
            // By base: "/blog/post/{id}/{title}" ==> extracts "/blog/post"
            $baseArr = preg_split($this::BASE, $route);
            $base = array_shift($baseArr);

            // If the base uri is not seen in the first
            // position of the request uri, just continue to the next.
            if (strpos($rUri, $base) != 0) {
                continue;
            }

            // For instance, the request uri is "/blog/post/123/This-Is-A-Post",
            // then this line extracts the values from that uri.
            //
            // array (
            //     [0] => "123",
            //     [1] => "This-Is-A-Post"
            // )
            $uriVarValues = explode("/", substr($rUri, strlen($base) + 1));


            // On the other hand, the lines below extracts the keys
            // from the user defined uri.
            //
            // Given: "/blog/post/{id}/{title}"
            //
            // array (
            //     [0] => "id",
            //     [1] => "title"
            // )
            preg_match_all($this::VARS, $route, $matches);
            $uriVarKeys = array_pop($matches);

            $valuesCount = count($uriVarValues);
            $KeysCount = count($uriVarKeys);

            if ($valuesCount != $KeysCount) {
                continue;
            }

            // If all checks are okay, just combine the keys and values
            // array that will be used on the controller routine.
            //
            // array (
            //    "id" => "123",
            //    "title" => "This-Is-A-Post"
            // )
            $this->params = array_combine($uriVarKeys, $uriVarValues);
            $this->action = $data["handler"];
            return true;
        }

        return false;
    }
}

I have a rewrite rule in my .htaccess to force remove trailing slash on incoming request uri.

What I'm trying to ask here is:

  1. Is there a better way of doing this in pure PHP? Is this even the right way of doing it?
  2. I'm planning to create a blog-like site using this routing routine. Can you point out security/performance concerns?
  3. What are other caveats I should know about regarding uri parsing and routing in PHP?
  4. Would you suggest something to make this even better?

This is my first Stack Overflow Code Review question, if I'm missing something or violating any rules from this community please kindly point out as well.

PS: I did not include in the class the methods where the user adds routes.

Thank you.

\$\endgroup\$
  • \$\begingroup\$ Routing can be a bit trickier then it first seems. I don't have time right now to review. but a couple of suggestions that come to mind. 1. Have you looked over packages like github.com/nikic/FastRoute, even if you don't intend to use it, it can give you some inspiration. 2. This is something that could easily be unit tested to prove it works for all your use cases \$\endgroup\$ – bumperbox Sep 29 '17 at 9:28
  • \$\begingroup\$ Ah, you're right. Another thing I have to consider is testability. Thanks for your input! Will check the link. \$\endgroup\$ – Wel Marq Sep 29 '17 at 9:31

Your Answer

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

Browse other questions tagged or ask your own question.