Знаниями нужно делится...

Маршрутизатор запросов на PHP c поддержкой ЧПУ.

Назад к списку | Просмотров: 4694

Маршрутизатор запросов на PHP c поддержкой ЧПУ.
Часто, перед разработчиком вэб приложений, встает   задача распределения  http(https) запросов. Программисты новички, на первых парах делаю для каждого запроса свой исполняемый файл, что может усложнить поддержку приложения и организацию файлов при большом их количестве. Более удобный способ для маршрутизации использование одно главного входного файла, который будет подключать нужный исполняемый файл в зависимости от запроса. Эту задачу я и попытался решить, предложив свое решение.

Любую большую задачу, можно гораздо проще решить, разложив ее на несколько простых.
1) Мы принимаем запрос в виде строки.
2) Разбираем запрос на отдельные переменные.
3) Находим действие для запроса.
3) Выполняем действие запроса.

Как известно все запросы по HTTP(HTTPS) передаются в виде строки – ключ=значение объединенные знаком “&”. Но при использовании ЧПУ (веб-адреса, удобные для восприятия человеком) встает вопрос разбора строки. Существуют разные методы решения этой задачи. Я выбрал наиболее простой и в то же время универсальный вариант.  Я разрешу  серверу принимать любые запросы от корня сайта и буду делать  разбор уже внутри программы. Для этого нужно отредактировать файл .htaccess следующим образом:

#включаем поддержку перезаписи запросов
RewriteEngine On
#базовая папка от которой будет использоваться перезапись запросов (закомментировано)
#RewriteBase /
#опеределяем файлы и папки
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
#Передаем все в исполняемый файл index
RewriteRule ^(.*)$ ./index.php
Вся строка запроса располагается в $_SERVER['REQUEST_URI']. Класс  запроса (VsRequest) будет принимать эту строку и разбирать ее на переменные согласно правилам, которые я укажу в нем.
 
class VsRequest {
   
    /**
     * Опция указывает об использовании или не использовании директивы RewriteEngine
    * От этого будет завесить поведение в методе разбора данного класса
    
     * @var boolean
     */
    public $useReWrite = false;
    
    /**
     * Знак разделения строки запроса на отдельные переменные
 * Будет использован в случаи использования опции $useReWrite TRUE
     * @var string
     */
    public $separator = '/';
    
    /**
     * Строка запроса, можно задать как опцию, по умолчанию $_SERVER['REQUEST_URI']
     * @var string
     */
    public $baseRequest;
    
    /**
     * Шаблон ключей для запроса, под названием ключа подставляется значение найденное в запросе того же порядкового номера.
     * @var array
     */
    public $templateVariable = array('language', 'action',);
    
    /**
     * Массив куда будет сохранены переменные из запроса
     * @var array
     */
    public $data = array();
    
    /**
     * Ключ под которым находится значение адресанта запроса (действия)
     * @var string
     */
    public $actionKey = 'action';
    
    /**
     * Адресант запроса (действие) по умолчанию
     * @var string
     */
    public $defaultActionName = 'main';


    /**
     * Псевдонимы для запроса, опция позволяет подменять адресанта запроса, шаблон ключей и включать дополнительные переменные.
     * Если значение представлено в виде строки, то это должна быть последовательность ключ=значение объединенные &. Эти переменные будут включены в запрос и переданы получателю.
     * Если это обычный массив строк, то он будет воспринят как   шаблон ключей переменных.
     * Если это ассоциативной массив, то в нем будет искаться ключи addQuery – дополнительные переменные, и  template – шаблон ключей переменных.
* Для примера: public $aliases = array('some_alias'=>array(
            'addQuery'=>'action=page&category=27',
            'template'=>array('id', 'sort'),
         ),
);  В случаи нахождения действия some_alias, ему будут переданные дополнительные переменные   action=page и category=27, так же изменен шаблон ключей переменных  на array('id', 'sort').
     * @var array
     */
    public $aliases = array(
        'some_alias'=>array(
            'addQuery'=>'action=page&category=27',
            'template'=>array('id', 'sort'),
         ),
        'news'=>array('id', 'sort'),
        'router'=>array('a','b','c','d'),
    );
    
    
    public function __construct($args) {
        $this->init($args);
    }


    /**
     * Инициализирует переменные объекта и значения по умолчанию
     * @param array $args
     */
    public function init($args = NULL) {
        if(is_null($args)) {
            $args = array();
        }
        $this->baseRequest = $_SERVER['REQUEST_URI'];
        
        foreach ($args as $k=>$v) {
            $this->{$k} = $v;
        }
    }
    
    /**
     * Дает возможность безопасно извлечь переменную из запроса. Если значения нет, вернет значение по умолчанию.
     * @param string $key
     * @param mixed $def
     * @return mixed
     */
    public function getValue($key, $def = null) {
        return isset($this->data[$key]) ? $this->data[$key] : $def;
    }
    
    /**
     * Дате возможность получить значение переменной отвечающей за действие в запросе (адресант запроса)
     * @return string
     */
    public function getActionValue() {
        return $this->getValue($this->actionKey, $this->defaultActionName);
    }
    
    /**
     * Функция запускает алгоритм разбора запроса
     */
    public function parse() {
        if($this->useReWrite) {
            $data = explode($this->separator, trim($this->baseRequest, $this->separator));
            if(isset($data[0])) {
                //alias
                if(isset($this->aliases[$data[0]])) {
                    $alias = $this->aliases[$data[0]];                    
                    
                    if(is_string($alias)) { // query string
                        parse_str((parse_url($alias, PHP_URL_QUERY)), $this->data);
                        unset($data[0]);
                    } else if(is_array($alias) && !isset($alias['addQuery']) && !isset($alias['template'])) {//template query
                        $this->templateVariable  = $alias;
                    } else if(isset($alias['addQuery']) && is_string($alias['addQuery'])) {
                        parse_str((parse_url($alias, PHP_URL_QUERY)), $this->data);
                        unset($data[0]);
                    } else if(isset($alias['template']) && is_array($alias['template'])) {
                        $this->templateVariable  = $alias['template'];
                    }
                }
                
                foreach($data as $index=>$value) {
                   $key = isset($this->templateVariable[$index]) ? $this->templateVariable[$index] : $index;
                   $this->data[$key] = $value;
                }
            }
        } else {          
           parse_str((parse_url($this->baseRequest, PHP_URL_QUERY)), $this->data);
        }
    }
}

У меня вышел небольшой класс, который может принимать и разбирать запросы, также он дает  гарантию получения адресанта запроса, за счет значения по умолчанию.

Теперь рассмотрим код самого адресанта запроса (action-действие) и маршрутизатора, который будет находить, и вызывать  какое либо действие.
Для начала,  приведу код маршрутизатора запроса (VsRouter).
class VsRouter {
 
    /**
     * Текущий экземпляр  класса запроса
     * @var VsRequest
     */
    public $request = null;
    
    /**
     * Опции, которые будут переданные классу VsRequest при инициализации
     * @var array
     */
    public $requestOption = array();
    
    /**
     * Текущее выполняемое действие
     * @var IVsAction
     */
    public $action = null;
    
    /**
     *  Название класса действия по умолчанию  
     * @var strng
     */
    public $defaultActionClass = 'VsBaseAction';
    
    /**
     * Список путей, где можно искать  файлы действий
     * @var array
     */
    public $paths = array(
       './actions/',  
       './',
    );


    public function __construct($args) {
        $this->init($args);
    }
    
    /**
     * Инициализирует переменные объекта и значения по умолчанию
     * @param array $args
     */
    public function init($args = NULL) {
        if(is_null($args)) {
            $args = array();
        }
        
        foreach ($args as $k=>$v) {
            $this->{$k} = $v;
        }
    }
   
    /**
     * Выполнение маршрута.  Создается экземпляр действия, поиск действия и выполнение действия.
     */
    public function run() {
        if(is_null($this->request)){
            $this->request = new VsRequest();
        }
        $this-> request->init($this->requestOption);
        $this->request->parse();        
        //поиск файла действия
//название файла соответствует названию класса по шаблону {НазваниеДействия}Action  
        if(isset($this->paths[0])) {
            $actionName = $this->request->getActionValue();
            foreach($this->paths as $path) {
                $className = $actionName.'Action';
                if(file_exists($path.$className.'.php')) {
                    require $path.$className.'.php';
                    
                    $this->defaultActionClass = $className;
                    break;
                }
            }
        }
        
        $this->action = new $this->defaultActionClass($this->request);
        $this->action->run();
    }
    
}

Реализация класса действия, может быть любой.  Он может выводить страницу, строить XML дерево или возвращать JSON. Поэтому, для предания большей гибкости, я сделал интерфейс:

interface IVsAction {
    
    function __construct(VsRequest $request);
    
    /**
     * Выполнение
     */
    function run();
}

Простая реализация интерфейса:

class VsBaseAction implements IVsAction {
    
    /**
     *
     * @var VsRequest
     */
    public $request;
    
 
    
    public function __construct(VsRequest $request) {
        $this->request = $request;
    }


    public function run() {
        echo ("VsBaseAction name [".$this->request->getActionValue()."] values [".  http_build_query($this->request->data)."]");
    }
}


В более развернутой версии этого решения можно использовать подгружаемые конфигурационные файлы с маршрутами или даже загружаемые из базы данных.  
Запускаем всю конструкцию.

$router = new VsRouter(array(
    'requestOption' => array(
        'useReWrite'=>1),
));
$router->run();

//запрос в браузере http://localhost/router/news/2008/25
//Ответ VsBaseAction name [VsBase] values [a=router&b=news&c=2008&d=25]

Как видно из результата запрос выбрал шаблон 'router'=>array('a','b','c','d'),


автор Vench дата 23/07/2013


Оставить комментарий
7 + 7 =
flaminiothEk 25.08.2014 03:20

овощи на мангале

bmwguideVat 15.09.2014 16:48

И одна корова, да жрать здорова.

mykomptaws 09.12.2014 13:23

Тут вы узнаете много полезной информации о компьютерах, их работе, железо компьютерное

Oscinami 14.01.2015 23:52

В походе. - Фу-ух... Рядовой, вы взяли что-нибудь от комаров? - Так точно, товарищ сержант! От комаров я взял всё самое лучшее - скорость, ловкость, смелость, упорство!