PHP класс роутера

Здравствуйте!
Прописываю маршруты в виде регулярных выражений.
Ниже класс маршрутизатора и пример маршрутов.

Интересует:
1. на сколько это коряво?
2. на сколько это небезопасно?
3. на сколько это небыстро?
4. стоит ли вообще так делать?
5. что посоветуете?

Класс маршрутизатора.
<?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();
	}

}

Непосредственно сами маршруты
<?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',
);
  • Вопрос задан
  • 14106 просмотров
Пригласить эксперта
Ответы на вопрос 6
EugeneOZ
@EugeneOZ
Это очень коряво. одно название класса «D» уже достаточно, чтобы понять, что код будет хреновым.
Это не ООП код, не обманывайте себя, классы Вам не нужны здесь. Если Вы любите статические методы — пишите функции, т.к. статические методы это процедурное программирование.

Искали ли Вы существующие готовые решения? Вот это, например: symfony.com/doc/current/components/routing.html

Тратьте больше времени на чтение книг, меньше на изобретение того, что уже сотни раз изобретено.
Ответ написан
DjPhoeniX
@DjPhoeniX
Hardcore iOS & ESP developer & DJ
mod_rewrite будет явно быстрее. По безопасности ничего не заметил.
Ответ написан
colonel
@colonel
Разработчик PHP, Laravel
Я сейчас использую Symfony2, а старые проекты на самописном
фреймворке, где свой роутер используется, но он у меня получился
как-то намного проще, и работает без проблем, например запрос:

example.com/bla

запустит контроллер Controller_Bla и экшн indexAction

example.com/bla/blum

Проверит, есть ли контроллер Controller_Bla и метод в нем blumAction

Если нет, то проверит, есть ли контроллер Controller_Bla_Blum и метод в нем indexAction
(последовательность может и другая, не суть, в коде видно будет)

Суть: я не прописываю нигде пути больше.
Если я создал Controller_Example и в нем testAction (ну плюс еще фреймворк проверяет шаблон для этого экшна),
то страница /example/test появляется у меня автоматом.

Вот код роутера:

    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);
    }


Роутинг получается автоматическим.
Если есть нужный контроллер и экшн, то больше нигде ничего прописывать не нужно.

Точка входа приложения:

Ts_App::run();
Ответ написан
@egorinsk
Ошибка тут одна главная — дурацкие названия. Что еще за DRouter и MCA?

Также, почему параметры module/action пишутся в request? Также, нафига делать unset локальным переменным? Думаете, счетчик ссылок без этого не обнулится? Также, неявный addcslashes надо заменять на явное экранирование спецсимволов — или заменить ограничивающие ругулярку слеши на другой символ.

Код так себе, но в популярных фреймворках примерно такой же, так что сойдет.

Я бы сделал индекс для ускоренного поиска роута без тупого перебора всех регулярок. Это так примитивно.

Также, я не понимаю, откуда взялась мода делать HTTP-редиректы и ответы через выбрасывание исключений. Кто придумал это? Какому из паттернов это соответствует?
Ответ написан
miraage
@miraage
Старый прогер
Вижу '^(.*)$' => 'controller=site&action=error' в списке маршрутов.
Вижу $str = 'controller=site&action=error' в DRouter::setMCA.
Что-то из них лишнее. Я бы оставил в роутах и убрал из сеттера.

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);

Я бы вынес в $ctrl $act названия контроллеров и сделал один блок if.
Ответ написан
Комментировать
@madfriend
Посмотрите в сторону микро-фреймворка limonade. По сути, из него Вам нужна только маршрутизация. github.com/sofadesign/limonade
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы