HTTP 请求
介绍
Laravel 的 Illuminate\Http\Request
类提供了一种面向对象的方式,用于与当前由应用程序处理的 HTTP 请求进行交互,并检索随请求一起提交的输入、Cookies 和文件。
与请求交互
访问请求
要通过依赖注入获取当前 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']);
请求头
你可以使用 header
方法从 Illuminate\Http\Request
实例中检索请求头。如果请求中没有该头部,将返回 null
。不过,header
方法接受一个可选的第二个参数,如果请求中没有该头部,则返回该默认值:
$value = $request->header('X-Header-Name');
$value = $request->header('X-Header-Name', 'default');
hasHeader
方法可用于判断请求中是否包含指定的头部:
if ($request->hasHeader('X-Header-Name')) {
// ...
}
为了方便起见,bearerToken
方法可以用来从 Authorization
头部中检索 Bearer token。如果该头部不存在,将返回一个空字符串:
$token = $request->bearerToken();
请求IP地址
ip
方法可以用来检索发起请求的客户端的 IP 地址:
$ipAddress = $request->ip();
如果你想检索一个包含所有通过代理转发的客户端 IP 地址的数组,可以使用 ips
方法。数组中的 “原始” 客户端 IP 地址将位于最后:
$ipAddresses = $request->ips();
通常情况下,IP 地址应视为不可信的、用户控制的输入,仅供参考用途。
内容协商
Laravel 提供了几种方法来检查传入请求的 Accept
头部中的请求内容类型。首先,getAcceptableContentTypes
方法将返回一个包含请求接受的所有内容类型的数组:
$contentTypes = $request->getAcceptableContentTypes();
accepts
方法接受一个内容类型数组,如果请求接受其中任何一个内容类型,则返回 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 请求:
use Psr\Http\Message\ServerRequestInterface;
Route::get('/', function (ServerRequestInterface $request) {
// ...
});
如果你从路由或控制器返回一个 PSR-7 响应实例,它将自动转换回一个 Laravel 响应实例,并由框架显示。 |
输入
获取输入
检索所有输入数据
你可以使用 all
方法检索所有传入请求的输入数据,返回一个 array
。无论请求来自 HTML 表单还是 XHR 请求,都可以使用此方法:
$input = $request->all();
通过 collect
方法,你可以将所有传入请求的输入数据作为【集合】进行检索:
$input = $request->collect();
collect
方法还允许你检索传入请求的子集数据,作为集合返回:
$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');
如果请求的查询字符串值不存在,将返回此方法的第二个参数:
$name = $request->query('name', 'Helen');
你还可以调用 query
方法而不传递任何参数,以便将所有查询字符串值作为关联数组返回:
$query = $request->query();
检索 JSON 输入值
当向你的应用程序发送 JSON 请求时,你可以通过 input
方法访问 JSON 数据,只要请求的 Content-Type
头被正确设置为 application/json
。你甚至可以使用 “点” 符号来检索嵌套在 JSON 数组/对象中的值:
$name = $request->input('user.name');
检索字符串输入值
你可以使用 string
方法来获取请求的数据作为 Illuminate\Support\Stringable
实例,而不是原始字符串:
$name = $request->string('name')->trim();
检索整数输入值
要将输入值作为整数检索,你可以使用 integer
方法。该方法会尝试将输入值转换为整数。如果输入不存在或转换失败,则返回你指定的默认值。这对于分页或其它数字输入特别有用:
$perPage = $request->integer('per_page');
检索布尔输入值
在处理 HTML 元素(如复选框)时,应用程序可能会收到 “真值” 值,这些值实际上是字符串。例如,"true" 或 "on"。为了方便起见,你可以使用 boolean
方法将这些值检索为布尔值。boolean
方法对以下值返回 true
:1, "1", true, "true", "on", 和 "yes"。其它所有值将返回 false
:
$archived = $request->boolean('archived');
检索日期输入值
为了方便起见,包含日期/时间的输入值可以使用 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);
如果输入值是一个数组,且数组中的值与 PHP 枚举对应,你可以使用 enums
方法将这些值作为枚举实例数组进行检索:
use App\Enums\Product;
$products = $request->enums('products', Product::class);
输入存在性
你可以使用 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')) {
// ...
}
whenMissing
方法会在请求中缺少某个值时执行给定的闭包:
$request->whenMissing('name', function () {
// "name" 值缺失...
}, function () {
// "name" 值存在...
});
合并附加输入
有时你可能需要手动将额外的输入数据合并到请求的现有输入数据中。为此,你可以使用 merge
方法。如果请求中已经存在某个输入键,merge
方法会用提供的数据覆盖该键的值:
$request->merge(['votes' => 0]);
mergeIfMissing
方法可以用来在请求的输入数据中没有对应的键时合并输入数据:
$request->mergeIfMissing(['votes' => 0]);
旧输入
Laravel 允许你在下一次请求中保留上一次请求的输入数据。这个功能特别适用于在检测到验证错误后重新填充表单。然而,如果你使用了 Laravel 的内置【验证功能】,可能不需要手动使用这些会话输入闪存方法,因为 Laravel 的一些内置验证设施会自动调用它们。
将输入闪存到会话
Illuminate\Http\Request
类中的 flash
方法会将当前输入数据闪存到【会话】中,以便在用户的下一次请求中使用:
$request->flash();
你还可以使用 flashOnly
和 flashExcept
方法将请求数据的子集闪存到会话中。这些方法对于将敏感信息(例如密码)从会话中排除非常有用:
$request->flashOnly(['username', 'email']);
$request->flashExcept('password');
输入修剪与标准化
默认情况下,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/*'),
]);
})
文件
获取上传的文件
您可以使用 file
方法或动态属性从 Illuminate\Http\Request
实例中检索上传的文件。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
类提供了一个 store
方法,它会将上传的文件移动到您的磁盘上,磁盘可以是本地文件系统上的位置,或者像 Amazon S3 这样的云存储位置。
store
方法接受一个路径参数,表示文件应存储的位置,该路径是相对于文件系统配置的根目录。这个路径不应该包含文件名,因为系统会自动生成一个唯一的文件名。
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 证书时,您可能会注意到应用程序有时不会生成 HTTPS 链接,尤其是在使用 url
助手函数时。通常,这是因为您的应用程序通过端口 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, |
配置受信主机
Laravel 默认会响应所有请求,无论 HTTP 请求的 Host
头部内容是什么。此外,在生成应用程序的绝对 URL 时,会使用 Host
头部的值。
通常,您应该配置 Web 服务器(如 Nginx 或 Apache)仅将匹配给定主机名的请求发送到您的应用程序。然而,如果您无法直接自定义 Web 服务器并需要指示 Laravel 仅响应某些主机名,您可以通过启用 Illuminate\Http\Middleware\TrustHosts
中间件来实现这一点。
要启用 TrustHosts
中间件,您应在应用程序的 bootstrap/app.php
文件中调用 trustHosts
中间件方法。使用该方法的 at
参数,您可以指定应用程序应响应的主机名。对于其它 Host
头部的传入请求,Laravel 会拒绝它们:
->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'));
})