ФорумПрограммированиеPHP для идиотов → Оптимизация роутера запросов

Оптимизация роутера запросов

  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 25 января 2010 г. 21:51, спустя 3 дня 6 часов 8 минут

    Это новая эволюция роутера из темы Нужен разбор URL как в WP. Тема раздулась и с последнего обновления прошло слишком много времени. Поэтому стартую новую.

    Имеем таблицу маршрутизации типа такой:

    array(
    'board.index' => '',
    'board.index.p' => 'page-{$page}.html',
    'board.new' => 'boards/new.html',
    // …
    'topic.new' => 'boards/{$bid}/new.html',
    'topic.view' => 'topics/{$tid}/',
    'topic.view.p' => 'topics/{$tid}/page-{$page}.html',
    // …
    'search.text' => 'search.php?search=text&keywords={$text}',
    'search.new' => 'search.php?action=show_recent',
    'search.user' => 'search.php?action=show_user&user_id={$userid}',

    );

    Вероятно в таблице будет до сотни строк.

    Класс Router умеет генерировать ссылки по шаблону

    Router::url('topic.view.p', array('tid'=>10, 'page'=>2))

    и наоборот находить маршрут для данного URL

    $request = Router::request(); // "причесанный" запрос
    echo 'request = "'.$request."\"\n\n";

    $route = Router::route($request); // ид. маршрута или false
    echo 'route = '.$route."\n\n";
    if ($route !== FALSE)
    {
    echo "Variables:\n";
    print_r(Router::getVars()); // переменные, если есть
    }


    Я нашел очень простой способ уменьшить число вызываемых регекспов на один запрос. Надо сравнивать входящий УРЛ с подстрокой шаблона до первой "переменной". Если постоянная часть шаблона маршрута не совпала, значит не совпадет и маршрут в-целом — незачем вызывать регексп. Таким образом, бОльшая часть сравнений делается побыстрому.
    И ещё я сделал возможным маршрутизировать запросы с GET параметрами. Порядок перечисленных параметров рояля не играет, т.к. они сортируются перед сравнением.
    Конечно используется кеш компилированных маршрутов.
    для каждого маршрута генерится такая цепочка:
    ($key, $quick, $pattern, $vars) — ид. маршрута, подстрока для быстрого сравнения, шаблон регексп, имена переменных


    Порядок разбора:

    1. Стрижом и причесываем строку запроса. Если в запросе есть get-параметры, их порядок может измениться.
    http ://mysite.ru/path/to/search.php?user_id=5&action=show_user
    =>
    search.php?action=show_user&user_id=5

    2. Перебираем скомпилированный список маршрутов, используя простое строковое сравнение, пока не наткнемся на подходящую начальную подстроку
    "search.php?action=show_user&user_id="

    3. Через preg_match делаем окончательный вывод и забираем параметры

    Вот чего я пока не сделал, а возможно стОит: отсортировать список маршрутов и искать быстрым поиском, например методом половинного деления. Тогда количество проверок упадет до ln(n).
    Update 2010-01-21: есть версия с двоичным поиском

    Архив во вложении
    ιιlllιlllι унц-унц
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:00, спустя 14 часов 8 минут 17 секунд

    А в чем прикол юзать таблицу маршрутиризации в таком виде?

    Я например юзаю так:

    Panjo::url('^/product/(?<id>[0-9]{1,5})/$', 'product');
    Спустя 42 сек.
    где product это ф-ия
    https://smappi.org/ - платформа по созданию API на все случаи жизни
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 22 января 2010 г. 9:59, спустя 1 день 21 час 59 минут

    в глазах рябит от этих регексп значков :)
    в "скомпилированном" виде маршрут как-то так и выглядит. а ключи вроде 'board.index' на самом деле и есть ссылка на функцию. то есть то же самое. разница в наглядности.

    adw0rd, твой пример работает в обе стороны? в моём примере да — две зеркальные функции url() и route()
    возможно ли у тебя работать с URL вида script?a=x&b=y , а если случится script?b=y&a=x ???
    еще в моём примере есть некоторое ускорение за счет уменьшения числа регекспов. фишка в этом!
    Спустя 287 сек.

    foreach (…)
    {

    if (substr($request, 0, $len) != $quick)
    continue;
    if (preg_match($pattern, $request, $matches))
    {

    return $key;
    }
    }
    return FALSE;
    ιιlllιlllι унц-унц
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:36, спустя 2 часа 36 минут 34 секунды

    регекспы более универсальны и не надо кучу кода городить…

    возможно ли у тебя работать с URL вида script?a=x&b=y , а если случится script?b=y&a=x ???

    это гет-данные, нафиг мне их в роутер пихать?)

    я не вижу смысла во всех твоих фишках, но видимо я просто не понимаю всей прелести… ссорь
    https://smappi.org/ - платформа по созданию API на все случаи жизни
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:48, спустя 12 минут 31 секунду

    я расскажу нафиг нужен GET. скрипты вроде поиска или вывод большой таблицы с возможными сортировками по колонками фильтрами выглядят как каша из гет-параметров. почему эти случаи я должен обрабатывать как-то иначе в обход универсального роутинга?
    другой вариант: я рефакторю (не побоюсь этого слова) старый рабочий код. одна из целей — постепенный перевод ссылок на ЧПУ. я смогу обрабатывать ссылки старого и новго вида одновременно.

    регексп более универсален чем что?

    пара 'board.index.p' => 'page-{$page}.html'
    компилируется в
    (key, quick, pattern, vars):

    Array
    (
       [0] => board.index.p
       [1] => page-
       [2] => |^page-(\d+)\.html$|
       [3] => Array
           (
               [0] => page
           )

    )

    как видишь внутри есть паттерн регекспа :)
    вначале запрос сравнивается с quick. в большинстве случаев уже на этом этапе отбрасывается неверный вариант.
    ιιlllιlllι унц-унц
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:53, спустя 4 минуты 34 секунды

    artoodetoo, ты предлагаешь этот вариант использовать или что? не понял немного :) или проблема какая то возникла?
    Спустя 100 сек.
    class Router
    {
    private static
    $_routes,
    $_items,
    $_vars;

    public static function request()
    {
    $tmp = parse_url(urldecode('http://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']));
    $request = substr($tmp['path'], strlen(Qb::$config['prefix']));
    // Sort query parameters if any
    if (isset($tmp['query']))
    {
    $tmp = explode('&', $tmp['query']);
    sort($tmp);
    $request .= '?' . implode('&', $tmp);
    }
    return $request;
    }

    public static function route($request)
    {
    self::_prepare();

    self::$_vars = array();
    foreach (self::$_items as $item)
    {
    list($key, $quick, $pattern, $vars) = $item;
    $len = strlen($quick);

    if (substr($request, 0, $len) != $quick)
    continue;
    if (preg_match($pattern, $request, $matches))
    {
    for ($i = 1, $n = count($matches); $i < $n; ++$i)
    self::$_vars[$vars[$i-1]] = $matches[$i];
    return $key;
    }
    }

    return FALSE;
    }

    public static function getVars()
    {
    return self::$_vars;
    }

    public static function url($key, $vars = NULL)
    {
    self::_prepare();

    if (is_null($vars))
    return Qb::$config['baseUrl'].self::$_routes[$key];
    else
    {
    $url = self::$_routes[$key];
    foreach ($vars as $id => $value)
    $url = str_replace('{$'.$id.'}', $value, $url);
    return Qb::$config['baseUrl'].$url;
    }
    }

    private static function _prepare()
    {
    if (!is_null(self::$_routes))
    return;

    $raw = Qb::$configDir.'routes.php';
    $compiled = Qb::$config['cacheDir'].'routes.dat';

    if (file_exists($compiled) && filemtime($compiled) > filemtime($raw))
    {
    list(self::$_routes, self::$_items) = unserialize(file_get_contents($compiled));
    return;
    }

    self::$_routes = include($raw);
    self::$_items = array();
    foreach (self::$_routes as $key => &$route)
    {
    // Sort query parameters if any
    if (($p = strpos($route, '?')) !== FALSE)
    {
    $tmp = explode('&', substr($route, $p+1));
    sort($tmp);
    $route = substr($route, 0, $p+1) . implode('&', $tmp);
    }

    // Has the route string any parameter?
    if (($p = strpos($route, '{$')) === FALSE)
    {
    // No, it hasn't
    $quick = $route;
    $pattern = '|^'.preg_quote($route, '|').'$|';
    $vars = NULL;
    }
    else
    {
    // Yes, it has. Save heading substring
    $quick = substr($route, 0, $p);
    $pattern = '';
    $vars = array();
    // Process variable parts
    foreach (preg_split('|({\$.+})|U', $route, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY) as $part)
    {
    if (substr($part, 0, 2) == '{$')
    {
    $vars[] = $var = substr($part, 2, -1);
    if ($var == 'page' || substr($var, -2) == 'id')
    $pattern .= '(\d+)';
    else
    $pattern .= '(.+)';
    }
    else
    $pattern .= preg_quote($part, '|');
    }
    $pattern = '|^'.$pattern.'$|';
    }
    self::$_items[] = array($key, $quick, $pattern, $vars);
    } // foreach routes

    file_put_contents($compiled, serialize(array(self::$_routes, self::$_items)));
    }

    }

    сделаю это за тебя, мы же роутер обсуждаем :)
    Сапожник без сапог
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:54, спустя 1 минуту 25 секунд

    У меня роутер занимается только обработкой урлов, а гет/пост параметры это другая сущность. Поэтому я не вижу смысла обрабатывать гет в роутере.
    https://smappi.org/ - платформа по созданию API на все случаи жизни
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:54, спустя

    никаких проблемм. предлагается к обсуждению и тестированию
    ιιlllιlllι унц-унц
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:55, спустя 53 секунды

    регексп более универсален чем что?
    да
    https://smappi.org/ - платформа по созданию API на все случаи жизни
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 12:55, спустя 24 секунды

    ох ебать он большой :)
    Сапожник без сапог
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 13:01, спустя 5 минут 15 секунд


    У меня роутер занимается только обработкой урлов, а гет/пост параметры это другая сущность. Поэтому я не вижу смысла обрабатывать гет в роутере.

    ну это просто другая точка зрения. формально гет-параметры это часть адреса и есть масса примеров когда тот же search ищет абсолютно разные сущности в зависимости от параметров. то есть ему какбы соответствуют разные контроллеры.

    на самом деле никто не заставляет. вопросик МОЖНО использовать с этим роутером, а можно НЕ использовать.
    кто-нибудь выскажитесь про мой способ избежания регекспов, пожалуйста.
    ιιlllιlllι унц-унц
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 13:01, спустя 23 секунды

    ахуенный роутер такто мы тогда придумали. тут портал какой то хотят предложить делать, если чо, там заюзаю)
    Сапожник без сапог
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 13:04, спустя 2 минуты 49 секунд

    большой, но бОльшая часть кода относится к prepare кеша. так что не страшно
    ιιlllιlllι унц-унц
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 13:07, спустя 3 минуты 15 секунд

                                            if (substr($part, 0, 2) == '{$')
    {
    $vars[] = $var = substr($part, 2, -1);
    if ($var == 'page' || substr($var, -2) == 'id')
    $pattern .= '(\d+)';
    else
    $pattern .= '(.+)';
    }
    else
    $pattern .= preg_quote($part, '|');
    судя по этому маски сейчас не подобавляешь?)
    Сапожник без сапог
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 20 января 2010 г. 13:07, спустя 18 секунд

    artoodetoo, твой роутер разруливат тип данных? То есть например есть урл:
    /post/213/

    у меня разруливается так:
    url('/post/(\d{2,4})/', 'post')
    https://smappi.org/ - платформа по созданию API на все случаи жизни

Пожалуйста, авторизуйтесь, чтобы написать комментарий!