Passed
Push — master ( 9ed588...0002a7 )
by Kevin
01:39
created

Schedule::addNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Zenstruck\ScheduleBundle;
4
5
use Symfony\Component\Process\Process;
6
use Zenstruck\ScheduleBundle\Event\BeforeScheduleEvent;
7
use Zenstruck\ScheduleBundle\Schedule\Exception\SkipSchedule;
8
use Zenstruck\ScheduleBundle\Schedule\Extension;
9
use Zenstruck\ScheduleBundle\Schedule\Extension\CallbackExtension;
10
use Zenstruck\ScheduleBundle\Schedule\Extension\EmailExtension;
11
use Zenstruck\ScheduleBundle\Schedule\Extension\EnvironmentExtension;
12
use Zenstruck\ScheduleBundle\Schedule\Extension\PingExtension;
13
use Zenstruck\ScheduleBundle\Schedule\Extension\SingleServerExtension;
14
use Zenstruck\ScheduleBundle\Schedule\Task;
15
use Zenstruck\ScheduleBundle\Schedule\Task\CallbackTask;
16
use Zenstruck\ScheduleBundle\Schedule\Task\CommandTask;
17
use Zenstruck\ScheduleBundle\Schedule\Task\CompoundTask;
18
use Zenstruck\ScheduleBundle\Schedule\Task\NullTask;
19
use Zenstruck\ScheduleBundle\Schedule\Task\ProcessTask;
20
21
/**
22
 * @author Kevin Bond <[email protected]>
23
 */
24
final class Schedule
25
{
26
    private $tasks = [];
27
    private $allTasks;
28
    private $dueTasks;
29
    private $timezone;
30
31
    /** @var Extension[] */
32
    private $extensions = [];
33
34 3
    public function getId(): string
35
    {
36 3
        $tasks = \array_map(
37
            function (Task $task) {
38 2
                return $task->getId();
39 3
            },
40 3
            $this->all()
41
        );
42
43 3
        return \sha1(\implode('', $tasks));
44
    }
45
46 34
    public function add(Task $task): Task
47
    {
48 34
        $this->resetCache();
49
50 34
        return $this->tasks[] = $task;
51
    }
52
53
    /**
54
     * @param string $name Command class or name (my:command)
55
     */
56 8
    public function addCommand(string $name, string ...$arguments): CommandTask
57
    {
58 8
        return $this->add(new CommandTask($name, ...$arguments));
59
    }
60
61
    /**
62
     * @param callable $callback Return value is considered "output"
63
     */
64 2
    public function addCallback(callable $callback): CallbackTask
65
    {
66 2
        return $this->add(new CallbackTask($callback));
67
    }
68
69
    /**
70
     * @param string|Process $process
71
     */
72 2
    public function addProcess($process): ProcessTask
73
    {
74 2
        return $this->add(new ProcessTask($process));
75
    }
76
77 1
    public function addNull(string $description): NullTask
78
    {
79 1
        return $this->add(new NullTask($description));
80
    }
81
82 1
    public function addCompound(): CompoundTask
83
    {
84 1
        return $this->add(new CompoundTask());
85
    }
86
87 28
    public function addExtension(Extension $extension): self
88
    {
89 28
        $this->extensions[] = $extension;
90
91 28
        return $this;
92
    }
93
94
    /**
95
     * Prevent schedule from running if callback throws \Zenstruck\ScheduleBundle\Schedule\Exception\SkipSchedule.
96
     *
97
     * @param callable $callback Receives an instance of \Zenstruck\ScheduleBundle\Event\BeforeScheduleEvent
98
     */
99 12
    public function filter(callable $callback): self
100
    {
101 12
        return $this->addExtension(CallbackExtension::scheduleFilter($callback));
102
    }
103
104
    /**
105
     * Only run schedule if true.
106
     *
107
     * @param bool|callable $callback bool: skip if false, callable: skip if return value is false
108
     *                                callable receives an instance of \Zenstruck\ScheduleBundle\Event\BeforeScheduleEvent
109
     */
110 4
    public function when(string $description, $callback): self
111
    {
112
        $callback = \is_callable($callback) ? $callback : function () use ($callback) {
113 2
            return (bool) $callback;
114 4
        };
115
116
        return $this->filter(function (BeforeScheduleEvent $event) use ($callback, $description) {
117 4
            if (!$callback($event)) {
118 2
                throw new SkipSchedule($description);
119
            }
120 4
        });
121
    }
122
123
    /**
124
     * Skip schedule if true.
125
     *
126
     * @param bool|callable $callback bool: skip if true, callable: skip if return value is true
127
     *                                callable receives an instance of \Zenstruck\ScheduleBundle\Event\BeforeScheduleEvent
128
     */
129 5
    public function skip(string $description, $callback): self
130
    {
131
        $callback = \is_callable($callback) ? $callback : function () use ($callback) {
132 3
            return (bool) $callback;
133 5
        };
134
135
        return $this->filter(function (BeforeScheduleEvent $event) use ($callback, $description) {
136 5
            if ($callback($event)) {
137 3
                throw new SkipSchedule($description);
138
            }
139 5
        });
140
    }
141
142
    /**
143
     * Execute callback before tasks run (even if no tasks are due).
144
     *
145
     * @param callable $callback Receives an instance of \Zenstruck\ScheduleBundle\Event\BeforeScheduleEvent
146
     */
147 3
    public function before(callable $callback): self
148
    {
149 3
        return $this->addExtension(CallbackExtension::scheduleBefore($callback));
150
    }
151
152
    /**
153
     * Execute callback after tasks run (even if no tasks ran).
154
     *
155
     * @param callable $callback Receives an instance of \Zenstruck\ScheduleBundle\Event\AfterScheduleEvent
156
     */
157 3
    public function after(callable $callback): self
158
    {
159 3
        return $this->addExtension(CallbackExtension::scheduleAfter($callback));
160
    }
161
162
    /**
163
     * Alias for after().
164
     */
165 1
    public function then(callable $callback): self
166
    {
167 1
        return $this->after($callback);
168
    }
169
170
    /**
171
     * Execute callback after tasks run if all tasks succeeded
172
     *  - even if no tasks ran
173
     *  - skipped tasks are considered successful.
174
     *
175
     * @param callable $callback Receives an instance of \Zenstruck\ScheduleBundle\Event\AfterScheduleEvent
176
     */
177 3
    public function onSuccess(callable $callback): self
178
    {
179 3
        return $this->addExtension(CallbackExtension::scheduleSuccess($callback));
180
    }
181
182
    /**
183
     * Execute callback after tasks run if one or more tasks failed
184
     *  - skipped tasks are considered successful.
185
     *
186
     * @param callable $callback Receives an instance of \Zenstruck\ScheduleBundle\Event\AfterScheduleEvent
187
     */
188 3
    public function onFailure(callable $callback): self
189
    {
190 3
        return $this->addExtension(CallbackExtension::scheduleFailure($callback));
191
    }
192
193
    /**
194
     * Ping a webhook before any tasks run (even if none are due).
195
     * If you want to control the HttpClientInterface used, configure `zenstruck_schedule.ping_handler`.
196
     *
197
     * @param array $options See HttpClientInterface::OPTIONS_DEFAULTS
198
     */
199 3
    public function pingBefore(string $url, string $method = 'GET', array $options = []): self
200
    {
201 3
        return $this->addExtension(PingExtension::scheduleBefore($url, $method, $options));
202
    }
203
204
    /**
205
     * Ping a webhook after tasks ran (even if none ran).
206
     * If you want to control the HttpClientInterface used, configure `zenstruck_schedule.ping_handler`.
207
     *
208
     * @param array $options See HttpClientInterface::OPTIONS_DEFAULTS
209
     */
210 3
    public function pingAfter(string $url, string $method = 'GET', array $options = []): self
211
    {
212 3
        return $this->addExtension(PingExtension::scheduleAfter($url, $method, $options));
213
    }
214
215
    /**
216
     * Alias for pingAfter().
217
     */
218 1
    public function thenPing(string $url, string $method = 'GET', array $options = []): self
219
    {
220 1
        return $this->pingAfter($url, $method, $options);
221
    }
222
223
    /**
224
     * Ping a webhook after tasks run if all tasks succeeded (skipped tasks are considered successful).
225
     * If you want to control the HttpClientInterface used, configure `zenstruck_schedule.ping_handler`.
226
     *
227
     * @param array $options See HttpClientInterface::OPTIONS_DEFAULTS
228
     */
229 3
    public function pingOnSuccess(string $url, string $method = 'GET', array $options = []): self
230
    {
231 3
        return $this->addExtension(PingExtension::scheduleSuccess($url, $method, $options));
232
    }
233
234
    /**
235
     * Ping a webhook after tasks run if one or more tasks failed.
236
     * If you want to control the HttpClientInterface used, configure `zenstruck_schedule.ping_handler`.
237
     *
238
     * @param array $options See HttpClientInterface::OPTIONS_DEFAULTS
239
     */
240 3
    public function pingOnFailure(string $url, string $method = 'GET', array $options = []): self
241
    {
242 3
        return $this->addExtension(PingExtension::scheduleFailure($url, $method, $options));
243
    }
244
245
    /**
246
     * Email failed task detail after tasks run if one or more tasks failed.
247
     * Be sure to configure `zenstruck_schedule.email_handler`.
248
     *
249
     * @param string|string[] $to       Email address(es)
250
     * @param callable|null   $callback Add your own headers etc
251
     *                                  Receives an instance of \Symfony\Component\Mime\Email
252
     */
253 5
    public function emailOnFailure($to = null, string $subject = null, callable $callback = null): self
254
    {
255 5
        return $this->addExtension(EmailExtension::scheduleFailure($to, $subject, $callback));
256
    }
257
258
    /**
259
     * Restrict running of schedule to a single server.
260
     * Be sure to configure `zenstruck_schedule.single_server_handler`.
261
     *
262
     * @param int $ttl Maximum expected lock duration in seconds
263
     */
264 1
    public function onSingleServer(int $ttl = SingleServerExtension::DEFAULT_TTL): self
265
    {
266 1
        return $this->addExtension(new SingleServerExtension($ttl));
267
    }
268
269
    /**
270
     * Define the application environment(s) you wish to run the schedule in. Trying to
271
     * run in another environment will skip the schedule.
272
     */
273 1
    public function environments(string ...$environments): self
274
    {
275 1
        return $this->addExtension(new EnvironmentExtension($environments));
276
    }
277
278
    /**
279
     * The default timezone for tasks (tasks can override).
280
     *
281
     * @param string|\DateTimeZone $value
282
     */
283 2
    public function timezone($value): self
284
    {
285 2
        $this->resetCache();
286
287 2
        if (!$value instanceof \DateTimeZone) {
288 2
            $value = new \DateTimeZone($value);
289
        }
290
291 2
        $this->timezone = $value;
292
293 2
        return $this;
294
    }
295
296
    /**
297
     * @return Extension[]
298
     */
299 47
    public function getExtensions(): array
300
    {
301 47
        return $this->extensions;
302
    }
303
304 34
    public function getTimezone(): ?\DateTimeZone
305
    {
306 34
        return $this->timezone;
307
    }
308
309
    /**
310
     * @return Task[]
311
     */
312 39
    public function all(): array
313
    {
314 39
        if (null !== $this->allTasks) {
315 24
            return $this->allTasks;
316
        }
317
318 39
        $this->allTasks = [];
319
320 39
        foreach ($this->taskIterator() as $task) {
321 34
            $task = clone $task;
322
323 34
            if ($this->getTimezone() && !$task->getTimezone()) {
324 2
                $task->timezone($this->getTimezone());
325
            }
326
327 34
            $this->allTasks[] = $task;
328
        }
329
330 39
        return $this->allTasks;
331
    }
332
333
    /**
334
     * @return Task[]
335
     */
336 29
    public function due(): array
337
    {
338 29
        if (null !== $this->dueTasks) {
339 15
            return $this->dueTasks;
340
        }
341
342 29
        $this->dueTasks = [];
343
344 29
        foreach ($this->all() as $task) {
345 25
            if ($task->isDue()) {
346 25
                $this->dueTasks[] = $task;
347
            }
348
        }
349
350 29
        return $this->dueTasks;
351
    }
352
353
    /**
354
     * @return Task[]
355
     */
356 39
    private function taskIterator(): \Generator
357
    {
358 39
        foreach ($this->tasks as $task) {
359 34
            if ($task instanceof CompoundTask) {
360 2
                yield from $task;
0 ignored issues
show
Bug Best Practice introduced by
The expression YieldFromNode returns the type Generator which is incompatible with the documented return type Zenstruck\ScheduleBundle\Schedule\Task[].
Loading history...
361
362 2
                continue;
363
            }
364
365 34
            yield $task;
366
        }
367 39
    }
368
369 34
    private function resetCache(): void
370
    {
371 34
        $this->allTasks = $this->dueTasks = null;
372 34
    }
373
}
374