Completed
Push — master ( 9742d1...63b17b )
by Igor
02:06
created

Event.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace yii2mod\scheduling;
4
5
use Cron\CronExpression;
6
use GuzzleHttp\Client as HttpClient;
0 ignored issues
show
The type GuzzleHttp\Client was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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