Как работает маршрутизация MVC?
Итак, я начал изучать MVC (реальный MVC, а не фреймворк MVC) немного более углубленно, и я пытаюсь разработать небольшой фреймворк. Я работаю, читая другие фреймворки, такие как Symphony и Zend, наблюдая, как они выполняют свою работу, и пытаюсь реализовать ее самостоятельно.
Местом, где я застрял, была система маршрутизации URL-адресов:
<?php
namespace Application\Common;
class RouteBuilder {
public function create($name, $parameters) {
$route = new Route($name);
$route->resource = array_keys($parameters)[0];
$route->defaults = $parameters["defaults"];
$notation = $parameters["notation"];
$notation = preg_replace("/\[(.*)\]/", "(:?$1)?", $notation);
foreach ($parameters["conditions"] as $param => $condition) {
$notation = \str_replace($param, $condition, $notation);
}
$notation = preg_replace("/:([a-z]+)/i", "(?P<$1>[^/.,;?\n]+)", $notation);
//@TODO: Continue pattern replacement!!
}
}
/* How a single entry looks like
* "main": {
"notation": "/:action",
"defaults": {
"resource" : "Authentication",
},
"conditions": {
":action" : "(login)|(register)"
}
},
*/
Я просто не могу как следует разобраться в этом. Каков рабочий процесс приложения отсюда?
Шаблон сгенерирован, вероятно, объект Route
, который будет храниться под объектом Request
или что-то в этом роде, тогда что? Как это работает?
P.S. Ищу реальный, хорошо объясненный ответ здесь. Я действительно хочу понять эту тему. Я был бы признателен, если бы кто-нибудь нашел время написать по-настоящему подробный ответ.
2 answers
Класс Router
(или Dispatcher
, как некоторые его назвали бы) проверяет URL-адрес HTTP-запроса и пытается сопоставить его составляющие компоненты с конкретным Controller
и методом (т.е. действием или командой), определенным в этом контроллере. Он также передает аргументы нужному методу Controller
, если таковые имеются в URL-адресе.
Стандартный URL-адрес: Формат строки запроса
В HTTP-сервере Apache, без использования mod_rewrite, URL в HTTP-запросе вероятно, будет в формате строки запроса:
http://localhost/index.php?route=news/economics/param1/param2
Переписанный URL-адрес: Желаемый формат
URL-адрес после перезаписи веб-сервером, как правило, выглядит следующим образом:
http://localhost/news/economics/param1/param2
Без перезаписи URL-адреса вам понадобится класс, который разрешает параметр route
в жале запроса из стандартного URL-адреса. Это первое, что может сделать класс Routing
. В этом случае он разрешает param
следующим образом:
- контроллер = новостной контроллер
- метод = экономика()
- параметры: [парам1, парам2]
Если все пойдет хорошо, произойдет нечто подобное:
$controller = new NewsController();
$controller->economics([param1, param2])
Класс Router
создает экземпляр запрошенного конкретного дочернего элемента Controller
, вызывает запрошенный метод из экземпляра контроллера и передает методу контроллера его аргументы (если таковые имеются).
Теперь класс, который вы показываете, разрешает запрошенный "маршрут" к нужному контроллеру/действию. Так, например, URL-адрес ниже:
http://localhost/index.php?route=news/economics/param1/param2
... является английским URL-адресом. Отсюда и слово новости в строке запроса. Предположим, вы хотите, чтобы URL-адрес работал на голландском языке.
http://localhost/index.php?route=nieuws/economie/param1/param2
Это означало бы, что конкретный Controller
будет называться nieuwsController.php
, но его не существует. Вот где ваш пример вступает в игру: класс RouteBuilder
.
1) Ваш класс Router
должен сначала проверить, существует ли конкретный Controller
, который он может создать (используя имя, указанное в URL, плюс слово "Контроллер"). Если контроллер найден, проверьте наличие запрошенного метода (действие).
2) Если Router
не может найти и загрузить необходимый PHP во время выполнения (рекомендуется использовать автозагрузчик ) для создания экземпляра конкретного дочернего элемента Controller
, он должен затем проверить массив (обычно находящийся в другом имени класса Route
), чтобы увидеть, соответствует ли запрошенный URL , используя регулярные выражения, любому из элементов, содержащихся внутри. Основной скелет следует класс Route
.
Примечание: .*?
= Ноль или более любого символа, без захвата.
class Route
{
private $routes = array(
array(
'url' => 'nieuws/economie/.*?', // regular expression.
'controller' => 'news',
'action' => 'economie'
),
array(
'url' => 'weerbericht/locatie/.*?', // regular expression.
'controller' => 'weather',
'action' => 'location'
)
);
public function __contstruct()
{
}
public function getRoutes()
{
return $this->routes;
}
}
Зачем использовать регулярное выражение? Маловероятно, что после второго прямого слэша в URL-адресе будет выполнено надежное сопоставление данных.
/controller/method/param1/param2/...
, где param[x] может быть чем угодно!
Предупреждение: Рекомендуется изменить разделитель шаблонов регулярных выражений по умолчанию ('/'), если целевые данные содержат разделитель шаблонов (в в этом случае косая черта '/'. Почти любой недопустимый символ URL-адреса был бы отличным выбором.
Метод класса Router
будет перебирать массив Route::routes
, чтобы проверить, соответствует ли регулярное выражение целевому URL-адресу и string
значение, связанное с индексом 2-го уровня url
. Если найдено совпадение, Router
затем знает, какой конкретный Controller
создать экземпляр и последующий метод для вызова. Аргументы будут переданы методу по мере необходимости.
Всегда будьте осторожны с крайними случаями, такими как URL-адреса, представляющие следующее.
`/` // Should take you to the home page / HomeController by default
`''` // Should take you to the home page / HomeController by default
`/gibberish&^&*^&*%#&(*$%&*#` // Reject
Класс маршрутизатора из моей платформы. Код рассказывает историю:
class Router
{
const default_action = 'index';
const default_controller = 'index';
protected $request = array();
public function __construct( $url )
{
$this->SetRoute( $url ? $url : self::default_controller );
}
/*
* The magic gets transforms $router->action into $router->GetAction();
*/
public function __get( $name )
{
if( method_exists( $this, 'Get' . $name ))
return $this->{'Get' . $name}();
else
return null;
}
public function SetRoute( $route )
{
$route = rtrim( $route, '/' );
$this->request = explode( '/', $route );
}
private function GetAction()
{
if( isset( $this->request[1] ))
return $this->request[1];
else
return self::default_action;
}
private function GetParams()
{
if( count( $this->request ) > 2 )
return array_slice ( $this->request, 2 );
else
return array();
}
private function GetPost()
{
return $_SERVER['REQUEST_METHOD'] == 'POST';
}
private function GetController()
{
if( isset( $this->request[0] ))
return $this->request[0];
else
return self::default_controller;
}
private function GetRequest()
{
return $this->request;
}