118

I want to add GET parameters to URLs that may and may not contain GET parameters without repeating ? or &.

Example:

If I want to add category=action

$url="http://www.acme.com";
 // will add ?category=action at the end

$url="http://www.acme.com/movies?sort=popular";
 // will add &category=action at the end

If you notice I'm trying to not repeat the question mark if it's found.

The URL is just a string.

What is a reliable way to append a specific GET parameter?

iWillGetBetter
  • 780
  • 1
  • 15
  • 35
CodeOverload
  • 47,274
  • 54
  • 131
  • 219
  • Simply use: `echo http_build_url($url, array("query" => "the=query&parts=here"), HTTP_URL_JOIN_QUERY);`. But you'll need `pecl install pecl_http` or install [jakeasmith/http_build_url](https://github.com/jakeasmith/http_build_url) via composer. – Ema4rl Jan 04 '17 at 21:24

17 Answers17

225

Basic method

$query = parse_url($url, PHP_URL_QUERY);

// Returns a string if the URL has parameters or NULL if not
if ($query) {
    $url .= '&category=1';
} else {
    $url .= '?category=1';
}

More advanced

$url = 'http://example.com/search?keyword=test&category=1&tags[]=fun&tags[]=great';

$url_parts = parse_url($url);
// If URL doesn't have a query string.
if (isset($url_parts['query'])) { // Avoid 'Undefined index: query'
    parse_str($url_parts['query'], $params);
} else {
    $params = array();
}

$params['category'] = 2;     // Overwrite if exists
$params['tags'][] = 'cool';  // Allows multiple values

// Note that this will url_encode all values
$url_parts['query'] = http_build_query($params);

// If you have pecl_http
echo http_build_url($url_parts);

// If not
echo $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $url_parts['query'];

You should put this in a function at least, if not a class.

quantme
  • 3,609
  • 4
  • 34
  • 49
andrewtweber
  • 24,520
  • 22
  • 88
  • 110
  • 2
    also dont't forget to append the value of category – Doug T. Apr 27 '11 at 20:08
  • 4
    @DougT. not all parameters need a value. For instance, `?logout` can be checked with `isset($_GET["logout"])` – rybo111 Jan 04 '14 at 12:28
  • This approach is good when you know that 'category' parameter is not already in the URL. If the parameter is in a URL already then PHP should take the value of the last occurrence of the parameter in the URL, so the solution of @andrewtweber still works. Yet, I like the following solution http://stackoverflow.com/a/4101638/99256 better. – MartyIX Jul 16 '14 at 08:49
  • -1 for not replacing a parameter if it already exists. It's not explicitly mentioned in the question, but it's logically and semantically mandatory. – Daniel W. May 11 '15 at 10:21
  • @DanFromGermany No it's not. It's possible to have the same parameter multiple times, e.g. `tag[]=cool&tag[]=fun`. – andrewtweber May 11 '15 at 23:57
  • @DanFromGermany There's exceptions to every rule, and it's not my responsibility to provide a solution that's flawless in 100% of scenarios and follows coding principles etc. I gave a basic solution which anybody should be able to take and adapt to their needs. That being said, updated my answer. – andrewtweber May 12 '15 at 03:31
  • Note that this does not work if the URL contains a hash tag (client-side URL parameter). Ex. http://www.yahoo.com#foo – Alex Weinstein Aug 06 '16 at 22:36
  • 1
    Simply use: `echo http_build_url($url, array("query" => "the=query&parts=here"), HTTP_URL_JOIN_QUERY);`. But you'll need `pecl install pecl_http` or install [jakeasmith/http_build_url](https://github.com/jakeasmith/http_build_url) via composer. – Ema4rl Jan 04 '17 at 21:23
  • The basic method allow double insert of "category" parameter, if it's already in the $_GET array. It doesn't update the value. Better is `$arr=$_GET; $arr['category']=1; $new_link=$_SERVER['PHP_SELF']."?".http_build_query($arr);`. This method is basic AND update the value.. – Daniele Rugginenti Feb 24 '22 at 03:16
74

Here's a shorter version of the accepted answer:

$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . 'category=action';

Edit: as discussed in the accepted answer, this is flawed in that it doesn't check to see if category already exists. A better solution would be to treat the $_GET for what it is - an array - and use functions like in_array().

rybo111
  • 12,240
  • 4
  • 61
  • 70
  • Note this will only work with a single added parameter, otherwise it will append a ? if there was no original query. – Mgamerz Nov 23 '14 at 05:58
  • @Mgamerz unless I'm misunderstanding you, that is what the OP asked for. It works multiple times, because of the `.` before the `=`. – rybo111 Nov 23 '14 at 09:05
  • But wont query not exist if you add a parameter to url (hence adding a ?), and if you do it again it will add another ? (Unless you're supposed to run the first line again)? How does $query get updated? – Mgamerz Nov 23 '14 at 16:41
  • @Mgamerz I see what you mean now. Yes, you would need to repeat the first line each time to check whether `$url` already has parameters. Edited. – rybo111 Nov 23 '14 at 20:30
  • Simply use: `echo http_build_url($url, array("query" => "the=query&parts=here"), HTTP_URL_JOIN_QUERY);`. But you'll need `pecl install pecl_http` or install [jakeasmith/http_build_url](https://github.com/jakeasmith/http_build_url) via composer. – Ema4rl Jan 04 '17 at 21:23
  • 4
    Doesn't handle URLs with `#`. – NateS Nov 17 '18 at 01:41
  • @NateS The server doesn't receive the `#` part of the URL. That's for the benefit of the browser. – rybo111 Nov 18 '18 at 07:53
  • @rybo111 True, but not all URL parsing is as a result of a server request. – NateS Nov 18 '18 at 12:09
  • if you have multiple parameters instead of "'category=action'" you can use: "http_build_query($parameters)" – amir May 02 '22 at 09:40
25
$data = array('foo'=>'bar',
              'baz'=>'boom',
              'cow'=>'milk',
              'php'=>'hypertext processor');

$queryString =  http_build_query($data);
//$queryString = foo=bar&baz=boom&cow=milk&php=hypertext+processor

echo 'http://domain.com?'.$queryString;
//output: http://domain.com?foo=bar&baz=boom&cow=milk&php=hypertext+processor
Tom Claus
  • 1,301
  • 1
  • 9
  • 13
  • Simply use: `echo http_build_url($url, array("query" => "the=query&parts=here"), HTTP_URL_JOIN_QUERY);`. But you'll need `pecl install pecl_http` or install [jakeasmith/http_build_url](https://github.com/jakeasmith/http_build_url) via composer. – Ema4rl Jan 04 '17 at 21:23
9

This function overwrites an existing argument

function addToURL( $key, $value, $url) {
    $info = parse_url( $url );
    parse_str( $info['query'], $query );
    return $info['scheme'] . '://' . $info['host'] . $info['path'] . '?' . http_build_query( $query ? array_merge( $query, array($key => $value ) ) : array( $key => $value ) );
}
user2171027
  • 91
  • 1
  • 1
9

Example with updating existent parameters.

Also url_encode used, and possibility to don't specify parameter value

    <?
    /**
     * Add parameter to URL
     * @param string $url
     * @param string $key
     * @param string $value
     * @return string result URL
     */
    function addToUrl($url, $key, $value = null) {
        $query = parse_url($url, PHP_URL_QUERY);
        if ($query) {
            parse_str($query, $queryParams);
            $queryParams[$key] = $value;
            $url = str_replace("?$query", '?' . http_build_query($queryParams), $url);
        } else {
            $url .= '?' . urlencode($key) . '=' . urlencode($value);
        }
        return $url;
    }
9

Use strpos to detect a ?. Since ? can only appear in the URL at the beginning of a query string, you know if its there get params already exist and you need to add params using &

function addGetParamToUrl(&$url, $varName, $value)
{
    // is there already an ?
    if (strpos($url, "?"))
    {
        $url .= "&" . $varName . "=" . $value; 
    }
    else
    {
        $url .= "?" . $varName . "=" . $value;
    }
}
Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746
Doug T.
  • 64,223
  • 27
  • 138
  • 202
  • 1
    Good and easy approach if you are concerned about performance, since this will be a lot faster than parse_url and also less cpu intensive. Tradeoff is that it is not checking for an existing $varName. – Dennis Stücken Apr 04 '18 at 12:13
  • Classic mistake is to use strpos() return in if clause directly. `$url .= (strpos($url, '?') === false ? '?' : '&').'category=action';` And performance wise would be better to use single quotes. Other than that, this solution fits the requirements better. – Sergei Kovalenko Nov 26 '20 at 10:58
6

One-liner:

$url .= (strpos($url, '?') ? '&' : '?') . http_build_query($additionalParams);

using http_build_query is recommended because it encodes special characters (for example spaces or @ in email addresses)


Improved version for 2022

This allows existing parameters to be replaced, and also preserves existing URL fragment (the part after # at the end of an URL)

function addParametersToUrl(string $url, array $newParams): string
{
    $url = parse_url($url);
    parse_str($url['query'] ?? '', $existingParams);

    $newQuery = array_merge($existingParams, $newParams);

    $newUrl = $url['scheme'] . '://' . $url['host'] . ($url['path'] ?? '');
    if ($newQuery) {
        $newUrl .= '?' . http_build_query($newQuery);
    }

    if (isset($url['fragment'])) {
        $newUrl .= '#' . $url['fragment'];
    }

    return $newUrl;
}

Testing:

$newParams = [
    'newKey' => 'newValue',
    'existingKey' => 'new',
];

echo addParametersToUrl('https://www.example.com', $newParams);
// https://www.example.com?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/', $newParams);
// https://www.example.com/dir/?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/', $newParams);
// https://www.example.com/dir/?newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar', $newParams);
// https://www.example.com/dir/file?foo=bar&newKey=newValue&existingKey=new
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar&existingKey=old', $newParams);
// https://www.example.com/dir/file?foo=bar&existingKey=new&newKey=newValue
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar&existingKey=old#hash', $newParams);
// https://www.example.com/dir/file?foo=bar&existingKey=new&newKey=newValue#hash
echo addParametersToUrl('https://www.example.com/dir/file#hash', $newParams);
// https://www.example.com/dir/file?newKey=newValue&existingKey=new#hash
echo addParametersToUrl('https://www.example.com/dir/file?foo=bar#hash', $newParams);
// https://www.example.com/dir/file?foo=bar&newKey=newValue&existingKey=new#hash
the_nuts
  • 5,634
  • 1
  • 36
  • 68
5
 /**
 * @example addParamToUrl('/path/to/?find=1', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
 * @example addParamToUrl('//example.com/path/to/?find=1', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
 * @example addParamToUrl('https://example.com/path/to/?find=1&FILTER=Y', array('find' => array('search', 2), 'FILTER' => 'STATUS'))
 *
 * @param       $url string url
 * @param array $addParams
 *
 * @return string
 */
function addParamToUrl($url, array $addParams) {
  if (!is_array($addParams)) {
    return $url;
  }

  $info = parse_url($url);

  $query = array();

  if ($info['query']) {
    parse_str($info['query'], $query);
  }

  if (!is_array($query)) {
    $query = array();
  }

  $params = array_merge($query, $addParams);

  $result = '';

  if ($info['scheme']) {
    $result .= $info['scheme'] . ':';
  }

  if ($info['host']) {
    $result .= '//' . $info['host'];
  }

  if ($info['path']) {
    $result .= $info['path'];
  }

  if ($params) {
    $result .= '?' . http_build_query($params);
  }

  return $result;
}
Londeren
  • 3,202
  • 25
  • 26
5
<?php
$url1 = '/test?a=4&b=3';
$url2 = 'www.baidu.com/test?a=4&b=3&try_count=1';
$url3 = 'http://www.baidu.com/test?a=4&b=3&try_count=2';
$url4 = '/test';
function add_or_update_params($url,$key,$value){
    $a = parse_url($url);
    $query = $a['query'] ? $a['query'] : '';
    parse_str($query,$params);
    $params[$key] = $value;
    $query = http_build_query($params);
    $result = '';
    if($a['scheme']){
        $result .= $a['scheme'] . ':';
    }
    if($a['host']){
        $result .= '//' . $a['host'];
    }
    if($a['path']){
        $result .=  $a['path'];
    }
    if($query){
        $result .=  '?' . $query;
    }
    return $result;
}
echo add_or_update_params($url1,'try_count',1);
echo "\n";
echo add_or_update_params($url2,'try_count',2);
echo "\n";
echo add_or_update_params($url3,'try_count',3);
echo "\n";
echo add_or_update_params($url4,'try_count',4);
echo "\n";
shengbin_xu
  • 128
  • 3
  • 14
  • 3
    great work, only replace the line ``$query = $a['query'] ? $a['query'] : '';`` by ``$query = isset($a['query']) ? $a['query'] : '';`` – emmgfx Apr 24 '17 at 07:53
4
$parameters = array();

foreach ($get as $key => $value)
{
     $parameters[] = $key.'='.$value;
}

$url = 'http://example.com/movies?'.implode('&', $parameters);
TaylorOtwell
  • 7,177
  • 7
  • 32
  • 42
3

I think you should do it something like this.

class myURL {
    protected $baseURL, $requestParameters;

    public function __construct ($newURL) {
        $this->baseurl = $newURL;
        $this->requestParameters = array();
    }

    public function addParameter ($parameter) {
        $this->requestParameters[] = $parameter;
    }

    public function __toString () {
        return $this->baseurl.
               ( count($this->requestParameters) ?
                 '?'.implode('&', $this->requestParameters) :
                 ''
                 );
    }
}

$url1 = new myURL ('http://www.acme.com');
$url2 = new myURL ('http://www.acme.com');
$url2->addParameter('sort=popular');
$url2->addParameter('category=action');
$url1->addParameter('category=action');

echo $url1."\n".$url2;
Hammerite
  • 21,755
  • 6
  • 70
  • 91
1

After searching for many resources/answers on this topic, I decided to code my own. Based on @TaylorOtwell's answer here, this is how I process incoming $_GET request and modify/manipulate each element.

Assuming the url is: http://domain.com/category/page.php?a=b&x=y And I want only one parameter for sorting: either ?desc=column_name or ?asc=column_name. This way, single url parameter is enough to sort and order simultaneously. So the URL will be http://domain.com/category/page.php?a=b&x=y&desc=column_name on first click of the associated table header row.

Then I have table row headings that I want to sort DESC on my first click, and ASC on the second click of the same heading. (Each first click should "ORDER BY column DESC" first) And if there is no sorting, it will sort by "date then id" by default.

You may improve it further, like you may add cleaning/filtering functions to each $_GET component but the below structure lays the foundation.

foreach ($_GET AS $KEY => $VALUE){
    if ($KEY == 'desc'){
        $SORT = $VALUE;
        $ORDER = "ORDER BY $VALUE DESC";
        $URL_ORDER = $URL_ORDER . "&asc=$VALUE";
    } elseif ($KEY == 'asc'){
        $SORT = $VALUE;
        $ORDER = "ORDER BY $VALUE ASC";
        $URL_ORDER = $URL_ORDER . "&desc=$VALUE";
    } else {
        $URL_ORDER .= "&$KEY=$VALUE";
        $URL .= "&$KEY=$VALUE";
    }
}
if (!$ORDER){$ORDER = 'ORDER BY date DESC, id DESC';}
if ($URL_ORDER){$URL_ORDER = $_SERVER[SCRIPT_URL] . '?' . trim($URL_ORDER, '&');}
if ($URL){$URL = $_SERVER[SCRIPT_URL] . '?' . trim($URL, '&');}

(You may use $_SERVER[SCRIPT_URI] for full URL beginning with http://domain.com)

Then I use resulting $ORDER I get above, in the MySQL query:

"SELECT * FROM table WHERE limiter = 'any' $ORDER";

Now the function to look at the URL if there is a previous sorting and add sorting (and ordering) parameter to URL with "?" or "&" according to the sequence:

        function sort_order ($_SORT){
            global $SORT, $URL_ORDER, $URL;
            if ($SORT == $_SORT){
                return $URL_ORDER;
            } else {
                if (strpos($URL, '?') !== false){
                    return "$URL&desc=$_SORT";
                } else {                        
                    return "$URL?desc=$_SORT";
                }
            }
        }

Finally, the table row header to use the function:

        echo "<th><a href='".sort_order('id')."'>ID</a></th>";

Summary: this will read the URL, modify each of the $_GET components and make the final URL with parameters of your choice with the correct form of usage of "?" and "&"

Tarik
  • 4,270
  • 38
  • 35
0
 public function addGetParamToUrl($url, $params)
{
    foreach ($params as $param) {
         if (strpos($url, "?"))
        {
            $url .= "&" .http_build_query($param); 
        }
        else
        {
            $url .= "?" .http_build_query($param); 
        }
    }
    return $url;
}
Maor Kavod
  • 129
  • 1
  • 9
0

another improved function version. Mix of existing answers with small improvements (port support) and bugfixes (checking keys properly).

/**
 * @param string $url original url to modify - can be relative, partial etc
 * @param array $paramsOverride associative array, can be empty
 * @return string modified url
 */
protected function overrideUrlQueryParams($url, $paramsOverride){
    if (!is_array($paramsOverride)){
        return $url;
    }

    $url_parts = parse_url($url);

    if (isset($url_parts['query'])) {
        parse_str($url_parts['query'], $params);
    } else {
        $params = [];
    }

    $params = array_merge($params, $paramsOverride);

    $res = '';

    if(isset($url_parts['scheme'])) {
        $res .= $url_parts['scheme'] . ':';
    }

    if(isset($url_parts['host'])) {
        $res .= '//' . $url_parts['host'];
    }

    if(isset($url_parts['port'])) {
        $res .= ':' . $url_parts['port'];
    }

    if (isset($url_parts['path'])) {
        $res .= $url_parts['path'];
    }

    if (count($params) > 0) {
        $res .= '?' . http_build_query($params);
    }

    return $res;
}
Roman86
  • 1,990
  • 23
  • 22
0

Try this function to add URL parameters.

Then you can disable the link when parameter is set so there is no url parameter duplicate.

<?php
  function addQueryString($a)
                {
             if (empty($_SERVER['QUERY_STRING']))
               return '?' . $a;
             else if (!empty($_SERVER['QUERY_STRING']))
              return '?' . $_SERVER['QUERY_STRING'] . '&' . $a;
                }
?>

 <a href="<?php echo addQueryString('lang=en'); ?>">test</a>
 <a href="<?php echo addQueryString('category=5'); ?>">sat</a>
csandreas1
  • 2,026
  • 1
  • 26
  • 48
0

In case you are using WordPress you can simply use

    add_query_args(['sort' => 'asc'], 'http:/example.com/?search=news')

Docs https://developer.wordpress.org/reference/functions/add_query_arg/

khandaniel
  • 306
  • 4
  • 13
0

Solution for Laravel

If you are using Laravel you can use a function off of the Request object.

// In a controller function
function foo(Request $request) {
    // will be www.laravel.test?hi=there
    $urlWithNewQueryParam = $request->fullUrlWithQuery(['hi' => 'there']);
}

// Outside of a controller function (like in a blade)
request()->fullUrlWithQuery(['hi' => 'there']); // will be www.laravel.test?hi=there

This will also correctly add & and overwrite query params with the same name if they already exist.

You can read more in the docs here https://laravel.com/docs/10.x/requests#retrieving-the-request-url.

Tom Headifen
  • 1,885
  • 1
  • 18
  • 35