加了强类型如何将请求参数转为整型
最近公司项目,加了强类型,踩了不少坑,这次分享一下关于强制转换请求参数格式的问题。
背景
我们有很多接口,需要前端传$limit这个参数来限制请求数据条数,在Controller中获取参数
$params = $request->validate([
'limit' => 'required|integer'
]);
$this->service->foo($params['limit']);
在Service中定义具体实现方法
public function foo(int $limit): array
{
return [];
}
这样子看起来没啥问题,但实际运行,就会报错,Argument 1 passed to foo() must be of the type integer, string given。虽然我们在validate中限定了integer,但是实际上传过来的参数还是字符串,这样一旦加了强类型,方法就会报错了,那遇到这种情况该如何处理呢,总不能每次调用方法的时候都加一个(int)强制类型转换吧,下面我会介绍一下处理的几种方式。
中间件
我们接口有自定义了一些中间件,最开始的想法就只直接在中间件中强制转换一下$limit的类型
$params = $request->all();
foreach ($params as $key => $param) {
if ('limit' != $key) {
continue;
}
$request->request->set($key, intval($param));
}
这种做法,虽然解决了$limit的类型问题,但是有很多局限性,比如$quantity等其他参数也需要转换呢?而且这种写法也损耗性能。
重写ValidatesRequests
我们在基类Controller当中是有引用Illuminate\Foundation\Validation\ValidatesRequests这个trait的,既然我们的参数都要经过validate,那么我们就直接重写这个文件,在验证的时候,就进行类型转换
<?php
declare(strict_types=1);
namespace App\Http\Validation;
use Illuminate\Foundation\Validation\ValidatesRequests as BaseValidatesRequests;
use Illuminate\Http\Request;
trait ValidatesRequests
{
use BaseValidatesRequests;
/**
* {@inheritdoc}
*/
public function validate(Request $request, array $rules, array $messages = [], array $customAttributes = []): array
{
if (array_get($rules, 'limit')) {
$request->request->set('limit', intval($request->limit));
}
return $this->getValidationFactory()->make(
$request->all(), $rules, $messages, $customAttributes
)->validate();
}
}
这种做法也局限了参数,不同的参数需要写成一个数组,使用in_array()去判断,并不是一种好的实现方式,不过不需要每个请求都判断一次了,有validate才判断,性能损耗比上一种方式好一些。
为了更好的匹配不同参数,又进行了优化下,将验证后的数据取出,根据判断验证规则中是否包含integer规则来处理类型转换
$validated = $this->getValidationFactory()->make(
$request->all(), $rules, $messages, $customAttributes
)->validate();
foreach ($rules as $key => $rule) {
if (str_contains($rule, 'integer')) {
$validated[$key] = intval($validated[$key]);
}
}
return $validated
这样子,就不需要特殊定义需要转换的参数了。
重写ValidatorFactory
第三种实现方式其实与第二种类似,区别就是第一种方式是引用的trait,所以我们控制器中验证参数的时候就必须使用,$this->validate($request, []),而我们的代码中很多是使用$request->validate([]),这种方式的,为了不大量修改代码,所以换了一种实现方式。我们重写了ValidationServiceProvider,这个文件只是复制继承一下原文件,然后在配置文件app.php中使用我们自己定义的ValidationServiceProvider,最后我们需要重写Illuminate\Validation\Factory这个文件,去实现我们的需求。
public function make(array $data, array $rules, array $messages = [], array $customAttributes = []): \Illuminate\Validation\Validator
{
$validator = parent::make($data, $rules, $messages, $customAttributes);
$validator->after(function ($v): void {
if (! $v->getMessageBag()->isEmpty()) {
return;
}
$data = $v->getData();
foreach (array_keys($v->getRules()) as $attribute) {
if (! $v->hasRule($attribute, 'Integer')) {
continue;
}
$value = array_get($data, $attribute, null);
//只有当用户有传值进来时, 才转换
if (null === $value) {
continue;
}
$data[$attribute] = $value;
}
$v->setData($data);
});
return $validator;
}
可以看出,三种不同的方式,一种种在改善,正如我们老大所说的,没有最完美的实现方式,我们能做的,就是不断的去完善自己的代码,不断的去code review。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: