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

Templated Uri Bundle Laravel Package

hautelook/templated-uri-bundle

Symfony bundle that adds support for RFC 6570 URI templates, letting you generate and expand dynamic URIs from route or link templates with named parameters. Useful for hypermedia APIs and consistent link generation.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require hautelook/templated-uri-bundle:^3.5

Note the naming mismatch you'll hit immediately: the package you install is hautelook/templated-uri-bundle, but the repository is goetas/TemplatedUriBundle. Confirm in composer.lock which namespace actually landed in vendor/ before writing imports — the bundle class is Goetas\TemplatedUriBundle\TemplatedUriBundle in the upstream repo.

As of 3.5.0 the real URI-template work is done by hautelook/templated-uri-router, which this release bumps to ^4.0 (a major bump of the dependency, shipped in a minor bundle release). Confirm the resolved version with composer show hautelook/templated-uri-router before writing anything that touches the lower-level expansion API.

Register it in config/bundles.php:

return [
    // ...
    Goetas\TemplatedUriBundle\TemplatedUriBundle::class => ['all' => true],
];

Where to look first

  1. TemplatedRouterInterface (and the concrete templated_uri.router service it aliases) — this is the whole public surface of the bundle. If it doesn't do what you need, nothing else in the bundle will save you.
  2. composer show hautelook/templated-uri-router — read the installed version and, if you're coming from a pre-3.5 install, the 4.0 changelog. The router is where the actual RFC 6570 expansion lives; the bundle is a thin adapter over it.
  3. The bundle's DependencyInjection/Configuration.php for the config tree — options are minimal, but check for an enabled/replace flag before assuming the router is wired into the container the way you expect.

First use case

The canonical value proposition is hypermedia / API discovery: you want to emit a link template into a response so a client can fill in the blanks, instead of guessing URL shapes.

Define a route like any other:

// config/routes.yaml
users_index:
    path: /users
    controller: App\Controller\UserController::index

Then, instead of $urlGenerator->generate('users_index', ['page' => 2]) which commits you to ?page=2, ask for the template:

use Goetas\TemplatedUriBundle\Routing\TemplatedRouterInterface;

final class UserController
{
    public function __construct(private TemplatedRouterInterface $router) {}

    public function index(): JsonResponse
    {
        return $this->json([
            '_links' => [
                'self' => $this->router->generateTemplated('users_index', []),
                // -> /users{?page,limit,sort}
            ],
        ]);
    }
}

That string is RFC 6570-valid and can be expanded by any conforming client.

Implementation Patterns

Template now, expand later. Keep two call sites separate in your head: concrete URL generation via Symfony's UrlGeneratorInterface when the client already gave you the parameters, and generateTemplated() when you're publishing capabilities. Don't try to use one for the other.

Pagination and filter discovery. The highest-leverage pattern is a resource representation that advertises its own query surface:

$links = [
    'self'   => $router->generateTemplated('users_index', []),
    'next'   => $urlGenerator->generate('users_index', ['page' => $page + 1]),
    'search' => $router->generateTemplated('users_search', []),
];

Clients get a stable contract for "what can I ask for" without you hard-coding ?page={page}&limit={limit} strings in docs that drift.

Wrap it before you spread it. Maintenance profile is still thin — one 2023 release, then a single-contributor dependency-bump release in 3.5.0. Do not inject TemplatedRouterInterface across twenty controllers. Create one thin application service:

final class LinkTemplateFactory
{
    public function __construct(
        private TemplatedRouterInterface $router,
        private UrlGeneratorInterface $urlGenerator,
    ) {}

    public function template(string $route, array $params = []): string
    {
        try {
            return $this->router->generateTemplated($route, $params);
        } catch (\Throwable $e) {
            // fall back to a concrete URL so a bad template never 500s a response
            return $this->urlGenerator->generate($route, $params);
        }
    }
}

That gives you a single seam to swap in hautelook/templated-uri-router directly (or your own expansion) if the bundle stops working on a future Symfony.

Twig integration. If you need templates in views, don't rely on a bundle-provided Twig function existing — expose your LinkTemplateFactory as a Twig extension yourself. One class, full control, no dependency on bundle internals.

Testing against the spec, not the bundle. Assert your generated templates expand to the URLs you expect using the same underlying URI-template library the bundle uses. Check the class name in vendor/hautelook/templated-uri-router first — the 4.0 bump may have moved it:

$template = $factory->template('users_index');          // /users{?page,limit}
$expanded = $expander->expand($template, ['page' => 2]); // hautelook/templated-uri-router API
self::assertSame('/users?page=2', $expanded);

This keeps your tests meaningful even if you replace the bundle.

OpenAPI / API docs. This is a clean fit: emit the template string as the path in an OpenAPI operation, or as servers entries, so your generated docs and your runtime hypermedia agree.

Gotchas and Tips

Package identity is a real hazard. hautelook/templated-uri-bundle pointing at goetas/TemplatedUriBundle means you're on a fork whose provenance you should verify. Check the composer.json name, autoload PSR-4 prefix, and the actual bundle class. If the fork's namespace differs from the upstream docs you're reading, every snippet you copy will fail to autoload.

3.5.0 is a dependency major bump, not a feature release. The entire diff is "Update hautelook/templated-uri-router to 4.0" — no bundle code changed. Two consequences:

  • Treat it as potentially breaking: a router 4.0 major can change expansion output (operator support, encoding, null/empty-value handling). Read the router's 4.0 changelog before upgrading an existing install, and diff your generated templates against a fixture before/after.
  • The bundle's own adapter code is unchanged since 2023, so it is now calling into a v4 router. If anything in the adapter relied on v3 internals, nobody has exercised that combination at scale.

Pin explicitly (hautelook/templated-uri-bundle:3.5.*) and let composer.lock hold the router at a known-good 4.x, rather than floating on ^3.4.

Symfony compatibility is still the big risk — verify it, don't assume it. The bundle itself hasn't been touched since January 2023 and predates recent Symfony majors. The router bump may have improved things, but run composer why-not symfony/framework-bundle 7 (or your target version) and confirm both the bundle's and the router's composer.json constraints. With zero dependents, don't expect a community fix.

RFC 6570 syntax collides with Symfony's placeholder syntax. Symfony route paths use {name} for path variables. RFC 6570 also uses {name} but adds operators: {+path}, {#fragment}, {?query,params}, {/segments}. Expressions with ?, #, +, / inside braces can confuse the route compiler or get treated as literal characters in the compiled regex. Practical guidance:

  • Keep path variables as plain {id} — those map cleanly.
  • Don't expect {?page,limit} to be matchable in a route path:; it's an output-only construct. Add query parameters to the template at generation time via the parameter array rather than baking the operator into the route definition.
  • Verify with bin/console debug:router after adding anything exotic.

Clearing the cache is not optional. The router (and any compiled route/template cache) won't notice route changes without bin/console cache:clear. If generateTemplated() returns stale or null output after a route edit — or immediately after the 3.5.0 upgrade — this is the first thing to try.

Encoding surprises. An expanded template's values get percent-encoded per the RFC's rules. A client expanding {id} with a/b may get a%2Fb, which then won't route the way you expect. Use reserved-expansion operators ({+id}) deliberately if you want slashes preserved — and understand the security implications of doing so. Re-check this specifically after the router 4.0 bump.

Templates are not clickable URLs. Never put the raw output of generateTemplated() into an href a browser will follow — the braces won't expand server-side. It belongs in a machine-readable payload (JSON-LD, HAL, OpenAPI), not in rendered HTML navigation.

Route leakage. Publishing templates exposes your route structure, including parameters you might not want advertised (internal IDs, admin flags). Gate template generation per-route rather than exposing everything the router knows.

Escape hatch: skip the bundle. It's a thin adapter over hautelook/templated-uri-router. If you only need expand($template, $vars) and not Symfony route-name integration, depend on the router library directly and delete this dependency. That removes the unmaintained-adapter risk — and for a package this size, it's usually the more defensible choice. Use the bundle only if the route-name-to-template mapping genuinely saves you work.

Extension points are limited. The practical ones are: decorate templated_uri.router with your own service implementing TemplatedRouterInterface (add caching, fallback, or logging there), or bypass the bundle. There's no plugin ecosystem to lean on.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo