控制器和闭包路由中的基本类型
发布时间 作者 Paul Redmond
我之前没有考虑过在 Laravel 控制器中对基本类型进行类型提示。PHP 只有四种标量基本类型:bool
、int
、float
和 string
- 关于路由,string
和 int
是您最可能想要的类型。但是,我通常不会在我的控制器中对标量基本类型进行类型提示。
我最近看到一个由 TypeError 引起的对控制器操作进行类型提示的问题,因此我想演示一些可以在其中安全使用带有 int
类型的类型提示控制器的示例。
考虑以下路由操作,并思考当调用时 $orderId
将是什么类型
Route::get('/order/{order_id}', function ($orderId) { return [ 'type' => gettype($orderId), 'value' => $orderId, ];});
当调用此闭包时,$orderId
将是一个 string
。如果您进行快速测试,您将看到以下内容通过
/** * A basic test example. * * @return void */public function test_example(){ $response = $this->get('/order/123'); $response->assertJson([ 'type' => 'string', 'value' => '123', ]);}
现在,假设您期望订单 ID 始终是一个整数,因此您想对参数进行类型提示
Route::get('/order/{order_id}', function (int $orderId) { return [ 'type' => gettype($orderId), 'value' => $orderId, ];});
如果您返回到您的测试,它现在将失败,并显示以下输出
--- Expected+++ Actual@@ @@ array (- 'type' => 'string',- 'value' => '123',+ 'type' => 'integer',+ 'value' => 123, )
尽管我们实际上将 123
的 string
传递给路由函数,但 PHP 通过类型强制默认处理它。换句话说,PHP 在调用路由函数时尝试将我们的值从字符串转换为整数。
虽然从技术上讲这种方法将起作用并保证 integer
类型,但我们仍然有另一个您可能已经发现的问题:如果用户传递了一些无法从字符串转换为整数的内容怎么办?
如果我们将我们的测试更新为以下内容,我们将收到一个 TypeError
public function test_example(){ $this->withoutExceptionHandling(); $response = $this->get('/order/ABC-123'); $response->assertJson([ 'type' => 'integer', 'value' => 123, ]);}
运行测试将给出以下错误
TypeError: Illuminate\Routing\RouteFileRegistrar::{closure}():Argument #1 ($orderId) must be of type int, string given, called in .../vendor/laravel/framework/src/Illuminate/Routing/Route.php on line 238
如果我们想在路由中进行类型提示整数,我们应该确保我们的路由具有 正则表达式约束
Route::get('/order/{order_id}', function (int $orderId) { return [ 'type' => gettype($orderId), 'value' => $orderId, ];})->where('order_id', '[0-9]+');
在添加路由参数约束后,只有数字值才能匹配路由,从而确保类型强制按预期工作。以下测试将更准确,以确保您无法使用非数字路由器参数匹配订单路由
public function test_example(){ $response = $this->get('/order/ABC-123'); $response->assertNotFound();}
现在,如果您想使用类型提示,您可以假设您的闭包路由的类型可以安全地强制转换为整数。虽然用例有限,但我认为了解这种细微差别可以帮助尝试对路由器参数进行类型提示的人们。
这如何受到严格类型的影响?
我想指出,declare(strict_types=1);
没有效果,因为调用代码位于 Laravel 框架内,该框架不使用 strict_types
声明,因此将发生类型强制
- Laravel 的 Controller::callAction() 用于控制器
- Laravel 的 Route::runCallable() 用于基于闭包的路由
在 PHP 的类型 声明文档 中,严格类型具有以下关于严格类型如何工作的说明
严格类型适用于从内部启用严格类型的文件进行的函数调用,而不适用于在该文件中声明的函数。如果未启用严格类型的文件调用在启用严格类型的文件中定义的函数,则将尊重调用者的首选项(强制类型),并且将强制转换该值。
其他方法
如果您在控制器和闭包中具有标量路由参数,您可以省略标量类型并在您的控制器方法中进行类型转换
Route::get( '/order/{order_id}', function ($orderId, SomeService $someService) { // Cast for a method that strictly type-hints an integer $result = $someService->someMethod((int) $orderId); // ... });
大多数情况下,您将使用 路由模型绑定 用于匹配数字 ID 的路由参数;但是,当您具有想要使用基本标量类型 int
进行类型提示的数字路由参数时,请考虑这种方法。