Laravel: Сокращение дублирования кода
Во многих кодовых базах, которые я видел как Laravel разработчик, консольные команды всегда казались забытой областью или той чаю, где люди должны уделять больше внимания качеству.
В этой статье я расскажу, как можно подойти к написанию кода с упором на уменьшение дублирования кода. Для начала рассмотрим гипотезу. У нас есть Laravel приложение, представляющее собой интернет-магазин, и раз в день мы хотим генерировать отчёт обо всех продажах и состоянии поставок. Наш текущий подход заключается в том, чтобы войти в панель администрирования и нажать кнопку для генерации отчёта.
Это может выглядеть нереалистично, так как в первую очередь должно быть автоматизировано. Но оставайтесь со мной, пока мы решаем, как улучшить эту идею.
Первым шагом будет создание команды artisan или набор команд 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();
// отправить информацию в конструктор отчётов
}
}
У нас есть простая команда, которую можно запустить и получить данные о продажах за вчерашний день, помеченные как завершённые. Сам запрос достаточно прост. Он проверяет статус и дату вчерашнего дня, затем упорядочивает их от последнего к первому, для построения хронологического отчёта.
Но как мы можем это улучшить? Есть ли другие аспекты приложения, где нам нужно получить эти заказы в аналогичном порядке? Приступим к процессу рефакторинга.
Во-первых, этот конкретный запрос нужно выполнить в нескольких разных областях. Таким образом, мы можем переместить это в отдельный класс для запуска.
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();
// отправить информацию в конструктор отчётов
}
}
Мы реализовали созданный нами запрос для фильтрации на основе временного интервала. Где ещё мы можем взять это, чтобы сделать его чище и эффективнее? Можем ли мы создать специальный сервис для обработки этого аспекта отчётности. Это сервис пригодиться в других областях?
Наша панели электронной коммерции, скорее всего, будет иметь некоторую информацию из этих отчётов, поэтому повторное использование некоторого кода уже реализовано. Давайте перенесём его в сервис.
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()
);
// отправить информацию в конструктор отчётов
}
Как видите у нас есть новая чистая команда, прекрасно использующая общий код с другими областями приложения.