From d6639b4439d9c0ff35f43eadc1021aba3b18f83b Mon Sep 17 00:00:00 2001 From: Neil Carlo Sucuangco Date: Sun, 16 Nov 2025 03:34:57 +0800 Subject: [PATCH] Resolve route model binding when routes are defined in Action routes() method --- src/Decorators/ControllerDecorator.php | 36 +++++++++++++++- ...ctionWithRoutesAndImplicitBindingsTest.php | 42 +++++++++++++++++++ ...ollerWithRoutesAndImplicitBindingsTest.php | 42 +++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 tests/AsActionWithRoutesAndImplicitBindingsTest.php create mode 100644 tests/AsControllerWithRoutesAndImplicitBindingsTest.php diff --git a/src/Decorators/ControllerDecorator.php b/src/Decorators/ControllerDecorator.php index a8d7696..736898e 100644 --- a/src/Decorators/ControllerDecorator.php +++ b/src/Decorators/ControllerDecorator.php @@ -3,12 +3,14 @@ namespace Lorisleiva\Actions\Decorators; use Illuminate\Container\Container; +use Illuminate\Database\Eloquent\Model; use Illuminate\Routing\Route; use Illuminate\Routing\RouteDependencyResolverTrait; use Illuminate\Support\Str; use Lorisleiva\Actions\ActionRequest; use Lorisleiva\Actions\Concerns\DecorateActions; use Lorisleiva\Actions\Concerns\WithAttributes; +use ReflectionMethod; class ControllerDecorator { @@ -155,12 +157,44 @@ protected function resolveFromRouteAndCall($method) { $this->container = Container::getInstance(); + $this->container->instance(Route::class, $this->route); + + $parameters = $this->route->parametersWithoutNulls(); + $parameters = $this->resolveRouteModelBindings($parameters, $method); + $arguments = $this->resolveClassMethodDependencies( - $this->route->parametersWithoutNulls(), + $parameters, $this->action, $method ); return $this->action->{$method}(...array_values($arguments)); } + + protected function resolveRouteModelBindings(array $parameters, string $method): array + { + foreach ($parameters as $key => $value) { + if (is_object($value)) { + continue; + } + + $reflection = new ReflectionMethod($this->action, $method); + $reflectionParameters = $reflection->getParameters(); + + foreach ($reflectionParameters as $reflectionParameter) { + if ($reflectionParameter->getName() === $key) { + $type = $reflectionParameter->getType(); + if ($type && !$type->isBuiltin() && class_exists($type->getName())) { + $modelClass = $type->getName(); + if (is_subclass_of($modelClass, Model::class)) { + $parameters[$key] = $modelClass::findOrFail($value); + break; + } + } + } + } + } + + return $parameters; + } } diff --git a/tests/AsActionWithRoutesAndImplicitBindingsTest.php b/tests/AsActionWithRoutesAndImplicitBindingsTest.php new file mode 100644 index 0000000..e2948b8 --- /dev/null +++ b/tests/AsActionWithRoutesAndImplicitBindingsTest.php @@ -0,0 +1,42 @@ +get('/from-action/users/{user}', static::class); + } + + public function handle(User $user): User + { + return $user; + } +} + +it('supports implicit route model binding when using AsAction and route is defined in routes() method', function () { + Actions::registerRoutesForAction(AsActionWithRoutesAndImplicitBindingsTest::class); + + loadMigrations(); + createUser([ + 'id' => 42, + 'name' => 'John Doe', + ]); + + $response = $this->getJson('/from-action/users/42'); + + $response->assertOk(); + $response->assertJson([ + 'id' => 42, + 'name' => 'John Doe', + ]); +}); + diff --git a/tests/AsControllerWithRoutesAndImplicitBindingsTest.php b/tests/AsControllerWithRoutesAndImplicitBindingsTest.php new file mode 100644 index 0000000..c624a06 --- /dev/null +++ b/tests/AsControllerWithRoutesAndImplicitBindingsTest.php @@ -0,0 +1,42 @@ +get('/from-action/users/{user}', static::class); + } + + public function handle(User $user): User + { + return $user; + } +} + +it('supports implicit route model binding when route is defined in routes() method', function () { + Actions::registerRoutesForAction(AsControllerWithRoutesAndImplicitBindingsTest::class); + + loadMigrations(); + createUser([ + 'id' => 42, + 'name' => 'John Doe', + ]); + + $response = $this->getJson('/from-action/users/42'); + + $response->assertOk(); + $response->assertJson([ + 'id' => 42, + 'name' => 'John Doe', + ]); +}); +