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 Laravel Package

windwalker/uri

A PHP URI library for parsing, building, and manipulating URLs and URIs. Create immutable URI objects, resolve relative references, handle query strings and fragments, and normalize paths with a clean, standards-aware API.

View on GitHub
Deep Wiki
Context7

Getting Started

windwalker/uri is a small PSR-7 UriInterface implementation whose real value is the string-munging helpers around it (UriHelper, part-level parsing, query building). It is not a router, a request factory, or a URL generator — think of it as "parse_url() with object ergonomics and PSR-7 compatibility."

Minimal steps:

composer require windwalker/uri

First files to read, in order:

  1. src/Uri.php — the PSR-7 implementation; look at __toString(), withQuery(), withPath(), and any static create()/fromString() factory.
  2. src/UriHelper.php — static utilities (buildQuery, parseUrl, removeIndex, etc.). In practice you will call these more than the class itself.
  3. src/AbstractUri.php / UriData.php (if present) — where the actual parse_url behavior and defaults live.

First use case — normalize an inbound URL before storing or redirecting:

use Windwalker\Uri\Uri;

$uri = new Uri('HTTPS://user:[email protected]:443/foo/../bar?b=2&a=1#frag');

(string) $uri;              // normalized string form
$uri->getHost();            // 'example.com'
$uri->getPath();            // '/foo/../bar'  (no dot-segment resolution!)
$uri->getQuery();           // 'b=2&a=1'      (string, not array)

If that output matches what you expect, you're ready. If you expected dot-segment collapsing or query sorting, see the gotchas below — this package deliberately stays close to parse_url semantics.

For a Laravel app, wire it in as a thin adapter rather than a facade: Uri is a plain value object with no container coupling and no service provider.

Implementation Patterns

1. Middleware that rewrites the request URI

Because Uri is PSR-7, you can hand it straight to a request:

use Windwalker\Uri\Uri;

public function handle($request, Closure $next)
{
    $original = new Uri((string) $request->getUri());

    $normalized = $original
        ->withScheme('https')
        ->withHost(strtolower($original->getHost()))
        ->withPath(rtrim($original->getPath(), '/') ?: '/');

    $request->server->set('REQUEST_URI', (string) $normalized);

    return $next($request);
}

2. Building URLs from parts in a link/sitemap builder

$url = (new Uri())
    ->withScheme('https')
    ->withHost('example.com')
    ->withPath('/products/' . $product->slug)
    ->withQuery(UriHelper::buildQuery([
        'utm_source' => 'newsletter',
        'page'       => 2,
        'filters'    => ['color' => 'red', 'size' => ['m', 'l']],
    ]));

The UriHelper::buildQuery() helper is the reason to pick this package over rolling your own — it handles nested arrays with the bracket syntax users expect, without you hitting http_build_query()'s encoding quirks mid-pipeline.

3. Safe query mutation (the pattern you'll use most)

Never str_replace on a query string. Parse, mutate, rebuild:

$uri   = new Uri($incoming);
$query = UriHelper::parseQuery($uri->getQuery()); // array

unset($query['page']);
$query['utm_campaign'] = 'spring';

$out = $uri->withQuery(UriHelper::buildQuery($query));

This is where the package shines day-to-day: adding tracking params, stripping pagination, appending signed tokens — all without breaking existing keys.

4. Relative URL resolution against a base

$base = new Uri('https://example.com/app/');
$link = new Uri('../docs/intro?x=1');

// Resolve manually using parts from both, or use UriHelper::resolve()
// if your version ships it. Keep the logic in one method — this is the
// spot where hand-rolled resolvers quietly diverge from RFC 3986.

5. Interop with Guzzle / PSR-18 / PSR-7 bridges

Uri satisfies Psr\Http\Message\UriInterface, so Guzzle will accept it directly:

$client->request('GET', new Uri('https://api.example.com/v1/items?limit=50'));

If you're on Laravel's HTTP client, cast to string first — the underlying client re-parses anyway, and an explicit (string) avoids surprises when the object is logged.

6. Comparing URLs without string comparison

For cache keys or dedupe logic, compare a canonical subset rather than the whole string:

$key = implode('|', [
    $a->getScheme(), $a->getHost(), $a->getPort(), $a->getPath(),
    UriHelper::buildQuery(ksort_recursive(UriHelper::parseQuery($a->getQuery()))),
]);

Gotchas and Tips

  • Immutability bites. Every withX() returns a clone. $uri->withQuery('a=1'); silently does nothing. Assign it back. This is the #1 source of "the package doesn't work" reports.

  • getQuery() is a string, not an array. There is no getQueryParams() in the PSR-7 contract and this package doesn't add one. You are expected to pair it with UriHelper::parseQuery() / buildQuery(). Forgetting this and calling $uri->getQuery()['key'] will throw a string-offset error.

  • No dot-segment normalization. parse_url-level behavior means /a/../b survives. If you need RFC 3986 normalization for routing or security-sensitive path checks, add it explicitly.

  • Encoding is not guaranteed idempotent. Round-tripping (string) (new Uri($s)) can change percent-encoding. Treat it as "safe to send," not "byte-stable." Don't use the string form as a cache key unless you've verified stability for your inputs.

  • Default ports. Expect :443 and :80 to be dropped by __toString() when the scheme matches. Compare with getPort() (which returns null for defaults) rather than string equality.

  • parse_str heritage. If UriHelper::parseQuery() delegates to PHP's parser, dots and spaces in keys become underscores, and foo[]=1&foo[]=2 collapses. Validate nested query shapes before trusting them.

  • Read-only repo, zero dependents. The repository is flagged [READ ONLY], has no stars, and no downstream users. That means: pin an exact version in composer.json, vendor the code if it's load-bearing, and expect no upstream fixes. Do not build a critical URL-signing path on an unmaintained dependency without a fork plan.

  • No Laravel integration. No service provider, no facade, no config. That's fine — but don't expect Uri to understand APP_URL or URL::to(). Wrap it in a single UrlNormalizer service class in app/Support/ so the rest of the codebase never imports a Windwalker namespace directly. That one indirection makes swapping to guzzlehttp/psr7 or league/uri later a mechanical change.

  • Extension point. The cleanest way to customize is to extend Uri and override __toString() and the relevant withX() methods, then instantiate your subclass everywhere. Because everything is withX-based, a subclass survives the whole immutable chain — unlike wrapping it in a decorator, which loses the concrete type after the first with...() call.

  • Debugging tip. When a URL "looks wrong," dump the parts individually:

    dump([
        'scheme' => $uri->getScheme(), 'host' => $uri->getHost(),
        'port' => $uri->getPort(), 'path' => $uri->getPath(),
        'query' => $uri->getQuery(), 'fragment' => $uri->getFragment(),
    ]);
    

    Nine times out of ten the problem is a value that parse_url refused to populate (e.g. a URL with no scheme and a host containing a colon) rather than the package misbehaving.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource