Для вопроса https://toster.ru/q/321702
- Исключительно абстракция
- Не проверялось на работоспособность
Обо всех замечаниях пишите на [email protected]
Для вопроса https://toster.ru/q/321702
Обо всех замечаниях пишите на [email protected]
<?php | |
/** | |
* Пример набора классов для демонстрации роутинга с помощью набора роутеров | |
*/ | |
/** | |
* Статический роутер, умеет проверять URI на полное совпадение с одним из элементов списка путей | |
*/ | |
class StaticRouter implements RouterInterface | |
{ | |
protected $routes; | |
public function __construct($routes) | |
{ | |
$this->routes = $routes; | |
} | |
/** | |
* Выполняет разбор параметров HTTP запроса | |
* @param HttpRequest $request | |
* @return mixed | |
*/ | |
public function route($request) | |
{ | |
$uri = $request->getRequestUri(); | |
foreach($this->routes as $route => $rule) | |
{ | |
if ($route===$uri) // сравнение на полное совпадение, без регулярок | |
return $rule; | |
} | |
return false; | |
} | |
} | |
/** | |
* Пример модуля | |
*/ | |
class ModuleUser extends Module | |
{ | |
} | |
/** | |
* Пример модуля | |
*/ | |
class ModuleBlog extends Module | |
{ | |
} | |
/** | |
* Пример роутера для модуля ModuleBlog | |
*/ | |
class ModuleBlogRouter | |
{ | |
public function route(HttpRequest $request) | |
{ | |
$uri = $request->getRequestUri(); | |
/* Выполнить что-то типа | |
SELECT `blog_post_id` FROM `blog_post` where `slug` = :uri | |
Если нашлось | |
return [ | |
'controller' => 'ModuleBlogController', | |
'action' => 'view', | |
'id' => blog_post_id из найденной записи | |
] | |
*/ | |
return false; | |
} | |
} |
<?php | |
/** | |
* Базовая логика | |
*/ | |
/** | |
* Класс исключений при обработке HTTP | |
*/ | |
class HttpException extends Exception {} | |
/** | |
* Класс исключений при обработке роутинга - для перехвата ситуаций "Страница/объект не найдены) | |
*/ | |
class NotFoundException extends Exception {} | |
/** | |
* Класс обработки входящих запросов | |
*/ | |
class HttpRequest | |
{ | |
protected $_requestUri; | |
public function getRequestUri() | |
{ | |
if($this->_requestUri===null) | |
{ | |
if(isset($_SERVER['HTTP_X_REWRITE_URL'])) // IIS | |
$this->_requestUri=$_SERVER['HTTP_X_REWRITE_URL']; | |
elseif(isset($_SERVER['REQUEST_URI'])) | |
{ | |
$this->_requestUri=$_SERVER['REQUEST_URI']; | |
if(!empty($_SERVER['HTTP_HOST'])) | |
{ | |
if(strpos($this->_requestUri,$_SERVER['HTTP_HOST'])!==false) | |
$this->_requestUri=preg_replace('/^\w+:\/\/[^\/]+/','',$this->_requestUri); | |
} | |
else | |
$this->_requestUri=preg_replace('/^(http|https):\/\/[^\/]+/i','',$this->_requestUri); | |
} | |
elseif(isset($_SERVER['ORIG_PATH_INFO'])) // IIS 5.0 CGI | |
{ | |
$this->_requestUri=$_SERVER['ORIG_PATH_INFO']; | |
if(!empty($_SERVER['QUERY_STRING'])) | |
$this->_requestUri.='?'.$_SERVER['QUERY_STRING']; | |
} | |
else | |
throw new HttpException('HttpRequest is unable to determine the request URI.'); | |
} | |
return $this->_requestUri; | |
} | |
} | |
/** | |
* Класс обработки ответов | |
*/ | |
class HttpResponse { | |
public function send($headers, $content) | |
{ | |
} | |
} | |
/** | |
* Интерфейс для классов, реализующих сопоставление параметров HTTP запроса и логики | |
*/ | |
interface RouterInterface { | |
/** | |
* Выполняет разбор параметров HTTP запроса | |
* Возвращает false если не смог найти подходящее правило | |
* @param HttpRequest $request | |
* @return mixed|false | |
*/ | |
public function route($request); | |
} | |
/** | |
* Класс для блоков-компонент | |
*/ | |
class Component | |
{ | |
/** | |
* @var WebApplication | |
*/ | |
protected $app; | |
protected $config; | |
public function __construct(WebApplication $app, $config = []) | |
{ | |
$this->app = $app; | |
$this->config = $config; | |
} | |
public function init() | |
{ | |
} | |
} | |
/** | |
* Класс компонент типа Модуль | |
*/ | |
abstract class Module extends Component | |
{ | |
} | |
/** | |
* Класс компонент типа Диспетчер правил роутинга | |
*/ | |
abstract class Dispatcher extends Component | |
{ | |
/** | |
* Метод должен извлечь из $route информацию об исполнимом коде, который должен в итоге сгенерировать ответ на HTTP запрос | |
* @param $route | |
* @return mixed | |
*/ | |
abstract public function dispatch($route); /** тут расписывать не буду */ | |
} | |
/** | |
* Класс приложений типа "Веб-приложение" | |
*/ | |
class WebApplication | |
{ | |
protected $request; | |
protected $response; | |
protected $config; | |
public function __construct($config = []) | |
{ | |
$this->request = new HttpRequest; | |
$this->response = new HttpResponse; | |
$this->config = $config; | |
} | |
public function init() | |
{ | |
// инициализация модулей | |
if (isset($this->config['module']) && is_array($this->config['module'])) | |
{ | |
foreach($this->config['module'] as $config) | |
$module = $this->getComponent($config); // при этом неявно вызывается module->init; | |
} | |
} | |
public function run() | |
{ | |
// поиск роута с помощью списка роутеров | |
if (!isset($this->config['router'])) | |
throw new NotFoundException('WebApplication is unable to find code to run'); | |
$routers = $this->config['router']; | |
if (is_string($routers)) | |
$routers = [ $routers ]; | |
foreach($routers as $config) | |
{ | |
if (null!== ($router = $this->getComponent($config))) | |
{ | |
if ($router instanceof RouterInterface || method_exists($router, 'route')) | |
{ | |
$route = $router->route($this->request); | |
if (false!==$route) | |
{ | |
/* Этот блок кода не реализован */ | |
/* | |
$dispatcherConfig = isset($this->config['dispatcher']) ? $this->config['dispatcher'] : []; | |
if (null!== ($dispatcher = $this->getComponent($dispatcherConfig))) | |
{ | |
$dispatcher->dispatch($route); | |
} | |
*/ | |
/* по окончении блока кода надо завершить цикл поиска роутеров */ | |
return; | |
} | |
} | |
} | |
} | |
/* Значит либо нет ни одного роутера, либо ни один из них не смог распознать запрос */ | |
throw new NotFoundException('WebApplication is unable to find code to run'); | |
} | |
/** | |
* Регистрация дополнительного роутера | |
* @param $config | |
*/ | |
public function registerRouter($config) | |
{ | |
if (!isset($this->config['router'])) | |
$this->config['router'] = []; | |
$this->config['router'][] = $config; | |
} | |
/** | |
* Инициализация класса, реализующего логику компонент | |
* @param $config | |
* @return null | |
*/ | |
protected function getComponent($config) | |
{ | |
$className = null; | |
if (is_string($config)) | |
{ | |
$className = $config; | |
$config = []; | |
} elseif(is_array($config)) | |
{ | |
if (isset($config['class'])) | |
{ | |
$className = $config['class']; | |
unset($config['class']); | |
} | |
} | |
if(!is_null($className)) | |
{ | |
$component = new $className($this, $config); | |
if ($component instanceof Component || method_exists($component, 'init')) | |
$component->init(); | |
return $component; | |
} | |
return null; | |
} | |
} |
<?php | |
/** | |
* Стартовый файл тестового приложения | |
*/ | |
/* Пример конфигурации */ | |
$config = [ | |
'module' => [ // список модулей | |
'ModuleUser', // или имя класса модуля | |
[ // или массив из имени класса и настроек | |
'class' => 'ModuleBlog', | |
'router' => [ // у этого модуля свой роутер | |
'class' => 'ModuleBlogRouter', | |
] | |
], | |
], | |
'router' => [ // список роутеров | |
'HomeRouter',// или имя класса роутера | |
[ // или массив из имени класса и настроек | |
'class' => 'StaticRouter', | |
'routes' => [ | |
// трудно придумать правила роутинга - это абстрактный список правил | |
'/about' => ['controller' => 'HomeController', 'action' => 'about'], | |
'/hello' => function(HttpRequest $request, HttpResponse $response){ $response->send("text/plain", "Hello, World"); } | |
'/denied' => 403, | |
'/bye' => [ 200, 'text/html', 'path/to/file.html' ], | |
], | |
], | |
], | |
]; | |
require('core.php'); | |
require('application.php'); | |
/* запуск приложения */ | |
$app = new WebApplication($config); | |
$app->init(); | |
$app->run(); |