V
V
Vasiliy Shilov2012-05-14 01:44:04
PHP
Vasiliy Shilov, 2012-05-14 01:44:04

PHP router class

Hello!
I register routes in the form of regular expressions.
Below is a router class and an example of routes.

Interested in:
1. How clumsy is it?
2. how unsafe is it?
3. how slow is it?
4. Is it worth doing this at all?
5. What do you recommend?

Router class.

<?php

class DRouter {
  
  private function setMCA($urlData, $routes) {
    $str = 'controller=site&action=error';
    foreach(require $routes as $pattern => $route) {
      if (preg_match('/'.addcslashes($pattern, '/').'/i', $urlData['path'])) {
        $str = preg_replace('/' . addcslashes($pattern, '/') . '/i', $route, $urlData['path']);
        break;
      }
    }
    parse_str($str, $data);
    D::app()->request = $data;
    D::app()->request['module'] = (!empty($data['module'])) ? 'modules/'.$data['module'].'/' : '';
    D::app()->request['controller'] = (!empty($data['controller'])) ? $data['controller'] : 'site';
    D::app()->request['action'] = (!empty($data['action'])) ? $data['action'] : 'index';
    unset($data,$str);
    return $this;
  }
  
  public static function run($routes) {
    $oi = new self();
    $oi->setMCA(parse_url($_SERVER['REQUEST_URI']), $routes);
    if (!class_exists(D::app()->request['controller'].'Controller'))
      throw new DException('Страница не найдена', 404);
    if (!method_exists(D::app()->request['controller'].'Controller', D::app()->request['action'].'Action'))
      throw new DException('Страница не найдена', 404);
    $oi->forward(D::app()->request['controller'], D::app()->request['action']);
  }
  
  public static function forward($controller='site', $action='index', $data=null) {
      $controller = $controller.'Controller';
      $action = $action.'Action';
      $oi = new $controller();
      $oi->$action($data);
      if (method_exists($controller, 'run'))
        $controller->run();
      if (method_exists($controller, 'init'))
        $controller->init();
  }

}

The routes themselves
<?php

return array(
  '^/$' => 'controller=site&action=index',
  '^/admin(?:/|)$' => 'module=admin&controller=site&action=index',
  '^/admin/([a-z0-9]{1,15})(?:/|)$' => 'module=admin&controller=$1&action=index',
  '^/admin/([a-z0-9]{1,15})/([0-9]{1,15})(?:/|)$' => 'module=admin&controller=$1&action=view&id=$2',
  '^/admin/([a-z0-9]{1,15})/([a-z0-9]{1,15})(?:/|)$' => 'module=admin&controller=$1&action=$2',
  '^/([a-z0-9]{1,15})(?:/|)$' => 'controller=$1&action=index',
  '^/([a-z0-9]{1,15})/([0-9]{1,15})(?:/|)$' => 'controller=$1&action=view&id=$2',
  '^/([a-z0-9]{1,15})/([a-z0-9]{1,15})(?:/|)$' => 'controller=$1&action=$2',
  '^/([a-z0-9]{1,15})/([a-z0-9]{1,15})/([0-9]{1,15})(?:/|)$' => 'controller=$1&action=$2&id=$3',
  '^(.*)$' => 'controller=site&action=error',
);

Answer the question

In order to leave comments, you need to log in

6 answer(s)
E
EugeneOZ, 2012-05-14
@EugeneOZ

It's very sloppy. one class name "D" is already enough to understand that the code is going to be shitty.
This is not OOP code, don't be fooled, you don't need classes here. If you like static methods, write functions, because static methods are procedural programming.
Have you looked for existing turnkey solutions? Here's an example: symfony.com/doc/current/components/routing.html Spend
more time reading books, less time inventing what has already been invented hundreds of times.

Y
Yuri Popov, 2012-05-14
@DjPhoeniX

mod_rewrite will obviously be faster. Nothing about security.

G
Gleb Starkov, 2012-05-14
@colonel

I am currently using Symfony2, and the old projects are on a self-written
framework, where my own router is used, but I got
it somehow much simpler and works without problems, for example, a request:
example.com/bla
launch the Controller_Bla controller and the indexAction
example.com/bla/blum
action Check if there is a Controller_Bla controller and a method blumAction in it
If not, then it will check if there is a Controller_Bla_Blum controller and an indexAction method in it
(the sequence may be different, it’s not the essence, it will be visible in the code)
The bottom line: I don’t write paths anywhere else.
If I created a Controller_Example and testAction in it (well, plus the framework checks the template for this action),
then the /example/test page appears automatically for me.
Here is the router code:

    public static function run()
    {   
        $obj = null;
        $action = false;

        $pathCtrl = TS_CODE_DIR . '/Controller/';
        $classCtrl = 'Controller_';
        
        $redirect = isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : '';
        
        if (  empty ($redirect) ) {
            Ts_App::showMain();
        }
        
        $items = explode('/', $redirect);
        $els = array();
        
        for ($i=0; $i < count($items); $i++) {
            if ( !empty($items[$i]) ) {
                if ( !preg_match('/^[a-zA-Z0-9]+$/', $items[$i]) ) {
                    Ts_App::show404();
                }
                $els[] = ucfirst(strtolower($items[$i]));
            }
        }
        
        $cnt = count($els);
        for ($i=0; $i < $cnt; $i++) {
            if ( $i < ($cnt - 1) ) {
                $pathCtrl  .= $els[$i] . '/';
                $classCtrl .= $els[$i] . '_';
            } else {
                $pathCtrl .= $els[$i] . '.php';
                $classCtrl .= $els[$i];
            }
        }
        
        if ( file_exists($pathCtrl) && !is_dir($pathCtrl) ) {
            $obj = new $classCtrl();
        } else {
            // проверка наличия экшна в родительском контроллере
            preg_match('~(^.+)_([^_]+)$~', $classCtrl, $_crm);
            if ( isset($_crm[1]) && isset($_crm[2]) ) {
                $pathCtrl = preg_replace("~\/{$_crm[2]}\.php$~", '.php', $pathCtrl);
                
                if ( file_exists($pathCtrl) ) {
                    $action = strtolower($_crm[2]);
                    $obj = new $_crm[1]();
                }
            }
        }
        
        if ( empty($obj) ) {
             Ts_App::show404();
        }

        $obj->run($action);
    }

Routing is automatic.
If you have the right controller and action, then you don’t need to write anything else anywhere.
Application entry point:
Ts_App::run();

E
egorinsk, 2012-05-14
@egorinsk

There is only one main mistake - stupid names. What else for DRouter and MCA?
Also, why are module/action parameters written to request? Also, what for to do unset local variables? Do you think the link counter will not reset without this? Also, implicit addcslashes must be replaced with explicit escaping of special characters - or replace the slashes limiting the regular with another character.
The code is so-so, but in popular frameworks it is about the same, so it will do.
I would make an index for an accelerated search for a route without a stupid enumeration of all regular expressions. It's so primitive.
Also, I don't understand where the trend to do HTTP redirects and responses through throwing exceptions came from. Who came up with this? Which pattern does this match?

M
Mikhail Osher, 2012-05-14
@miraage

I see '^(.*)$' => 'controller=site&action=error'in the list of routes.
I see $str = 'controller=site&action=error'in DRouter::setMCA.
Some of them are redundant. I would leave it in the routes and remove it from the setter.

if (!class_exists(D::app()->request['controller'].'Controller'))
    throw new DException('Страница не найдена', 404);
if (!method_exists(D::app()->request['controller'].'Controller', D::app()->request['action'].'Action'))
    throw new DException('Страница не найдена', 404);

I would put the names of the controllers in $ctrl $act and make one if block.

M
madfriend, 2012-05-17
@madfriend

Take a look at the lemonade micro-framework. In fact, you only need routing from it. github.com/sofadesign/limonade

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question