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

juststeveking/uri-builder

juststeveking/uri-builder is a lightweight, fluent URI builder for PHP and Laravel. It lets you construct and manipulate URLs through a clean, chainable API for schemes, hosts, paths, query parameters, and fragments, avoiding fragile manual string concatenation. Ideal for consistent link generation.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install: composer require juststeveking/uri-builder. It is a pure Composer package — no service provider, facade, config, or migrations. You do not need to register anything in config/app.php or a provider.
  • Where to look first: read the package README.md, then composer.json for the PHP constraint, then src/ and tests/. This codebase is tiny (single value object plus a builder); reading the source is faster and more reliable than inferring from examples. Confirm the actual exported class name and method names in the version you install before writing app code.
  • First use case: building outbound URIs — webhook callback URLs, third-party API endpoints, canonical/sitemap links, referral/UTM links, pagination URLs — where you currently see sprintf('%s://%s/%s?%s', ...) or .=-style concatenation.

Typical shape (verify exact method names against your installed version):

use JustSteveKing\UriBuilder\Uri;

$uri = Uri::make()
    ->withScheme('https')
    ->withHost('api.example.com')
    ->withPath('/v1/webhooks')
    ->withQuery(['tenant' => 'acme', 'page' => 2]);

echo (string) $uri;
// https://api.example.com/v1/webhooks?tenant=acme&page=2

Everything is immutable, so each with* returns a new instance. That means the first habit to build is capturing the return value:

$base = Uri::make()->withScheme('https')->withHost('api.example.com');
$endpoint = $base->withPath('/v1/orders'); // $base is unchanged

Implementation Patterns

Wrap it before you spread it. Because there is no Laravel integration, the correct pattern is a one-file internal factory that exposes only the components you need. This is also your exit ramp if the package is abandoned.

namespace App\Support;

use JustSteveKing\UriBuilder\Uri;

interface EndpointBuilder
{
    public function build(string $path, array $query = []): string;
}

final class UriEndpointBuilder implements EndpointBuilder
{
    public function __construct(private string $scheme, private string $host) {}

    public function build(string $path, array $query = []): string
    {
        return (string) Uri::make()
            ->withScheme($this->scheme)
            ->withHost($this->host)
            ->withPath($path)
            ->withQuery($query);
    }
}

Bind the interface in a provider, keep the package behind it, and a future swap to league/uri, PSR-7, or Laravel 11's native Illuminate\Support\Uri is a contained change rather than a grep-and-replace across controllers, jobs, and mailables.

Reuse an immutable base URI. The value-object design makes "tenant base + endpoint path" clean. Resolve the base once per tenant/environment, then derive endpoints from it without defensive copying:

$tenantBase = Uri::make()
    ->withScheme(config('services.crm.scheme'))
    ->withHost($tenant->api_host)
    ->withPort($tenant->api_port);

Config-driven hosts and environment awareness. Put scheme/host/port/base-path in config/services.php and build the Uri from that config. Do not string-interpolate hostnames into paths — component-first construction is the whole point.

Query strings belong in withQuery(), not in the path string. Keeping the path and query separated is what lets the builder handle encoding once. Treat array query values as a thing to verify (see gotchas) rather than assuming http_build_query parity.

Use it only for outbound construction. Leave route(), url(), action(), and signed URLs to Laravel. This package has no route-name awareness, no locale handling, and no signature integration — using it there is a regression, not an abstraction.

Pin behavior with characterization tests. Before adopting in more than one place, write tests that assert the exact output string for your top edge cases: values containing spaces, &, #, =, ?, unicode, and already-percent-encoded input. These tests are what let you upgrade, fork, or swap the implementation deliberately instead of discovering a change in production.

Pilot in one bounded surface. Pick a single non-security-critical path (a webhook dispatcher or a reporting endpoint builder), ship it, then expand after a release cycle without incident.

Gotchas and Tips

  • Stale release. Last tag is 2022-07-15, 20 stars, single maintainer, zero dependents. Pin an exact version in composer.json and assume you own patching. MIT licensing means forking or vendoring the (small) source is a legitimate fallback — keep the adapter seam so it stays cheap.
  • "Slightly opinionated" is the real risk. Do not assume RFC 3986 or http_build_query() parity. Specifically verify: space encoding (%20 vs +), reserved characters (&, #, =, ?) inside query values, repeated query keys, array values, unicode/IDN hosts, default ports (does https + port 443 get elided?), IPv6 hosts, empty paths, and trailing slashes. Mis-encoding on user-controlled input is a correctness and injection concern, not a cosmetic one.
  • Verify PHP constraint compatibility. Check composer.json against your matrix (8.2/8.3/8.4). A 2022 constraint may not include newer PHP, and no upstream fix may be coming — you may need composer.json overrides or a fork.
  • Build-only, not parse. Do not reach for this to parse, inspect, or mutate an incoming URL, resolve relative references, or round-trip an arbitrary URI. That is league/uri territory. Confirm in the source whether it even accepts an existing URI string.
  • No PSR-7/PSR-17 interop. It does not implement UriInterface, so it cannot be handed to Guzzle, Symfony HTTP, middleware, or anything expecting a PSR-7 URI. Convert at the boundary if you need both.
  • No container injection out of the box. There is no service provider, so app(EndpointBuilder::class) only works because you bound your wrapper. Do not sprinkle new Uri/Uri::make() through controllers; that defeats the seam and makes the swap expensive.
  • Debugging encoding issues: dd((string) $uri); early, and compare side-by-side against http_build_query($params). When output diverges, that divergence is either a deliberate opinionation (document it) or a bug in your usage — the comparison makes it obvious which.
  • Watch for double-encoding. If the builder encodes on assignment and again on stringification, pre-encoded input will get mangled. Test by feeding a value containing %20 and seeing whether it becomes %2520.
  • Extension points are limited by design. Immutability means the cleanest extension is composition — a subclass or a small value object that holds a Uri and adds domain helpers (forTenant(), withCursor()). Avoid patching the package in vendor/; fork it if you truly need to change behavior, so it is reviewable and versioned.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package