Event::weekly()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
/*
4
 * This file is part of the mytheam/yii2-schedule.
5
 *
6
 * (c) mytheam <[email protected]>
7
 *
8
 * This source file is subject to the BSD license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace mythteam\schedule;
13
14
use Cron\CronExpression;
15
use GuzzleHttp\Client as HttpClient;
16
use Symfony\Component\Process\Process;
17
use yii\base\Application;
18
use yii\base\Component;
19
use yii\base\InvalidCallException;
20
use yii\mail\MailerInterface;
21
22
class Event extends Component
23
{
24
    const EVENT_BEFORE_RUN = 'beforeRun';
25
    const EVENT_AFTER_RUN = 'afterRun';
26
27
    /**
28
     * @var string The command to be executed.
29
     */
30
    public $command;
31
32
    /**
33
     * @var string The crontab expression of the event's frequency.
34
     */
35
    protected $_expression = '* * * * * *';
36
37
    /**
38
     * @var \DateTimeZone|string The timezone the date should be evaluated.
39
     */
40
    protected $_timezone;
41
42
    /**
43
     * @var string The user should run the command.
44
     */
45
    protected $_user;
46
47
    /**
48
     * @var \Closure The filter callback.
49
     */
50
    protected $_filter;
51
52
    /**
53
     * @var \Closure The reject callback.
54
     */
55
    protected $_reject;
56
57
    /**
58
     * @var string The location that output should be send to.
59
     */
60
    protected $_output;
61
62
    /**
63
     * @var array The array of callbacks of to be run after event is finished.
64
     */
65
    protected $_afterCallbacks = [];
66
67
    /**
68
     * @var string The human readable description of the event.
69
     */
70
    protected $_description;
71
72
    /**
73
     * Create a new event instance.
74
     *
75
     * @param string $command
76
     * @param array $config
77
     */
78 32
    public function __construct($command, $config = [])
79
    {
80 32
        $this->command = $command;
81 32
        $this->_output = $this->getDefaultOutput();
82 32
        parent::__construct($config);
83 32
    }
84
85
    /**
86
     * Get the expression.
87
     *
88
     * @return string
89
     */
90 52
    public function getExpression()
91
    {
92 52
        return $this->_expression;
93
    }
94
95
    /**
96
     * Set the timezone.
97
     *
98
     * @param \DateTimeZone|string $timezone
99
     *
100
     * @return $this
101
     */
102 2
    public function timezone($timezone)
103
    {
104 2
        $this->_timezone = $timezone;
105
106 2
        return $this;
107
    }
108
109
    /**
110
     * Set the execute user.
111
     *
112
     * @param string $user
113
     *
114
     * @return $this
115
     */
116
    public function user($user)
117
    {
118
        $this->_user = $user;
119
120
        return $this;
121
    }
122
123
    /**
124
     * Set the command output message export location.
125
     *
126
     * @param string $path
127
     *
128
     * @return $this
129
     */
130 2
    public function output($path)
131
    {
132 2
        $this->_output = $path;
133
134 2
        return $this;
135
    }
136
137
    /**
138
     * Set the description.
139
     *
140
     * @param string $description
141
     *
142
     * @return $this
143
     */
144 2
    public function description($description)
145
    {
146 2
        $this->_description = $description;
147
148 2
        return $this;
149
    }
150
151
    /**
152
     * Get the summary of the event for display.
153
     *
154
     * @return string
155
     */
156 1
    public function getSummaryForDisplay()
157
    {
158 1
        if (is_string($this->_description)) {
159 1
            return $this->_description;
160
        }
161
162 1
        return $this->buildCommand();
163
    }
164
165
    /**
166
     * Register the callback function to execute after schedule.
167
     *
168
     * @param \Closure $callback
169
     *
170
     * @return $this
171
     */
172
    public function then(\Closure $callback)
173
    {
174
        $this->_afterCallbacks[] = $callback;
175
176
        return $this;
177
    }
178
179
    /**
180
     * Register a callback to the ping a given URL after the job runs.
181
     *
182
     * @param string $url
183
     *
184
     * @return $this
185
     */
186
    public function thenPing($url)
187
    {
188
        return $this->then(function () use ($url) {
189
            (new HttpClient())->get($url);
190
        });
191
    }
192
193
    /**
194
     * Register a callback  to further filter the  schedule.
195
     *
196
     * @param \Closure $callback
197
     *
198
     * @return $this
199
     */
200
    public function when(\Closure $callback)
201
    {
202
        $this->_filter = $callback;
203
204
        return $this;
205
    }
206
207
    /**
208
     * Register a callback to further filter the schedule.
209
     *
210
     * @param \Closure $callback
211
     *
212
     * @return $this
213
     */
214
    public function skip(\Closure $callback)
215
    {
216
        $this->_reject = $callback;
217
218
        return $this;
219
    }
220
221
    /**
222
     * Register the send email logic.
223
     *
224
     * @param array $address
225
     *
226
     * @return $this
227
     */
228
    public function emailOutput($address)
229
    {
230
        if (is_null($this->_output)
231
            || $this->_output == $this->getDefaultOutput()
232
        ) {
233
            throw new InvalidCallException('Must direct output to file in order to email results.');
234
        }
235
        $address = is_array($address) ? $address : func_get_args();
236
237
        return $this->then(function (Application $app) use ($address) {
238
            $this->sendEmail($app->mailer, $address);
239
        });
240
    }
241
242
    /**
243
     * Run the event.
244
     *
245
     * @param \yii\base\Application $app
246
     */
247
    public function run(Application $app)
248
    {
249
        $this->trigger(self::EVENT_BEFORE_RUN);
250
        if (count($this->_afterCallbacks) > 0) {
251
            $this->runCommandInForeground($app);
252
        } else {
253
            $this->runCommandInBackground($app);
254
        }
255
        $this->trigger(self::EVENT_AFTER_RUN);
256
    }
257
258
    /**
259
     * @param \yii\base\Application $app
260
     */
261
    protected function runCommandInForeground(Application $app)
262
    {
263
        (new Process(
264
            trim($this->buildCommand(), '& '), dirname($app->request->getScriptFile()), null, null, null
265
        ))->run();
266
        $this->callAfterCallbacks($app);
267
    }
268
269
    /**
270
     * Call all of the "after" callbacks for the event.
271
     *
272
     * @param Application $app
273
     */
274 1
    protected function callAfterCallbacks(Application $app)
275
    {
276 1
        foreach ($this->_afterCallbacks as $callback) {
277
            call_user_func($callback, $app);
278 1
        }
279 1
    }
280
281
    /**
282
     * Run the command in the background using exec.
283
     *
284
     * @param Application $app
285
     */
286
    protected function runCommandInBackground(Application $app)
287
    {
288
        chdir(dirname($app->request->getScriptFile()));
289
        exec($this->buildCommand());
290
    }
291
292
    /**
293
     * Determine if the given event should run based on the Cron expression.
294
     *
295
     * @param Application $app
296
     *
297
     * @return bool
298
     */
299 1
    public function isDue(Application $app)
300
    {
301 1
        return $this->expressionPasses() && $this->filtersPass($app);
302
    }
303
304
    /**
305
     * Determine if the Cron expression passes.
306
     *
307
     * @return bool
308
     */
309 1
    protected function expressionPasses()
310
    {
311 1
        $date = new \DateTime('now');
312 1
        if ($this->_timezone) {
313
            $date->setTimezone($this->_timezone);
314
        }
315
316 1
        return CronExpression::factory($this->_expression)->isDue($date);
317
    }
318
319
    /**
320
     * Determine if the filters pass for the event.
321
     *
322
     * @param Application $app
323
     *
324
     * @return bool
325
     */
326 1
    protected function filtersPass(Application $app)
327
    {
328 1
        if (($this->_filter && ($this->_filter))
329 1
            || $this->_reject && call_user_func($this->_reject, $app)
330 1
        ) {
331
            return false;
332
        }
333
334 1
        return true;
335
    }
336
337
    /**
338
     * The Cron expression representing the event's frequency.
339
     *
340
     * @param string $expression
341
     *
342
     * @return $this
343
     */
344 50
    public function cron($expression)
345
    {
346 50
        $this->_expression = $expression;
347
348 50
        return $this;
349
    }
350
351
    /**
352
     * Schedule the event to run hourly.
353
     *
354
     * @return $this
355
     */
356 2
    public function hourly()
357
    {
358 2
        return $this->cron('0 * * * * *');
359
    }
360
361
    /**
362
     * Schedule the event to run daily.
363
     *
364
     * @return $this
365
     */
366 2
    public function daily()
367
    {
368 2
        return $this->cron('0 0 * * * *');
369
    }
370
371
    /**
372
     * Schedule the command at a given time.
373
     *
374
     * @param string $time
375
     *
376
     * @return $this
377
     */
378 2
    public function at($time)
379
    {
380 2
        return $this->dailyAt($time);
381
    }
382
383
    /**
384
     * Schedule the event to run daily at a given time (10:00, 19:30, etc).
385
     *
386
     * @param string $time
387
     *
388
     * @return $this
389
     */
390 8
    public function dailyAt($time)
391
    {
392 8
        $segments = explode(':', $time);
393
394 8
        return $this->spliceIntoPosition(2, (int)$segments[0])
395 8
            ->spliceIntoPosition(1, count($segments) == 2 ? (int)$segments[1] : '0');
396
    }
397
398
    /**
399
     * Schedule the event to run twice daily.
400
     *
401
     * @return $this
402
     */
403 2
    public function twiceDaily()
404
    {
405 2
        return $this->cron('0 1,13 * * * *');
406
    }
407
408
    /**
409
     * Schedule the event to run only on weekdays.
410
     *
411
     * @return $this
412
     */
413 2
    public function weekdays()
414
    {
415 2
        return $this->spliceIntoPosition(5, '1-5');
416
    }
417
418
    /**
419
     * Schedule the event to run only on Mondays.
420
     *
421
     * @return $this
422
     */
423 2
    public function mondays()
424
    {
425 2
        return $this->days(1);
426
    }
427
428
    /**
429
     * Set the days of the week the command should run on.
430
     *
431
     * @param array|int $days
432
     *
433
     * @return $this
434
     */
435 16
    public function days($days)
436
    {
437 16
        $days = is_array($days) ? $days : func_get_args();
438
439 16
        return $this->spliceIntoPosition(5, implode(',', $days));
440
    }
441
442
    /**
443
     * Schedule the event to run only on Tuesdays.
444
     *
445
     * @return $this
446
     */
447 2
    public function tuesdays()
448
    {
449 2
        return $this->days(2);
450
    }
451
452
    /**
453
     * Schedule the event to run only on Wednesdays.
454
     *
455
     * @return $this
456
     */
457 2
    public function wednesdays()
458
    {
459 2
        return $this->days(3);
460
    }
461
462
    /**
463
     * Schedule the event to run only on Thursdays.
464
     *
465
     * @return $this
466
     */
467 2
    public function thursdays()
468
    {
469 2
        return $this->days(4);
470
    }
471
472
    /**
473
     * Schedule the event to run only on Fridays.
474
     *
475
     * @return $this
476
     */
477 2
    public function fridays()
478
    {
479 2
        return $this->days(5);
480
    }
481
482
    /**
483
     * Schedule the event to run only on Saturdays.
484
     *
485
     * @return $this
486
     */
487 2
    public function saturdays()
488
    {
489 2
        return $this->days(6);
490
    }
491
492
    /**
493
     * Schedule the event to run only on Sundays.
494
     *
495
     * @return $this
496
     */
497 2
    public function sundays()
498
    {
499 2
        return $this->days(0);
500
    }
501
502
    /**
503
     * Schedule the event to run weekly.
504
     *
505
     * @return $this
506
     */
507 2
    public function weekly()
508
    {
509 2
        return $this->cron('0 0 * * 0 *');
510
    }
511
512
    /**
513
     * Schedule the event to run weekly on a given day and time.
514
     *
515
     * @param int $day
516
     * @param string $time
517
     *
518
     * @return $this
519
     */
520 4
    public function weeklyOn($day, $time = '0:0')
521
    {
522 4
        $this->dailyAt($time);
523
524 4
        return $this->spliceIntoPosition(5, $day);
525
    }
526
527
    /**
528
     * Schedule the event to run monthly.
529
     *
530
     * @return $this
531
     */
532 2
    public function monthly()
533
    {
534 2
        return $this->cron('0 0 1 * * *');
535
    }
536
537
    /**
538
     * Schedule the event to run yearly.
539
     *
540
     * @return $this
541
     */
542 2
    public function yearly()
543
    {
544 2
        return $this->cron('0 0 1 1 * *');
545
    }
546
547
    /**
548
     * Schedule the event to run every minute.
549
     *
550
     * @return $this
551
     */
552 2
    public function everyMinute()
553
    {
554 2
        return $this->cron('* * * * * *');
555
    }
556
557
    /**
558
     * Schedule the event to run every N minutes.
559
     *
560
     * @param int|string $minutes
561
     *
562
     * @return $this
563
     */
564 6
    public function everyNMinutes($minutes)
565
    {
566 6
        return $this->cron('*/' . $minutes . ' * * * * *');
567
    }
568
569
    /**
570
     * Schedule the event to run every five minutes.
571
     *
572
     * @return $this
573
     */
574 2
    public function everyFiveMinutes()
575
    {
576 2
        return $this->everyNMinutes(5);
577
    }
578
579
    /**
580
     * Schedule the event to run every ten minutes.
581
     *
582
     * @return $this
583
     */
584 2
    public function everyTenMinutes()
585
    {
586 2
        return $this->everyNMinutes(10);
587
    }
588
589
    /**
590
     * Schedule the event to run every thirty minutes.
591
     *
592
     * @return $this
593
     */
594 2
    public function everyThirtyMinutes()
595
    {
596 2
        return $this->cron('0,30 * * * * *');
597
    }
598
599
    /**
600
     * Splice the given value into the given position of the expression.
601
     *
602
     * @param int $position
603
     * @param string $value
604
     *
605
     * @return Event
606
     */
607 26
    protected function spliceIntoPosition($position, $value)
608
    {
609 26
        $segments = explode(' ', $this->_expression);
610 26
        $segments[$position - 1] = $value;
611
612 26
        return $this->cron(implode(' ', $segments));
613
    }
614
615
    /**
616
     * Build the execute command.
617
     *
618
     * @return string
619
     */
620 3
    public function buildCommand()
621
    {
622 3
        $command = $this->command . ' >> ' . $this->_output . ' 2>&1 &';
623
624 3
        return $this->_user ? 'sudo -u ' . $this->_user . ' ' . $command : $command;
625
    }
626
627
    /**
628
     * Send email logic.
629
     *
630
     * @param MailerInterface $mailer
631
     * @param array $address
632
     */
633
    protected function sendEmail(MailerInterface $mailer, $address)
634
    {
635
        $message = $mailer->compose();
636
        $message->setTextBody(file_get_contents($this->_output))
637
            ->setSubject($this->getEmailSubject())
638
            ->setTo($address);
639
640
        $message->send();
641
    }
642
643
    /**
644
     * Return the email subject.
645
     *
646
     * @return string
647
     */
648
    protected function getEmailSubject()
649
    {
650
        if ($this->_description) {
651
            return 'Scheduled Job Output (' . $this->_description . ')';
652
        }
653
654
        return 'Scheduled Job Output';
655
    }
656
657 32
    public function getDefaultOutput()
658
    {
659 32
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
660
            return 'NUL';
661
        } else {
662 32
            return '/dev/null';
663
        }
664
    }
665
}
666