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::fromBaseUri("./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 fromBaseUri
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 no base URI is provided, the URI to resolve MUST be absolute. Otherwise, the base URI MUST be absolute.
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.
use League\Uri\Uri;
$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 representation
But Uri
can have multiple string representation depending on its scheme or context. As
such the package provides several other string representations.
The Uri
instance can be json encoded using the same URI representation from JavaScript to allow
easier interoperability
use League\Uri\Uri;
$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"
Two new URI string representation are added, the toNormalizedString
normalizes the URI using
destructive normalization. Please refer to the URI normalization section for more information.
The toDisplayString
returns a human-readable representation of the URI. The returned value
may represent an invalid URI but can be used to display the URI to the client for instance as the
content of a a
HTML tag.
use League\Uri\Uri;
$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->toNormalizedString(); //displays 'example://a/b/c/%7Bfoo%7D?foo%5B%5D=bar'
echo $uri->toDisplayString(); //displays 'example://a/b/c/{foo}?foo[]=bar'
File specific representation are added to allow representing Unix and Windows Path.
use League\Uri\Uri;
$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'
HTML specific representation are added to allow adding URI to your HTML/Markdown page.
use League\Uri\Uri;
$uri = Uri::new('eXAMPLE://a/./b/../b/%63/%7bfoo%7d?foo[]=bar');
echo $uri->toMarkdown();
//display '[example://a/b/c/{foo}?foo[]=bar](example://a/./b/../b/%63/%7bfoo%7d?foo%5B%5D=bar)
echo $uri->toMarkdown('my link');
//display '[my link](example://a/./b/../b/%63/%7bfoo%7d?foo%5B%5D=bar)
echo $uri->toAnchorTag();
// display '<a href="example://a/./b/../b/%63/%7bfoo%7d?foo%5B%5D=bar">example://a/b/c/{foo}?foo[]=bar</a>'
echo $uri->toAnchorTag('my link');
// display '<a href="example://a/./b/../b/%63/%7bfoo%7d?foo%5B%5D=bar">my link</a>'
You can also generate the Link tag
and/or header
depending on how you want your URI link to be rendered:
use League\Uri\Uri;
$uri = Uri::new('https://example.com/my/css/v1.3');
echo $uri->toLinkTag(['rel' => 'stylesheet']);
//display '<link href="https://example.com/my/css/v1.3" rel="stylesheet">
echo $uri->toLinkFieldValue(['rel' => 'stylesheet']);
//display 'https://example.com/my/css/v1.3 ;rel=stylesheet'
Accessing URI properties
Let’s examine the result of building a URI:
$uri = Uri::new("http://foo:bar@www.example.com:81/how/are/you?foo=baz#title");
echo $uri->getScheme(); //displays "http"
echo $uri->getUser(); //displays "foo"
echo $uri->getPassword(); //displays "bar"
echo $uri->getUserInfo(); //displays "foo:bar"
echo $uri->getHost(); //displays "www.example.com"
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" => "www.example.com",
// "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.
use League\Uri\Uri;
$foo = '';
echo Uri::new('https://uri.thephpleague.com/components/7.0/modifiers/')
->when(
'' !== $foo,
fn (Uri $uri) => $uri->withPath('/'.$foo), //on true
fn (Uri $uri) => $uri->withPath('/default'), //on false
)
->toString();
// returns 'https://uri.thephpleague.com/default';
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 modifier the URI definitions, those extra rules are:
- removing dot segments from the path
- sorting the query pairs
- 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.
$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', excludeFragment: false); // 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.