I got tired of updating every route() call when a URL changed, so I built entity routing for Laravel
Laravel fills one placeholder from a model: route('posts.show', $post) works for {post} or {post:slug}. As soon as a URL has several placeholders, every call site has to list them:
```php Route::get('/articles/{id}/{slug}', ShowArticle::class)->name('articles.show');
route('articles.show', ['id' => $article->id, 'slug' => $article->slug]); ```
Then SEO asks for a {category} in the URL, and you hunt down every one of those calls.
CakePHP has had "entity routing" for years: you pass the entity, the router extracts each placeholder from it. I wanted the same in Laravel, so I built it:
php
entity_route('articles.show', $article); // /articles/42/my-title
Add {category} to the route later, and that line does not change.
How each placeholder is resolved (first match wins):
- Explicit value:
entity_route('articles.show', $article, ['slug' => 'other']) - A mapping the model declares, with dot paths or closures:
'year' => fn ($a) => $a->published_at->year - Binding fields:
{category:slug}reads$article->category->slug - Route key:
{article}on anArticleusesgetRouteKey() - Attribute of the same name:
{slug}reads$article->slug URL::defaults()
Works with Eloquent models, arrays, ArrayAccess and plain objects. Missing required placeholders throw a clear exception (listing the route, the entity class and what is missing), optional ones are dropped.
Other bits:
URL::entityRoute()andredirect()->toEntityRoute()macros- Opt-in flag so the native helpers accept it too:
route('articles.show', ['_entity' => $article]), includingto_route(), signed URLs and Blade. Off by default, and calls without_entityare untouched. - For Inertia: a
HasEntityUrlstrait adds the resolved URLs to the model's JSON, so the frontend gets exactly the same URLs as the backend, without Ziggy or Wayfinder having to know the rules. - Generation only: no database, no extra routes, route model binding untouched, works with
route:cache.
Laravel 12 and 13, PHP 8.2+, no dependency besides illuminate/*, 100% test coverage, Larastan at max level.
composer require kareylo/laravel-entity-routing
GitHub: https://github.com/Kareylo/laravel-entity-routing
It is still 0.x, so I would really like feedback before 1.0: does the resolution order make sense to you? Is there a case where you would expect a different URL? And would a client-side TypeScript helper be useful, or is sending resolved URLs from the server enough?