Минимум: Роутинг

619
.
Задача:
Роутинг с иерархической структурой.

Решение:
Для максимальной гибкости, решил отказаться от декларативной реализации, остановившись на рекурсивной спецификации. Термин сложный, но если понять, то получите очень мощный инструмент.

Функция:
function route(){                                                                                                                                      
  $rules = func_get_args();                                                        
                                                                                   
  return function() use($rules){                                                   
    $args = func_get_args();                                                       
                                                                                   
    foreach($rules as $rule){                                                      
      if(is_callable($rule)){                                                      
        $rule = call_user_func_array($rule, $args);                                
      }                                                                            
                                                                                   
      if($rule === true){                                                          
        continue;                                                                  
      }                                                                            
      elseif($rule === false){                                                     
        return true;                                                               
      }                                                                            
                                                                                   
      return $rule;                                                                
    }                                                                              
  };                                                                               
}


Используется так:
$router = route(                                                                
  // Обработка запроса вида: /article/view
  route(                                                                        
    preg_match('~^/article/view~', $_SERVER['REQUEST_URI']) == 1,                                 
    function(){                                                                 
      return 'Просмотр статьи';                                                    
    }                                                                           
  ),                                                                            
  route(                                                                        
    preg_match('~^/article~', $_SERVER['REQUEST_URI']) == 1,                                      
    // Обработка запроса вида: POST /article    
    route(                                                                      
      $_SERVER['REQUEST_METHOD'] == 'POST',                                                        
      function(){                                                               
        return 'Создание статьи';                                                
      }                                                                         
    ),                                                                          
    // Обработка запроса вида: GET /article    
    route(                                                                      
      $_SERVER['REQUEST_METHOD'] == 'GET',                                                         
      function(){                                                               
        return 'Список статей';                                                  
      }                                                                         
    )                                                                           
  )                                                                             
);                                                                              
                                                                                
echo $router(); // Выполнение роутинга
.
С помощью этой же функции можно выполнять роутинг и CLI приложения:
$router = route(
  php_sapi_name() === 'cli-server',
  ... // Обработчик
);
.
Чтоб было немного понятнее, как это чудо работает, разберу следующее. К примеру нам надо обработать следующие роуты:
/article - список статей
/article/view - статья
POST /article - создать статью
PUT /article/id - редактировать статью
DELETE /article/id - удалить статью

В первую очередь важно понять, что функция route это "фабрика", а не процедура. Она использует ваши правила роутинга и создает роутер, который эти правила применяет:
$router = route(
  ... // Правила роутинга
);

// Выполнение роутинга
$router(
  ... // Все параметры роутера передаются напрямую в правила роутинга
);


Второе важное замечание, правила роутинга делятся на "условия" и "контроллеры". Условия определяют, в верном ли направлении движется роутинг, а контроллеры обрабатывают запрос:
$router = route(
  route(
    preg_match('~^/article/view~', $path) == 1, // Условие
    function(){  // Контроллер                                                               
      return 'Статья';                                                    
    }
  ),
  ... // Следующие варианты роутинга
);

В качестве условия может выступать функция, а не логическое выражение, что делает решение еще более гибким:
$router = route(
  route(
    function(){
      return preg_match('~^/article/view~', $path) == 1; // Условие
    },
    function(){  // Контроллер                                                               
      return 'Статья';                                                    
    }
  ),
  ... // Следующие варианты роутинга
);

Вложенные условия позволяют разделить роутер:
$router = route(
  ... // Роутинг статьи
  // Следующий вариант роутинга
  route(                                                                        
    preg_match('~^/article~', $_SERVER['REQUEST_URI']) == 1,                                      
    // Вложенный роутинг  
    route(                                                                      
      $_SERVER['REQUEST_METHOD'] == 'POST',                                                        
      function(){                                                               
        return 'Создание статьи';                                                
      }                                                                         
    ),                                                                             
    route(                                                                      
      $_SERVER['REQUEST_METHOD'] == 'GET',                                                         
      function(){                                                               
        return 'Список статей';                                                  
      }                                                                         
    ),
    ...                                                                           
  )                                                                             
);
.
Плохо что регулярные выражения не лениво исполняются. Будет очень медленно работать при паре-тройке десятков маршрутов.
.
Delphinum
L!MP, условия можно обернуть функцией, тогда выполняться будет лениво:
$router = route(                                                                
  route(                                                                        
    function(){
      return preg_match('~^/article/view~', $path) == 1;                                 
    },
    function(){                                                                 
      return 'View article';                                                    
    }                                                                           
  ),
  ...
);
.
╭∩╮ (`-`) ╭∩╮
Глянь сюда: http://usman.it/php-router-140 ... ters/
Там как раз экспериментируют с минимально возможным по размеру роутером.
.
AlkatraZ, а роутинговать по методам запроса (GET, POST, PUT, DELETE), я так понимаю, оно не умеет?

Я полностью переехал на zend-expressive с fast-router в качестве роутера и микросервисы.
.
╭∩╮ (`-`) ╭∩╮
# Delphinum (15.05.2017 / 00:17)
Я полностью переехал на zend-expressive с fast-router в качестве роутера и микросервисы.
Это правильно, я в mobicms давно с Expressive работаю.
Правда fastroute не использую, он немного дремучий, хоть и быстрый.
Раз уж PSR-7, то тогда новый Aura.router 3 который как раз базируется на PSR-7.
---
А вот в mobicms-classic нужна максимальная простота и понятность (сам знаешь нашу аудиторию), посему туда нашел простейшее, но очень интересное решение.
.
# AlkatraZ (15.05.2017 / 00:39)
посему туда нашел простейшее, но очень интересное решение.
а именно?
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
Добавлено: 16.05.2017 / 14:55
# Delphinum (15.05.2017 / 01:17)
а именно?
Ну тут, чтоб меня правильно поняли, надо будет описать всю предысторию событий.
В принципе, статья в тему, потому напишу...
---
В архивном репозитории mobiCMS и на сайте в качестве роутера стоит "самодельный велосипед с квадратными колесами". Это писалось много лет назад как эксперимент. Хотя и на тот момент были некоторые годные разработки в нужной области, но ты сам прекрасно знаешь правило: пока сам не построишь СВОЙ велосипед, ты до конца, "изнутри" полностью не поймешь что и как и не будешь точно знать "а что же мне реально нужно?".

Вот я и шел тем путем... Что точно знал - применял готовые решения. В чем были сомнения - проходил путь с нуля, писал свое. Результат можно было видеть в JohnCMS 7: DI контейнер на основе zend-servicemanager и конфигов, мультиязычность основанная на Gettext явились плодом теоретических изысканий в mobiCMS. Пока САМ не пройдешь весь путь, не поймешь "а действительно ли это нужно" и "нужно ли именно это?".
---
Все, вступление написал, дальше будет действительно про Роутер гг

Добавлено: 16.05.2017 / 15:06
Ну а теперь про Роутер
---
Каждый проект имеет свою аудиторию, иначе это будет "самоделка одного человека". Со временем подключаются другие разработчики и это является переломным моментом любого открытого (к чему я всегда стремился) проекта. Он переходит от стадии "одиночки" в раздел "самоподдерживающегося". Иными словами: если уйдет изначальный разработчик, проект не заглохнет, его продолжат другие.
И это хорошо

Исходя из вышесказанного и оглядываясь на аудиторию проекта, с "чувством глубокого удовлетворения" отмечаю, что мы занимаем нишу проекта для начинающих, кодящих со смартфона и т.д. Короче, главным козырем является низкий порог вхождения в проект. Не надо быть дипломированным кодером, чтоб разобраться в сути и написать что-то свое. Достаточно быть любителем и иметь небольшой уровень начальных знаний в области РНР.

Ну вот, наконец про роутер...
Запилил бы я Fastroute, или еще глубже Aura Router, как много человек из аудитории разобралось бы в сути? Но без роутера тоже нельзя было, посему и кинулся писать свой вариант, который позволил бы сохранить процедурный подход в модулях, но при этом давал бы возможности полноценных роутеров.
Всего: 10