2
\$\begingroup\$

i'm building an url router and i want to improve it to respect SOLID principles, i want suggestions of how can i make it less coupled.

here's my current code:

Router.php

namespace App\Helpers;

use App\Interfaces\Request\IRequest;
use App\Exceptions\MethodNotAllowedException;

class Router
{
    private $routes = [];
    private $methodsAllowed = ['GET', 'POST'];
    private $request;
    private $uri;
    private $matchTypes = [
        'i'  => '[0-9]++',
        'a'  => '[0-9A-Za-z]++',
        '*'  => '.+?',
    ];

    public function __construct(IRequest $request)
    {
        $this->request = $request;
        $this->setUri($request->getUri());
    }

    public function setUri($uri): void
    {
        $this->uri = $uri;
    }

    public function getUri(): string
    {
        return $this->uri;
    }

    public function on($method, $route, $callback): void
    {
        $this->setRoute($route, $method, $callback);
    }

    function setRoute($route, $method, $callback): self
    {
        if(!\in_array($method, $this->methodsAllowed)) {
            throw new MethodNotAllowedException('Method not allowed');
        }

        $method = strtoupper($method);

        $this->routes[] = [
            'uri' => $this->getUri(),
            'route' => $this->handleRouteUri($route),
            'method' => $method,
            'callback' => $callback,
            'params' => []
        ];

        return $this;
    }

    public function handleRouteUri ($route): string
    {   
        //other filters
        $route = $this->filterRoutePattern($route); 
        return $route;
    }

    public function filterRoutePattern($route): string
    {   
        foreach($this->matchTypes as $matchType => $pattern) {
            $route = str_replace("[$matchType]", "($pattern)", $route);
        }

        return $route;
    }

    public function getRoutes(): array
    {
        return $this->routes;
    }

    public function getMethod(): string
    {
        return $this->request->getMethod();
    }

    public function match()
    {   
        foreach($this->routes as $route) {

            if($route['method'] != $this->getMethod())
            continue;

            $routeUri = $route['route'];
            $pattern = sprintf('~^%s$~', $routeUri);

            if( preg_match($pattern, $this->getUri(), $matches) ) {

                array_shift($matches);

                $route['params'] = $matches;

                return $route;
            }

        }
    }
}

Request.php

<?php

namespace App\Helpers;

use App\Interfaces\Request\IRequest;

class Request implements IRequest {

    private static $params = [];
    private static $getParams = [];
    private static $postParams = [];
    private static $serverParams = [];

    public function __construct() 
    {   
        self::setParams();
    }

    public function setParams(): void
    {
        self::$params =  $_REQUEST;
        self::$getParams = $_GET;
        self::$postParams = $_GET;
        self::$serverParams = $_SERVER;
    }

    public function input($name): mixed
    {
        return self::$params[$name];
    }

    public function post($name): mixed
    {
        return self::$postParams[$name];
    }

    public function get($name): mixed
    {
        return self::$params_get[$name];
    }

    public function merge($name, $value): void
    {
        self::$params[$name] = $value;
    }

    public function getMethod(): string
    {
        return self::$serverParams['REQUEST_METHOD'];
    }

    public function getUri(): string
    {
        return self::$serverParams['REQUEST_URI'];
    }
}

usage:

use App\Helpers\Request;
use App\Helpers\Router;

$router = new Router(new Request());

$router->on('GET', '/', function() {});
$router->on('GET', 'users/[i]', 'UsersController@show');
$router->on('POST', 'user/store', function() {});

$match = $router->match();

if($match == true) {
    echo '<pre>';
    print_r($match);
} else {
    print_r($router->getUri());
}

1 - in order to achieve the Open Closed Principle, if i wanted to add more $methodsAllowed how could i do it without changing the structure of the class?

2 - i think that would be better to have a Route class where it could store its own properties and methods, but how could i group multiple routes and call it? a class RouteBag or RouteCollection?

3 - should i create a separated class to deal with the regex expressions? (filtering, etc...)

4 - if i wanted to create a separated route file and use it anywhere i wanted, like web.php in laravel, how should i do it? return an array with the routes or some kind of a collection as i mentioned in step 2

5 - if some route matches a given URL it's returned an array with the route's info, how can i handle calling the controller/method or running the closure callback?

feel free to suggest anything you think would be useful

\$\endgroup\$

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.