Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Console\Scheduling;
4
 
5
use Closure;
6
use Cron\CronExpression;
7
use DateTimeZone;
8
use Illuminate\Console\Application;
9
use Illuminate\Console\Command;
10
use Illuminate\Support\Carbon;
11
use ReflectionClass;
12
use ReflectionFunction;
13
use Symfony\Component\Console\Attribute\AsCommand;
14
use Symfony\Component\Console\Terminal;
15
 
16
#[AsCommand(name: 'schedule:list')]
17
class ScheduleListCommand extends Command
18
{
19
    /**
20
     * The console command name.
21
     *
22
     * @var string
23
     */
24
    protected $signature = 'schedule:list
25
        {--timezone= : The timezone that times should be displayed in}
26
        {--next : Sort the listed tasks by their next due date}
27
    ';
28
 
29
    /**
30
     * The name of the console command.
31
     *
32
     * This name is used to identify the command during lazy loading.
33
     *
34
     * @var string|null
35
     *
36
     * @deprecated
37
     */
38
    protected static $defaultName = 'schedule:list';
39
 
40
    /**
41
     * The console command description.
42
     *
43
     * @var string
44
     */
45
    protected $description = 'List all scheduled tasks';
46
 
47
    /**
48
     * The terminal width resolver callback.
49
     *
50
     * @var \Closure|null
51
     */
52
    protected static $terminalWidthResolver;
53
 
54
    /**
55
     * Execute the console command.
56
     *
57
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
58
     * @return void
59
     *
60
     * @throws \Exception
61
     */
62
    public function handle(Schedule $schedule)
63
    {
64
        $events = collect($schedule->events());
65
 
66
        if ($events->isEmpty()) {
67
            $this->components->info('No scheduled tasks have been defined.');
68
 
69
            return;
70
        }
71
 
72
        $terminalWidth = self::getTerminalWidth();
73
 
74
        $expressionSpacing = $this->getCronExpressionSpacing($events);
75
 
76
        $timezone = new DateTimeZone($this->option('timezone') ?? config('app.timezone'));
77
 
78
        $events = $this->sortEvents($events, $timezone);
79
 
80
        $events = $events->map(function ($event) use ($terminalWidth, $expressionSpacing, $timezone) {
81
            $expression = $this->formatCronExpression($event->expression, $expressionSpacing);
82
 
83
            $command = $event->command ?? '';
84
 
85
            $description = $event->description ?? '';
86
 
87
            if (! $this->output->isVerbose()) {
88
                $command = str_replace([Application::phpBinary(), Application::artisanBinary()], [
89
                    'php',
90
                    preg_replace("#['\"]#", '', Application::artisanBinary()),
91
                ], $command);
92
            }
93
 
94
            if ($event instanceof CallbackEvent) {
95
                if (class_exists($description)) {
96
                    $command = $description;
97
                    $description = '';
98
                } else {
99
                    $command = 'Closure at: '.$this->getClosureLocation($event);
100
                }
101
            }
102
 
103
            $command = mb_strlen($command) > 1 ? "{$command} " : '';
104
 
105
            $nextDueDateLabel = 'Next Due:';
106
 
107
            $nextDueDate = $this->getNextDueDateForEvent($event, $timezone);
108
 
109
            $nextDueDate = $this->output->isVerbose()
110
                ? $nextDueDate->format('Y-m-d H:i:s P')
111
                : $nextDueDate->diffForHumans();
112
 
113
            $hasMutex = $event->mutex->exists($event) ? 'Has Mutex › ' : '';
114
 
115
            $dots = str_repeat('.', max(
116
                $terminalWidth - mb_strlen($expression.$command.$nextDueDateLabel.$nextDueDate.$hasMutex) - 8, 0
117
            ));
118
 
119
            // Highlight the parameters...
120
            $command = preg_replace("#(php artisan [\w\-:]+) (.+)#", '$1 <fg=yellow;options=bold>$2</>', $command);
121
 
122
            return [sprintf(
123
                '  <fg=yellow>%s</>  %s<fg=#6C7280>%s %s%s %s</>',
124
                $expression,
125
                $command,
126
                $dots,
127
                $hasMutex,
128
                $nextDueDateLabel,
129
                $nextDueDate
130
            ), $this->output->isVerbose() && mb_strlen($description) > 1 ? sprintf(
131
                '  <fg=#6C7280>%s%s %s</>',
132
                str_repeat(' ', mb_strlen($expression) + 2),
133
                '⇁',
134
                $description
135
            ) : ''];
136
        });
137
 
138
        $this->line(
139
            $events->flatten()->filter()->prepend('')->push('')->toArray()
140
        );
141
    }
142
 
143
    /**
144
     * Gets the spacing to be used on each event row.
145
     *
146
     * @param  \Illuminate\Support\Collection  $events
147
     * @return array<int, int>
148
     */
149
    private function getCronExpressionSpacing($events)
150
    {
151
        $rows = $events->map(fn ($event) => array_map('mb_strlen', preg_split("/\s+/", $event->expression)));
152
 
153
        return collect($rows[0] ?? [])->keys()->map(fn ($key) => $rows->max($key))->all();
154
    }
155
 
156
    /**
157
     * Sorts the events by due date if option set.
158
     *
159
     * @param  \Illuminate\Support\Collection  $events
160
     * @param  \DateTimeZone  $timezone
161
     * @return \Illuminate\Support\Collection
162
     */
163
    private function sortEvents(\Illuminate\Support\Collection $events, DateTimeZone $timezone)
164
    {
165
        return $this->option('next')
166
                    ? $events->sortBy(fn ($event) => $this->getNextDueDateForEvent($event, $timezone))
167
                    : $events;
168
    }
169
 
170
    /**
171
     * Get the next due date for an event.
172
     *
173
     * @param  \Illuminate\Console\Scheduling\Event  $event
174
     * @param  \DateTimeZone  $timezone
175
     * @return \Illuminate\Support\Carbon
176
     */
177
    private function getNextDueDateForEvent($event, DateTimeZone $timezone)
178
    {
179
        return Carbon::instance(
180
            (new CronExpression($event->expression))
181
                ->getNextRunDate(Carbon::now()->setTimezone($event->timezone))
182
                ->setTimezone($timezone)
183
        );
184
    }
185
 
186
    /**
187
     * Formats the cron expression based on the spacing provided.
188
     *
189
     * @param  string  $expression
190
     * @param  array<int, int>  $spacing
191
     * @return string
192
     */
193
    private function formatCronExpression($expression, $spacing)
194
    {
195
        $expressions = preg_split("/\s+/", $expression);
196
 
197
        return collect($spacing)
198
            ->map(fn ($length, $index) => str_pad($expressions[$index], $length))
199
            ->implode(' ');
200
    }
201
 
202
    /**
203
     * Get the file and line number for the event closure.
204
     *
205
     * @param  \Illuminate\Console\Scheduling\CallbackEvent  $event
206
     * @return string
207
     */
208
    private function getClosureLocation(CallbackEvent $event)
209
    {
210
        $callback = tap((new ReflectionClass($event))->getProperty('callback'))
211
                        ->setAccessible(true)
212
                        ->getValue($event);
213
 
214
        if ($callback instanceof Closure) {
215
            $function = new ReflectionFunction($callback);
216
 
217
            return sprintf(
218
                '%s:%s',
219
                str_replace($this->laravel->basePath().DIRECTORY_SEPARATOR, '', $function->getFileName() ?: ''),
220
                $function->getStartLine()
221
            );
222
        }
223
 
224
        if (is_string($callback)) {
225
            return $callback;
226
        }
227
 
228
        if (is_array($callback)) {
229
            $className = is_string($callback[0]) ? $callback[0] : $callback[0]::class;
230
 
231
            return sprintf('%s::%s', $className, $callback[1]);
232
        }
233
 
234
        return sprintf('%s::__invoke', $callback::class);
235
    }
236
 
237
    /**
238
     * Get the terminal width.
239
     *
240
     * @return int
241
     */
242
    public static function getTerminalWidth()
243
    {
244
        return is_null(static::$terminalWidthResolver)
245
            ? (new Terminal)->getWidth()
246
            : call_user_func(static::$terminalWidthResolver);
247
    }
248
 
249
    /**
250
     * Set a callback that should be used when resolving the terminal width.
251
     *
252
     * @param  \Closure|null  $resolver
253
     * @return void
254
     */
255
    public static function resolveTerminalWidthUsing($resolver)
256
    {
257
        static::$terminalWidthResolver = $resolver;
258
    }
259
}