Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Uri Schemes Laravel Package

league/uri-schemes

A PHP library for working with URI schemes, offering dedicated classes to parse, validate, and manipulate HTTP, FTP, WebSocket, and other scheme-specific URIs. Built on league/uri components for consistent, standards-compliant URI handling.

View on GitHub
Deep Wiki
Context7

Getting Started

league/uri-schemes gives you typed URI objects (one class per scheme) so you can parse, validate and manipulate URLs without juggling parse_url() arrays by hand.

Before you start, know this: the repository is archived and superseded. The package's functionality was folded into league/uri v4+. The latest patch, 1.2.1, is a maintenance fix (see Gotchas) — it does not modernise the package or change that recommendation. For new projects, install league/uri instead. Use league/uri-schemes only if you're maintaining an existing codebase already pinned to it.

Install (legacy projects):

composer require league/uri-schemes

Pin explicitly if you need the regression fix: composer require league/uri-schemes:^1.2.1.

Where to look first:

  • src/ — one class per scheme: League\Uri\Schemes\Http, Https, Ftp, File, Data, Mailto, Sftp, Ws, Wss. Each extends/mirrors a common Uri base.
  • League\Uri\UriInterface — the contract you should type-hint against.
  • League\Uri\Schemes\Uri::createFromString() / createFromComponents() — the two factories you'll use everywhere.

First use case — parse and inspect an HTTP URL:

use League\Uri\Schemes\Http;

$uri = Http::createFromString('https://user:[email protected]:8443/path/to?q=1#frag');

$uri->getScheme();   // 'https'
$uri->getUser();     // 'user'
$uri->getPass();     // 'pass'
$uri->getHost();     // 'example.com'
$uri->getPort();     // 8443
$uri->getPath();     // '/path/to'
$uri->getQuery();    // 'q=1'
$uri->getFragment(); // 'frag'

(string) $uri;       // reassembled string

That's the 90% case: take a URL string in, get a validated, scheme-aware object out, query individual components safely.

Implementation Patterns

Immutability + withers. Every mutation returns a new instance — this is the core pattern to internalise:

$uri = Http::createFromString('https://example.com/path');
$new = $uri->withHost('api.example.com')->withPath('/v2/users');
// $uri is unchanged; $new is a fresh object

Available withers on the base class: withScheme, withUserInfo, withHost, withPort, withPath, withQuery, withFragment. Chain them freely.

Parse once, then type-hint against the interface. Don't spread factory calls through your code — parse at the boundary (request handler, config loader, DB hydration) and pass UriInterface around:

use League\Uri\UriInterface;

function buildCallback(UriInterface $base): string
{
    return (string) $base->withPath('/oauth/callback');
}

You can accept any scheme-specific instance this way and let callers choose Http, Https, etc.

Scheme-aware validation is a real feature — lean on it. Each subclass enforces rules its own scheme requires, throwing on invalid input in createFromString() / createFromComponents():

// Http requires a host; this throws
Http::createFromString('/no-host');

// File requires an absolute path for absolute file URIs
File::createFromString('file:///var/log/app.log');

Use this as your input validator: wrap createFromString() in a try/catch, translate the exception into a 400 at your HTTP boundary, and downstream code can trust every Http it receives is well-formed.

Scheme-specific accessors. Don't hand-roll parsing for special schemes:

use League\Uri\Schemes\Data;
use League\Uri\Schemes\Mailto;

$data = Data::createFromString('data:text/plain;charset=utf-8;base64,SGVsbG8=');
$data->getMimeType();   // 'text/plain'
$data->getParameters(); // ['charset' => 'utf-8', 'base64' => true]
$data->getData();       // 'SGVsbG8='

$mail = Mailto::createFromString('mailto:[email protected],[email protected]?subject=Hi');
$mail->getAddresses();  // ['[email protected]', '[email protected]']

Integrating with PSR-7 / HTTP clients. This package is a URI value object, not a PSR-7 message. To bridge:

// From a PSR-7 request to a schemes object
$uri = Http::createFromString((string) $request->getUri());

// From a schemes object back to a PSR-7 URI
$psrUri = (new \GuzzleHttp\Psr7\Uri())->withPath($uri->getPath());

Keep the conversion at the edges; don't mix the two URI object types in your domain layer.

Building from $_SERVER. Both Uri and Http expose createFromServer($_SERVER), which is handy in legacy apps that still key off superglobals — one call gives you a normalized absolute URL instead of re-implementing the HTTP_HOST / REQUEST_URI dance.

Caching. Because instances are immutable, they're safe to cache (and to store in long-lived service objects). Parse once at startup, reuse.

Gotchas and Tips

The archive status is the biggest gotcha. No security fixes, no PHP 8.1+ deprecation patches. 1.2.1 only adds PHP 7.3 to the Travis matrix — it is not a signal of renewed maintenance. v1 targeted PHP 7.1. If you're on a modern PHP, expect friction — particularly around UriInterface signatures and strict property typing. Migrate to league/uri v6/v7: the new package's League\Uri\Http, League\Uri\Uri and friends are direct successors, and the wither API is compatible enough that most code translates by changing namespaces.

Upgrade to 1.2.1 if you resolve relative/scheme-less references. #13 fixes a regression in scheme resolution introduced upstream. If your code does things like resolving a path-only reference against a base URI, or relies on a scheme being inferred, pin ^1.2.1 and re-run your URI test suite — behaviour that "worked" on a broken resolution path may now produce a different (correct) canonical string. Anything that asserted against the buggy output will need its expectations updated.

Intl is a runtime requirement for registered-name formatting. As of 1.2.1, Uri::formatRegisteredName() throws UnexpectedValueException when the intl extension is missing or misconfigured (bad ICU/IDN data). Two consequences: (a) ext-intl must be present in every environment that touches host-bearing URIs — including CI images and slim production containers; (b) at your ingestion boundary, catch UnexpectedValueException alongside the usual InvalidArgumentException and translate it to a 400/500 rather than letting it bubble, so a broken extension config doesn't masquerade as bad user input.

Namespace drift between versions. Older releases used League\Uri\Scheme\... (singular) or placed classes under League\Uri\.... Check the release you actually installed with composer show league/uri-schemes and confirm the namespace in vendor/ before copy-pasting examples from the README.

Encoding: what you put in is not necessarily what comes out. The library percent-encodes path, query and fragment characters per RFC 3986 during __toString(). If you're debugging a mismatched signature or URL, always compare (string) $uri against your input — not the getters. Getters return the decoded/parsed component; the string cast returns the canonical encoding.

Ports: null vs 0 vs default. getPort() returns null when no port was specified, and it may return null when the port equals the scheme's default (80/443) after normalization. Don't assume truthiness — use !== null:

$port = $uri->getPort() ?? $uri->getDefaultPort(); // or hardcode per scheme

Exception types. createFromString() throws League\Uri\Exception\InvalidArgumentException (subclasses of \InvalidArgumentException) for malformed input and \RuntimeException/UriException for schema-specific violations, plus UnexpectedValueException for Intl misconfiguration. Catch broadly at boundaries, but don't catch \Exception inside library code — it hides real bugs.

createFromComponents() is stricter than parse_url(). It refuses arrays missing required pieces for the given scheme (e.g. no host for Http) and validates types. That's a feature, but it means round-tripping a parse_url() result through createFromComponents() can throw where parse_url() silently returned garbage. Validate at ingestion, then pass clean components.

IDN and Unicode hosts are not normalized for you in v1. Punycode conversion and lowercasing are best-effort, and the 1.2.1 Intl change only makes failures louder — it does not add IDN support. If your app accepts internationalised domains, normalize with idn_to_ascii() before passing in, and test with real IDN examples — don't rely on the library to do it.

Immutability surprises people who expect references. This is the #1 bug I've seen:

$uri->withHost('new.example.com'); // no-op — return value discarded
$uri = $uri->withHost('new.example.com'); // correct

Enable a static analyser (PHPStan/Psalm) with league/uri stubs or just make sure your team knows the pattern — it catches this class of mistake cheaply.

Extension point: subclass the scheme classes. If you need a custom scheme or extra validation, extending League\Uri\Schemes\Uri (or any scheme subclass) and overriding createFromString() / isValid() is the intended hook. Prefer composition if you can — subclassing an archived base is a long-term liability when you finally migrate to league/uri, and patch releases like 1.2.1 can change protected-path behaviour (e.g. formatRegisteredName, scheme resolution) underneath you.

Don't reach for it for query-string parsing alone. If all you need is parse_str() on the query component, you don't need this package — but its getQuery() gives you the raw string, not a parsed array. Pair with League\Uri\Query::parse() (or parse_str()) if you need key/value access.

Testing tip. Because (string) $uri gives you a canonical form, a simple assertion pattern works well:

$this->assertSame(
    'https://example.com/new?x=1',
    (string) Http::createFromString('https://example.com/old')->withPath('/new')->withQuery('x=1')
);

That one line covers parsing, wither chaining, encoding and reassembly — the exact code path your app uses in production. Add one resolution test per base-URI pattern you rely on, so the 1.2.1 scheme-resolution fix can't silently change your output later.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core