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

gdbots/uri-template

A PHP implementation of URI templates (RFC 6570) for expanding and matching parameterized URI patterns. Build and parse templates with simple or complex expressions to generate links, API paths, and identifiers cleanly.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is. A standalone, framework-agnostic implementation of RFC 6570 (URI Template) expansion plus a small bundled catalog of common templates. It turns a parameterized URL string like {/tenant}/projects{?page,limit} into a concrete URI, handling percent-encoding, lists, key/value maps, and the RFC's reserved/path/query/fragment operators. There is no service provider, no config, no migrations — it is a leaf dependency you call from your own code.

Minimal steps.

  1. Validate it installs before anything else. In a scratch branch:

    composer require gdbots/uri-template --no-interaction
    composer why-not php 8.3    # confirm the declared constraint isn't blocking
    

    A 2021-era package can declare ^7.x and refuse to resolve against Laravel 10/11's PHP 8.1+ floor. If it does not install cleanly, stop — do not use --ignore-platform-reqs to force it.

  2. Locate the entry point first. Open vendor/gdbots/uri-template/src/ and the README. The public surface is small — one template class with an expand method and something for extracting variable names. Confirm the exact class and method names in the installed version rather than trusting the description; that is a two-minute check that prevents an hour of guessing.

  3. First use case: build one endpoint URL you currently hand-assemble. A good first target is an outbound API client, not internal routing:

    use Gdbots\UriTemplate\UriTemplate; // verify name in the installed version
    
    $url = UriTemplate::expand(
        'https://api.example.com{/tenant}/projects{?page,limit,fields}',
        ['tenant' => 'acme', 'page' => 2, 'limit' => 50, 'fields' => ['id', 'name']]
    );
    
  4. Do not call it from application code. Immediately wrap it (see next section). The package is unmaintained, so the goal of day one is that exactly one file in your codebase references the Gdbots\ namespace.

Where this earns its keep. RFC 6570 templates are URL structure as data. That is only valuable when the structure needs to vary outside code: multi-tenant hosts, partner-facing hypermedia, stored webhook callbacks, generated SDKs, OpenAPI links objects. If all you need is {id} interpolation into a known route, use route() and skip this entirely.

Implementation Patterns

Pattern 0 — the adapter (do this first). Bind a narrow contract in a service provider and never let the namespace leak:

// app/Contracts/ExpandsUriTemplates.php
interface ExpandsUriTemplates
{
    public function expand(string $template, array $variables): string;
    public function variables(string $template): array;
}

// app/Services/UriTemplateExpander.php
final class UriTemplateExpander implements ExpandsUriTemplates
{
    public function expand(string $template, array $variables): string
    {
        // translate the package's semantics into *your* pinned semantics here
        return UriTemplate::expand($template, $variables);
    }
}

Everything below assumes this seam exists. It is what makes swapping to guzzlehttp/uri-template or an internal fork a one-file change.

Pattern 1 — endpoint catalog in config. Store templates as configuration, not as concatenated strings scattered across controllers and jobs:

// config/endpoints.php
'projects.show' => 'https://{+host}{/tenant}/projects/{id}{?fields,expand}',
$url = $expander->expand(config('endpoints.projects.show'), [
    'host' => config('services.api.host'),
    'tenant' => $tenant->slug,
    'id' => $project->id,
    'fields' => ['id', 'name', 'status'],
]);

This is the highest-value pattern: changing a host or path prefix becomes a config deploy, not a code sweep.

Pattern 2 — hypermedia links (HAL, JSON:API, Siren). Persist the template in your resource payload and expand per request context. This is where RFC 6570 exists to be used:

'links' => [
    'self'     => ['href' => $expander->expand('{+base}/projects/{id}', $vars)],
    'documents'=> ['href' => '{+base}/projects/{id}/documents{?page,limit}'], // template, not expanded
],

Publish templates unexpanded; expand only when a specific relation's parameters are known.

Pattern 3 — webhook / callback URLs. Store the template per subscription, expand on dispatch with event type, tenant, and version. This makes the integration contract explicit and testable, and it removes a category of "the callback URL is wrong for this tenant" bugs.

Pattern 4 — deep links and campaign URLs. UTM builders, mobile deep links, and shareable resource links benefit from one implementation of encoding instead of per-team sprintf habits. The win is small but measurable: fewer broken links and no double-encoding incidents.

Pattern 5 — conformance tests as the real deliverable. The expanded output is your contract, so pin it with golden files. Data-provider over the expression levels you actually use:

#[DataProvider('templates')]
public function test_expansion_is_stable(string $tpl, array $vars, string $expected): void
{
    $this->assertSame($expected, app(ExpandsUriTemplates::class)->expand($tpl, $vars));
}

Cover explicitly: {var}, {+var} (reserved), {#frag}, {?a,b}, {/path*}, empty string, null/missing, non-ASCII, and values containing /, ?, &, #, and spaces. These tests survive a swap of the underlying library — that is their point.

Pattern 6 — where not to use it. Internal named routes, redirects to your own controllers, asset URLs handled by asset()/Vite. Laravel already solves those; the seam only earns complexity for external or data-driven templates.

Gotchas and Tips

The dependency is the risk, not the code. Zero stars, zero dependents, no release since December 2021, no issue triage. Treat it as source you adopt, not a dependency you trust: vendor or fork it under your org namespace, pin the commit, add it to the SBOM as an accepted-risk item with a named owner, and write the one-paragraph decision record. Apache-2.0 makes internal forking straightforward.

Strongly consider the alternative before writing any code. If Guzzle is already in your composer.lock (it usually is in a Laravel stack), guzzlehttp/uri-template is actively maintained, widely depended upon, and covers the same RFC. Same interface, strictly less risk. Use gdbots/uri-template only if it is genuinely a better fit — and its bundled template catalog is not a reason, since that catalog is gdbots-specific and should be treated as reference material, never as a shared contract.

PHP version drift is the failure most likely to hurt. A platform upgrade will eventually make this package unresolvable and block the framework upgrade at the worst moment. Fork it before that happens, not after; the code surface is small enough that the fork is a few hours of work.

Pin missing-variable behavior in your adapter. Implementations differ on whether an absent variable yields an empty string, leaves the literal {var}, or throws. Whichever the package does, choose your contract explicitly in UriTemplateExpander and test it. Silent divergence between "no value" and "empty value" is a production bug that passes unit tests.

Encoding edge cases are where implementations quietly diverge. Reserved vs. unreserved characters, Unicode, empty lists, empty maps, and the prefix (:n) and explode (*) modifiers are the usual suspects. Do not assume "supports all four levels" means "tested against your templates." Golden-file the top ten real templates from your codebase — half a day, and it is the entire adoption gate.

Expansion is not sanitization. If variables carry user input and the expanded URL drives a redirect or a server-side outbound request, validate the result (scheme allowlist, host allowlist), not just the template. A template that looks safe with {+var} is an open-redirect surface.

No config, no facade, no service provider means no extension points. There is nothing to publish, override, or hook. Your extension point is the adapter seam plus your own test suite — treat that as the design, not a workaround.

Do not cache expansions. It is pure CPU, stateless, allocation-light, sub-microsecond work. Caching adds invalidation risk for no measurable gain.

Enforce the seam mechanically. A grep -r "Gdbots\\\\" app/ in CI (or a Composer/arch rule) keeps the namespace confined to the adapter file. Without that, the abstraction erodes in three sprints and the swap becomes a refactor instead of a one-line change.

Debugging approach. When an expansion looks wrong, reduce to the smallest template and dump which operator level it resolves to before suspecting the library. Most surprises are template syntax mistakes ({?a,b} vs. {&a,b} when chaining, forgetting + for a path that must keep slashes), not library bugs — which matters, because there is no upstream to file against.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager