Данная статья документации актуальна только для версии 5.0 и была удалена в версии 5.1.
Введение
Командная шина Laravel предоставляет удобный способ инкапсуляции задач вашего приложения в простые и понятные «команды». Чтобы понять назначение команд, давайте представим, что мы создаём приложение, которое позволяет пользователям покупать подкасты.
Когда пользователь покупает подкаст, должно произойти несколько событий. Например, мы должны снять деньги с карты пользователя, добавить запись о покупке в нашу базу данных и послать электронное письмо с подтверждением покупки. Возможно, мы также должны выполнить некоторые проверки, например, разрешено ли пользователю вообще покупать подкасты.
Мы могли бы поместить всю эту логику в метод контроллера. Однако у этого подхода есть несколько недостатков. Во-первых, наш контроллер, вероятно, обрабатывает и несколько других входящих HTTP-действий, поэтому включение сложной логики в каждый метод контроллера приведёт к его быстрому разрастанию, и его будет сложно читать. Во-вторых, будет трудно снова использовать логику покупки подкаста за пределами контекста контроллера. В-третьих, будет сложнее проводить юнит-тесты команд, поскольку нам придётся также генерировать заглушки для HTTP-запросов и выполнять полноценный запрос к приложению, чтобы протестировать логику покупки подкаста.
Вместо размещения этой логики в контроллере, мы можем принять решение инкапсулировать её в объект «команды», такой как команда PHPPurchasePodcast
.
Создание команд
Командная строка Artisan может генерировать новые классы команды, используя команду shmake:command
:
shphp artisan make:command PurchasePodcast
Только что сгенерированный класс будет помещён в каталог app/Commands. По умолчанию команда содержит два метода: конструктор и метод PHPhandle
. Конструктор позволяет вам передавать любые соответствующие объекты команде, в то время как метод PHPhandle
выполняет команду. Например:
class PurchasePodcast extends Command implements SelfHandling {
protected $user, $podcast;
/**
* Создание нового экземпляра команды.
*
* @return void
*/
public function __construct(User $user, Podcast $podcast)
{
$this->user = $user;
$this->podcast = $podcast;
}
/**
* Выполнение команды.
*
* @return void
*/
public function handle()
{
// Логика покупки подкаста...
event(new PodcastWasPurchased($this->user, $this->podcast));
}
}
Метод PHPhandle
может использовать указание типов зависимостей, и они будут автоматически внедрены сервис-контейнером.
/**
* Выполнение команды.
*
* @return void
*/
public function handle(BillingGateway $billing)
{
// Логика покупки подкаста...
}
Выполнение команд
Как выполнить только что созданную команду? Можно непосредственно вызвать метод PHPhandle
. Однако, выполнение команд через «командную шину» Laravel имеет ряд преимуществ, которые мы обсудим далее.
Если вы посмотрите на основной контроллер своего приложения, то вы заметите типаж PHPDispatchesCommands
. Он позволяет вызывать метод PHPdispatch
из любого контроллера.
public function purchasePodcast($podcastId)
{
$this->dispatch(
new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
);
}
Командная шина займётся выполнением команды и вызовом IoC-контейнера для внедрения любых необходимых зависимостей в метод PHPhandle
.
Вы можете добавить типаж Illuminate\Foundation\Bus\DispatchesCommands в любой класс. Если вы хотите получить экземпляр командной шины через конструктор какого-либо из ваших классов, вы можете указать тип интерфейса PHPIlluminate\Contracts\Bus\Dispatcher
. Наконец, вы можете просто использовать фасад PHPBus
, чтобы быстро выполнить команду:
Bus::dispatch(
new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
);
Передача параметров в запросах
Передача переменных HTTP-запроса в команды очень распространена. Поэтому, вместо того чтобы делать это вручную для каждого запроса, Laravel предоставляет некоторые вспомогательные методы. Давайте взглянем на метод PHPdispatchFrom
, доступный в типаже PHPDispatchesCommands
:
$this->dispatchFrom('Command\Class\Name', $request);
Этот метод проверяет конструктор класса команды, который он получил первым аргументом, и затем извлекает переменные из HTTP-запроса (или любого другого объекта типа PHPArrayAccess
), чтобы заполнить необходимые параметры конструктора команды. Так, если наш класс команды примет аргумент PHPfirstName
в своём конструкторе, то командная шина попытается получить параметр PHPfirstName
из HTTP-запроса.
Вы можете также передать массив как третий аргумент метода PHPdispatchFrom
. Этот массив будет использоваться, чтобы заполнить любые параметры конструктора, которые не доступны из запроса:
$this->dispatchFrom('Command\Class\Name', $request, [
'firstName' => 'Taylor',
]);
Очередь команд
Командная шина предназначена не только для синхронных задач, которые работают во время прохождения текущего запроса. Она также служит основным методом создания очередей задач в Laravel. Так как же заставить командную шину поставить нашу задачу в очередь для фоновой обработки вместо того, чтобы выполнить её синхронно? Очень просто. Во-первых, при генерации новой команды, просто добавьте к команде флаг sh--queued
:
shphp artisan make:command PurchasePodcast --queued
И вы увидите, это добавляет ещё несколько функций к команде, а именно, интерфейс Illuminate\Contracts\Queue\ShouldBeQueued и типаж PHPSerializesModels
. С помощью них командная шина ставит команды в очередь, а также корректно сериализует и десериализует любые Eloquent-модели, которые содержатся в вашей команде как свойства.
Если вы хотите преобразовать существующую команду в команду для очереди, просто реализуйте интерфейс Illuminate\Contracts\Queue\ShouldBeQueued в классе вручную. Он не содержит методов и просто служит индикатором для командной шины.
Теперь просто пишите свою команду как обычно. Шина автоматически поставит команду в очередь для фоновой обработки.
Для получения дополнительной информации о взаимодействии с очередью команд просмотрите полную документацию по очередям.
Конвейер команд
Прежде чем команда передастся в обработчик, вы можете передать её через другие классы на «конвейер». Каналы команд работают как HTTP-middleware, за исключением ваших команд! Например, канал команды может обернуть всю работу команды в транзакцию базы данных, или просто зарегистрировать в логе её выполнение.
Чтобы добавить канал в вашу шину, вызовите метод диспетчера PHPpipeThrough
из вашего метода PHPApp\Providers\BusServiceProvider::boot
:
$dispatcher->pipeThrough(['UseDatabaseTransactions', 'LogCommand']);
Канал команды определяется в методе PHPhandle
, подобно middleware:
class UseDatabaseTransactions {
public function handle($command, $next)
{
return DB::transaction(function() use ($command, $next)
{
return $next($command);
});
}
}
Классы каналов команд выполняются через IoC-контейнер, поэтому свободно указывайте типы любых необходимых зависимостей в их конструкторах.
Вы можете даже определить PHPClosure
в качестве канала команды:
$dispatcher->pipeThrough([function($command, $next)
{
return DB::transaction(function() use ($command, $next)
{
return $next($command);
});
}]);
Комментарии (1)
Как определить выполняется ли команда в данный момент?