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.

The class handles all URI schemes and default to RFC3986 rules if the scheme is not present and not recognized as special.

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);

If you supply your own hash to fromComponents, you are responsible for providing well parsed components without their URI delimiters.

The fromServer method only relies on the server's safe parameters to determine the current URI. If you are using the library behind a proxy the result may differ from your expectation as no $_SERVER['HTTP_X_*'] header is taken into account for security reasons.

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'

fromRfc8089 is added since version 7.4.0

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"

Available since version 7.6.0

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",
// }

Apart from the path component, which is always a string, all the other URI components can be null

getUsername and getPassword are added in version 7.5.0

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.

getOrigin is added in version 7.6.0

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

For absolute URI with the file scheme the method will return null (as this is left to the implementation decision)

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.

Any modification method can trigger a League\Uri\Contracts\UriException exception if the resulting URI is not valid. Just like with the instantiation methods, validation is scheme dependant.

Apart from the path component, which is always a string, to delete a URI component you need to specify its content as being null

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

The withUser and withPassword methods are available since version 7.6.0 to be inline with PHP native Uri interface.

The when method is available since version 7.6.0

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

Available since version 7.6.0

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?#

The last example depends on the presence of the idn_to_* functions, otherwise the code will trigger a MissingFeature exception

Destructive normalization

Available since version 7.6.0

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.