Any one have the ultimate PHP function(s) to add/remove parameters from a query string? It needs to handle all possible cases, Ive seen ones that handle some cases, but not
Alright I wrote my own, based on zerkms's sketch
class URL {
public static function each_get($url, $each_callback = null, $last_callback = null) {
$url = parse_url($url);
$result = '';
if (isset($url['scheme'])) $result .= $url['scheme'].'://';
if (isset($url['user'])) {
$result .= $url['user'];
if (isset($url['pass'])) $result .= ':'.$url['pass'];
$result .= '@';
}
if (isset($url['host'])) $result .= $url['host'];
if (isset($url['path'])) $result .= $url['path'];
if (!isset($url['query'])) $url['query'] = '';
$query = array();
$callable = is_callable($each_callback);
foreach (explode('&', $url['query']) as $param) {
if ($param == '') {
continue;
}
if (!$callable) {
$query[] = $param;
continue;
}
$callback_result = $each_callback($param);
if ($callback_result === true) {
$query[] = $param;
} elseif ($callback_result !== false) {
$query[] = $callback_result;
}
}
if (is_callable($last_callback)) {
$query = $last_callback($query);
}
$query = implode('&', $query);
$result .= strlen($query) ? '?'.$query : '';
if (isset($url['fragment'])) $result .= '#'.$url['fragment'];
return $result;
}
public static function add_get($url, $new_param, $new_value = null) {
return
static::each_get($url, function($param) {
$param = explode('=', $param);
if (isset($param[1])) {
return $param[0].'='.$param[1];
}
return $param[0];
}, function($query) use($new_param, $new_value) {
if ($new_value === null) {
$query[] = $new_param;
} else {
$query[] = $new_param.'='.$new_value;
}
return $query;
});
}
public static function remove_get($url, $remove_param) {
return
static::each_get($url, function($param) use($remove_param) {
$param = explode('=', $param);
return $param[0] != $remove_param;
});
}
public static function replace_get($url, $name, $value = null) {
return static::add_get(static::remove_get($url, $name), $name, $value);
}
}
And here is my limited test cases:
function test($test, $result) {
static $i;
$i++;
if ($test !== $result) {
echo $i.' Fail: got '.$test.' should be '.PHP_EOL.' '.$result.'<br>'.PHP_EOL;
} else {
echo $i.' Pass: '.$test.'<br>'.PHP_EOL;
}
}
$urls = array(
0 => 'http://user:pass@www.site.com?a=1',
1 => 'http://www.site.com?a=1',
2 => '/dir/page.php?a=1',
3 => '/dir/?a=1',
4 => '/dir?a=1',
5 => '/?a=1',
6 => '?a=1',
7 => 'http://user:pass@www.site.com?a=1#hash',
8 => 'http://www.site.com?a=1#hash',
9 => '/dir/page.php?a=1#hash',
10 => '/dir/?a=1#hash',
11 => '/dir?a=1#hash',
12 => '/?a=1#hash',
13 => '?a=1#hash',
14 => 'http://www.site.com/?a=1',
15 => 'http://www.site.com/?a=1#hash',
16 => '/dir/page.php?a=1&b=2&c=3',
);
test(URL::add_get($urls[0], 'b', 2), 'http://user:pass@www.site.com?a=1&b=2');
test(URL::add_get($urls[1], 'b', 2), 'http://www.site.com?a=1&b=2');
test(URL::add_get($urls[2], 'b', 2), '/dir/page.php?a=1&b=2');
test(URL::add_get($urls[3], 'b', 2), '/dir/?a=1&b=2');
test(URL::add_get($urls[4], 'b', 2), '/dir?a=1&b=2');
test(URL::add_get($urls[5], 'b', 2), '/?a=1&b=2');
test(URL::add_get($urls[6], 'b', 2), '?a=1&b=2');
test(URL::add_get($urls[7], 'b', 2), 'http://user:pass@www.site.com?a=1&b=2#hash');
test(URL::add_get($urls[8], 'b', 2), 'http://www.site.com?a=1&b=2#hash');
test(URL::add_get($urls[9], 'b', 2), '/dir/page.php?a=1&b=2#hash');
test(URL::add_get($urls[10], 'b'), '/dir/?a=1&b#hash');
test(URL::add_get($urls[11], 'berLongBla 1235_+'), '/dir?a=1&berLongBla 1235_+#hash');
test(URL::add_get($urls[12], 'a', 2), '/?a=1&a=2#hash');
test(URL::add_get($urls[13], 'a[]', 2), '?a=1&a[]=2#hash');
test(URL::add_get($urls[14], 'b', 2), 'http://www.site.com/?a=1&b=2');
test(URL::add_get($urls[15], 'b', 2), 'http://www.site.com/?a=1&b=2#hash');
test(URL::remove_get($urls[0], 'a'), 'http://user:pass@www.site.com');
test(URL::remove_get($urls[1], 'a'), 'http://www.site.com');
test(URL::remove_get($urls[2], 'a'), '/dir/page.php');
test(URL::remove_get($urls[3], 'a'), '/dir/');
test(URL::remove_get($urls[4], 'a'), '/dir');
test(URL::remove_get($urls[5], 'a'), '/');
test(URL::remove_get($urls[6], 'a'), '');
test(URL::remove_get($urls[16], 'b'), '/dir/page.php?a=1&c=3');
I also converted it to JavaScript/jQuery
URL = {};
URL.parse_url = function(str, component) {
var o = {
strictMode: false,
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/\/?)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // Added one optional slash to post-protocol to catch file:/// (should restrict this)
}
};
var m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
uri = {},
i = 14;
while (i--) {uri[o.key[i]] = m[i] || "";}
switch (component) {
case 'PHP_URL_SCHEME':
return uri.protocol;
case 'PHP_URL_HOST':
return uri.host;
case 'PHP_URL_PORT':
return uri.port;
case 'PHP_URL_USER':
return uri.user;
case 'PHP_URL_PASS':
return uri.password;
case 'PHP_URL_PATH':
return uri.path;
case 'PHP_URL_QUERY':
return uri.query;
case 'PHP_URL_FRAGMENT':
return uri.anchor;
default:
var retArr = {};
if (uri.protocol !== '') {retArr.scheme=uri.protocol;}
if (uri.host !== '') {retArr.host=uri.host;}
if (uri.port !== '') {retArr.port=uri.port;}
if (uri.user !== '') {retArr.user=uri.user;}
if (uri.password !== '') {retArr.pass=uri.password;}
if (uri.path !== '') {retArr.path=uri.path;}
if (uri.query !== '') {retArr.query=uri.query;}
if (uri.anchor !== '') {retArr.fragment=uri.anchor;}
return retArr;
}
}
URL.each_get = function(url, each_callback, last_callback) {
url = URL.parse_url(url);
var result = '';
if (url.scheme) result += url.scheme+'://';
if (url.user) {
result += url.user;
if (url.pass) result += ':'+url.pass;
result += '@';
}
if (url.host) result += url.host;
if (url.path) result += url.path;
if (!url.query) url.query = '';
var query = [];
$.each(url.query.split('&'), function(key, param) {
if (param == '') {
return;
}
if (!each_callback) {
query.push(param);
return;
}
var callback_result = each_callback(param);
if (callback_result === true) {
query.push(param);
} else if (callback_result !== false) {
query.push(callback_result);
}
});
if (last_callback) {
query = last_callback(query);
}
query = query.join('&');
result += query.length ? '?'+query : '';
if (url.fragment) result += '#'+url.fragment;
return result;
}
URL.add_get = function(url, new_param, new_value) {
return URL.each_get(url, function(param) {
param = param.split('=');
if (typeof param[1] != 'undefined') {
return param[0]+'='+param[1];
}
return param[0];
}, function(query) {
if (typeof new_value == 'undefined') {
query.push(new_param);
} else {
query.push(new_param+'='+new_value);
}
return query;
});
}
URL.remove_get = function(url, remove_param) {
return URL.each_get(url, function(param) {
param = param.split('=');
return param[0] != remove_param;
});
}
URL.replace_get = function(url, name, value) {
return URL.add_get(URL.remove_get(url, name), name, value);
}
var i = 0;
function test(test, result) {
i++;
if (test !== result) {
console.debug(i+' Fail: got '+test+' should be '+result);
} else {
console.debug(i+' Pass: '+test);
}
}
And the limited text cases:
var urls = {
0 : 'http://user:pass@www.site.com?a=1',
1 : 'http://www.site.com?a=1',
2 : '/dir/page.php?a=1',
3 : '/dir/?a=1',
4 : '/dir?a=1',
5 : '/?a=1',
6 : '?a=1',
7 : 'http://user:pass@www.site.com?a=1#hash',
8 : 'http://www.site.com?a=1#hash',
9 : '/dir/page.php?a=1#hash',
10 : '/dir/?a=1#hash',
11 : '/dir?a=1#hash',
12 : '/?a=1#hash',
13 : '?a=1#hash',
14 : 'http://www.site.com/?a=1',
15 : 'http://www.site.com/?a=1#hash',
16 : '/dir/page.php?a=1&b=2&c=3'
};
test(URL.add_get(urls[0], 'b', 2), 'http://user:pass@www.site.com?a=1&b=2');
test(URL.add_get(urls[1], 'b', 2), 'http://www.site.com?a=1&b=2');
test(URL.add_get(urls[2], 'b', 2), '/dir/page.php?a=1&b=2');
test(URL.add_get(urls[3], 'b', 2), '/dir/?a=1&b=2');
test(URL.add_get(urls[4], 'b', 2), '/dir?a=1&b=2');
test(URL.add_get(urls[5], 'b', 2), '/?a=1&b=2');
test(URL.add_get(urls[6], 'b', 2), '?a=1&b=2');
test(URL.add_get(urls[7], 'b', 2), 'http://user:pass@www.site.com?a=1&b=2#hash');
test(URL.add_get(urls[8], 'b', 2), 'http://www.site.com?a=1&b=2#hash');
test(URL.add_get(urls[9], 'b', 2), '/dir/page.php?a=1&b=2#hash');
test(URL.add_get(urls[10], 'b'), '/dir/?a=1&b#hash');
test(URL.add_get(urls[11], 'berLongBla 1235_+'), '/dir?a=1&berLongBla 1235_+#hash');
test(URL.add_get(urls[12], 'a', 2), '/?a=1&a=2#hash');
test(URL.add_get(urls[13], 'a[]', 2), '?a=1&a[]=2#hash');
test(URL.add_get(urls[14], 'b', 2), 'http://www.site.com/?a=1&b=2');
test(URL.add_get(urls[15], 'b', 2), 'http://www.site.com/?a=1&b=2#hash');
This is the sketch for the function you can start with:
function add_get_param($url, $param, $value)
{
$parts_url = parse_url($url);
parse_str($parts_url['query'], $parts_query);
$parts_query[$param] = $value;
return $parts_url['scheme'] . '://' . $parts_url['host'] . '/' . $parts_url['path'] . '?' . http_build_query($parts_query);
}
var_dump(add_get_param('http://mysite.com?param1=1¶m2=2', 'param3', 3));
UPD: since parse_str
breaks the data (replaces dots with underscores) I don't think this sketch is useful.
I don't know about the ultimate solution. But PHP has several very helpful native functions that making your own defined function about should be easy.
Check out: html_build_query(), parse_str(), and parse_url()
try the built in http_build_query
and parse_str
functions, they use associative arrays in the same style as $_GET
as intermediates, but seem to do what you want...