From 333d2efff5a381b704806abb18d3e17a86d94269 Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Wed, 12 Jul 2023 12:32:46 +1000 Subject: [PATCH] [10.x] Improve display of sub-minute tasks in `schedule:list` command. (#47720) * Display next due time correctly for sub-minute events * Display the repeat expression for sub-minute events * formatting --------- Co-authored-by: Taylor Otwell --- .../Scheduling/ScheduleListCommand.php | 175 ++++++++++++------ 1 file changed, 119 insertions(+), 56 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php index 6a1128827183..831b595aa269 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleListCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleListCommand.php @@ -62,87 +62,130 @@ public function handle(Schedule $schedule) $expressionSpacing = $this->getCronExpressionSpacing($events); + $repeatExpressionSpacing = $this->getRepeatExpressionSpacing($events); + $timezone = new DateTimeZone($this->option('timezone') ?? config('app.timezone')); $events = $this->sortEvents($events, $timezone); - $events = $events->map(function ($event) use ($terminalWidth, $expressionSpacing, $timezone) { - $expression = $this->formatCronExpression($event->expression, $expressionSpacing); + $events = $events->map(function ($event) use ($terminalWidth, $expressionSpacing, $repeatExpressionSpacing, $timezone) { + return $this->listEvent($event, $terminalWidth, $expressionSpacing, $repeatExpressionSpacing, $timezone); + }); - $command = $event->command ?? ''; + $this->line( + $events->flatten()->filter()->prepend('')->push('')->toArray() + ); + } - $description = $event->description ?? ''; + /** + * Get the spacing to be used on each event row. + * + * @param \Illuminate\Support\Collection $events + * @return array + */ + private function getCronExpressionSpacing($events) + { + $rows = $events->map(fn ($event) => array_map('mb_strlen', preg_split("/\s+/", $event->expression))); - if (! $this->output->isVerbose()) { - $command = str_replace([Application::phpBinary(), Application::artisanBinary()], [ - 'php', - preg_replace("#['\"]#", '', Application::artisanBinary()), - ], $command); - } + return collect($rows[0] ?? [])->keys()->map(fn ($key) => $rows->max($key))->all(); + } - if ($event instanceof CallbackEvent) { - $command = $event->getSummaryForDisplay(); + /** + * Get the spacing to be used on each event row. + * + * @param \Illuminate\Support\Collection $events + * @return int + */ + private function getRepeatExpressionSpacing($events) + { + return $events->map(fn ($event) => mb_strlen($this->getRepeatExpression($event)))->max(); + } - if (in_array($command, ['Closure', 'Callback'])) { - $command = 'Closure at: '.$this->getClosureLocation($event); - } - } + /** + * List the given even in the console. + * + * @param \Illuminate\Console\Scheduling\Event + * @param int $terminalWidth + * @param array $expressionSpacing + * @param int $repeatExpressionSpacing + * @param array $repeatExpressionSpacing + * @param \DateTimeZone $timezone + * @return \Illuminate\Support\DateTimeZone + */ + private function listEvent($event, $terminalWidth, $expressionSpacing, $repeatExpressionSpacing, $timezone) + { + $expression = $this->formatCronExpression($event->expression, $expressionSpacing); - $command = mb_strlen($command) > 1 ? "{$command} " : ''; + $repeatExpression = str_pad($this->getRepeatExpression($event), $repeatExpressionSpacing); - $nextDueDateLabel = 'Next Due:'; + $command = $event->command ?? ''; - $nextDueDate = $this->getNextDueDateForEvent($event, $timezone); + $description = $event->description ?? ''; - $nextDueDate = $this->output->isVerbose() - ? $nextDueDate->format('Y-m-d H:i:s P') - : $nextDueDate->diffForHumans(); + if (! $this->output->isVerbose()) { + $command = str_replace([Application::phpBinary(), Application::artisanBinary()], [ + 'php', + preg_replace("#['\"]#", '', Application::artisanBinary()), + ], $command); + } - $hasMutex = $event->mutex->exists($event) ? 'Has Mutex › ' : ''; + if ($event instanceof CallbackEvent) { + $command = $event->getSummaryForDisplay(); - $dots = str_repeat('.', max( - $terminalWidth - mb_strlen($expression.$command.$nextDueDateLabel.$nextDueDate.$hasMutex) - 8, 0 - )); + if (in_array($command, ['Closure', 'Callback'])) { + $command = 'Closure at: '.$this->getClosureLocation($event); + } + } - // Highlight the parameters... - $command = preg_replace("#(php artisan [\w\-:]+) (.+)#", '$1 $2', $command); + $command = mb_strlen($command) > 1 ? "{$command} " : ''; - return [sprintf( - ' %s %s%s %s%s %s', - $expression, - $command, - $dots, - $hasMutex, - $nextDueDateLabel, - $nextDueDate - ), $this->output->isVerbose() && mb_strlen($description) > 1 ? sprintf( - ' %s%s %s', - str_repeat(' ', mb_strlen($expression) + 2), - '⇁', - $description - ) : '']; - }); + $nextDueDateLabel = 'Next Due:'; - $this->line( - $events->flatten()->filter()->prepend('')->push('')->toArray() - ); + $nextDueDate = $this->getNextDueDateForEvent($event, $timezone); + + $nextDueDate = $this->output->isVerbose() + ? $nextDueDate->format('Y-m-d H:i:s P') + : $nextDueDate->diffForHumans(); + + $hasMutex = $event->mutex->exists($event) ? 'Has Mutex › ' : ''; + + $dots = str_repeat('.', max( + $terminalWidth - mb_strlen($expression.$repeatExpression.$command.$nextDueDateLabel.$nextDueDate.$hasMutex) - 8, 0 + )); + + // Highlight the parameters... + $command = preg_replace("#(php artisan [\w\-:]+) (.+)#", '$1 $2', $command); + + return [sprintf( + ' %s %s %s%s %s%s %s', + $expression, + $repeatExpression, + $command, + $dots, + $hasMutex, + $nextDueDateLabel, + $nextDueDate + ), $this->output->isVerbose() && mb_strlen($description) > 1 ? sprintf( + ' %s%s %s', + str_repeat(' ', mb_strlen($expression) + 2), + '⇁', + $description + ) : '']; } /** - * Gets the spacing to be used on each event row. + * Get the repeat expression for an event. * - * @param \Illuminate\Support\Collection $events - * @return array + * @param \Illuminate\Console\Scheduling\Event $event + * @return string */ - private function getCronExpressionSpacing($events) + private function getRepeatExpression($event) { - $rows = $events->map(fn ($event) => array_map('mb_strlen', preg_split("/\s+/", $event->expression))); - - return collect($rows[0] ?? [])->keys()->map(fn ($key) => $rows->max($key))->all(); + return $event->isRepeatable() ? "{$event->repeatSeconds}s " : ''; } /** - * Sorts the events by due date if option set. + * Sort the events by due date if option set. * * @param \Illuminate\Support\Collection $events * @param \DateTimeZone $timezone @@ -164,15 +207,35 @@ private function sortEvents(\Illuminate\Support\Collection $events, DateTimeZone */ private function getNextDueDateForEvent($event, DateTimeZone $timezone) { - return Carbon::instance( + $nextDueDate = Carbon::instance( (new CronExpression($event->expression)) ->getNextRunDate(Carbon::now()->setTimezone($event->timezone)) ->setTimezone($timezone) ); + + if (! $event->isRepeatable()) { + return $nextDueDate; + } + + $previousDueDate = Carbon::instance( + (new CronExpression($event->expression)) + ->getPreviousRunDate(Carbon::now()->setTimezone($event->timezone), allowCurrentDate: true) + ->setTimezone($timezone) + ); + + $now = Carbon::now()->setTimezone($event->timezone); + + if (! $now->copy()->startOfMinute()->eq($previousDueDate)) { + return $nextDueDate; + } + + return $now + ->endOfSecond() + ->ceilSeconds($event->repeatSeconds); } /** - * Formats the cron expression based on the spacing provided. + * Format the cron expression based on the spacing provided. * * @param string $expression * @param array $spacing