Эта тема на forum.dklab.ru


Антон Макаренко: Функция QSA для URL
Функция, добавляет/заемещает/удаляет переменные из строки GET-параметров. Нечто похожее на модификатор [QSA] в Apache mod_rewrite, только для PHP.
Покритикуйте, пожалуйста.

$variables
Массив переменных => значений. Логика для каждой переменной такая:
- если значение null, то переменная удаляется из строки
- если пустое, то добавляется/заменяется именем переменной без знака "="
- если со значением, то добавляется/заменяется, а значение экранируется с помощью urlencode()

$query_string
Собственно, строка запроса, в которой проводить изменения. По умолчанию $_SERVER['QUERY_STRING']

$exclude_vars
Массив имен переменных, которых по умолчанию удалять из строки. Задается только если вызвать функцию с $exclude_vars не равным null; при этом запоминается в статическую переменную.


<?php
/**
* The same as qsarray(), but for one variable only
*
* @param string $var_name
* @param string $var_value
* @param string $query_string
* @param array $exclude_vars
* @return string
*/
function qsa($var_name, $var_value = null, $query_string = null, $exclude_vars = null)
{
return qsarray(array($var_name => $var_value), $query_string, $exclude_vars);
}

/**
* Append/replace specified variables in specified query string
*
* Possible values for $variables:
* $variables = array(
* 'var_1' => 'value_1' // will be added/replaced
* ,'var_2' => null // will be removed
* ,'var_3' => '' // will be added/replaced by empty value
* )
*
* $query_string must be something like $_SERVER['QUERY_STRING']
*
* $exclude_vars - array of variable names to be removed by default.
* If called with not null value, it will be rewritten to static variable
*
* @param array $variables
* @param string $query_string
* @param array $exclude_vars
* @return string
*/
function qsarray($variables = array(), $query_string = null, $exclude_vars = null)
{
// set default query string
if (null === $query_string)
$query_string = $_SERVER['QUERY_STRING'];

// setup variables to be excluded
static $_exclude_vars_static = array();
if (null !== $exclude_vars)
$_exclude_vars_static = $exclude_vars;

$_exclude_vars = array();
if (!empty($_exclude_vars_static))
foreach ($_exclude_vars_static as $var)
$_exclude_vars[] = $var;

// exclude specified variables, too
if (!empty($variables))
foreach ($variables as $var => $value)
$_exclude_vars[] = $var;

// parse query and exclude variables
parse_str($query_string, $parsed_query);
if (!empty($_exclude_vars))
foreach ($_exclude_vars as $var)
unset($parsed_query[$var]);

// add required variables to query
if (!empty($variables))
foreach ($variables as $var => $value)
if (null !== $value)
$parsed_query[$var] = $value;

return http_build_query($parsed_query);
}

.

Пример из жизни
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*?)-(.+)$ index.php?_url=$1&_url_suffix=$2 [NC,L,QSA]

Т.е, в скрипте могут быть переменные $_GET['_url'] и $_GET['_url_suffix']

Функцию используем для построения запросов для header('Location: ...'); в контроллере, а также для "запоминания" некоторых GET-параметров в php-шаблонах.
Чтобы избавиться от ненужных (в ЧПУ) нам _url и _url_suffix, вызываем первый раз лишь чтобы установить дефолты:

qsarray(null, null, array('_url', '_url_suffix'));

.

Для чего это все нужно...

Попробуйте уловить основную мысль, остальное придумаете ;)

При написании админских интерфейсов, бывает, что нужно сортировать таблицы-выборки по одной из колонок с переключением режима ASC/DESC

Мне надоело писать для каждой такой таблицы обработчики снова и снова. Решил унифицировать:
- выборка всегда сортируется по какой-либо колонке (ORDER BY ?d). Значение берется из $_GET['orderby']
- эта колонка сортируется ASC или DESC. ASC по умолчанию, DESC если задан $_GET['desc']
Для этого где-нибудь в начале контроллера (какого-нибудь общего инклуда для группы модулей) это и реализовываем:

// setup cross-script order by and asc/desc parameters
$order_by = 1;
if (!empty($_GET['orderby']))
$order_by = @abs($_GET['orderby']);
define('_ORDER_BY', $order_by); // ORDER BY, по умолчанию 1
define('_ASC_DESC', (isset($_GET['desc']) ? 'DESC' : 'ASC')); // ASC или DESC для SQL
define('_IS_DESC', ('ASC' == _ASC_DESC ? null : true)); // то же самое, для вставки в URL

Эта унификация сделана ради эксперимента. Как видите, функция qsa() здесь пока что ни при чем.

В контроллере при запросах используем так:

$DB->select('
SELECT
...
ORDER BY ?d ' . _ASC_DESC .'
', _ORDER_BY
);


Наконец, используем эти константы с qsa() в шаблоне php:

<!-- для сохранения параметров -->
<a href="?<?=qsarray(array('orderby' => _ORDER_BY, 'desc' => _IS_DESC))?>">...</a>

<!-- сортируем по второй колонке, DESC -->
<a href="?<?=qsarray(array('orderby' => 2, 'desc' => ''))?>">...</a>

<!-- сортируем по той же колонке, просто меняем ASC на DESC или наоборот -->
<a href="?<?=qsarray(array('orderby' => _ORDER_BY, 'desc' => (_IS_DESC ? null : '')))?>">...</a>


Q: Что это нам дает?
A: Возможность делать заголовки колонок с гиперссылками и индикатором сортировки. Т.е. по какой колонке сейчас сортировка, по по возростанию она или нет. При нажатии на ссылку-заголовок без индикатора, делаем сортировку по указанной колонке. А при нажатии на ссылку с индикатором, инвертируем направленность сортировки.
Ничего нового, кроме упрощения такой полезной и такой рутинной фичи качественного пользовательского интерфейса.

См. скриншоты

<?php
$is_desc = (_IS_DESC ? null : '');
$ob = array(
2 => array('orderby' => 2, 'desc' => $is_desc)
,1 => array('orderby' => 1, 'desc' => $is_desc)
,3 => array('orderby' => 3, 'desc' => $is_desc)
);
$desc_asc[_ORDER_BY] = (_IS_DESC ? '&#x25B2;' : '&#x25BC;');
?>
<tr>
<th class="m r"><a href="?<?=tpl_escape(qsarray($ob[2]))?>">ID</a><?=@$desc_asc[2]?></th>
<th class="m l"><a href="?<?=tpl_escape(qsarray($ob[1]))?>">Товар</a><?=@$desc_asc[1]?></th>
<th class="m r"><a href="?<?=tpl_escape(qsarray($ob[3]))?>">Цена</a><?=@$desc_asc[3]?></th>
</tr>

Рефакторинг "не отходя от кассы" (красиво, но может показаться непонятным):
<?php
$ob = array();
$not_is_desc = (_IS_DESC ? null : '');
foreach (array(2, 1, 3) as $k)
$ob[$k] = array('orderby' => $k, 'desc' => ($k == _ORDER_BY ? $not_is_desc : null));
$desc_asc[_ORDER_BY] = (_IS_DESC ? '&#x25B2;' : '&#x25BC;');
?>
<tr>
<th class="m r"><a href="?<?=tpl_escape(qsarray($ob[2]))?>">ID</a><?=@$desc_asc[2]?></th>
<th class="m l"><a href="?<?=tpl_escape(qsarray($ob[1]))?>">Товар</a><?=@$desc_asc[1]?></th>
<th class="m r"><a href="?<?=tpl_escape(qsarray($ob[3]))?>">Цена</a><?=@$desc_asc[3]?></th>
</tr>
Антон Макаренко:
Вот как бы упростить эту кучу регулярных выражений?
уже неактуально
$reg_expressions['/&' . $var . '=[^&]+/'] = '';
$reg_expressions['/&' . $var . '=/'] = '';
$reg_expressions['/&' . $var . '/'] = '';
Миша Спларов:
А не удобней-ли будет использовать массив, который получается после использования фунцкии parse_str?
Антон Макаренко:
Миша Спларов
Хорошая идея, попробую переписать с использованием parse_str() и http_build_query()
Антон Макаренко:
Обновил функцию в шапке.
Изменения:

[*:2caac4f76e] (bugfix) переменные, определенные в exclude, удаляются из запроса даже если параметры qsa пустые
[*:2caac4f76e] рег. выражения заменены стандартными parse_str() и http_build_query()
[*:2caac4f76e] (feature) в результате поддерживается многомерный массив параметров

Миша Спларов:
echo qsarray( array( 'test' => 'val', 'multi' => array( 1, 7 ) ), '?test=10&multi[]=1&multi[]=20&a=100', array( 'a' ) );
Разве это даст на выходе ?test=val&multi[]=1&multi[]=7&multi[]=20?
Проверить не могу, т.к. на доступной машине нету php5, но если так, какая поддержка многомерных массивов имеется ввиду? :-)

Кроме этого, какой смысл в двух функциях? Т.е. можно бы было писать только qsa( array( 'foo' => 'bar' ) ); - не нужно запоминать два имени и два способа передачи аргументов.
Антон Макаренко:
echo qsarray( array( 'test' => 'val', 'multi' => array( 1, 7 ) ), '?test=10&multi[]=1&multi[]=20&a=100', array( 'a' ) );
выдаст

%3Ftest=10&test=val&multi%5B0%5D=1&multi%5B1%5D=7
без url-кодирования это выглядит так:
?test=10&test=val&multi[0]=1&multi[1]=7

знак вопроса в начале строки запроса писать не следует (см. $_SERVER['QUERY_STRING']), потому что он "приклеивается" к первому параметру и получается "?test"
если убрать знак вопроса, то выдает:

test=val&multi%5B0%5D=1&multi%5B1%5D=7
т.е.
test=val&multi[0]=1&multi[1]=7


Насчет двух функций - мне так показалось логичным, если функция позиционируется как аналог QSA в Апаче.

[*:438612a9c7] QSA в Апаче использует один аргумент, о массивах речь не идет
[*:438612a9c7] qsarray() создана для того, чтоб не вызывать qsa несколько раз, например qsa('var2', 'value2', qsa('var1', 'value1'))

Вобщем, название qsarray() самодостаточно и не противоречит qsa()

Эта тема на forum.dklab.ru