Blame | Letzte Änderung | Log anzeigen | RSS feed
<?phpnamespace Illuminate\Console\Scheduling;use Closure;use Cron\CronExpression;use DateTimeZone;use Illuminate\Console\Application;use Illuminate\Console\Command;use Illuminate\Support\Carbon;use ReflectionClass;use ReflectionFunction;use Symfony\Component\Console\Attribute\AsCommand;use Symfony\Component\Console\Terminal;#[AsCommand(name: 'schedule:list')]class ScheduleListCommand extends Command{/*** The console command name.** @var string*/protected $signature = 'schedule:list{--timezone= : The timezone that times should be displayed in}{--next : Sort the listed tasks by their next due date}';/*** The name of the console command.** This name is used to identify the command during lazy loading.** @var string|null** @deprecated*/protected static $defaultName = 'schedule:list';/*** The console command description.** @var string*/protected $description = 'List all scheduled tasks';/*** The terminal width resolver callback.** @var \Closure|null*/protected static $terminalWidthResolver;/*** Execute the console command.** @param \Illuminate\Console\Scheduling\Schedule $schedule* @return void** @throws \Exception*/public function handle(Schedule $schedule){$events = collect($schedule->events());if ($events->isEmpty()) {$this->components->info('No scheduled tasks have been defined.');return;}$terminalWidth = self::getTerminalWidth();$expressionSpacing = $this->getCronExpressionSpacing($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);$command = $event->command ?? '';$description = $event->description ?? '';if (! $this->output->isVerbose()) {$command = str_replace([Application::phpBinary(), Application::artisanBinary()], ['php',preg_replace("#['\"]#", '', Application::artisanBinary()),], $command);}if ($event instanceof CallbackEvent) {if (class_exists($description)) {$command = $description;$description = '';} else {$command = 'Closure at: '.$this->getClosureLocation($event);}}$command = mb_strlen($command) > 1 ? "{$command} " : '';$nextDueDateLabel = 'Next Due:';$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.$command.$nextDueDateLabel.$nextDueDate.$hasMutex) - 8, 0));// Highlight the parameters...$command = preg_replace("#(php artisan [\w\-:]+) (.+)#", '$1 <fg=yellow;options=bold>$2</>', $command);return [sprintf(' <fg=yellow>%s</> %s<fg=#6C7280>%s %s%s %s</>',$expression,$command,$dots,$hasMutex,$nextDueDateLabel,$nextDueDate), $this->output->isVerbose() && mb_strlen($description) > 1 ? sprintf(' <fg=#6C7280>%s%s %s</>',str_repeat(' ', mb_strlen($expression) + 2),'⇁',$description) : ''];});$this->line($events->flatten()->filter()->prepend('')->push('')->toArray());}/*** Gets the spacing to be used on each event row.** @param \Illuminate\Support\Collection $events* @return array<int, int>*/private function getCronExpressionSpacing($events){$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();}/*** Sorts the events by due date if option set.** @param \Illuminate\Support\Collection $events* @param \DateTimeZone $timezone* @return \Illuminate\Support\Collection*/private function sortEvents(\Illuminate\Support\Collection $events, DateTimeZone $timezone){return $this->option('next')? $events->sortBy(fn ($event) => $this->getNextDueDateForEvent($event, $timezone)): $events;}/*** Get the next due date for an event.** @param \Illuminate\Console\Scheduling\Event $event* @param \DateTimeZone $timezone* @return \Illuminate\Support\Carbon*/private function getNextDueDateForEvent($event, DateTimeZone $timezone){return Carbon::instance((new CronExpression($event->expression))->getNextRunDate(Carbon::now()->setTimezone($event->timezone))->setTimezone($timezone));}/*** Formats the cron expression based on the spacing provided.** @param string $expression* @param array<int, int> $spacing* @return string*/private function formatCronExpression($expression, $spacing){$expressions = preg_split("/\s+/", $expression);return collect($spacing)->map(fn ($length, $index) => str_pad($expressions[$index], $length))->implode(' ');}/*** Get the file and line number for the event closure.** @param \Illuminate\Console\Scheduling\CallbackEvent $event* @return string*/private function getClosureLocation(CallbackEvent $event){$callback = tap((new ReflectionClass($event))->getProperty('callback'))->setAccessible(true)->getValue($event);if ($callback instanceof Closure) {$function = new ReflectionFunction($callback);return sprintf('%s:%s',str_replace($this->laravel->basePath().DIRECTORY_SEPARATOR, '', $function->getFileName() ?: ''),$function->getStartLine());}if (is_string($callback)) {return $callback;}if (is_array($callback)) {$className = is_string($callback[0]) ? $callback[0] : $callback[0]::class;return sprintf('%s::%s', $className, $callback[1]);}return sprintf('%s::__invoke', $callback::class);}/*** Get the terminal width.** @return int*/public static function getTerminalWidth(){return is_null(static::$terminalWidthResolver)? (new Terminal)->getWidth(): call_user_func(static::$terminalWidthResolver);}/*** Set a callback that should be used when resolving the terminal width.** @param \Closure|null $resolver* @return void*/public static function resolveTerminalWidthUsing($resolver){static::$terminalWidthResolver = $resolver;}}