Skip to content

Instantly share code, notes, and snippets.

@pvolyntsev
Last active June 6, 2016 13:31
Show Gist options
  • Save pvolyntsev/ff31c04c00ee134a880d83a5eba1be45 to your computer and use it in GitHub Desktop.
Save pvolyntsev/ff31c04c00ee134a880d83a5eba1be45 to your computer and use it in GitHub Desktop.
Пример кода, реализующего логику гибкого роутинга HTTP запросов

Пример кода, реализующего логику гибкого роутинга HTTP запросов

Для вопроса https://toster.ru/q/321702

  • Исключительно абстракция
  • Не проверялось на работоспособность

-- [ webmentor.pro ]

  • Наставник и ментор по веб-технологиям
  • Помощь в изучении веб-технологий
  • Ревью кода Javascript Python NodeJS PHP HTML
  • Консультации по проектированию и реализации веб-проектов
  • Разработка индивидуальных планов самостоятельного изучения
  • Передача навыков удалённой работы
  • Передача навыков командной веб-разработки
<?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();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment