1 | <?php |
||
2 | |||
3 | namespace Zenstruck\ScheduleBundle\Schedule; |
||
4 | |||
5 | use Cron\CronExpression as CronSchedule; |
||
6 | |||
7 | /** |
||
8 | * @author Kevin Bond <[email protected]> |
||
9 | */ |
||
10 | final class CronExpression |
||
11 | { |
||
12 | public const MINUTE = 0; |
||
13 | public const HOUR = 1; |
||
14 | public const DOM = 2; |
||
15 | public const MONTH = 3; |
||
16 | public const DOW = 4; |
||
17 | |||
18 | public const ALIASES = [ |
||
19 | '@hourly', |
||
20 | '@daily', |
||
21 | '@weekly', |
||
22 | '@monthly', |
||
23 | '@yearly', |
||
24 | '@annually', |
||
25 | ]; |
||
26 | |||
27 | private const HASH_ALIAS_MAP = [ |
||
28 | '#hourly' => '# * * * *', |
||
29 | '#daily' => '# # * * *', |
||
30 | '#weekly' => '# # * * #', |
||
31 | '#monthly' => '# # # * *', |
||
32 | '#annually' => '# # # # *', |
||
33 | '#yearly' => '# # # # *', |
||
34 | '#midnight' => '# #(0-2) * * *', |
||
35 | ]; |
||
36 | |||
37 | private const RANGES = [ |
||
38 | self::MINUTE => [0, 59], |
||
39 | self::HOUR => [0, 23], |
||
40 | self::DOM => [1, 28], |
||
41 | self::MONTH => [1, 12], |
||
42 | self::DOW => [0, 6], |
||
43 | ]; |
||
44 | |||
45 | /** @var string */ |
||
46 | private $value; |
||
47 | |||
48 | /** @var string[] */ |
||
49 | private $parts; |
||
50 | 192 | ||
51 | /** @var string */ |
||
52 | 192 | private $context; |
|
53 | 192 | ||
54 | /** @var string */ |
||
55 | 192 | private $parsedValue; |
|
56 | 17 | ||
57 | public function __construct(string $value, string $context) |
||
58 | { |
||
59 | 177 | $this->value = $value; |
|
60 | 177 | $this->context = $context; |
|
61 | |||
62 | 177 | if (\in_array($value, self::ALIASES, true)) { |
|
63 | 8 | return; |
|
64 | } |
||
65 | |||
66 | 169 | $value = self::HASH_ALIAS_MAP[$value] ?? $value; |
|
67 | 169 | $parts = \explode(' ', $value); |
|
68 | |||
69 | 168 | if (5 !== \count($parts)) { |
|
70 | throw new \InvalidArgumentException("\"{$value}\" is an invalid cron expression."); |
||
71 | 168 | } |
|
72 | |||
73 | $this->parts = $parts; |
||
74 | 38 | } |
|
75 | |||
76 | 38 | public function __toString(): string |
|
77 | { |
||
78 | return $this->getParsedValue(); |
||
79 | 174 | } |
|
80 | |||
81 | 174 | public function getRawValue(): string |
|
82 | 16 | { |
|
83 | return $this->value; |
||
84 | } |
||
85 | 160 | ||
86 | 160 | public function getParsedValue(): string |
|
87 | 160 | { |
|
88 | 160 | if (!$this->parts) { |
|
0 ignored issues
–
show
|
|||
89 | 160 | return $this->getRawValue(); |
|
90 | 160 | } |
|
91 | |||
92 | return $this->parsedValue ?: $this->parsedValue = \implode(' ', [ |
||
93 | $this->parsePart(self::MINUTE), |
||
94 | 28 | $this->parsePart(self::HOUR), |
|
95 | $this->parsePart(self::DOM), |
||
96 | 28 | $this->parsePart(self::MONTH), |
|
97 | $this->parsePart(self::DOW), |
||
98 | ]); |
||
99 | 88 | } |
|
100 | |||
101 | 88 | public function isHashed(): bool |
|
102 | { |
||
103 | return $this->getRawValue() !== $this->getParsedValue(); |
||
104 | 68 | } |
|
105 | |||
106 | 68 | public function getNextRun(?string $timezone = null): \DateTimeInterface |
|
107 | { |
||
108 | return CronSchedule::factory($this->getParsedValue())->getNextRunDate('now', 0, false, $timezone); |
||
109 | 160 | } |
|
110 | |||
111 | 160 | public function isDue(\DateTimeInterface $timestamp, ?string $timezone = null): bool |
|
112 | { |
||
113 | 160 | return CronSchedule::factory($this->getParsedValue())->isDue($timestamp, $timezone); |
|
114 | 22 | } |
|
115 | 22 | ||
116 | 22 | private function parsePart(int $position): string |
|
117 | { |
||
118 | $value = $this->parts[$position]; |
||
119 | |||
120 | 160 | if (\preg_match('#^\#(\((\d+)-(\d+)\))?$#', $value, $matches)) { |
|
121 | $value = $this->hashField( |
||
122 | $matches[2] ?? self::RANGES[$position][0], |
||
123 | 22 | $matches[3] ?? self::RANGES[$position][1] |
|
124 | ); |
||
125 | 22 | } |
|
126 | |||
127 | 22 | return $value; |
|
128 | } |
||
129 | |||
130 | private function hashField(int $start, int $end): string |
||
131 | { |
||
132 | $possibleValues = \range($start, $end); |
||
133 | |||
134 | return (string) $possibleValues[(int) \fmod(\hexdec(\mb_substr(\md5($this->context), 0, 10)), \count($possibleValues))]; |
||
135 | } |
||
136 | } |
||
137 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.