league/uri-parser
league/uri-parser is a lightweight, RFC 3986 compliant URI parser for PHP. It parses URIs into their components (scheme, user info, host, port, path, query, fragment) and offers a simple, standards-based API for handling URLs reliably, with no external dependencies.
league/uri-parser is a small, dependency-free library that does exactly one thing: split a URI string into its RFC 3986 components. It does not build, normalize, validate, or resolve URIs — that is left to league/uri and league/uri-components.
Install it (PHP 7.0+; ext-intl must be present and correctly configured as of 1.4.1):
composer require league/uri-parser:^1.4.1
First use case — splitting an absolute URL into parts:
use League\Uri\Parser;
$parser = new Parser();
$components = $parser->parse('https://user:[email protected]:8080/path/to?q=1#frag');
// [
// 'scheme' => 'https',
// 'user_info' => 'user:pass',
// 'host' => 'example.com',
// 'port' => 8080,
// 'path' => '/path/to',
// 'query' => 'q=1',
// 'fragment' => 'frag',
// ]
Where to look first:
Parser class — it exposes parse(), plus component-level methods: parseScheme(), parseUserInfo(), parseHost(), parsePort(), parsePath(), parseQuery(), parseFragment(), parseAuthority().ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )) via issue #19.The first realistic use case is ingesting a URL from user input, config, or a log line and needing its parts before handing them to a different library (e.g. league/uri-schemes, a database column splitter, or a routing layer).
Parse-then-reassemble. Because parse() returns a flat array, the common workflow is:
$parts = (new Parser())->parse($uri);
$parts['host'] = strtolower($parts['host']); // RFC 3986 case-folding
$parts['scheme'] = strtolower($parts['scheme']);
// rebuild using http_build_url(), league/uri, or manual concat
Parse a single component. If you only care about the query string or the authority, call the focused method instead of running the full parser:
$parser->parseQuery('a=1&b=2');
$parser->parseAuthority('user:[email protected]:8080');
$parser->parsePath('/a/b/c');
$parser->parseScheme('HTTPS'); // stricter since 1.4.1
Wrap in try/catch for user input — and catch two different failure modes. Parsing failures and environment failures now throw different exception types, so catch them separately and log accordingly:
use League\Uri\Parser;
try {
$parts = (new Parser())->parse($raw);
} catch (\UnexpectedValueException $e) {
// 1.4.1+: Intl extension missing/misconfigured on this host.
// This is an infrastructure bug, not bad user input — alert, don't reject.
report($e);
throw $e;
} catch (\Exception $e) {
// genuinely unsplittable input — reject or fall back
}
Compensate for what the parser deliberately omits. Day-to-day you'll almost always pair it with:
%XX sequences.UriResolver lives in league/uri, not here).Integration pattern — request/log pipelines. A common shape is a small UriComponents value object that wraps the array, exposes scheme(), host(), port(), etc., and centralizes the normalization rules so every consumer sees consistent output. Put the UnexpectedValueException handling in this wrapper so callers never have to think about it.
Component-level composition. Use parseAuthority() when you already know you have an authority-only string (e.g. reading Host + credentials from a config), and skip the full parser round-trip.
Pin and test the runtime, not just the version. Because 1.4.1 adds a hard environment precondition, add a CI job (and a health check) that constructs new Parser() and parses one URI, so a broken ICU setup fails the build rather than production traffic.
league/uri / league/uri-components, which is where new work happens. Patch releases like 1.4.1 do still land. For new code, install league/uri and use League\Uri\UriString::parse() (or the Parser class shipped there) — the maintained equivalent. Only reach for league/uri-parser if you are pinned to an old dependency tree.Parser throws UnexpectedValueException if the Intl extension is misconfigured for the PHP/OS platform. This is the classic ICU-data-mismatch situation: Alpine/Docker images, distros where php-intl lags the system ICU, or a PHP upgrade without the matching intl build. It is not a parse failure — don't let it fall into your "invalid URL" branch.scheme key may now come back as path-only, and vice versa. If you have fixtures or a DB of stored URIs, re-run them after the bump and diff the resulting arrays before deploying. Strings like 1abc://x, ://x, or drive-letter-looking prefixes such as C:/path are the ones most likely to shift.port, user_info, query, and fragment are absent when the URI does not contain them. Always use $parts['port'] ?? null rather than assuming the key exists.:80 vs none), empty fragments, and percent-encoding are all preserved verbatim. If downstream code does string comparison on hosts, normalize yourself or you'll get example.com ≠ EXAMPLE.com.parse('../a/b') gives you a path component; it does not apply remove_dot_segments or resolve against a base URI. Use league/uri for resolution.var_export($parser->parse($uri)) first — the array shape tells you immediately whether your bug is in parsing (missing/extra key) or in your downstream normalization. If you instead get an UnexpectedValueException, skip the debugger and check php -m | grep intl and php -i | grep ICU.^1.4.1 (not a floating constraint) in composer.json and consider vendoring the ~single-class parser, since upstream work has moved to league/uri and no further patches are guaranteed.How can I help you explore Laravel packages today?