Issues (91)

src/Schedule/CronExpression.php (1 issue)

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
Bug Best Practice introduced by
The expression $this->parts of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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