MVC: Front Controller, Controller и Router
12 февраля 2010
В прошлый раз я описал построение простейшего, но довольно функционального компонента View. В этот раз займёмся Front Controller, Controller и Router. Код, приведённый ниже может не запускаться, не является безопасным, но объясняет общие принципы работы большинства MVC-фреймворков.
Front Controller является диспетчером запросов и, в зависимости от URL запускает нужный контроллер с нужными параметрами. В этом ему помогает Router, занимающийся непосредственно разбором URL и применением различных правил.
Для начала необходимо перенаправить все запросы на Front Controller, т.е. index.php. Для Apache это можно сделать через файл .htaccess, расположенный в той же директории, что и index.php:
RewriteEngine on # если папка или файл реально существуют, используем их RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # если нет — отдаём всё index.php RewriteRule . index.php
Далее опишем index.php:
// подключаем необходимые файлы define('ROOT', dirname(__FILE__)); require_once(ROOT.'/../lib/Router.php'); // подключаем конфигурацию URL $routes=ROOT.'/../app/config/routes.php'; // запускаем роутер $router = new Router($routes); $router->run();
Конфигурация в routes.php будет выглядеть следующим образом:
return array( 'about' => 'page/show/about', 'page/([-_a-z0-9]+)' => 'page/show/$1', 'users/([-_a-z0-9]+)' => 'users/show/$1', );
Что должен сделать роутер?
Получить URI.
Применить первое совпавшее правило из конфигурации. Вызвать соответствующий правилу контроллер с нужными параметрами.
class Router { // Хранит конфигурацию маршрутов. private $routes; function __construct($routesPath){ // Получаем конфигурацию из файла. $this->routes = include($routesPath); } // Метод получает URI. Несколько вариантов представлены для надёжности. function getURI(){ if(!empty($_SERVER['REQUEST_URI'])) { return trim($_SERVER['REQUEST_URI'], '/'); } if(!empty($_SERVER['PATH_INFO'])) { return trim($_SERVER['PATH_INFO'], '/'); } if(!empty($_SERVER['QUERY_STRING'])) { return trim($_SERVER['QUERY_STRING'], '/'); } } function run(){ // Получаем URI. $uri = $this->getURI(); // Пытаемся применить к нему правила из конфигуации. foreach($this->routes as $pattern => $route){ // Если правило совпало. if(preg_match("~$pattern~", $uri)){ // Получаем внутренний путь из внешнего согласно правилу. $internalRoute = preg_replace("~$pattern~", $route, $uri); // Разбиваем внутренний путь на сегменты. $segments = explode('/', $internalRoute); // Первый сегмент — контроллер. $controller = ucfirst(array_shift($segments)).'Controller'; // Второй — действие. $action = 'action'.ucfirst(array_shift($segments)); // Остальные сегменты — параметры. $parameters = $segments; // Подключаем файл контроллера, если он имеется $controllerFile = ROOT.'../app/controllers/'.$controller.'.php'; if(file_exists($controllerFile)){ include($controllerFile); } // Если не загружен нужный класс контроллера или в нём нет // нужного метода — 404 if(!is_callable(array($controller, $action))){ header("HTTP/1.0 404 Not Found"); return; } // Вызываем действие контроллера с параметрами call_user_func_array(array($controller, $action), $params); } } // Ничего не применилось. 404. header("HTTP/1.0 404 Not Found"); return; } }
Базовый класс контроллера будет выглядеть примерно так:
class Controller { protected $view; function __construct(){ // используем наш View, описанный ранее $this->view = new View(); } // другие полезные методы вроде redirect($url); }
Сам контроллер:
class PageController extends Controller { // параметр отдаётся из правила 'page/([-_a-z0-9]+)' => 'page/show/$1', function actionShow($url = null){ // получаем страницу $page = $this->getPage($url); // выводим её при помощи View $this->view->render('page', array('page' => $page)); } }
Комментарии RSS по email OK
Еще бы объединить все куски в один простенький фреймворк - было бы отлично!
Поддерживаю!
aktuba, Dr.Death, Yii вам в помощь!
Угадывается неизгладимое влияние от него(Yii) на автора :-)
Sam, нет желания также, на пальцах, рассказать об устройстве ActiveFinder'а? В Yii он крайне интересен!
Yii меня только расстроил... Не понравился. Вернулся с него на CI, а теперь перехожу на кохану.
меня смущает вот что:
"Front Controller является диспетчером запросов и, в зависимости от URL запускает нужный контроллер с нужными параметрами" - но запускает и создает контроллер роутер.
мне кажется не его (Роутера) задача создавать объект контроллера - он должен распарсить правила и отдать параметры.
а уже фронтконтроллер создает необходимый контроллер.
разве не так?
Сергей
Обычно кроме роутера есть ещё несколько звеньев.
aktuba, Dr.Death
Фреймворк уже давно написан, но поддерживать его оказалось не таким простым делом. Намного приятнее пользоваться CodeIgniter или Yii и помогать их авторам.
Exel
Об устройстве подобия ActiveFinder'а как-нибудь расскажу. Но это опять же будет не Yii, а нечто попроще.
В принципе смысла статьи это не меняет, но есть ошибочка:
Фреймворк уже давно написан, но поддерживать его оказалось не таким простым делом. Намного приятнее пользоваться CodeIgniter или Yii и помогать их авторам.
это основная причина по которой я забросил когда-то свой фреймворк и перешел на Yii :)
ошибок много
Я все же думал вы реализуете как в Yii, т.е. экшены будут получать не просто массив ($parameters = $segments) с параметрами, а роутер будет распарсивать урл и вытаскивать данные в массив $_GET.
так наверно будет, чтоб без ошибок
и у меня есть подозрения что $route[0] это будет обращение к символу строки тк $route на тот момент будет содержать строку
да и хотелось бы уточнить... обычно бывает два входных файла index.php и admin.php.. всё будет автоматом уходить на index.php. Хотелось бы более подробно об этом)
Хорошо написано, поможет начинающим не просто работать с готовыми решениями, но и понимать их внутренние механизмы работы. С удовольствием бы почитал аналогичную статью о внутреннем устройстве множественного наследования в php на примере поведений в Yii и CakePHP.
Я только учусь и мне эта информация оказалась полезна, спасибо автору, не судите его строго он ведь писал наглядный пример.
Спасибо большое! Как жаль, что инфы по MVC так мало толковой в нете. Я тоже только перехожу на MVC и многие моменты для меня остаются непонятными. Пытался разобраться с зендом, но там уж очень все намудрено. Честно говоря до конца не понял, зачем нужен frontController, в данном примере все делает роутер
if(preg_match($route[0]), $uri) пишет синтаксическую ошибку ..? ...
Дену: скобка закрывающая не там стоит, сляпой чтоль... ёптить...
foreach($this->routes as $route){ надо заменить на foreach($this->routes as $route[0]=>$route[1]){
private $routes - точку с запятой добавить
} }
} вызовет ошибку
Я позволили себе подправить.
а как будет выглядеть getPage?
Да, метод со этой строчки, как выглядит ? $page = $this->getPage($url);
Anton, примерно так:
Что то не получается собрать в мини каркас, у кого есть исходный код ? Спасибо
Каким образом, как я полагаю при помощи регулярки, избавиться от лишних параметров? Что бы если я написал, site.ru/about/some/params/, выдавало ошибку?
Всем привет. А регулярка в самом начале роута не губит производительность?
Может и губит. Надо замерять...
Также интересует этот вопрос.