URI Value Object
The package 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('Héllo World!', 'text/plain', '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'
URI string representation
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 representatio "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 URI 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:https://mozilla.org:443')->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::isSameDocument
Tells whether the given URI object represents the same document.
Uri::new("example.com?foo=bar#🏳️🌈")->isSameDocument("exAMpLE.com?foo=bar#🍣🍺"); //returns true
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./path')
->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.
Modifying URI 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 and comparison
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();
echo Uri::new('eXAMPLE://a/./b/../b/%63/%7bfoo%7d')->toNormalizedString();
// both calls display example://a/b/c/%7Bfoo%7D
If you are only interested in the normalized string version of the URI you can call the toNormalizedString
which is the equivalent to calling toString after calling normalize.
URI comparison
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'); // returns true
$uri->equals('eXAMPLE://a/./b/../b/%63/%7bfoo%7d'); // returns true
$uri->equals('eXAMPLE://a/./b/../b/%63/%7bfoo%7d', comparisonMode: 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.