Для вопроса https://toster.ru/q/321702
- Исключительно абстракция
- Не проверялось на работоспособность
Обо всех замечаниях пишите на pavel.volyntsev@copist.ru
Для вопроса https://toster.ru/q/321702
Обо всех замечаниях пишите на pavel.volyntsev@copist.ru
| <?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(); |