Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jlrdw's avatar
Level 75

PHP general router

A while back a question came up from

The PHP Practitioner Series

That got me sparked up to finish a router form an older custom framework I have kept updated, it's even php 7.3 compatible now. It is a combination of the old SMVC framework, Mini 3 framework, and a lot of my code combined. Mini only comes with front controller routing.

This is NOT for laravel, again it is just to show how a router could be built and protect some routes requiring a login for php.

First of all this is not a copy and paste router, it is just to show how a router can be built.

Also I use static methods, but instance methods could be used as well.

I have two ways to route,

  • Routes not needing a login I auto dispatch (front controller method)
  • Routes needing login I verify against an Auth array and route

It starts with a simple routes file, just what I named it:

<?php

use Mini\Core\Router;
use Mini\Helpers\Clnsantize as Cln;
use Mini\Core\AuthRoutes;

$auth = "notauth";

if (Cln::chkAdmin() === 'authadmin') {
    $auth = "isauth";
    $routes = AuthRoutes::chkAuth($auth);
}
Router::checkAdmin($routes, $auth);

A required login routes list:

<?php

namespace Mini\Core;

class AuthRoutes
{

    public static function chkAuth($auth = 'notauth')
    {
        if ($auth = 'isauth') {
            $admin = [
                'dog/indexadmin' => '\Mini\Controller\DogController@indexAdmin',
                'dog/edit' => '\Mini\Controller\DogController@edit',
                'admin/logout' => '\Mini\Controller\AdminController@logout'
            ];
            return $admin;
        }
    }

}

And the router:

<?php

namespace Mini\Core;

use Mini\Helpers\Clnsantize as Cln;

class Router
{

    /** The routes */
    protected static $routes = [];

    /** Any parameters, not querystring */
    protected static $last = [];

    /** uri */
    protected static $uri = [];

    /** Check if authorized */
    protected static $auth = '';

    /** Run any authorized routes */
    public static function checkAdmin($routes = array(), $auth)
    {
        self::$uri = self::getUri();
        if ($auth === 'isauth') {
            self::$routes = $routes;
            $key_to_check = self::chkUri(self::$uri[2], self::$uri[3]);
            if (array_key_exists($key_to_check, self::$routes)) {
                $torun = self::$routes[$key_to_check];
                self::run($torun, $auth);
            } else {
                self::autoDispatch();
            }
        } else {
            self::autoDispatch();
        }
    }

    public static function run($torun = null, $auth)
    {
        self::$auth = $auth;
        foreach (self::$uri as $i => $key) {
            $i > 0;
            $key;
            if ($i > 3) {
                $newkey = self::getKey($key);
                array_push(self::$last, $newkey);
            }
        }
        self::dispatch($torun);
    }

    public static function dispatch($torun)
    {
        if (self::$auth === 'isauth') {
            $uparts = explode('@', $torun);
            $controller = isset($uparts[0]) ? $uparts[0] : null;
            $action = isset($uparts[1]) ? $uparts[1] : null;
            self::load($controller, $action, self::$last);
        } else {
            self::defaultController();
        }
    }

    public static function load($controller, $action, $last = array())
    {
        $c = new $controller;
        return $c->$action(...array_values($last));
    }

    public static function getKey($key)
    {
        $fixedKey = Cln::fixUri($key);
        return $fixedKey;
    }

    public static function chkUri($u1 = null, $u2 = null)
    {
        $uricheck = Cln::chkForSingle($u1, $u2);

        return $uricheck;
    }

    public static function getUri()
    {
        $uri = explode('/', parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
        return $uri;
    }

    public static function autoDispatch()
    {
        foreach (self::$uri as $i => $key) {
            $i > 0;
            $key;
            if ($i > 3) {
                $newkey = self::getKey($key);
                //echo $newkey;
                array_push(self::$last, $newkey);
            }
        }
        $key_to_check = self::chkUri(self::$uri[2], self::$uri[3]);
        $uparts = explode('/', $key_to_check);
        if (file_exists(APP . 'Controller' . '/' . ucfirst($uparts[0]) . 'Controller.php')) {
            $controller = isset($uparts[0]) ? '\Mini\Controller\' . ucfirst($uparts[0]) . 'Controller' : null;
            $action = isset($uparts[1]) ? $uparts[1] : null;

            if (method_exists($controller, $action) && is_callable(array($controller, $action))) {
                self::load($controller, $action, self::$last);
            } else {
                self::defaultController();
            }
        } else {
            self::defaultController();
        }
    }

    public static function defaultController()
    {
        $c = new \Mini\Controller\HomeController();
        $action = 'index';
        $c->$action();
    }

}

Parts like

$fixedKey = Cln::fixUri($key);

is just calling a method in a class that:

public static function fixUri($rvalue)
    {
        $rvalue = empty($rvalue) && !is_numeric($rvalue) ? NULL : trim(strip_tags($rvalue));
        $rvalue = strtok($rvalue, '?');
        $rvalue = htmlspecialchars($rvalue);
        return $rvalue;
    }

And this function:

public static function chkUri($u1 = null, $u2 = null)
    {
        $uricheck = Cln::chkForSingle($u1, $u2);
        return $uricheck;
    }

makes it work if a second part isn't used, like

site/dog/index 
site/dog  //would work if in routes that way

The $last is getting any extra passed parameters, not counting query string:

site/dog/indexadmin 

// adding params

site/dog/indexadmin/hello/world/1457

Query string is taken care of by HTTP anyway. No routing is required. Like:

site/dog/indexadmin/hello/world?page=2

?page=2 // is done no routing required.

I moved this project to laravel a while back, but had never completed the router. This router works, but I imagine most will want instance methods. But static is perfect routing, get route and it's done.

For more on the splat operator see:

(...array_values($last)

See https://laracasts.com/discuss/channels/general-discussion/callstatic

and

https://laracasts.com/discuss/channels/general-discussion/php-general-question-1

0 likes
1 reply
jlrdw's avatar
Level 75

To note I don't use this, it's all a laravel app now, I still use my Auth system with custom middleware, namely:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Session;
use App\Helpers\Clnsantize as Cln;

class CheckLog
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Cln::chklog() === 'notlogged') {
            return redirect('admin');
        }

        return $next($request);
    }

}

I only worked on this old in work router to see what I could come up with, and just trying to keep it up to date with latest PHP version. As stated above I load routes if someone is logged in.

The way to load only the logged in users routes with their role is as follows.

<?php

namespace Mini\Core;

use Mini\Helpers\Session;

class AuthRoutes
{

    public static function chkAuth($auth = 'notauth')
    {
        if ($auth = 'isauth') {
            $role = trim(Session::get('thisrole'));
            $roles = explode(',', $role);
            $routes = [];
            $admin = [
                'dog/indexadmin' => '\Mini\Controller\DogController@indexAdmin',
                'dog/edit' => '\Mini\Controller\DogController@edit',
                'admin/logout' => '\Mini\Controller\AdminController@logout'
            ];
            in_array('admin', $roles) ? $routes = array_merge($routes, $admin) : 'not_matched';

            
            $bkeep = [
                'accounts/index' => '\Mini\Controller\AccountsController@index'
            ];
            $match = in_array('bkeep', $roles) ? $routes = array_merge($routes, $bkeep) : 'not_matched';

            return $routes;
        }
    }

}

And as suggested from snapey I implemented multi roles in a field roles:

roles
admin, bkeep

Or

roles
bkeep

If bookkeeper is the only role.

So I explode the roles string to use in_array:

in_array('bkeep', $roles) ? $routes = array_merge($routes, $bkeep) : 'not_matched';

So if Bob logs in and has roles admin and bkeep (for bookkeeper) then the admin and bkeep routes are available.

However if Suzy logs in and has role of only bkeep, then only the bookkeeping routes are loaded.

Still a work in progress, eventually I hope to have a finalized version on Github.

I am still looking for a better way to include the logged in users routes. But array merge isn't to bad.

1 like

Please or to participate in this conversation.