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

Url Laravel Package

rowbot/url

A PHP implementation of the WHATWG URL Standard for parsing and manipulating URLs with spec-compliant accuracy. It handles internationalized domains, percent-encoding, relative resolution, and edge cases browsers agree on, offering a dependable alternative to PHP's parse_url for modern URL work.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require rowbot/url

Requires a modern PHP (8.1+ at the time of writing — check composer.json). It pulls in rowbot/idna / rowbot/punycode for IDN handling. There is no Laravel service provider or config: it is a plain library, so you can use it anywhere.

Where to look first

  • src/URL.php — the main mutable value object. It mirrors the browser URL API almost 1:1.
  • src/URLSearchParams.php — the query-string object; it is a live view of the URL's query.
  • src/URLRecord.php and src/BasicURLParser.php — the low-level parser/record if you ever need spec-level control.
  • The README examples map directly to the WHATWG spec; when in doubt, compare behavior against new URL(...) in a browser.

Mental model: URL is mutable, URLSearchParams is live, and serialization ($url->href, (string) $url) is always the canonical WHATWG form — not the input string.

First use case: normalize a user-supplied URL

use Rowbot\URL\URL;
use Rowbot\URL\Exception\URLException;

try {
    $url = new URL('HTTPS://Example.COM:443/a/../b?x=1#frag');
    echo $url->href; // https://example.com/b?x=1#frag
} catch (URLException $e) {
    // invalid URL
}

// Relative resolution needs a base:
$url = new URL('../c?y=2', 'https://example.com/a/b/');
echo $url->href; // https://example.com/a/c?y=2

The package also exposes static helpers such as URL::parse() and URL::canParse() — useful when you want a boolean check instead of exceptions in hot paths.

Implementation Patterns

1. Parse → inspect → serialize. Read the properties you need and store $url->href, never the object.

$url = new URL($input);
$url->protocol;   // "https:"
$url->host;       // "example.com:8443" (includes port)
$url->hostname;   // "example.com"
$url->port;       // 8443
$url->origin;     // "https://example.com:8443"
$url->pathname;   // "/foo/bar"
$url->search;     // "?a=1"
$url->hash;       // "#top"

2. Mutate and rebuild. Setters behave like the browser API and re-serialize per the spec.

$url = new URL('https://example.com/old');
$url->pathname = '/new';
$url->search   = '?page=2';   // leading "?" is optional
$url->hash     = '#section';  // leading "#" is optional
echo $url->href; // https://example.com/new?page=2#section

3. Query-string workflows. searchParams is the authoritative object; mutate it in place.

$url = new URL('https://example.com/search?q=php&tag=a&tag=b');
$params = $url->searchParams;

$params->set('q', 'laravel');     // replaces all "q" values
$params->append('tag', 'c');      // adds another "tag"
$params->delete('tag');           // deletes all "tag"
$params->sort();

echo $url->search; // ?q=laravel
echo $url->href;   // includes the updated query

$params->get('q');      // first value
$params->getAll('tag'); // all values
$params->has('q');      // true
count($params);         // number of pairs

foreach ($params as $name => $value) {
    // iterates every pair, including duplicates
}

URLSearchParams can also be used standalone for query-only work (webhook payloads, cache keys):

$params = new URLSearchParams('a=1&b=2');
$params = new URLSearchParams(['a' => '1', 'b' => '2']);

4. Laravel validation rule.

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Rowbot\URL\URL;
use Rowbot\URL\Exception\URLException;

final class WhatwgUrl implements ValidationRule
{
    public function __construct(
        private ?string $base = null,
        private array $schemes = ['http', 'https'],
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! is_string($value)) {
            $fail("The {$attribute} must be a string.");
            return;
        }

        try {
            $url = new URL($value, $this->base);
        } catch (URLException) {
            $fail("The {$attribute} must be a valid URL.");
            return;
        }

        if (! in_array(rtrim($url->protocol, ':'), $this->schemes, true)) {
            $fail("The {$attribute} must use " . implode(' or ', $this->schemes) . '.');
        }
    }
}

Use it in a FormRequest: 'website' => ['required', new WhatwgUrl()], and canonicalize in prepareForValidation() by replacing the input with (string) new URL($input).

5. Open-redirect protection / origin allow-list.

public function handle(Request $request, Closure $next): Response
{
    if ($request->filled('redirect')) {
        try {
            $target = new URL($request->input('redirect'), $request->fullUrl());
            $current = new URL($request->fullUrl());

            if ($target->origin !== $current->origin || $target->origin === 'null') {
                abort(422, 'Invalid redirect target.');
            }

            $request->merge(['redirect' => (string) $target]);
        } catch (URLException) {
            abort(422, 'Invalid redirect target.');
        }
    }

    return $next($request);
}

6. Outbound HTTP calls. Normalize external base URLs and paths before handing them to the HTTP client:

$endpoint = new URL('/api/v1/items', config('services.vendor.base_url'));
Http::withToken($token)->get((string) $endpoint);

This catches typos and IDN/encoding issues early rather than as a 404 from the vendor.

7. Canonical link / cache key generation. Strip tracking params with the live searchParams object:

$canonical = new URL($request->fullUrl());
foreach (['utm_source', 'utm_medium', 'utm_campaign', 'fbclid'] as $p) {
    $canonical->searchParams->delete($p);
}
$canonical->searchParams->sort();
$key = (string) $canonical;

Gotchas and Tips

  • WHATWG ≠ RFC 3986 ≠ parse_url(). Expect different normalization: scheme/host lowercased, IDN converted to punycode, default ports removed, dot segments resolved, trailing / added to empty paths, and percent-encoding applied. Do not mix parse_url() and this package on the same value.
  • Relative URLs require a base. new URL('/foo') throws; pass the current request URL or a configured base. The second constructor argument accepts string|URL|null.
  • Setters can throw or silently no-op. Some invalid assignments raise URLException; others follow spec steps that simply return without changing state (e.g. switching a special URL to an incompatible scheme). Re-read the property after assignment if correctness matters.
  • searchParams is live and authoritative. $url->search is derived from it. Setting $url->search replaces the entire query and therefore resets the URLSearchParams contents. Never assign to searchParams itself.
  • + vs %20 divergence. URLSearchParams serializes spaces as + and parses + as space, while the URL serializer percent-encodes spaces as %20. Round-tripping between the two representations (or with RFC 3986 tooling) is a classic source of bugs — pick one representation at your boundaries.
  • Duplicate query keys. get() returns the first value, getAll() returns all, set() collapses duplicates, append() preserves them. Iteration yields every pair, so iterator_to_array() loses duplicates unless you collect manually.
  • Empty query/hash. https://example.com/? has search === '' but serializes with a trailing ?. Same asymmetry for #. Don't assume search === '' means "no query in the input."
  • origin can be the string 'null'. Opaque origins (file:, data:, blob:, etc.) return 'null', not a URL — check for that explicitly in allow-lists.
  • Credentials leak via href. username/password are exposed and serializable. Redact them before logging or storing, e.g. build a display URL with $url->username = ''.
  • IDN output is ASCII. new URL('https://münchen.de') serializes as https://xn--mnchen-3ya.de/. Use hostname for comparisons; if you need Unicode for display, keep the original input or convert back with rowbot/idna.
  • Clone before mutating shared objects. URL is mutable. If an instance is passed around (e.g. into a service), clone it or re-parse from href. Verify searchParams isolation with a quick test, since it is a live child object.
  • Serialize as strings. (string) $url / $url->href is the safe boundary for queues, cache, and DB columns; re-parse on the way back in. Avoid PHP-serializing the object graph.
  • Performance is not parse_url(). Spec compliance costs more. In hot paths (log enrichment, request middleware on every request), normalize once and cache by input string; skip parsing when the input is already canonical.
  • Debugging. Log $url->href before and after mutation, catch URLException and log its message, and compare edge cases against a browser's new URL() — the package is tested against the web-platform-tests suite, so browser behavior is the reference.
  • Small ecosystem. Low star count and few dependents means you are relatively early; pin the version in composer.json, and wrap parsing in your own small service so swapping libraries later is a one-file change.
  • Extension points are spec-level. URLRecord, BasicURLParser, and ParserState exist for low-level work, but for day-to-day use, stay on URL and URLSearchParams; the internals are not a stable integration surface.
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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core