Вложенные задания Laravel

Я создал задание с циклом foreach, которое отправляет другое задание. Есть ли способ запустить даже после завершения всех вложенных заданий?

При срабатывании вот что происходит

Шаг 1. Сначала я запускаю пакетное задание GenerateBatchReports::dispatch($orderable);

Шаг 2. Затем мы запускаем цикл и ставим в очередь другие задания.

/**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $dir = storage_path('reports/tmp/'.str_slug($this->event->company) . '-event');

        if (file_exists($dir)) {
            File::deleteDirectory($dir);
        }

        foreach($this->event->participants as $participant) {
            $model = $participant->exercise;

            GenerateSingleReport::dispatch($model);
        }
    }

Мне просто нужно знать, когда все вложенные задания будут выполнены, чтобы я мог заархивировать отчеты и отправить их пользователю по электронной почте. Когда пакетное задание ставит в очередь все вложенные задания, оно удаляется из списка. Есть ли способ сохранить задание до тех пор, пока не будут выполнены вложенные задания, а затем запустить событие?

Любая помощь будет оценена по достоинству.

Вы можете запускать дочерние задания синхронно, что будет поддерживать родительское задание до тех пор, пока они все не будут выполнены: laravel.com/docs/5.8/очереди#синхронная-диспетчеризация

newUserName02 03.07.2019 01:03

Спасибо. Это выглядит многообещающе, но мое приложение все еще использует laravel 5.5. Похоже, что метод DispatchNow не был представлен до версии 5.7.

Juan Rangel 03.07.2019 01:13

В этом случае вы можете использовать sync. Проверьте нижнюю половину моего ответа.

newUserName02 03.07.2019 02:04

Найдите цепочку заданий в документах очереди Laravel. Это решит вашу проблему, но ваши задания не будут выполняться параллельно (то же самое произойдет при использовании синхронизации)

Elias Soares 03.07.2019 03:04

Что говорит против запуска пользовательского события в конце последней работы?

Namoshek 03.07.2019 06:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Поиск нового уровня в Laravel с помощью MeiliSearch и Scout
Поиск нового уровня в Laravel с помощью MeiliSearch и Scout
Laravel Scout - это популярный пакет, который предоставляет простой и удобный способ добавить полнотекстовый поиск в ваше приложение Laravel. Он...
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
6
5
4 257
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Для ларавела >= 5.7

Вы можете использовать метод dispatchNow. Это сохранит родительское задание, пока обрабатываются дочерние задания:

https://laravel.com/docs/5.8/queues#synchronous-dispatching

Родительская работа:

public function handle()
{
    // ...

    foreach($this->event->participants as $participant) {
        $model = $participant->exercise;

        GenerateSingleReport::dispatchNow($model);
    }

    // then do something else...
}

Для ларавела 5.2 - 5.6

Вы можете использовать соединение sync:

https://laravel.com/docs/5.5/queues#настройка-очереди-и-соединения

Убедитесь, что соединение определено в вашем config/queue.php:

https://github.com/laravel/laravel/blob/5.5/config/queue.php#L31

Родительское задание (ПРИМЕЧАНИЕ. Этот синтаксис предназначен для версии 5.5. Документация для версии 5.2 немного отличается):

public function handle()
{
    // ...

    foreach($this->event->participants as $participant) {
        $model = $participant->exercise;

        GenerateSingleReport::dispatch($model)->onConnection('sync');
    }

    // then do something else...
}

Использование sync противоречит идее использования очереди для обработки заданий в фоновом режиме. Никогда не делайте этого в производственной среде.

Namoshek 03.07.2019 06:33

@Namoshek Родительское задание все еще находится в асинхронной очереди, поэтому синхронизированные дочерние задания все равно будут выполняться вне основного цикла запроса.

newUserName02 03.07.2019 10:09

Это интересно, надо попробовать. Думаю, должна быть возможность построить что-то очень похожее с событиями (одно отправленное фоновое задание + события с обработчиками без очереди, где каждый обработчик запускает следующий обработчик через событие).

Namoshek 03.07.2019 11:06

Вы можете использовать Laravel цепочка заданий. Это позволяет вам запускать несколько заданий последовательно, и если одно из них выйдет из строя, остальные в цепочке не будут запущены.

Основной синтаксис выглядит следующим образом:

FirstJob::withChain([
    new SecondJob($param),
    new ThirdJob($param)
])->dispatch($param_for_first_job);

В вашем случае вы можете добавить все свои задания GenerateSingleReport в массив, кроме первого, и добавить, а затем добавить последнее задание, которое вы хотите запустить, в конец массива. Затем вы можете передать этот массив методу withChain в первом задании.

$jobs = [];
$first_job = null;
$first_parameter = null;

foreach($this->event->participants as $participant) {
    $model = $participant->exercise;

    if (empty($first_job)) {
        $first_job = GenerateSingleReport;
        $first_parameter = $model;
    } else {
        $jobs[] = new GenerateSingleReport($model);
    }            
}

$jobs[] = new FinalJob();

$first_job->withChain($jobs)->dispatch($first_parameter);

Обновлять: Laravel 8 (выпуск запланирован на 8 сентября 2020 г.) обеспечит пакетную обработку заданий. Эта функция уже задокументировано, вероятно, идеально подходит для сценария вложенных заданий и выглядит так:

$batch = Bus::batch([
    new ProcessPodcast(Podcast::find(1)),
    new ProcessPodcast(Podcast::find(2)),
    new ProcessPodcast(Podcast::find(3)),
    new ProcessPodcast(Podcast::find(4)),
    new ProcessPodcast(Podcast::find(5)),
])->then(function (Batch $batch) {
    // All jobs completed successfully...
})->catch(function (Batch $batch, Throwable $e) {
    // First batch job failure detected...
})->finally(function (Batch $batch) {
    // The batch has finished executing...
})->dispatch();

Мы также сможем добавлять дополнительные пакетные задания на лету:

$this->batch()->add(Collection::times(1000, function () {
    return new ImportContacts;
}));

Оригинальный ответ ?

Я придумал другое решение, потому что у меня есть очередь, использующая несколько процессов. Итак, для меня:

  • Нет dispatchNow, потому что я хочу, чтобы задания выполнялись параллельно.
  • Имея несколько процессов, мне нужно убедиться, что последнее вложенное задание не будет запускаться после последнего. Таким образом, простая цепочка не гарантирует этого.

Таким образом, мое не элегантное решение, удовлетворяющее требованиям, состоит в том, чтобы отправить все вложенные задания и, в последнем случае, отправить окончательное задание с задержкой в ​​​​пару секунд, чтобы убедиться, что все другие вложенные задания, которые могут все еще выполняться параллельно, будут прекращено.

/**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $last_participant_id = $this->event->participants->last()->id;

        foreach($this->event->participants as $participant) {
            $is_last = $participant->id === $last_participant_id;

            GenerateSingleReport::dispatch($model, $is_last);
        }
    }

и в GenerateSingleReport.php

class GenerateSingleReport implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $model;
    protected $runFinalJob;

    public function __construct($model, $run_final_job = false)
    {
        $this->model = $model;
        $this->runFinalJob = $run_final_job;
    }

    public function handle()
    {
        // job normal stuff…

        if ($this->runFinalJob) {
            FinalJob::dispatch()->delay(30);
        }
    }
}

Альтернативно

Подкидываю другую идею, значит код не безупречен. Возможно, можно создать задание-оболочку, предназначенное для запуска последнего вложенного задания, связанного с последним заданием.

/**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $last_participant_id = $this->event->participants->last()->id;

        foreach($this->event->participants as $participant) {
            $is_last = $participant->id === $last_participant_id;

            if ($is_last) {
                ChainWithDelay::dispatch(
                    new GenerateSingleReport($model), // last nested job
                    new FinalJob(), // final job
                    30 // delay
                );
            } else {
                GenerateSingleReport::dispatch($model, $is_last);
            }
        }
    }

И в ChainWithDelay.php

class ChainWithDelay implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $job;
    protected $finalJob;
    protected $delay;

    public function __construct($job, $final_job, $delay = 0)
    {
        $this->job = $job;
        $this->finalJob = $final_job;
        $this->delay = $delay;
    }

    public function handle()
    {
        $this->job
            ->withChain($this->finalJob->delay($this->delay))
            ->dispatchNow();
    }
}

Другие вопросы по теме