Skip to content

Commit

Permalink
adds support for int backed enums to implicit enum route binding
Browse files Browse the repository at this point in the history
  • Loading branch information
monurakkaya committed Apr 11, 2024
1 parent dec31b0 commit 61ab22e
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/Illuminate/Foundation/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException;
use Illuminate\Routing\Exceptions\BackedEnumCaseWrongTypeException;
use Illuminate\Routing\Router;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -139,6 +140,7 @@ class Handler implements ExceptionHandlerContract
AuthenticationException::class,
AuthorizationException::class,
BackedEnumCaseNotFoundException::class,
BackedEnumCaseWrongTypeException::class,
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
Expand Down Expand Up @@ -625,6 +627,7 @@ protected function prepareException(Throwable $e)
{
return match (true) {
$e instanceof BackedEnumCaseNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
$e instanceof BackedEnumCaseWrongTypeException => new NotFoundHttpException($e->getMessage(), $e),
$e instanceof ModelNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
$e instanceof AuthorizationException && $e->hasStatus() => new HttpException(
$e->status(), $e->response()?->message() ?: (Response::$statusTexts[$e->status()] ?? 'Whoops, looks like something went wrong.'), $e
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Illuminate\Routing\Exceptions;

use RuntimeException;

class BackedEnumCaseWrongTypeException extends RuntimeException
{
/**
* Create a new exception instance.
*
* @param string $backedEnumClass
* @param string $case
* @return void
*/
public function __construct(string $backedEnumClass, string $case)
{
parent::__construct("Case [{$case}] is not a valid type for Backed Enum [{$backedEnumClass}].");
}
}
10 changes: 9 additions & 1 deletion src/Illuminate/Routing/ImplicitRouteBinding.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException;
use Illuminate\Routing\Exceptions\BackedEnumCaseWrongTypeException;
use Illuminate\Support\Reflector;
use Illuminate\Support\Str;
use TypeError;

class ImplicitRouteBinding
{
Expand All @@ -20,6 +22,7 @@ class ImplicitRouteBinding
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseWrongTypeException
*/
public static function resolveForRoute($container, $route)
{
Expand Down Expand Up @@ -74,6 +77,7 @@ public static function resolveForRoute($container, $route)
* @return \Illuminate\Routing\Route
*
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseWrongTypeException
*/
protected static function resolveBackedEnumsForRoute($route, $parameters)
{
Expand All @@ -86,7 +90,11 @@ protected static function resolveBackedEnumsForRoute($route, $parameters)

$backedEnumClass = $parameter->getType()?->getName();

$backedEnum = $backedEnumClass::tryFrom((string) $parameterValue);
try {
$backedEnum = $backedEnumClass::tryFrom($parameterValue);
} catch (TypeError) {
throw new BackedEnumCaseWrongTypeException($backedEnumClass, $parameterValue);
}

if (is_null($backedEnum)) {
throw new BackedEnumCaseNotFoundException($backedEnumClass, $parameterValue);
Expand Down
2 changes: 1 addition & 1 deletion src/Illuminate/Routing/RouteSignatureParameters.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
Expand Down
2 changes: 2 additions & 0 deletions src/Illuminate/Routing/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@ public static function toResponse($request, $response)
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseWrongTypeException
*/
public function substituteBindings($route)
{
Expand All @@ -953,6 +954,7 @@ public function substituteBindings($route)
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseWrongTypeException
*/
public function substituteImplicitBindings($route)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Illuminate/Support/Reflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions tests/Routing/Enums.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ enum CategoryBackedEnum: string
case People = 'people';
case Fruits = 'fruits';
}

enum CategoryIntBackedEnum: int
{
case People = 1;
case Fruits = 2;
}
42 changes: 42 additions & 0 deletions tests/Routing/ImplicitRouteBindingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException;
use Illuminate\Routing\Exceptions\BackedEnumCaseWrongTypeException;
use Illuminate\Routing\ImplicitRouteBinding;
use Illuminate\Routing\Route;
use PHPUnit\Framework\TestCase;
Expand All @@ -31,6 +32,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) {
Expand Down Expand Up @@ -91,6 +110,29 @@ public function test_implicit_backed_enum_internal_exception()
ImplicitRouteBinding::resolveForRoute($container, $route);
}

public function test_implicit_backed_enum_type_error_exception_for_invalid_type()
{
$action = ['uses' => function (CategoryIntBackedEnum $category) {
return $category->value;
}];

$route = new Route('GET', '/test', $action);
$route->parameters = ['category' => 'cars'];

$route->prepareForSerialization();

$container = Container::getInstance();

$this->expectException(BackedEnumCaseWrongTypeException::class);
$this->expectExceptionMessage(sprintf(
'Case [%s] is not a valid type for Backed Enum [%s].',
'cars',
CategoryIntBackedEnum::class,
));

ImplicitRouteBinding::resolveForRoute($container, $route);
}

public function test_it_can_resolve_the_implicit_model_route_bindings_for_the_given_route()
{
$this->expectNotToPerformAssertions();
Expand Down

0 comments on commit 61ab22e

Please sign in to comment.