Passed
Push — master ( 68072d...6506ec )
by Kevin
01:47
created

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