diff --git a/src/Illuminate/Routing/ImplicitRouteBinding.php b/src/Illuminate/Routing/ImplicitRouteBinding.php index d3590de1d707..6f459faea793 100644 --- a/src/Illuminate/Routing/ImplicitRouteBinding.php +++ b/src/Illuminate/Routing/ImplicitRouteBinding.php @@ -8,6 +8,7 @@ use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException; use Illuminate\Support\Reflector; use Illuminate\Support\Str; +use ReflectionEnum; class ImplicitRouteBinding { @@ -69,11 +70,12 @@ public static function resolveForRoute($container, $route) /** * Resolve the Backed Enums route bindings for the route. * - * @param \Illuminate\Routing\Route $route - * @param array $parameters + * @param \Illuminate\Routing\Route $route + * @param array $parameters * @return \Illuminate\Routing\Route * * @throws \Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException + * @throws \ReflectionException */ protected static function resolveBackedEnumsForRoute($route, $parameters) { @@ -86,7 +88,12 @@ protected static function resolveBackedEnumsForRoute($route, $parameters) $backedEnumClass = $parameter->getType()?->getName(); - $backedEnum = $backedEnumClass::tryFrom((string) $parameterValue); + $reflectionEnum = new ReflectionEnum($backedEnumClass); + + match ($reflectionEnum->getBackingType()->getName()) { + 'int' => $backedEnum = collect($backedEnumClass::cases())->first(fn ($case) => (string) $case->value === (string) $parameterValue), + 'string' => $backedEnum = $backedEnumClass::tryFrom((string) $parameterValue), + }; if (is_null($backedEnum)) { throw new BackedEnumCaseNotFoundException($backedEnumClass, $parameterValue); diff --git a/src/Illuminate/Routing/RouteSignatureParameters.php b/src/Illuminate/Routing/RouteSignatureParameters.php index 7a4b6dadb996..239df49480ca 100644 --- a/src/Illuminate/Routing/RouteSignatureParameters.php +++ b/src/Illuminate/Routing/RouteSignatureParameters.php @@ -28,7 +28,7 @@ public static function fromAction(array $action, $conditions = []) return match (true) { ! empty($conditions['subClass']) => array_filter($parameters, fn ($p) => Reflector::isParameterSubclassOf($p, $conditions['subClass'])), - ! empty($conditions['backedEnum']) => array_filter($parameters, fn ($p) => Reflector::isParameterBackedEnumWithStringBackingType($p)), + ! empty($conditions['backedEnum']) => array_filter($parameters, fn ($p) => Reflector::isParameterBackedEnumWithValidBackingType($p)), default => $parameters, }; } diff --git a/src/Illuminate/Support/Reflector.php b/src/Illuminate/Support/Reflector.php index a767d5ea7073..83cb9c65c3c8 100644 --- a/src/Illuminate/Support/Reflector.php +++ b/src/Illuminate/Support/Reflector.php @@ -141,12 +141,12 @@ public static function isParameterSubclassOf($parameter, $className) } /** - * Determine if the parameter's type is a Backed Enum with a string backing type. + * Determine if the parameter's type is a Backed Enum with a string or int backing type. * * @param \ReflectionParameter $parameter * @return bool */ - public static function isParameterBackedEnumWithStringBackingType($parameter) + public static function isParameterBackedEnumWithValidBackingType($parameter) { if (! $parameter->getType() instanceof ReflectionNamedType) { return false; @@ -162,7 +162,7 @@ public static function isParameterBackedEnumWithStringBackingType($parameter) $reflectionBackedEnum = new ReflectionEnum($backedEnumClass); return $reflectionBackedEnum->isBacked() - && $reflectionBackedEnum->getBackingType()->getName() == 'string'; + && in_array($reflectionBackedEnum->getBackingType()->getName(), ['int', 'string']); } return false; diff --git a/tests/Routing/Enums.php b/tests/Routing/Enums.php index 6a97577a454c..ed2904a10d4b 100644 --- a/tests/Routing/Enums.php +++ b/tests/Routing/Enums.php @@ -13,3 +13,9 @@ enum CategoryBackedEnum: string case People = 'people'; case Fruits = 'fruits'; } + +enum CategoryIntBackedEnum: int +{ + case People = 1; + case Fruits = 2; +} diff --git a/tests/Routing/ImplicitRouteBindingTest.php b/tests/Routing/ImplicitRouteBindingTest.php index 18bb7a2ff2c1..29a1e6b6bee9 100644 --- a/tests/Routing/ImplicitRouteBindingTest.php +++ b/tests/Routing/ImplicitRouteBindingTest.php @@ -31,6 +31,24 @@ public function test_it_can_resolve_the_implicit_backed_enum_route_bindings_for_ $this->assertSame('fruits', $route->parameter('category')->value); } + public function test_it_can_resolve_the_implicit_int_backed_enum_route_bindings_for_the_given_route() + { + $action = ['uses' => function (CategoryIntBackedEnum $category) { + return $category->value; + }]; + + $route = new Route('GET', '/test', $action); + $route->parameters = ['category' => '1']; + + $route->prepareForSerialization(); + + $container = Container::getInstance(); + + ImplicitRouteBinding::resolveForRoute($container, $route); + + $this->assertSame(1, $route->parameter('category')->value); + } + public function test_it_can_resolve_the_implicit_backed_enum_route_bindings_for_the_given_route_with_optional_parameter() { $action = ['uses' => function (?CategoryBackedEnum $category = null) { @@ -91,6 +109,29 @@ public function test_implicit_backed_enum_internal_exception() ImplicitRouteBinding::resolveForRoute($container, $route); } + public function test_implicit_int_backed_enum_internal_exception() + { + $action = ['uses' => function (CategoryIntBackedEnum $category) { + return $category->value; + }]; + + $route = new Route('GET', '/test', $action); + $route->parameters = ['category' => ' 00001.']; + + $route->prepareForSerialization(); + + $container = Container::getInstance(); + + $this->expectException(BackedEnumCaseNotFoundException::class); + $this->expectExceptionMessage(sprintf( + 'Case [%s] not found on Backed Enum [%s].', + ' 00001.', + CategoryIntBackedEnum::class, + )); + + ImplicitRouteBinding::resolveForRoute($container, $route); + } + public function test_it_can_resolve_the_implicit_model_route_bindings_for_the_given_route() { $this->expectNotToPerformAssertions();