0
<?php 
    class Router {
        protected $conn;
        protected $currentController = "";
        protected $currentMethod = "";
        protected array $params = [];

        public function __construct(\PDO $conn){
            $this->conn = $conn;
        }

        public function dispatch(){
            $url = $this->getUrl();
            if(isset($url[4])){
                switch($url[4]){
                    case "events":
                        $this->currentController = new App\Controllers\EventController($this->conn);
                        $this->currentMethod = "getEvents";
                        break;
                    case "event":
                        $this->params = array($url[5]);
                        $this->currentController = new App\Controllers\EventController($this->conn);
                        $this->currentMethod = "getEvent";
                        break;

                }
            }
            call_user_func_array([$this->currentController, $this->currentMethod], $this->params);
            $this->currentController->display();
        }

        public function getUrl(){
            $uri = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
            $url = explode("/", $uri);
            return $url;
        }
    }
?>

$url[5] is supposed to be a numeric id for selecting an event or a string namely "?id=1" how can I pass this value to call_user_func_array I've seen doing it with preg_match expressions unfortunately I didn't understand how it works

7
  • what is your expected output? Commented Dec 21, 2021 at 10:59
  • So I'm trying to make a router and this shows events a var_dump() of all events, but when I try event/?id=1 so a var_dump of a specific event I get this error message Uncaught TypeError: call_user_func_array(): Argument #2 ($args) must be of type array, string given Commented Dec 21, 2021 at 11:02
  • @ilLeoneAmi yea so in your case $this->params = $url[5] is a text. Write in your router protected array $params = []; this way you make sure you cannot assign anything else than an array to it Commented Dec 21, 2021 at 11:07
  • I wrote protected array $params = [] and now I get that the line $this->params = $url[5] returns me the following error Uncaught TypeError: Cannot assign string to property Router::$params of type array with ?id=1 Commented Dec 21, 2021 at 11:10
  • @ilLeoneAmi yes because you think that $url[5] is an array, but it is a string. $this->params = [$url[5]]; would create an array out of your string but iam not sure if it will help you with the actual problem Commented Dec 21, 2021 at 11:38

1 Answer 1

1

Maybe you could try out this router. This is just a SIMPLIFICATION in order to understand how a router MIGHT look like. It is much better to install existing ones over packagist with composer. for example nikic/fast-route is a good option.

<?php

error_reporting(E_ALL);
ini_set('display_errors', 'On');


final class Router
{
    /**
     * This is a list of all routes with the regular expression in the key
     * the result loooks like following
     * (POST|GET)_/testMultiple/(\d+)/(\S+)
     * @var array<string, callable>
     */
    private array $routes = [];

    /**
     * Collect all routes
     * @param string $path
     * @param callable $action
     * @param string $methods
     * @return void
     */
    public function any(string $path, callable $action, string $methods = 'POST|GET'): void
    {
        //Skip router injections
        if (strpos($path, '..') !== false) {
            return;
        }
        $this->routes['(' . $methods . ')_' . $path] = $action;
    }

    /**
     * Create a regular expresion based on parameter and search for route
     * @param string $path
     * @return string
     * @throws Exception
     */
    public function callRoute(string $path): string
    {
        /**
         * $path might look like GET_/testMultiple/123/test
         */
        $path = $_SERVER['REQUEST_METHOD'] . '_' . parse_url($path,PHP_URL_PATH);

        /**
         * Iterate over all routes and cehck if  "GET_/testMultiple/123/test" can be found in an routes
         */
        foreach ($this->routes as $route => $action) {
            $regEx = "~^$route/?$~i";
            $matches = [];
            if (!preg_match($regEx, $path, $matches)) {
                continue;
            }
            //If we found the route we need to remove first 2 values from matched result so that only parameters from URL are inside $matched array
            array_shift($matches);
            array_shift($matches);
            $arguments = $matches;

            return call_user_func_array($action,$arguments);
        }
        throw new Exception(sprintf('Route %s not found', $path));
    }
}

final class TestController
{
    public function indexAction(): string
    {
        return 'Hello world';
    }
    public function testPost(): string
    {
        return 'Hello this is post only';
    }
    public function testAction(string $parameter1): string
    {
        return 'Hello ' . $parameter1;
    }

    public function testOptionalAction(?string $parameter1 = null): string
    {
        return 'Hello ' . $parameter1;
    }

    public function testMultiple(int $parameter1, string $parameter2): string
    {
        return 'The first value is ' . $parameter1 . ' and the second is ' . $parameter2;
    }
}


$services = [];
$services[TestController::class] = function () use ($services) {
    return new TestController();
};


$router = new Router();
$router->any('/', [$services[TestController::class](), 'indexAction']);

$router->any('/test/(\S+)', [$services[TestController::class](), 'testAction']);
$router->any('/testOptional/?(\S+)?', [$services[TestController::class](), 'testOptionalAction']);
$router->any('/testMultiple/(\d+)/(\S+)', [$services[TestController::class](), 'testMultiple']);
$router->any('/testJustPost', [$services[TestController::class](), 'testPost'],'POST');



echo $router->callRoute($_SERVER['REQUEST_URI']);

Hope that gives you some ideas for your router

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much for your answer I corrected my code so now it's working, I've looked to some router examples but still feel not at ease using code that I don't understand so I'm trying to write one of my own
@ilLeoneAmi i edited the router with more comments so you might see what it is actually doing, it is nearly like yours. but i use here Regular expression rather than explode by /

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.