URI Value Object
The class provides an expressive and efficient API around building and manipulating URI. It allows the easy creation of URI in multiple contexts to increase your DX while working with URI.
Instantiation
While the default constructor is private and can not be accessed to instantiate a new object,
the League\Uri\Uri class comes with the different named constructors to ease instantiation.
<?php
use League\Uri\Uri;
use League\Uri\UriString;
use Laminas\Diactoros\Uri as LaminasUri;
// using a string or an object which expose the `__toString` method
$uri = Uri::new('http://example.com/path/to?q=foo%20bar#section-42');
$uri->toString(); // display 'http://example.com/path/to?q=foo%20bar#section-4'
$laminasUri = new LaminasUri("http://www.example.com/path/to/the/sky");
$laminasUri->getQuery(); //return '';
Uri::new($laminasUri)->getQuery(); //return null;
// using `parse_url` or the package `UriString::parse` static method.
$uri = Uri::fromComponents(UriString::parse("http://uri.thephpleague/7.0/uri/api"));
//don't forget to provide the $_SERVER array
$uri = Uri::fromServer($_SERVER);
You can also return a URI based on standard specifications:
$uri = Uri::parse("./p#~toto", "http://www.example.com/path/to/the/sky/");
echo $uri; //displays "http://www.example.com/path/to/the/sky/p#~toto"
$template = 'https://example.com/hotels/{hotel}/bookings/{booking}';
$variables = ['booking' => '42', 'hotel' => 'Rest & Relax'];
echo Uri::fromTemplate($template, $variables)->toString();
//displays "https://example.com/hotels/Rest%20%26%20Relax/bookings/42"
$uri = Uri::fromFileContents('path/to/my/png/image.png');
echo $uri; //returns 'data:image/png;charset=binary;base64,...'
//where '...' represent the base64 representation of the file
$uri = Uri::fromData(data: 'Héllo World!', mimetype: 'text/plain', parameters: 'charset=utf8');
echo $uri; // returns data:text/plain;charset=utf8,H%C3%A9llo%20World%21
The parse method resolves URI using the same logic behind URL construction
in a browser and is inline with how the Javascript URL object constructor works.
If a new instance cannot be returned, the method returns null.
$uri = Uri::parse("invalid uri", "http://www.example.com/path/to/the/sky/");
var_dump($uri); // return null
The fromTemplate method resolves a URI using the rules and variable from the
URITemplate specification RFC6570:
The method expects at most two parameters. The URI template to resolve and the variables use
for resolution. You can get a more in-depth understanding of
URI Template in its dedicated section of the documentation.
The fromFileContents
named constructor generates a Data URI
following its RFC specification. with the provided file location, the method will
base64 encode the content of the file and return the generated URI.
The fromData
named constructor generates a Data URI
following its RFC specification. with the provided data and an optional mimetype and parameters.
Last but not least, you can easily translate RFC8089, Windows, Unix paths to URI using the three (3) following methods.
$uri = Uri::fromWindowsPath('c:\windows\My Documents\my word.docx');
echo $uri; //returns 'file://localhost/c:My%20Documents/my%20word.docx'
$uri = Uri::fromUnixPath('/path/to/my/file.xml');
echo $uri; //returns 'file://localhost/path/to/my/file.xml'
$uri = Uri::fromRfc8089('file:/etc/fstab');
echo $uri = //returns 'file:///etc/fstab'
Validation
A League\Uri\Contracts\UriException exception is triggered if an invalid URI is given.
$uri = Uri::new(':');
// throws a League\Uri\Exceptions\SyntaxError
// because the URI string is invalid
use League\Uri\Uri;
use League\Uri\Contracts\UriException;
try {
$uri = Uri::new(':');
} catch (UriException $e) {
}
RFC3986 Validation
By default, if the URI scheme is not recognized, the URI object will only validate RFC3986 rules. This means that depending on the URI scheme, the returned URI may not be valid.
$uri = Uri::new('toto://thephpleague.com/path/to?here#content');
//this will not throw an error because this URI satified RFC3986 rules
Scheme Validation
For the following URI schemes (order alphabetically) full validation is taken into account.
- data
- file
- ftp
- http(s)
- ws(s)
- blob (since version 7.6)
- mailto (since version 7.6)
URN Validation
The generic URN validation based on RFC8141 is used when encountering a URN scheme URI. Because URN may expose more refined specification rules depending on their namespace identifier, the validation rules applied can be considered as generic, and you may need more extra validation. Please refer to the URN object for more information.
$uri = Uri::new('urn://example.com');
// will throw a League\Uri\Exceptions\SyntaxError
$uri = Uri::new('urn:isbn:foobar/baz');
// will not throw but is an invalid isbn URN
// the ISBN namespace special string MUSR contain
// the ISBN-10 or ISBN-13 number
// possibly with hyphens only
Component Validation
Based on the IANA URI scheme lists, and on the different RFCs and specifications for each URI scheme, a validation based on URI component presence is used. For instance, the following URI will be rejected.
$uri = Uri::new('bitcoin:config/here');
//will work
$uri = Uri::new('bitcoin://thephpleague.com');
// will throw a League\Uri\Exceptions\SyntaxError
In the example, because a the bitcoin URI scheme cannot contain an authority component, an
exception is thrown. On the other hand, the path has not been checked to validate that it only
contained a valid bitcoin address. According to RFC3986, the path component is valid, but it is
not according to the bitcoin specification.
The list of supported URI scheme can be found on the UriScheme Enum
available since version 7.6.
String Representations
The Uri class handles URI according to RFC3986 as such you can retrieve its string representation using the
toString method.
$uri = Uri::new("http://foo:bar@www.example.com:81/how/are/you?foo=baz#title");
echo $uri->toString(); //displays RFC3986 string representation
echo $uri; //displays RFC3986 string representatio
But Uri can have multiple string representation depending on its scheme or context. As
such, the package provides several other string representations.
The Uri object also provides two complementary methods:
Uri:;toAsciiString() and Uri::toUnicodeString to allow accessing the URI string
representation with the ascii host as per RFC3986 definition or using the host unicode
version if available using the Uri::toUnicodeString. the toString() method is an
alias to toAsciiString() method.
$uri = Uri::new("http://BéBé.be:81/how/are/you?foo=baz#title");
echo $uri->toAsciiString(); //displays RFC3986 string representation "http://xn--bb-bjab.be:81/how/are/you?foo=baz#title"
echo $uri->toUnicodeString(); //displays RFC3987 string representation "http://bébé.be:81/how/are/you?foo=baz#title"
The Uri instance can be JSON encoded using the same URI representation from JavaScript to allow
easier interoperability
$uri = Uri::new("http://foo:bar@www.example.com:81/how/are/you?foo=baz#title");
json_encode($uri); //returns "http:\/\/foo:bar@www.example.com:81\/how\/are\/you?foo=baz#title"
A new URI string representation is added, The toDisplayString() method returns a human-readable
representation of the URI, corresponding to its IRI form as defined in RFC 3987. Although
the resulting value may not constitute a syntactically valid URI, it is intended for
presentation purposes — for example, as the textual content of an HTML <a> element.
$uri = Uri::new('eXAMPLE://a/./b/../b/%63/%7bfoo%7d?foo[]=bar');
echo $uri->toString(); //displays 'example://a/./b/../b/%63/%7bfoo%7d?foo%5B%5D=bar'
echo $uri->toDisplayString(); //displays 'example://a/./b/../b/c/{foo}?foo[]=bar'
For file scheme, specifics representations are added to allow representing Unix and Windows Path.
$uri = Uri::new('file:///c:/windows/My%20Documents%20100%2520/foo.txt');
echo $uri->toWindowsPath(); //display 'c:\windows\My Documents 100%20\foo.txt'
$uri = Uri::new('file:///path%20empty/bar');
echo $uri->toUnixPath(); // display '/path empty/bar'
$uri = Uri::new('file://localhost/etc/fstab');
echo $uri->toRfc8089(); //display 'file:/etc/fstab'
Last, but not least if you have a Data URI you can store the actual data into a file using the toFileContents method
$uri = Uri::new('data:text/plain;charset=utf-8;base64,SGVsbG8gd29ybGQh');
$uri->toFileContents(destination: 'my/path/file.txt'); //returns the number of bytes stored
echo file_get_contents('my/path/file.txt'); //will return 'Hello world!'
Accessing Properties
Let’s examine the result of building a URI:
$uri = Uri::new("http://foo:bar@bébé.be:81/how/are/you?foo=baz#title");
echo $uri->getScheme(); //displays "http"
echo $uri->getUsername(); //displays "foo"
echo $uri->getPassword(); //displays "bar"
echo $uri->getUserInfo(); //displays "foo:bar"
echo $uri->getHost(); //displays "xn--bb-bjab.be"
echo $uri->getUnicodeHost(); //displays "bébé.be"
echo $uri->getPort(); //displays 81 as an integer
echo $uri->getAuthority(); //displays "foo:bar@www.example.com:81"
echo $uri->getPath(); //displays "/how/are/you"
echo $uri->getQuery(); //displays "foo=baz"
echo $uri->getFragment(); //displays "title"
echo $uri->getOrigin(); //returns ''
$uri->getComponents();
// returns array {
// "scheme" => "http",
// "user" => "foo",
// "pass" => "bar",
// "host" => "xn--bb-bjab.be",
// "port" => 81,
// "path" => "/how/are/you",
// "query" => "foo=baz",
// "fragment" => "title",
// }
The returned value for each URI component is kept encoded. If you need the decoded value you should use the league/uri-component to extract and manipulate each individual component.
The getOrigin method returns the URI origin used for comparison when calling the isCrossOrigin and isSameOrigin methods.
The algorithm used is defined by the WHATWG URL Living standard
echo Uri::new('https://uri.thephpleague.com/uri/6.0/info/')->getOrigin(); //display 'https://uri.thephpleague.com';
echo Uri::new('blob:null/700475de-c453-11f0-8de9-0242ac120002')->getOrigin(); //display 'https://mozilla.org'
Uri::new('file:///usr/bin/php')->getOrigin(); //returns null
Uri::new('data:text/plain,Bonjour%20le%20monde%21')->getOrigin(); //returns null
Because the origin property does not exist in the RFC3986 specification this additional steps is implemented:
- For non-absolute URI the method will return
null
Uri::new('/path/to/endpoint')->getOrigin(); //returns null
URI Information
The class also exposes a list of public methods which returns the URI state.
Uri::isAbsolute
Tells whether the URI represents an absolute URI.
Uri::fromServer($_SERVER)->isAbsoulte(); //returns true
Uri::new("/🍣🍺")->isAbsolute(); //returns false
Uri::isAbsolutePath
Tells whether the URI represents an absolute URI path.
Uri::fromServer($_SERVER)->isAbsolutePath(); //returns false
Uri::new("/🍣🍺")->isAbsolutePath(); //returns true
Uri::isNetworkPath
Tells whether the URI represents a network path URI.
Uri::new("//example.com/toto")->isNetworkPath(); //returns true
Uri::new("/🍣🍺")->isNetworkPath(); //returns false
Uri::isOpaque
Tells whether the given URI object represents an opaque URI. An URI is said to be opaque if and only if it is absolute but does not have an authority
Uri::new("email:john@example.com?subject=🏳️🌈")->isOpaque(); //returns true
Uri::new("/🍣🍺")->isOpaque(); //returns false
Uri::isRelativePath
Tells whether the given URI object represents a relative path.
Uri::new("🏳️🌈")->isRelativePath(); //returns true
Uri::new("/🍣🍺")->isRelativePath(); //returns false
Uri::hasIDN
Tells whether the given URI object contains a IDN host.
Uri::new("https://bébé.be")->hasIdn(); //returns true
Uri::isCrossOrigin and Uri::isSameOrigin
Tells whether the given URI object represents different origins. According to RFC9110 The “origin” for a given URI is the triple of scheme, host, and port after normalizing the scheme and host to lowercase and normalizing the port to remove any leading zeros.
<?php
Uri::new('blob:http://xn--bb-bjab.be./700475de-c453-11f0-8de9-0242ac120002')
->isCrossOrigin('http://Bébé.BE./path'); // returns false
Uri::new('https://example.com/123')
->isSameOrigin('https://www.example.com/'); // returns false
The method takes into account i18n while comparing both URI if the PHP’s idn_* functions can be used.
Uri Host Type
RFC3986 defines several type of host, the Uri class exposes the following
methods to help you get the host type:
Uri::isIpv4Hosttells whether the host represents an IPv4 hostUri::isIpv6Hosttells whether the host represents an IPv6 hostUri::isIpvFutureHosttells whether the host represents an IPvFuture hostUri::isRegisteredNameHosttells whether the host represents a registered name hostUri::isDomainHosttells whether the host represents an domain name host
use League\Uri\Uri;
$urString = 'https://_uri.thephpleague.com'; // note the presence of the underscore!!
$uri = Uri::new($urString);
$uri->isRegisteredNameHost(); // returns true;
$uri->isDomainHost(); // returns false;
Modifying Properties
Use the modifying methods exposed by all URI instances to replace one of the URI component. If the modifications do not alter the current object, it is returned as is, otherwise, a new modified object is returned.
Since all URI objects are immutable you can chain each modifying methods to simplify URI creation and/or modification.
$uri = Uri::new("ftp://thephpleague.com/fr/")
->withScheme("yolo")
->withUserInfo("foo", "bar")
->withHost("www.example.com")
->withPort(81)
->withPath("")
->withQuery("foo=baz")
->withFragment('fine');
echo $uri; //displays yolo://foo:bar@www.example.com:81?foo=baz#fine
To ease building the instance, the when method is added to conditionally create your component.
echo Uri::new('https://uri.thephpleague.com/components/7.0/modifiers/')
->when(
fn (Uri $uri) => $uri->getPassword() !== null,
fn (Uri $uri) => $uri->withQuery('access=allowed'), //on true
fn (Uri $uri) => $uri->withQuery('access=deny'), //on false
)
->toString();
// returns 'https://uri.thephpleague.com/components/7.0/modifiers/?access=deny';
URI Resolution
The Uri::resolve resolves a URI as a browser would for a relative URI while the Uri::relativize
does the opposite.
$baseUri = Uri::new('http://www.ExaMPle.com');
$uri = 'http://www.example.com/?foo=toto#~typo';
$relativeUri = $baseUri->relativize($uri);
echo $relativeUri; // display "/?foo=toto#~typo
echo $baseUri->resolve($relativeUri);
echo $baseUri; // display 'http://www.example.com'
// display 'http://www.example.com/?foo=toto#~typo'
echo $baseUri->getUri()::class; //display \League\Uri\Uri
URI Normalization
Non-destructive Normalization
Out of the box, the package normalizes any given URI according to the non-destructive rules of RFC3986.
These non-destructive rules are:
- scheme and host components are lowercase;
- the host is converted to its ascii representation using punycode if needed
- query, path, fragment components are URI encoded if needed;
- the port number is removed from the URI string representation if the standard port is used;
$uri = Uri::new("hTTp://www.ExAmPLE.com:80/hello/./wor ld?who=f 3#title");
echo $uri; //displays http://www.example.com/hello/./wor%20ld?who=f%203#title
$uri = Uri::fromComponent(parse_url("hTTp://www.bébé.be?#"));
echo $uri; //displays http://xn--bb-bjab.be?#
Destructive Normalization
The normalize method applies extra normalization that may modify the original URI, those extra rules are:
- removing dot segments from the path
- normalizing the IPv6 and IPv4 host
- url-decode all non-reserved characters in the path and the query
echo Uri::new('eXAMPLE://a/./b/../b/%63/%7bfoo%7d')->normalize()->toString();
// both calls display example://a/b/c/%7Bfoo%7D
URI Equivalence
Once normalized a URI can be compare using the two new comparison methods, isSameDocument and equals methods.
The two methods uses the normalized string representation of two URI to tell whether they are referencing the same resource.
use League\Uri\Uri;
use League\Uri\UriComparisonMode;
$uri = Uri::new('example://a/b/c/%7Bfoo%7D?foo=bar');
$uri->isSameDocument('eXAMPLE://a/./b/../b/%63/%7bfoo%7d?foo=bar'); // returns true
$uri->equals('eXAMPLE://a/./b/../b/%63/%7bfoo%7d?foo=bar'); // returns true
$uri->equals('eXAMPLE://a/./b/../b/%63/%7bfoo%7d?foo=bar#fragment', uriComparisonMode: UriComparisonMode::IncludeFragment); // returns false
In the last example the equals method took into account the URI fragment component. The isSameDocument
follow closely RFC3986 and never takes into account the URI fragment component.