简介
条件是任何代码库的组成部分。如果不能条件性地执行代码(例如使用 if
和 else
),我们的软件将非常有限,无法完成更多操作。实际上,它们是你开始编程后最早学习的东西之一。
但使用 if
和 else
有时可能会阻止你连缀方法调用。
在这篇文章中,我们将简要介绍如何使用 Laravel 的 Illuminate\Support\Traits\Conditionable
特性来帮助你连缀方法调用,并将 when
方法添加到你自己的 PHP 类中。
什么是 Conditionable
特性?
为了了解 Conditionable
特性做了什么,让我们看看一些代码示例,然后重写使用特性的代码。
你是否经常写出像这样的代码,在 Eloquent 查询中条件性地添加一个 where
子句?
$query = User::query()
->where(...)
->where(...);
if (Auth::user()->isAdmin()) {
$query->where(...);
}
$users = $query->get();
从最初 glance 来看,由于代码分为三个独立的块,可能无法立即看出所有代码是否都与查询相关。我们可能会在进行 if
块的内部做一些操作,这些操作并未改变查询,而是运行了额外的逻辑(尽管我们在此示例中没有这样做)。我更希望能够保持查询在一个单独的链中。这只是我个人的观点。
旁注:你可能已经注意到我在查询的开始使用了 ::query()
。如果你以前没见过,我有一篇关于这是做什么的文章:[在 Laravel Eloquent 查询中使用 'query()'](https://ashallendesign.co.uk/blog/using-query-in-laravel-eloquent-queries).
如果是链式调用,它可能看起来更像这样
$users = User::query()
->where(...)
->where(...)
->when(
Auth::user()->isAdmin(),
fn ($query) => $query->where(...),
})
->get();
从上面的代码示例中我们可以看到,我们已经用 when
方法替换了 if
块。
我们能够访问这个方法,是因为我们用来构建 Eloquent 查询的 Illuminate\Database\Eloquent\Builder
类使用了 Illuminate\Database\Concerns\BuildsQueries
特性,该特性又使用了 Illuminate\Support\Traits\Conditionable
特性。
如果传递给 when
方法的第一个参数的结果为真,则执行第二个参数(闭包或箭头函数)。如果结果是假的,则不执行闭包。
我不知道你是否也有同样的感受,但我发现这种方式更易于阅读和理解。我的大脑可以立即看懂代码,并理解它与同一事物(在这种情况下是查询)有关。我猜这是因为代码紧凑并遵循了 Gestalt 原则中的距离?
Laravel 代码库中有几个其他地方可以使用 when
方法,例如
但如果我们想在自定义类中使用它怎么办呢?
让我们看看如何自己使用 Conditionable
特性。
添加 Conditionable
特性
开始使用 Conditionable
特性非常简单。你只需要将它添加到你的类中。
让我们假设我们有一个可以用来构建和存储 PDF 报告的示例类:
declare(strict_types=1);
namespace App\Services;
final class ReportBuilder
{
private bool $includeCharts = false;
private bool $includeTables = false;
private string $colour;
public function buildPdfReport(): string
{
// Build the PDF report.
// Save it in storage (e.g. - S3).
// Return the URL of the PDF report for downloading.
}
public function includeCharts(): self
{
$this->includeCharts = true;
return $this;
}
public function includeTables(): self
{
$this->includeTables = true;
return $this;
}
public function darkMode(): self
{
$this->colour = 'dark';
return $this;
}
public function lightMode(): self
{
$this->colour = 'light';
return $this;
}
}
我们可能想要在像 控制器 这样的地方使用这段代码:
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Services\ReportBuilder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
final class ReportController extends Controller
{
public function __invoke(Request $request, ReportBuilder $reportBuilder): JsonResponse
{
if ($request->boolean('includeCharts')) {
$reportBuilder->includeCharts();
}
if ($request->boolean('includeTables')) {
$reportBuilder->includeTables();
}
$request->boolean('darkMode')
? $reportBuilder->darkMode()
: $reportBuilder->lightMode();
$url = $reportBuilder->buildPdfReport();
return response()->json([
'url' => $url,
]);
}
}
在上面的例子中,我们使用 if
和 else
语句来在 ReportBuilder
类上条件性地调用方法。在构建完 PDF 报告后,我们在 JSON 响应中返回 PDF 报告的 URL。
让我们更新我们的 ReportBuilder
类以使用 Conditionable
特性
declare(strict_types=1);
namespace App\Services;
use Illuminate\Support\Traits\Conditionable;
final class ReportBuilder
{
use Conditionable;
// The rest of the class...
}
从这里,我们可以重构我们的控制器以使用 when
方法
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Services\ReportBuilder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
final class ReportController extends Controller
{
public function __invoke(Request $request, ReportBuilder $reportBuilder): JsonResponse
{
$url = $reportBuilder
->when(
value: $request->boolean('includeCharts'),
callback: fn ($builder) => $builder->includeCharts(),
)->when(
value: $request->boolean('includeTables'),
callback: fn ($builder) => $builder->includeTables(),
)->when(
value: $request->boolean('darkMode'),
callback: fn ($builder) => $builder->darkMode(),
default: fn ($builder) => $builder->lightMode(),
)->buildPdfReport();
return response()->json([
'url' => $url,
]);
}
}
从上面的代码示例中我们可以看到,我们已经用 when
方法替换了 if
和 else
语句。
你可能还注意到,我们也向第三个 when
方法传递了一个 default
参数,该参数确定了如果值是假的会发生什么。如果值是假的,则调用 lightMode
方法。
注意:为了使代码示例简短易懂,我没有为箭头函数添加返回类型和类型提示。在实际应用中,我建议添加这些以提供更严格类型安全和清晰度。
传递值给 when
回调
虽然我们没有在上面的例子中涵盖它,但你可以在回调函数(callback
参数)中访问第一个参数(value
参数)的结果。如果你需要在回调函数中使用这个值,这会非常有用。
例如,让我们看看这个代码块:
Post::query()
->when(
value: Auth::user(),
callback: function (Builder $query, User $user) {
// We can access the $user variable here...
$query->where('user_id', $user->id);
},
)
在上面的例子中,如果有已登录的用户(使用 Auth::user()
访问),它将被作为第二个参数传递给回调函数。
unless
方法
除了 when
方法,Conditionable
特性还提供了一个 unless
方法。这个方法与 when
方法相反。如果值是假的,则执行回调函数。如果值是真的,则不执行回调函数。
例如,我们可能想写一个查询来获取用户可以查看的一些博客文章。如果用户是管理员,则我们想获取所有文章。如果用户不是管理员,则我们只想获取已发布的文章
Post::query()
->unless(
value: Auth::user()->isAdmin(),
callback: function (Builder $query) {
$query->where('status', 'published');
},
)
我个人觉得unless
方法相当难以阅读和理解。我花费了一些时间才搞清楚正在使用的逻辑,所以我更喜欢使用when
方法。
但这并不是在批评unless
方法。这仅仅是因为我的大脑工作方式。你可能与我相反,觉得unless
方法更容易理解和使用。
我应该使用Conditionable
特性吗?
与开发中的许多事情一样,你是否有意愿使用Conditionable
特性完全取决于个人喜好。
对我而言,我发现当我需要链式调用方法时,使用when
方法会使代码更容易阅读和理解。它把所有与同一事物相关的内容都放在了一个地方。
然而,的确有某些情况下,使用if
和else
语句可能更为合适。例如,如果你有大量需要执行的逻辑,那么使用if
语句可能更好。你不想有一个难以阅读和理解的大闭包。或者,可能只是因为你觉得if
和else
语句更容易阅读和理解。
因此,这确实是一件你应该根据具体情况考虑的事情。这里没有正确或错误答案。只需确保它对你和你的团队都是舒适的,并且能够理解。
如果你选择将代码从使用if
和else
语句重构成使用when
方法,我建议确保你有一些测试覆盖了重构的代码。这将帮助你确保重构后的代码仍然按预期工作,并且在过程中没有破坏任何东西。
结论
希望这篇文章能让你快速了解Conditionable
特性是什么以及你如何在你的代码库中使用它。
如果你喜欢阅读这篇帖子,我很乐意听到你的反馈。同样,如果你有任何改进未来帖子的建议,我也很乐意听到。
你可能还感兴趣阅读我的220多页电子书《Battle Ready Laravel》,其中更深入地讨论了类似主题。
或者,你可能想阅读我的其他440多页电子书《Using APIs in Laravel》,它教你如何使用Laravel从其他服务中消费API。
如果你有兴趣在每次我发表新帖子时收到更新,请在下面免费注册我的通讯。
继续构建精彩的事物!🚀lara
driesvints 喜欢这篇文章