views:

112

answers:

4

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 all.

Some example cases:

It should ideally be something like:

function add_get_param($uri, $name, $value = null) { ... }
function remove_get_param($uri, $name) { ... }

Some example tests:

$var = add_get_param('http://mysite.com?param1=1&param2=2', 'param3', 3);
// http://mysite.com?param1=1&param2=2&param3=3

and:

$var = add_get_param('/dir/page.html?param1=1&param2=2#jump_to_bottom', 'param3');
// /dir/page.html?param1=1&param2=2&param3#jump_to_bottom

etc...

Alright, I wrote my own functions:

PHP: http://pastebin.org/170157 jQuery: http://pastebin.org/169981

+5  A: 

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...

tobyodavies
The problem with `http_build_query` and `parse_str` is that they modify the name/value of the parameter (remove space, converts dots to underscores etc), which is unacceptable.
Petah
maybe you should mention this in the question before you go downvoting people who actually answered the question you asked...All that aside - looks like you'll be wanting to use regexps and a loop...
tobyodavies
I said it needs to handle all possible cases. `/dir?a.b.c=123` is a possible case
Petah
+1  A: 

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&param2=2', 'param3', 3));

UPD: since parse_str breaks the data (replaces dots with underscores) I don't think this sketch is useful.

zerkms
parse_url fails on most test cases, it doesn't support relative URLs for one.
Petah
it **does** support relative urls. and you can modify my sketch to handle that. thanks for downvote.
zerkms
http://www.php.net/manual/en/function.parse-url.php "Note: This function doesn't work with relative URLs."
Petah
Have you tried? `var_dump(parse_url('dont/be?so=lazy'));`
zerkms
Alright, sorry, I take that back. Documentation fail.
Petah
+1 for being correct :)
alex
Thanks I used your sketch, and I would undo the down vote but I can't unless you edit your post.
Petah
+1  A: 

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()

Jason McCreary
The problem with `http_build_query` and `parse_str` is that they modify the name/value of the parameter (remove space, converts dots to underscores etc), which is unacceptable.
Petah
Wow, that's the last time I help you. How are you going to downvote all of us for mentioning the native PHP functions which you should be using?
Jason McCreary
+1 for that -1 that I think was undeserving.
alex
A: 

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:[email protected]?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:[email protected]?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:[email protected]?a=1&amp;b=2');
test(URL::add_get($urls[1], 'b', 2), 'http://www.site.com?a=1&amp;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:[email protected]?a=1&amp;b=2#hash');
test(URL::add_get($urls[8], 'b', 2), 'http://www.site.com?a=1&amp;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&amp;b=2');
test(URL::add_get($urls[15], 'b', 2), 'http://www.site.com/?a=1&amp;b=2#hash');

test(URL::remove_get($urls[0], 'a'), 'http://user:[email protected]');
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:[email protected]?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:[email protected]?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:[email protected]?a=1&amp;b=2');
test(URL.add_get(urls[1], 'b', 2), 'http://www.site.com?a=1&amp;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:[email protected]?a=1&amp;b=2#hash');
test(URL.add_get(urls[8], 'b', 2), 'http://www.site.com?a=1&amp;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&amp;b=2');
test(URL.add_get(urls[15], 'b', 2), 'http://www.site.com/?a=1&amp;b=2#hash');
Petah
your function produces invalid URLs, lol
Col. Shrapnel
How does it produce invalid urls?, the output should be just as invalid as the input.
Petah