laravel
4. 基础知识
HTTP 请求

介绍

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 方法,您可以确定传入的请求是否匹配了一个[命名路由](/docs/{{version}}/routing#named - routes):

if ($request->routeIs('admin.*')) {
    //...
}

检索请求 URL

要检索传入请求的完整 URL,您可以使用 urlfullUrl 方法。url 方法将返回不带查询字符串的 URL,而 fullUrl 方法则包括查询字符串:

$url = $request->url();

$urlWithQueryString = $request->fullUrl();

如果您想将查询字符串数据附加到当前 URL 上,可以调用 fullUrlWithQuery 方法。此方法将给定的查询字符串变量数组与当前查询字符串合并:

$request->fullUrlWithQuery(['type' => 'phone']);

如果您想获取当前 URL 中不包含给定查询字符串参数的 URL,可以使用 fullUrlWithoutQuery 方法:

$request->fullUrlWithoutQuery(['type']);

检索请求主机

您可以通过 hosthttpHostschemeAndHttpHost 方法检索传入请求的“主机”:

$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 方法将返回一个包含请求所接受的所有内容类型的数组:

$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 标准](https://www.php (opens in a new tab) - fig.org/psr/psr - 7/)指定了 HTTP 消息(包括请求和响应)的接口。如果您想获取 PSR - 7 请求的实例而不是 Laravel 请求的实例,您首先需要安装几个库。Laravel 使用 Symfony HTTP 消息桥 组件将典型的 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 请求时,只要请求的 Content-Type 标头正确设置为 application/json,您就可以通过 input 方法访问 JSON 数据。您甚至可以使用“点”语法来检索嵌套在 JSON 数组/对象中的值:

$name = $request->input('user.name');

检索可字符串化的输入值

您可以使用 string 方法将请求数据作为 Illuminate\Support\Stringable 的实例进行检索,而不是将请求的输入数据作为原始 string 进行检索:

$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');

检索日期输入值

为了方便起见,可以使用 date 方法将包含日期/时间的输入值作为 Carbon 实例进行检索。如果请求中不包含具有给定名称的输入值,则将返回 null

$birthday = $request->date('birthday');

date 方法接受的第二个和第三个参数可分别用于指定日期的格式和时区:

$elapsed = $request->date('elapsed', '!H:i', 'Europe/Madrid');

如果输入值存在但格式无效,则会抛出 InvalidArgumentException;因此,建议在调用 date 方法之前验证输入。

检索枚举输入值

PHP 枚举 (opens in a new tab) 相对应的输入值也可以从请求中检索。如果请求中不包含具有给定名称的输入值,或者枚举没有与输入值匹配的支持值,则将返回 nullenum 方法接受输入值的名称和枚举类作为其第一个和第二个参数:

use App\Enums\Status;
 
$status = $request->enum('status', Status::class);

通过动态属性检索输入

您还可以使用 Illuminate\Http\Request 实例上的动态属性来访问用户输入。例如,如果您的应用程序的一个表单包含一个 name 字段,您可以这样访问该字段的值:

$name = $request->name;

使用动态属性时,Laravel 将首先在请求有效负载中查找参数的值。如果不存在,Laravel 将在匹配的路由参数中搜索该字段。

检索输入数据的一部分

如果您需要检索输入数据的子集,可以使用 onlyexcept 方法。这两个方法都接受单个 array 或动态参数列表:

$input = $request->only(['username', 'password']);
$input = $request->only('username', 'password');
$input = $request->except(['credit_card']);
$input = $request->except('credit_card');

[!WARNING]
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" 值未填充...
});

要确定请求中是否缺少给定的键,可以使用 missingwhenMissing 方法:

if ($request->missing('name')) {
    //...
}
$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();

您还可以使用 flashOnlyflashExcept 方法将请求数据的子集闪存到会话中。这些方法对于将敏感信息(如密码)排除在会话之外很有用:

$request->flashOnly(['username', 'email']);
$request->flashExcept('password');

闪存输入然后重定向

由于您经常需要将输入闪存到会话中,然后重定向到上一页,因此您可以使用 withInput 方法轻松地将输入闪存链接到重定向上:

return redirect('/form')->withInput();
return redirect()->route('user.create')->withInput();
return redirect('/form')->withInput(
    $request->except('password')
);

检索旧输入

要从之前的请求中检索闪存输入,可以在 Illuminate\Http\Request 实例上调用 old 方法。old 方法将从 会话 中提取之前闪存的输入数据:

$username = $request->old('username');

Laravel 还提供了一个全局的 old 辅助函数。如果您在 Blade 模板 中显示旧输入,使用 old 辅助函数来重新填充表单会更加方便。如果给定字段不存在旧输入,则将返回 null

<input type="text" name="username" value="{{ old('username') }}">

Cookies(Cookie)

从请求中检索 Cookies

Laravel 框架创建的所有 Cookie 都经过加密并使用认证码进行签名,这意味着如果客户端对其进行了更改,它们将被视为无效。要从请求中检索 Cookie 值,可以在 Illuminate\Http\Request 实例上使用 cookie 方法:

$value = $request->cookie('name');

输入修剪和规范化

默认情况下,Laravel 在您的应用程序的全局中间件堆栈中包含了 Illuminate\Foundation\Http\Middleware\TrimStringsIlluminate\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 文件中使用 trimStringsconvertEmptyStringsToNull 中间件方法。这两个方法都接受一个闭包数组,这些闭包应返回 truefalse 以指示是否应跳过输入规范化:

->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 类还包含用于访问文件的完全限定路径及其扩展名的方法。extension 方法将尝试根据文件的内容猜测文件的扩展名。此扩展名可能与客户端提供的扩展名不同:

$path = $request->photo->path();

$extension = $request->photo->extension();

其他文件方法

UploadedFile 实例上还有许多其他方法。有关这些方法的更多信息,请查看 该类的 API 文档 (opens in a new tab)

存储上传的文件

要存储上传的文件,您通常会使用配置的 文件系统 之一。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');

[!NOTE]
有关 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
    );
})

[!NOTE]
如果您正在使用 AWS Elastic Load Balancing(亚马逊弹性负载均衡),headers 值应为 Request::HEADER_X_FORWARDED_AWS_ELB。如果您的负载均衡器使用 RFC 7239 (opens in a new tab) 中的标准 Forwarded 标头,headers 值应为 Request::HEADER_FORWARDED。有关 headers 值中可以使用的常量的更多信息,请查看 Symfony 关于 信任代理 (opens in a new tab) 的文档。

信任所有代理

如果您正在使用 Amazon AWS 或其他“云”负载均衡器提供商,您可能不知道实际均衡器的 IP 地址。在这种情况下,您可以使用 * 来信任所有代理:

->withMiddleware(function (Middleware $middleware) {
    $middleware->trustProxies(at: '*');
})

配置受信任的主机

默认情况下,Laravel 会响应它收到的所有请求,无论 HTTP 请求的 Host 标头的内容如何。此外,在 Web 请求期间为您的应用程序生成绝对 URL 时,将使用 Host 标头的值。

通常,您应该配置您的 Web 服务器(如 Nginx 或 Apache),以便仅将与给定主机名匹配的请求发送到您的应用程序。但是,如果您无法直接自定义您的 Web 服务器,并且需要指示 Laravel 仅响应某些主机名,则可以通过为您的应用程序启用 Illuminate\Http\Middleware\TrustHosts 中间件来实现。

要启用 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'));
})