请求
HTTP Requests
介绍
Laravel 的 Illuminate\Http\Request
类提供了一种面向对象的方式来与当前由应用程序处理的 HTTP 请求进行交互,并检索提交请求的输入内容、Cookie 和文件。
请求与交互
访问请求
要通过依赖注入获取当前的 HTTP 请求实例,你应该在路由闭包或控制器方法中导入 Illuminate\Http\Request
类。传入的请求实例将由 Laravel 服务容器 自动注入:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 存储新用户。
*/
public function store(Request $request): RedirectResponse
{
$name = $request->input('name');
// 存储用户...
return redirect('/users');
}
}
如上所述,你也可以在路由闭包上导入 Illuminate\Http\Request
类。服务容器将在执行时自动将传入请求注入到闭包中:
use Illuminate\Http\Request;
Route::get('/', function (Request $request) {
// ...
});
依赖注入和路由参数
如果你的控制器方法还需要从路由参数中接收输入,你应该在其他依赖项之后列出路由参数。例如,如果你的路由定义如下:
use App\Http\Controllers\UserController;
Route::put('/user/{id}', [UserController::class, 'update']);
你仍然可以类型提示 Illuminate\Http\Request
并通过以下方式定义控制器方法来访问你的 id
路由参数:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 更新指定的用户。
*/
public function update(Request $request, string $id): RedirectResponse
{
// 更新用户...
return redirect('/users');
}
}
请求路径、主机和方法
Illuminate\Http\Request
实例提供了多种方法来检查传入的 HTTP 请求,并扩展了 Symfony\Component\HttpFoundation\Request
类。我们将在下面讨论一些最重要的方法。
获取请求路径
path
方法返回请求的路径信息。因此,如果传入的请求指向 http://example.com/foo/bar
,path
方法将返回 foo/bar
:
$uri = $request->path();
检查请求路径 / 路由
is
方法允许你验证传入的请求路径是否与给定的模式匹配。使用此方法时,你可以使用 *
字符作为通配符:
if ($request->is('admin/*')) {
// ...
}
使用 routeIs
方法,你可以确定传入的请求是否匹配了 命名路由:
if ($request->routeIs('admin.*')) {
// ...
}
检索请求 URL
要检索传入请求的完整 URL,你可以使用 url
或 fullUrl
方法。url
方法将返回不带查询字符串的 URL,而 fullUrl
方法包括查询字符串:
$url = $request->url();
$urlWithQueryString = $request->fullUrl();
如果你想将查询字符串数据附加到当前 URL,你可以调用 fullUrlWithQuery
方法。此方法将给定数组的查询字符串变量与当前查询字符串合并:
$request->fullUrlWithQuery(['type' => 'phone']);
如果你想获取当前 URL 而不包含给定的查询字符串参数,你可以使用 fullUrlWithoutQuery
方法:
$request->fullUrlWithoutQuery(['type']);
检索请求主机
你可以通过 host
、httpHost
和 schemeAndHttpHost
方法检索传入请求的 “host”:
$request->host();
$request->httpHost();
$request->schemeAndHttpHost();
检索请求方法
method
方法将返回 HTTP 的请求操作。可以使用 isMethod
方法验证 HTTP 操作是否匹配给定字符串:
$method = $request->method();
if ($request->isMethod('post')) {
// ...
}
请求头
你可以使用 Illuminate\Http\Request
实例的 header
方法来检索请求头。如果请求中不存在头部信息,则会返回 null
。然而,如果请求中不存在头部信息,header
方法接受一个可选的第二个参数,该参数将当作头部信息返回:
$value = $request->header('X-Header-Name');
$value = $request->header('X-Header-Name', 'default');
hasHeader
方法可用来判断请求中是否包含给定的头:
if ($request->hasHeader('X-Header-Name')) {
// ...
}
为方便起见,可以使用 bearerToken
方法从 Authorization
头中检索承载令牌。如果没有这样的头,则会返回一个空字符串:
$token = $request->bearerToken();
请求 IP 地址
ip
方法可用来检索发出请求到你的应用程序的客户端的 IP 地址:
$ipAddress = $request->ip();
如果你想检索一个包含所有客户端 IP 地址的数组,包括所有代理转发的 IP 地址,你可以使用 ips
方法。「原始」 客户端 IP 地址将位于数组的末端:
$ipAddresses = $request->ips();
通常,应该将 IP 地址视为不可信的、用户控制的输入,并仅出于信息目的使用。
内容协商
Laravel 提供了几个方法通过请求的 Accept
头来检查进来的请求的请求内容类型。首先,getAcceptableContentTypes
方法将返回一个数组,包含请求头中所有的 Accept :
$contentTypes = $request->getAcceptableContentTypes();
accepts
方法接受一个内容类型的数组,如果数组和请求中的 accept 匹配,返回 true
,否则返回 false
:
if ($request->accepts(['text/html', 'application/json'])) {
// ...
}
你可以使用 prefers
方法来确定请求最倾向于给定数组中的哪种内容类型。如果请求不接受任何提供的内容类型,将返回 null
:
$preferred = $request->prefers(['text/html', 'application/json']);
由于许多应用只返回 HTML 或 JSON,你可以使用 expectsJson
方法快速判断传入请求是否期望一个 JSON 响应:
if ($request->expectsJson()) {
// ...
}
PSR-7 请求
PSR-7 标准定义了 HTTP 消息(包括请求和响应)的接口。如果你想获取一个 PSR-7 请求实例而不是 Laravel 请求实例,首先需要安装几个库。Laravel 使用 Symfony HTTP Message Bridge 组件,将典型的 Laravel 请求和响应转换为兼容 PSR-7 的实现:
composer require symfony/psr-http-message-bridge
composer require nyholm/psr7
安装完这些库后,你可以在路由闭包或控制器方法中通过类型提示 PSR-7 请求接口来获取 PSR-7 请求:
use Psr\Http\Message\ServerRequestInterface;
Route::get('/', function (ServerRequestInterface $request) {
// ...
});
[!注意]
I如果你从路由或控制器返回一个 PSR-7 响应实例,框架会自动将其转换回 Laravel 响应实例并显示。
输入(Input)
获取输入
获取所有输入数据
你可以使用 all
方法以数组形式获取请求的所有输入数据。无论请求是来自 HTML 表单还是 XHR 请求,都可使用此方法:
$input = $request->all();
你也可以使用 collect
方法,以 集合(collection) 的形式获取请求的所有输入数据:
$input = $request->collect();
collect
方法还允许你获取请求输入中的某个子集,并以集合(Collection)的形式返回:
$request->collect('users')->each(function (string $user) {
// ...
});
获取单个输入值
通过几个简单的方法,你可以从 Illuminate\Http\Request
实例中访问所有用户输入,而无需担心请求使用了哪种 HTTP 动作。无论 HTTP 动作是什么,input
方法都可以用来获取用户输入:
$name = $request->input('name');
你可以给 input
方法传入第二个参数作为默认值,如果请求中没有该输入值,则返回默认值:
$name = $request->input('name', 'Sally');
当处理包含数组输入的表单时,可以使用“点”符号访问数组元素:
$name = $request->input('products.0.name');
$names = $request->input('products.*.name');
你也可以不传任何参数调用 input
方法,以获取所有输入值的关联数组:
$input = $request->input();
从查询字符串中获取输入
虽然 input
方法会从整个请求载荷(包括查询字符串)中获取值,但 query
方法仅会从查询字符串中获取值:
$name = $request->query('name');
如果请求中没有该查询字符串值,会返回 query
方法第二个参数的默认值:
$name = $request->query('name', 'Helen');
你也可以不传参数调用 query
方法,以获取所有查询字符串值的关联数组:
$query = $request->query();
获取 JSON 输入值
当向应用发送 JSON 请求时,只要请求的 Content-Type
头正确设置为 application/json
,你就可以通过 input
方法访问 JSON 数据。你甚至可以使用“点”语法来获取嵌套在 JSON 数组或对象中的值:
$name = $request->input('user.name');
获取可字符串化(Stringable)的输入值
你可以不用直接获取原始的字符串,而是用 string
方法获取请求数据作为一个 Illuminate\Support\Stringable 实例:
$name = $request->string('name')->trim();
获取整型输入值
要以整数形式获取输入值,可以使用 integer
方法。该方法会尝试将输入值强制转换为整数。如果输入不存在或者转换失败,将返回你指定的默认值。这对分页或其他数字类型输入特别有用:
$perPage = $request->integer('per_page');
获取布尔型输入值
处理 HTML 元素如复选框时,应用可能收到一些“真假值”实际上是字符串,比如 "true"
或 "on"
。为方便起见,可以使用 boolean
方法将这些值作为布尔值获取。boolean
方法对 1
, "1"
, true
, "true"
, "on"
和 "yes"
返回 true
,其它所有值返回 false
:
$archived = $request->boolean('archived');
获取数组输入值
包含数组的输入值可以通过 array
方法获取。该方法会始终把输入值转换成数组。如果请求中没有给定名称的输入值,则返回空数组:
$versions = $request->array('versions');
获取日期输入值
为了方便起见,包含日期 / 时间的输入值可以通过 date
方法以 Carbon 实例的形式获取。
如果请求中不包含指定名称的输入值,将返回 null
:
$birthday = $request->date('birthday');
date
方法接受的第二个和第三个参数可分别用于指定日期的格式和时区:
$elapsed = $request->date('elapsed', '!H:i', 'Europe/Madrid');
如果输入值存在但格式无效,将抛出 InvalidArgumentException
异常;因此,建议在调用 date
方法前先验证输入值。
获取枚举输入值
与 PHP 枚举 对应的输入值也可以从请求中获取。
如果请求中不包含指定名称的输入值,或者该枚举没有与输入值匹配的底层值,将返回 null
。
enum
方法的第一个参数是输入值的名称,第二个参数是枚举类名:
use App\Enums\Status;
$status = $request->enum('status', Status::class);
你也可以提供一个默认值,如果输入值缺失或无效,将返回该默认值:
$status = $request->enum('status', Status::class, Status::Pending);
如果输入值是一个与 PHP 枚举对应的值数组,可以使用 enums
方法将该数组作为枚举实例数组获取:
use App\Enums\Product;
$products = $request->enums('products', Product::class);
通过动态属性获取输入值
你还可以通过 Illuminate\Http\Request
实例的动态属性来访问用户输入。
例如,如果你的应用表单中有一个 name
字段,可以像这样获取它的值:
$name = $request->name;
当使用动态属性时,Laravel 会先在请求载荷(payload)中查找该参数的值。
如果找不到,则会在已匹配路由的参数中继续查找该字段。
获取部分输入数据
如果需要获取输入数据的子集,可以使用 only
和 except
方法。
这两个方法都可以接受一个 数组
或动态参数列表:
$input = $request->only(['username', 'password']);
$input = $request->only('username', 'password');
$input = $request->except(['credit_card']);
$input = $request->except('credit_card');
[!警告]
only
方法会返回请求中存在的、你所指定的所有键值对;
但对于请求中不存在的键,不会返回对应的键值对。
判断输入是否存在
你可以使用 has
方法判断请求中是否存在某个值。
如果存在,has
方法会返回 true
:
if ($request->has('name')) {
// ...
}
当传入数组时,has
方法会判断数组中的所有值是否都存在:
if ($request->has(['name', 'email'])) {
// ...
}
hasAny
方法会在任意一个指定值存在时返回 true
:
if ($request->hasAny(['name', 'email'])) {
// ...
}
whenHas
方法会在请求中存在某个值时执行给定的闭包:
$request->whenHas('name', function (string $input) {
// ...
});
你也可以给 whenHas
方法传递第二个闭包,当指定的值不存在于请求中时执行:
$request->whenHas('name', function (string $input) {
// “name” 值存在...
}, function () {
// “name” 值不存在...
});
如果你想判断请求中是否存在某个值并且它不是空字符串,可以使用 filled
方法:
if ($request->filled('name')) {
// ...
}
如果你想判断请求中是否缺少某个值或它是空字符串,可以使用 isNotFilled
方法:
if ($request->isNotFilled('name')) {
// ...
}
当传入一个数组时,isNotFilled
方法会检查所有指定的值是否都缺失或为空:
if ($request->isNotFilled(['name', 'email'])) {
// ...
}
anyFilled
方法在指定的任意一个值不是空字符串时返回 true
:
if ($request->anyFilled(['name', 'email'])) {
// ...
}
whenFilled
方法会在请求中存在某个值且该值不是空字符串时执行给定的闭包:
$request->whenFilled('name', function (string $input) {
// ...
});
你也可以给 whenFilled
方法传递第二个闭包,当该值不是“已填充”状态时执行:
$request->whenFilled('name', function (string $input) {
// “name” 值已填充...
}, function () {
// “name” 值未填充...
});
如果想判断请求中是否缺少某个键,可以使用 missing
和 whenMissing
方法:
if ($request->missing('name')) {
// ...
}
$request->whenMissing('name', function () {
// “name” 值缺失...
}, function () {
// “name” 值存在...
});
合并额外输入
有时你可能需要手动将额外的输入合并到请求现有的输入数据中。为此,你可以使用 merge
方法。如果请求中已经存在给定的输入键,它将被 merge
方法提供的数据覆盖:
$request->merge(['votes' => 0]);
mergeIfMissing
方法可用于在请求的输入数据中对应的键不存在时才合并输入:
$request->mergeIfMissing(['votes' => 0]);
旧输入
Laravel 允许你在下一次请求中保留上一次请求的输入。此功能在检测到验证错误后重新填充表单时特别有用。然而,如果你使用 Laravel 自带的验证功能,你可能不需要直接手动使用这些会话输入闪存方法,因为一些内置的验证机制会自动调用它们。
将输入闪存到session中
Illuminate\Http\Request
类的 flash
方法会将当前请求的输入闪存到 session 中,以便在用户下一个请求时可用:
$request->flash();
你也可以使用 flashOnly
和 flashExcept
方法将请求数据的一个子集闪存到会话中。
这些方法对于避免将密码等敏感信息存储到会话中非常有用:
$request->flashOnly(['username', 'email']);
$request->flashExcept('password');
先保存用户输入,再跳转页面
由于你通常会希望将输入闪存到 session 中,然后再重定向回之前的页面,你可以使用 withInput
方法轻松地将输入闪存链式调用到重定向上:
return redirect('/form')->withInput();
return redirect()->route('user.create')->withInput();
return redirect('/form')->withInput(
$request->except('password')
);
获取旧输入
要从前一次请求中获取闪存的输入,可以在 Illuminate\Http\Request
实例上调用 old
方法。old
方法将从 session 中获取先前闪存的输入数据:
$username = $request->old('username');
Laravel 还提供了全局的 old
辅助函数。如果你在 Blade 模板 中显示旧输入,使用 old
辅助函数来重新填充表单会更加方便。如果指定字段没有旧输入,将返回 null
:
<input type="text" name="username" value="{{ old('username') }}">
Cookies
从请求中获取 Cookies
Laravel 框架创建的所有 cookie 都是加密的,并带有认证码签名,这意味着如果 cookie 被客户端修改,它们将被视为无效。要从请求中获取 cookie 值,请在 Illuminate\Http\Request
实例上使用 cookie
方法:
$value = $request->cookie('name');
输入修剪与标准化
默认情况下,Laravel 在应用程序的全局中间件栈中包含了 Illuminate\Foundation\Http\Middleware\TrimStrings
和 Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull
中间件。这些中间件会自动修剪请求中所有传入的字符串字段,并将任何空字符串字段转换为 null
。这使得你在路由和控制器中无需担心这些标准化问题。
禁用输入标准化
如果你希望对所有请求禁用此行为,可以通过在应用程序的 bootstrap/app.php
文件中调用 $middleware->remove
方法,从应用的中间件栈中移除这两个中间件:
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\TrimStrings;
->withMiddleware(function (Middleware $middleware) {
$middleware->remove([
ConvertEmptyStringsToNull::class,
TrimStrings::class,
]);
})
如果你只想对应用的一部分请求禁用字符串修剪和空字符串转换,可以在 bootstrap/app.php
文件中使用 trimStrings
和 convertEmptyStringsToNull
中间件方法。两个方法都接受一个闭包数组,闭包应返回 true
或 false
,用以指示是否跳过输入标准化:
->withMiddleware(function (Middleware $middleware) {
$middleware->convertEmptyStringsToNull(except: [
fn (Request $request) => $request->is('admin/*'),
]);
$middleware->trimStrings(except: [
fn (Request $request) => $request->is('admin/*'),
]);
})
文件
获取上传文件
你可以通过 Illuminate\Http\Request
实例的 file
方法或动态属性来获取上传的文件。file
方法返回 Illuminate\Http\UploadedFile
类的实例,该类继承自 PHP 的 SplFileInfo
类,并提供多种方法用于操作文件:
$file = $request->file('photo');
$file = $request->photo;
你可以使用 hasFile
方法判断请求中是否存在文件:
if ($request->hasFile('photo')) {
// ...
}
验证上传是否成功
除了检查文件是否存在之外,你还可以通过 isValid
方法验证文件上传过程中是否出现问题:
if ($request->file('photo')->isValid()) {
// ...
}
文件路径和扩展名
UploadedFile
类还包含用于访问文件完整路径和扩展名的方法。extension
方法会根据文件内容尝试猜测文件的扩展名。这个扩展名可能与客户端提供的扩展名不同:
$path = $request->photo->path();
$extension = $request->photo->extension();
其他文件方法
UploadedFile
实例上还有许多其他方法。有关这些方法的更多信息,请查看该类的 API 文档。
存储上传文件
要存储上传的文件,通常会使用配置好的 文件系统 之一。UploadedFile
类提供了 store
方法,它会将上传的文件移动到指定的磁盘上,该磁盘可以是本地文件系统上的位置,也可以是云存储位置,例如 Amazon S3。
store
方法接受一个路径参数,该路径相对于文件系统配置的根目录。此路径不应包含文件名,因为系统会自动生成一个唯一 ID 作为文件名。
store
方法还接受一个可选的第二个参数,用于指定存储文件的磁盘名称。该方法会返回文件相对于磁盘根目录的路径:
$path = $request->photo->store('images');
$path = $request->photo->store('images', 's3');
如果你不希望自动生成文件名,可以使用 storeAs
方法,它接受路径、文件名以及磁盘名称作为参数:
$path = $request->photo->storeAs('images', 'filename.jpg');
$path = $request->photo->storeAs('images', 'filename.jpg', 's3');
[!注意]
有关 Laravel 文件存储的更多信息,请查看完整的 文件存储文档。
配置受信任的代理
当你的应用运行在终止 TLS / SSL 证书的负载均衡器后面时,你可能会发现使用 url
辅助函数生成的链接有时不是 HTTPS。这通常是因为你的应用通过端口 80 被负载均衡器转发流量,而应用并不知道应该生成安全链接。
为了解决这个问题,你可以启用 Laravel 应用自带的 Illuminate\Http\Middleware\TrustProxies
中间件,它允许你快速自定义哪些负载均衡器或代理应该被应用信任。受信任的代理可以通过在应用的 bootstrap/app.php
文件中使用 trustProxies
中间件方法指定:
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(at: [
'192.168.1.1',
'10.0.0.0/8',
]);
})
除了配置受信任的代理,你还可以配置应被信任的代理头信息:
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(headers: Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB
);
})
[!注意]
如果你使用 AWS Elastic Load Balancing,headers
的值应该是Request::HEADER_X_FORWARDED_AWS_ELB
。如果你的负载均衡器使用 RFC 7239 标准的Forwarded
头,headers
的值应为Request::HEADER_FORWARDED
。有关headers
值可用常量的更多信息,请参考 Symfony 的 信任代理文档。
信任所有代理
如果你使用的是 Amazon AWS 或其他「云」负载均衡器提供商,则可能不知道实际负载均衡器的 IP 地址。在这种情况下,你可以使用 *
来信任所有代理:
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(at: '*');
})
配置可信任的 Host
默认情况下,Laravel 将响应它接收到的所有请求,而不管 HTTP 请求的 Host
标头的内容是什么。此外,在 web 请求期间生成应用程序的绝对 URL 时,将使用 Host
标头的值。
通常情况下,你应该配置你的 Web 服务器(如 Nginx 或 Apache)仅向匹配给定主机名的应用程序发送请求。然而,如果你没有直接自定义你的 Web 服务器的能力,可以通过为你的应用程序启用 Illuminate\Http\Middleware\TrustHosts
中间件,指示 Laravel 仅响应特定主机名的请求。
为了启用 TrustHosts
中间件,你应该在应用程序 bootstrap/app.php
文件中,调用 trustHosts
中间件方法。通过使用该方法中的 at
参数,你可以指定应用程序应该响应的主机名。具有其他 Host
标头值的传入请求将被拒绝:
->withMiddleware(function (Middleware $middleware) {
$middleware->trustHosts(at: ['laravel.test']);
})
默认情况下,来自应用程序 URL 子域名的请求也会自动被信任。如果你想禁用这种行为,可以使用 subdomains
参数:
->withMiddleware(function (Middleware $middleware) {
$middleware->trustHosts(at: ['laravel.test'], subdomains: false);
})
如果你需要访问应用程序的配置文件或数据库来确定你的可信任的主机,你可以再 at
参数提供一个闭包:
->withMiddleware(function (Middleware $middleware) {
$middleware->trustHosts(at: fn () => config('app.trusted_hosts'));
})
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: