20 KiB
URI Helpers
This page covers this package's Psr\Http\Message\UriInterface implementation
and URI helper classes for classifying, composing, resolving, normalizing,
comparing, and safely modifying URIs.
Aside from the standard Psr\Http\Message\UriInterface implementation provided
by the GuzzleHttp\Psr7\Uri class, this library also provides additional static
methods for working with URIs.
URI Types
An instance of Psr\Http\Message\UriInterface can either be an absolute URI or
a relative reference. An absolute URI has a scheme. A relative reference is used
to express a URI relative to another URI, the base URI. Relative references can
be divided into several forms according to
RFC 3986 Section 4.2:
- network-path references, e.g.
//example.com/path - absolute-path references, e.g.
/path - relative-path references, e.g.
subpath
The following methods can be used to identify the type of the URI.
GuzzleHttp\Psr7\Uri::isAbsolute
public static function isAbsolute(UriInterface $uri): bool
Whether the URI is absolute, i.e. it has a scheme.
GuzzleHttp\Psr7\Uri::isNetworkPathReference
public static function isNetworkPathReference(UriInterface $uri): bool
Whether the URI is a network-path reference. A relative reference that begins with two slash characters is termed a network-path reference.
GuzzleHttp\Psr7\Uri::isAbsolutePathReference
public static function isAbsolutePathReference(UriInterface $uri): bool
Whether the URI is an absolute-path reference. A relative reference that begins with a single slash character is termed an absolute-path reference.
GuzzleHttp\Psr7\Uri::isRelativePathReference
public static function isRelativePathReference(UriInterface $uri): bool
Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is termed a relative-path reference.
GuzzleHttp\Psr7\Uri::isSameDocumentReference
public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool
Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its fragment component, identical to the base URI. When no base URI is given, only an empty URI reference (apart from its fragment) is considered a same-document reference.
URI Syntax Validation
GuzzleHttp\Psr7\Rfc3986 provides static methods for validating and
canonicalizing individual URI components against the grammar defined by
RFC 3986. They operate on raw
component strings rather than on Psr\Http\Message\UriInterface instances.
GuzzleHttp\Psr7\Rfc3986::isValidScheme
public static function isValidScheme(string $scheme): bool
Whether the string is a valid URI scheme. Per
RFC 3986 Section 3.1,
a scheme must start with a letter, followed by any number of letters, digits,
+, -, or .. The empty string is also accepted, since a URI reference may
omit the scheme.
GuzzleHttp\Psr7\Rfc3986::isValidHost
public static function isValidHost(string $host): bool
Whether the string is a valid URI host. Per
RFC 3986 Section 3.2.2,
the host is an IP-literal, IPv4 address, or registered name. An empty host is
accepted, since the authority, and thus the host, may be empty. Bracketed values
are validated as IPv6 or IPvFuture literals; any other value is rejected if it
contains control characters, whitespace, an authority or path delimiter (/,
?, #, @, \), or an embedded colon denoting a port. Percent-encoding is
validated the same way: malformed sequences (a % not followed by two hex
digits) and percent-encoded octets that decode to one of the rejected bytes, to
a bracket ([, ]), or to % itself are invalid, while all other
percent-encoded octets are accepted. Rejecting these percent-encoded octets is a
deliberate guzzle host policy, stricter than the RFC 3986 reg-name grammar,
which permits any well-formed pct-encoded octet; it matches the stricter
policy used throughout the library. RFC 6874 IPv6 zone identifiers (for example
[fe80::1%25eth0]) are not supported.
Registered names are otherwise intentionally permissive: single-label hosts such
as localhost, underscores, sub-delims, and raw or percent-encoded non-ASCII
(IDN) data are accepted and preserved as given, with no punycode conversion.
IDNA is treated as a client concern. Consumers that need DNS IDNs must perform
the conversion themselves, for example via Guzzle's idn_conversion request
option.
GuzzleHttp\Psr7\Rfc3986::isValidPort
public static function isValidPort(string $port): bool
Whether the string is a valid port number. RFC 3986 defines the port as
*DIGIT, which also permits an empty port and has no upper bound; this applies
the stricter policy used throughout the library instead, accepting a non-empty
run of digits (leading zeros are accepted and normalized) that resolves to a
value in the range 0-65535.
GuzzleHttp\Psr7\Rfc3986::canonicalizeIpv6
public static function canonicalizeIpv6(string $address): string
Returns the RFC 5952
canonical form of a valid IPv6 address. The address must be a valid textual
IPv6 address without brackets and without a zone identifier, such as the
inside of an IP-literal accepted by isValidHost(). Canonicalization
lowercases the hexadecimal fields, suppresses leading zeros, and collapses the
longest run of two or more zero fields (the leftmost on a tie) with ::.
Embedded dotted-decimal notation follows the rendering policy of BIND-derived
inet_ntop() implementations and curl: exactly the IPv4-mapped
(::ffff:0:0/96) and deprecated IPv4-compatible (::/96) layouts use it,
while other embedded-IPv4 forms, including translated (NAT64) well-known
prefixes such as 64:ff9b::/96 (RFC 6052), serialize in pure hexadecimal
fields. An \InvalidArgumentException is thrown if the address cannot be
parsed.
Validation is strict and platform-independent: the address is checked against
the RFC 3986 IPv6address grammar with PHP's FILTER_VALIDATE_IP filter and
parsed in pure PHP, so spellings that only some platform parsers accept, such as
the zero-padded dotted octets in ::ffff:192.168.001.001, are rejected
everywhere.
Emitting dotted-decimal notation for these two selected layouts is the
BIND-derived inet_ntop() and curl compatibility policy used here.
RFC 5952 Section 5
permits mixed notation for recognizable embedded-IPv4 prefixes but does not
limit that category to these layouts; RFC 6052, for example, defines
64:ff9b::/96 as a Well-Known Prefix, which this implementation renders in
pure hexadecimal. The WHATWG URL Standard always emits pure hexadecimal
fields.
URI Components
Additional methods to work with URI components.
GuzzleHttp\Psr7\Uri::isDefaultPort
public static function isDefaultPort(UriInterface $uri): bool
Whether the URI has the default port of the current scheme.
Psr\Http\Message\UriInterface::getPort may return null or the standard port.
This method can be used independently of the implementation.
GuzzleHttp\Psr7\Uri::composeComponents
public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string
Composes a URI reference string from its various components according to
RFC 3986 Section 5.3.
Usually this method does not need to be called manually but instead is used
indirectly via Psr\Http\Message\UriInterface::__toString.
PSR-7 UriInterface treats an empty component the same as a missing component as
getQuery(), getFragment() etc. always return a string. This explains the
slight difference to RFC 3986 Section 5.3.
Another adjustment is that the authority separator is added even when the
authority is missing/empty for the "file" scheme. This is because PHP stream
functions like file_get_contents only work with file:///myfile but not with
file:/myfile although they are equivalent according to RFC 3986. But
file:/// is the more common syntax for the file scheme anyway (Chrome for
example redirects to that format). The separator is omitted when such a URI has
a rootless or empty path: adding it would turn the first path segment into the
authority of the composed URI, or compose the string file://, which cannot be
parsed back into a URI.
GuzzleHttp\Psr7\Uri::fromParts
public static function fromParts(array $parts): UriInterface
Creates a URI from a hash of
parse_url components.
GuzzleHttp\Psr7\Uri::withQueryValue
public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface
Creates a new URI with a specific query string value. Any existing query string values that exactly match the provided key are removed and replaced with the given key value pair. A value of null will set the query string key without a value, e.g. "key" instead of "key=value".
GuzzleHttp\Psr7\Uri::withQueryValues
public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
Creates a new URI with multiple query string values. It has the same behavior as
withQueryValue() but for an associative array of key => value.
GuzzleHttp\Psr7\Uri::withoutQueryValue
public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface
Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the provided key are removed.
Cross-Origin Detection
GuzzleHttp\Psr7\UriComparator provides methods to determine if a modified URI
should be considered cross-origin.
GuzzleHttp\Psr7\UriComparator::isCrossOrigin
public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool
Determines if a modified URI should be considered cross-origin with respect to an original URI.
Two URIs are cross-origin when their scheme, host, or effective port differ.
Host comparison is case-insensitive, and bracketed IPv6 literals are
canonicalized to their RFC 5952 form from any PSR-7 implementation before
comparison, so equivalent spellings of the same address are same-origin.
IPvFuture literals and bracketed values that cannot be parsed as an IPv6
address, such as those carrying zone identifiers, still compare as
case-insensitive text. Missing ports use the default port for http, https,
ws, or wss. Other schemes do not receive implicit default ports.
This helper only compares URI origins. It does not implement redirect handling or credential policy.
Reference Resolution
GuzzleHttp\Psr7\UriResolver provides methods to resolve a URI reference in the
context of a base URI according to
RFC 3986 Section 5.
This is also what web browsers do when resolving a link in a document based on
the current request URI.
GuzzleHttp\Psr7\UriResolver::resolve
public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
Converts the relative URI into a new URI that is resolved against the base URI.
GuzzleHttp\Psr7\UriResolver::removeDotSegments
public static function removeDotSegments(string $path): string
Removes dot segments from a path and returns the new path according to RFC 3986 Section 5.2.4.
Excess .. segments above the root of an absolute path are dropped without
consuming the root, so the result can begin with // (e.g. /..//a becomes
//a). Such a path is not valid for a URI without an authority (RFC 3986
Section 3.3); resolve() and UriNormalizer::normalize() serialize it with a
/. prefix in that case, like the WHATWG URL Standard.
GuzzleHttp\Psr7\UriResolver::relativize
public static function relativize(UriInterface $base, UriInterface $target): UriInterface
Returns the target URI as a relative reference from the base URI. This method is
the counterpart to resolve():
(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
One use case is to use the current request URI as the base URI and then generate relative links in your documents to reduce the document size or offer self-contained downloadable document archives.
$base = new Uri('http://example.com/a/b/');
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
echo UriResolver::relativize($base, new Uri('http://example.com')); // prints '//example.com'.
This method also accepts a target that is already relative and will try to relativize it further. Only a relative-path reference will be returned as-is.
echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
Normalization and Comparison
GuzzleHttp\Psr7\UriNormalizer provides methods to normalize and compare URIs
according to
RFC 3986 Section 6.
GuzzleHttp\Psr7\UriNormalizer::normalize
public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface
Returns a normalized URI. The scheme and host component are already normalized
to lowercase per PSR-7 UriInterface. This method adds additional normalizations
that can be configured with the $flags parameter, which is a bitmask of
normalizations to apply.
PSR-7 UriInterface cannot distinguish between an empty component and a missing
component as getQuery(), getFragment() etc. always return a string. This
means the URIs /?# and / are treated equivalent which is not necessarily
true according to RFC 3986. But that difference is highly uncommon in reality.
So this potential normalization is implied in PSR-7 as well.
The following normalizations are available:
-
UriNormalizer::PRESERVING_NORMALIZATIONSDefault normalizations which only include the ones that preserve semantics.
-
UriNormalizer::CAPITALIZE_PERCENT_ENCODINGAll letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. This applies to the userinfo, host, path, query, and fragment components. Bracketed IP-literal hosts are skipped as a legacy tolerance for nonstandard values other implementations may carry; zone-identifier text was briefly valid URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The userinfo and host are only rewritten when the value returned by the implementation matches the normalized form, and a userinfo with an empty user segment is never rewritten. No percent-encoding normalization is applied to a component that contains malformed percent syntax, such as a
%not followed by two hexadecimal digits.Example:
http://example.org/a%c2%b1b→http://example.org/a%C2%B1b -
UriNormalizer::DECODE_UNRESERVED_CHARACTERSDecodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers. This applies to the userinfo, host, path, query, and fragment components. Since the host is case-insensitive and PSR-7 requires it to be lowercase, octets decoded in the host are lowercased (e.g., "%41" becomes "a"). Bracketed IP-literal hosts are skipped as a legacy tolerance for nonstandard values other implementations may carry; zone-identifier text was briefly valid URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The userinfo and host are only rewritten when the value returned by the implementation matches the normalized form, and a userinfo with an empty user segment is never rewritten. No percent-encoding normalization is applied to a component that contains malformed percent syntax, such as a
%not followed by two hexadecimal digits.Example:
http://example.org/%7Eusern%61me/→http://example.org/~username/ -
UriNormalizer::CONVERT_EMPTY_PATHConverts the empty path to "/" for http and https URIs.
Example:
http://example.org→http://example.org/ -
UriNormalizer::REMOVE_DEFAULT_HOSTRemoves the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host "localhost". All of
file:/myfile,file:///myfile, andfile://localhost/myfileare equivalent according to RFC 3986.Example:
file://localhost/myfile→file:///myfile -
UriNormalizer::REMOVE_DEFAULT_PORTRemoves the default port of the given URI scheme from the URI.
Example:
http://example.org:80/→http://example.org/ -
UriNormalizer::REMOVE_DOT_SEGMENTSRemoves unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would change the semantics of the URI reference.
Example:
http://example.org/../a/b/../c/./d.html→http://example.org/a/c/d.html -
UriNormalizer::REMOVE_DUPLICATE_SLASHESPaths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization may change the semantics. Encoded slashes (%2F) are not removed.
Example:
http://example.org//foo///bar.html→http://example.org/foo/bar.html -
UriNormalizer::SORT_QUERY_PARAMETERSSort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be significant (this is not defined by the standard). So this normalization is not safe and may change the semantics of the URI.
Example:
?lang=en&article=fred→?article=fred&lang=en -
UriNormalizer::CANONICALIZE_IPV6_HOSTCanonicalizes IPv6 hosts to their RFC 5952 form. IPv6 addresses allow leading zeros and multiple placements of the
::elision, so the same address has many textual spellings. The canonical form is required for IPv6 literals in URIs by RFC 5952 Section 6 and never changes what the URI refers to. NativeUriinstances already guarantee canonical output; for other implementations, the canonical host is requested throughwithHost()and the result is kept only when the returnedgetHost()exactly matches the requested spelling, otherwise this step leaves the URI unchanged while other selected normalizations still apply, and setter exceptions propagate.Example:
http://[::0:0a]/→http://[::a]/
GuzzleHttp\Psr7\UriNormalizer::isEquivalent
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool
Whether two URIs can be considered equivalent. Both URIs are normalized
automatically before comparison with the given $normalizations bitmask. The
method also accepts relative URI references and returns true when they are
equivalent. This of course assumes they will be resolved against the same base
URI. If this is not the case, determination of equivalence or difference of
relative references does not mean anything.