减少代码重复
发布于 作者 史蒂夫·麦克道格
代码重复是许多开发人员最头疼的事情,你以为你已经解决了问题,但实际上却还有很多地方存在相同的问题。
在我作为 Laravel 开发人员所见过的许多代码库中,控制台命令总是被人遗忘的区域,或者说人们需要更加注意其质量的地方。
在本教程中,我将带您逐步了解如何编写代码以重点减少代码重复。首先,让我们假设一个情景。我们有一个 Laravel 应用程序,它是一个电子商务商店,我们希望每天生成一份关于所有销售额和货件状态的报告。我们目前的方法是登录到管理面板并点击一个按钮来生成报告。
这可能不切实际,因为这里第一个问题就是将其自动化。但请继续关注,我们将一起探索如何改进这个想法。
我们的第一步是创建一个或一组 Artisan 命令,用于生成报告。当我们开始研究报告时,创建明确命名为我们想要实现的目标的命令是有意义的。所以让我们从销售额开始。
final class SalesFigures extends Command{ public $signature = 'reports:sales'; public $description = 'Run a daily report on sales.'; public function handle(): int { $date = now()->subDay(); $sales = Order::query() ->where('status', Status::COMPLETE) ->whereBetween( 'completed_at', $date->startOfDay(), $date->endOfDay(), )->latest()->get(); // send information through to the report builder }}
我们这里有一个简单的命令,我们可以运行它并获取昨天标记为已完成的销售额。查询本身很简单。它检查状态和日期是否为昨天,然后对其进行排序,以便最新的是第一个 - 这允许我们构建一个按时间顺序排列的报告。
但是,我们如何改进它呢?在应用程序的其他方面,我们是否需要以类似的顺序获取这些订单?让我们开始重构过程。
首先,这个特定的查询是我们需要在几个不同的地方运行的。所以我们可以将其移到自己的类中,以便我们运行它。
final class ResultsForPeriod implements ResultsForPeriodContract{ public function handle( Builder $query, CarbonInterface $start, CarbonInterface $end, ): Builder { return $builder->whereBetween( 'completed_at', $start, $end, ); }}
这将允许我们跨任何模型获取某个时间段的结果 - 这对项目更有益。
final class SalesFigures extends Command{ public $signature = 'reports:sales'; public $description = 'Run a daily report on sales.'; public function handle(ResultsForPeriodContract $query): int { $date = now()->subDay(); $sales = $query->handle( query: Order::query() ->where('status', Status::COMPLETE), start: $date->startOfDay(), end: $date->endOfDay() )->latest()->get(); // send information through to the report builder }}
我们已经实现了我们构建的基于时间段进行过滤的查询。我们还可以将它带到哪里才能使其更简洁、更高效?我们可以创建一个专门的服务来处理这个报告方面吗?这项服务在其他领域也有帮助吗?
我们的电子商务仪表板可能包含这些报告中的某些信息,因此已经存在一些代码重用。让我们将其移到一个服务中。
final class ReportService implements ReportServiceContract{ public function __construct( private readonly ResultsForPeriodContract $periodFilter, ) {} public function dailySales(CarbonInterface $start, CarbonInterface $end): Collection { return $this->periodFilter->handle( query: Order::query()->where('status', Status::COMPLETE), )->latest()->get(); }}
现在我们可以将其移回 Artisan 命令中。
final class SalesFigures extends Command{ public $signature = 'reports:sales'; public $description = 'Run a daily report on sales.'; public function handle(ReportServiceContract $service): int { $date = now()->subDay(); $sales = $service->dailySales( query: Order::query(), start: $date->startOfDay(), end: $date->endOfDay() ); // send information through to the report builder }}
如您所见,我们有一个新的简洁命令,它很好地利用了与应用程序其他区域共享的代码。