Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions system/HTTP/URI.php
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,56 @@ public function addQuery(string $key, $value = null)
return $this;
}

/**
* Return an instance with one query var added, replaced, or removed.
*
* Note: Method not in PSR-7
*
* @param int|string|null $value Null removes the query var.
*
* @return static
*/
public function withQueryVar(string $key, $value)
{
$uri = clone $this;

if ($value === null) {
unset($uri->query[$key]);

return $uri;
}

$uri->query[$key] = $value;

return $uri;
}

/**
* Return an instance with multiple query vars added, replaced, or removed.
*
* Note: Method not in PSR-7
*
* @param array<string, int|string|null> $params Null values remove query vars.
*
* @return static
*/
public function withQueryVars(array $params)
{
$uri = clone $this;

foreach ($params as $key => $value) {
if ($value === null) {
unset($uri->query[$key]);

continue;
}

$uri->query[$key] = $value;
}

return $uri;
}

/**
* Removes one or more query vars from the URI.
*
Expand Down
59 changes: 59 additions & 0 deletions tests/system/HTTP/URITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,65 @@ public function testAddQueryVarRespectsExistingQueryVars(): void
$this->assertSame('http://example.com/foo?bar=baz&baz=foz', (string) $uri);
}

public function testWithQueryVarAddsQueryVarWithoutMutatingOriginal(): void
{
$base = 'http://example.com/foo';
$uri = new URI($base);

$new = $uri->withQueryVar('bar', 'baz');

$this->assertSame('http://example.com/foo?bar=baz', (string) $new);
$this->assertSame('http://example.com/foo', (string) $uri);
}

public function testWithQueryVarReplacesQueryVarAndPreservesFragment(): void
{
$base = 'http://example.com/foo?bar=baz#section';
$uri = new URI($base);

$new = $uri->withQueryVar('bar', 'foz');

$this->assertSame('http://example.com/foo?bar=foz#section', (string) $new);
$this->assertSame('http://example.com/foo?bar=baz#section', (string) $uri);
}

public function testWithQueryVarRemovesQueryVarWhenValueIsNull(): void
{
$base = 'http://example.com/foo?foo=bar&bar=baz&baz=foz';
$uri = new URI($base);

$new = $uri->withQueryVar('bar', null);

$this->assertSame('http://example.com/foo?foo=bar&baz=foz', (string) $new);
$this->assertSame('http://example.com/foo?foo=bar&bar=baz&baz=foz', (string) $uri);
}

public function testWithQueryVarKeepsEmptyStringQueryVar(): void
{
$base = 'http://example.com/foo?bar=baz';
$uri = new URI($base);

$new = $uri->withQueryVar('bar', '');

$this->assertSame('http://example.com/foo?bar=', (string) $new);
$this->assertSame('http://example.com/foo?bar=baz', (string) $uri);
}

public function testWithQueryVarsAddsReplacesAndRemovesWithoutMutatingOriginal(): void
{
$base = 'http://example.com/foo?foo=bar&bar=baz&baz=foz#section';
$uri = new URI($base);

$new = $uri->withQueryVars([
'bar' => null,
'baz' => 'updated',
'new' => 'value',
]);

$this->assertSame('http://example.com/foo?foo=bar&baz=updated&new=value#section', (string) $new);
$this->assertSame('http://example.com/foo?foo=bar&bar=baz&baz=foz#section', (string) $uri);
}

public function testStripQueryVars(): void
{
$base = 'http://example.com/foo?foo=bar&bar=baz&baz=foz';
Expand Down
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.8.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ HTTP
- ``CLIRequest`` now supports options with values specified using an equals sign (e.g., ``--option=value``) in addition to the existing space-separated syntax (e.g., ``--option value``).
This provides more flexibility in how you can pass options to CLI requests.
- Added ``$enableStyleNonce`` and ``$enableScriptNonce`` options to ``Config\App`` to automatically add nonces to control whether to add nonces to style-* and script-* directives in the Content Security Policy (CSP) header when CSP is enabled. See :ref:`csp-control-nonce-generation` for details.
- Added ``URI::withQueryVar()`` and ``URI::withQueryVars()`` to return a cloned URI with query variables added, replaced, or removed.
- ``URI`` now accepts an optional boolean second parameter in the constructor, defaulting to ``false``, to control how the query string is parsed in instantiation.
This is the behavior of ``->useRawQueryString()`` brought into the constructor for convenience. Previously, you need to call ``$uri->useRawQueryString(true)->setURI($uri)`` to get this behavior.
Now you can simply do ``new URI($uri, true)``.
Expand Down
13 changes: 13 additions & 0 deletions user_guide_src/source/libraries/uri.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,19 @@ parameter is the name of the variable, and the second parameter is the value:

.. literalinclude:: uri/019.php

Changing Query Values Without Mutation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 4.8.0

You can return a new URI instance with one or more query variables changed by using the ``withQueryVar()``
and ``withQueryVars()`` methods. Existing query variables are preserved unless they are replaced or removed.
Passing ``null`` removes a query variable:

.. literalinclude:: uri/028.php

The original URI instance is not modified.

Filtering Query Values
^^^^^^^^^^^^^^^^^^^^^^

Expand Down
16 changes: 16 additions & 0 deletions user_guide_src/source/libraries/uri/028.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

$uri = new \CodeIgniter\HTTP\URI('https://example.com/users?q=bob&page=1');

$nextPage = $uri->withQueryVar('page', 2);
// https://example.com/users?q=bob&page=2

$withoutSearch = $uri->withQueryVar('q', null);
// https://example.com/users?page=1

$filtered = $uri->withQueryVars([
'q' => null,
'page' => 1,
'role' => 'admin',
]);
// https://example.com/users?page=1&role=admin
Loading