jms/object-routing
jms/object-routing is a PHP library for routing based on object state rather than URLs. Define routes and generate targets by evaluating objects and their metadata, enabling flexible navigation and link generation in domain-driven apps.
Installation
composer require jms/object-routing
Add to composer.json under autoload:
{
"autoload": {
"psr-4": {
"App\\": "app/",
"JMS\\": "vendor/jms/object-routing/"
}
}
}
Run composer dump-autoload.
Basic Usage
Define a route generator class (e.g., app/Generators/UserGenerator.php):
use JMS\Router\Generator\GeneratorInterface;
use JMS\Router\Generator\GeneratorContext;
use JMS\Router\Generator\RouteGeneratorInterface;
class UserGenerator implements RouteGeneratorInterface {
public function generate(GeneratorContext $context) {
$user = $context->getObject();
return '/users/' . $user->getId();
}
}
Register Generator
Bind the generator in Laravel’s service container (e.g., in AppServiceProvider):
$this->app->bind(RouteGeneratorInterface::class, function ($app) {
return new UserGenerator();
});
Generate Routes Use the router in a controller or service:
use JMS\Router\Router;
use JMS\Router\Generator\RouteGeneratorInterface;
public function show(User $user) {
$router = new Router();
$router->addGenerator('user', $this->app->make(RouteGeneratorInterface::class));
$route = $router->generate('user', $user);
return redirect($route);
}
Dynamic Route Generation
Useful for RESTful APIs or admin panels where routes depend on object properties (e.g., /posts/{id}/comments/{commentId}).
Example:
class PostCommentGenerator implements RouteGeneratorInterface {
public function generate(GeneratorContext $context) {
$post = $context->getObject('post');
$comment = $context->getObject('comment');
return "/posts/{$post->getId()}/comments/{$comment->getId()}";
}
}
Contextual Routing
Pass additional context (e.g., locale, tenant) via GeneratorContext:
$context = new GeneratorContext($object, ['locale' => 'en']);
$router->generate('route_name', $object, $context);
Integration with Laravel’s Router
Combine with Laravel’s Route::get() for hybrid routing:
Route::get('/dynamic/{id}', function ($id) {
$user = User::find($id);
$router = app(Router::class);
return $router->generate('user_profile', $user);
});
Caching Routes Cache generated routes to avoid regeneration (e.g., in a service):
public function getCachedRoute($object, $generatorName) {
$cacheKey = md5($generatorName . serialize($object));
return Cache::remember($cacheKey, now()->addHours(1), function () use ($object, $generatorName) {
return $router->generate($generatorName, $object);
});
}
Validation Before Generation
Validate objects before generating routes (e.g., ensure id exists):
public function generate(GeneratorContext $context) {
$user = $context->getObject();
if (!$user->getId()) {
throw new \InvalidArgumentException('User ID is required.');
}
return "/users/{$user->getId()}";
}
Outdated Package
GeneratorContext:
use JMS\Router\Generator\GeneratorContext as BaseContext;
class GeneratorContext extends BaseContext {
public function __construct($object, array $parameters = []) {
parent::__construct($object, $parameters);
}
}
No Built-in Laravel Integration
public function __construct(private Router $router) {}
Context Cloning Issues
GeneratorContext may not clone objects properly. Use unserialize() or clone objects explicitly:
$context = new GeneratorContext(clone $object, $parameters);
Route Collisions
user_profile vs. user_edit). Use namespaces:
$router->addGenerator('user.profile', $generator);
Performance with Complex Objects
User->Posts->Comments) can be slow. Cache or lazy-load.Inspect Context
Dump GeneratorContext to verify objects/parameters:
$context = new GeneratorContext($user, ['tenant' => 'acme']);
dd($context->getObject(), $context->getParameters());
Test Edge Cases
if (!$context->getObject()) throw new \RuntimeException('Object missing.');method_exists() or isset() checks.Logging Generators Log generated routes for debugging:
public function generate(GeneratorContext $context) {
$route = "/users/{$context->getObject()->getId()}";
\Log::debug("Generated route: {$route}", ['object' => $context->getObject()]);
return $route;
}
Custom Generators
Extend RouteGeneratorInterface for domain-specific logic (e.g., API versioning):
class ApiV1UserGenerator implements RouteGeneratorInterface {
public function generate(GeneratorContext $context) {
return "/api/v1/users/{$context->getObject()->getId()}";
}
}
Middleware for Route Generation Create middleware to generate routes before dispatching:
public function handle($request, Closure $next) {
$request->merge(['route' => $this->router->generate('dynamic_route', $request->user)]);
return $next($request);
}
Reverse Routing
Combine with Laravel’s route() helper for bidirectional routing:
// Forward: Object → Route
$route = $router->generate('user', $user);
// Reverse: Route → Object (manual lookup)
$user = User::where('id', request('id'))->first();
Dynamic Route Parameters Support optional parameters in generators:
public function generate(GeneratorContext $context) {
$user = $context->getObject();
$params = $context->getParameters();
return "/users/{$user->getId()}" .
($params['action'] ?? null ? "/{$params['action']}" : '');
}
How can I help you explore Laravel packages today?