1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* This file is part of BlitzPHP Tasks. |
7
|
|
|
* |
8
|
|
|
* (c) 2025 Dimitri Sitchet Tomkeu <[email protected]> |
9
|
|
|
* |
10
|
|
|
* For the full copyright and license information, please view |
11
|
|
|
* the LICENSE file that was distributed with this source code. |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace BlitzPHP\Tasks; |
15
|
|
|
|
16
|
|
|
use BlitzPHP\Utilities\Date; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @credit <a href="https://tasks.codeigniter.com">CodeIgniter4 - CodeIgniter\Tasks\RunResolver</a> |
20
|
|
|
*/ |
21
|
|
|
class RunResolver |
22
|
|
|
{ |
23
|
|
|
/** |
24
|
|
|
* Le nombre maximal de fois à parcourir lors de la recherche de la prochaine date d'exécution. |
25
|
|
|
*/ |
26
|
|
|
protected int $maxIterations = 1000; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Prend une expression cron, par exemple '* * * * 4', |
30
|
|
|
* et renvoie une instance Dte qui représente la prochaine fois que cette expression s'exécutera. |
31
|
|
|
*/ |
32
|
|
|
public function nextRun(string $expression, Date $next): Date |
33
|
|
|
{ |
34
|
|
|
// Diviser l'expression en parties distinctes |
35
|
|
|
[ |
36
|
|
|
$minute, |
37
|
|
|
$hour, |
38
|
|
|
$monthDay, |
39
|
|
|
$month, |
40
|
|
|
$weekDay, |
41
|
|
|
] = explode(' ', $expression); |
42
|
|
|
|
43
|
|
|
$cron = [ |
44
|
|
|
'minute' => $minute, |
45
|
|
|
'hour' => $hour, |
46
|
|
|
'monthDay' => $monthDay, |
47
|
|
|
'month' => $month, |
48
|
|
|
'weekDay' => $weekDay, |
49
|
|
|
]; |
50
|
|
|
|
51
|
|
|
// Nous n'avons pas besoin de satisfaire les valeurs '*', donc supprimez-les pour avoir moins de boucles. |
52
|
|
|
$cron = array_filter($cron, static fn ($item) => $item !== '*'); |
53
|
|
|
|
54
|
|
|
// S'il ne reste plus rien, c'est toutes les minutes, alors réglez-le sur une minute à partir de maintenant. |
55
|
|
|
if ($cron === []) { |
56
|
|
|
return $next->addMinutes(1)->setSecond(0); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
// Bouclez sur chacun des éléments $cron restants jusqu'à ce que nous parvenions à les satisfaire tous |
60
|
|
|
for ($i = 1; $i <= $this->maxIterations; $i++) { |
61
|
|
|
foreach ($cron as $position => $value) { |
62
|
|
|
$satisfied = false; |
63
|
|
|
|
64
|
|
|
// La méthode à utiliser sur l'instance Date |
65
|
|
|
$method = 'get' . ucfirst($position); |
66
|
|
|
|
67
|
|
|
// monthDay et weekDay nécessitent des méthodes personnalisées |
68
|
|
|
if ($position === 'monthDay') { |
69
|
|
|
$method = 'getDay'; |
70
|
|
|
} |
71
|
|
|
if ($position === 'weekDay') { |
72
|
|
|
$method = 'getDayOfWeek'; |
73
|
|
|
|
74
|
|
|
$value = $this->convertDOWToNumbers($value); |
75
|
|
|
} |
76
|
|
|
$nextValue = $next->{$method}(); |
77
|
|
|
|
78
|
|
|
// S'il s'agit d'une valeur unique |
79
|
|
|
if ($nextValue === $value) { |
80
|
|
|
$satisfied = true; |
81
|
|
|
} |
82
|
|
|
// Si la valeur est une liste |
83
|
|
|
elseif (str_contains($value, ',')) { |
84
|
|
|
if ($this->isInList($nextValue, $value)) { |
85
|
|
|
$satisfied = true; |
86
|
|
|
} |
87
|
|
|
} |
88
|
|
|
// Si la valeur est une plage |
89
|
|
|
elseif (str_contains($value, '-')) { |
90
|
|
|
if ($this->isInRange($nextValue, $value)) { |
91
|
|
|
$satisfied = true; |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
// Si la valeur est un incrément |
95
|
|
|
elseif (str_contains($value, '/')) { |
96
|
|
|
if ($this->isInIncrement($nextValue, $value)) { |
97
|
|
|
$satisfied = true; |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
// Si nous ne le faisons pas correspondre, recommencez les itérations |
102
|
|
|
if (! $satisfied) { |
103
|
|
|
$next = $this->increment($next, $position); |
104
|
|
|
|
105
|
|
|
continue 2; |
106
|
|
|
} |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
return $next; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Incrémente la partie du cron à la suivante appropriée. |
115
|
|
|
* |
116
|
|
|
* Remarque : il s'agit d'une méthode assez brutale pour le faire. |
117
|
|
|
* Nous pourrions certainement le rendre plus intelligent à l'avenir pour réduire le nombre d'itérations nécessaires. |
118
|
|
|
*/ |
119
|
|
|
protected function increment(Date $next, string $position): Date |
120
|
|
|
{ |
121
|
|
|
switch ($position) { |
122
|
|
|
case 'minute': |
123
|
|
|
$next = $next->addMinutes(1); |
124
|
|
|
break; |
125
|
|
|
|
126
|
|
|
case 'hour': |
127
|
|
|
$next = $next->addHours(1); |
128
|
|
|
break; |
129
|
|
|
|
130
|
|
|
case 'monthDay': |
131
|
|
|
case 'weekDay': |
132
|
|
|
$next = $next->addDays(1); |
133
|
|
|
break; |
134
|
|
|
|
135
|
|
|
case 'month': |
136
|
|
|
$next = $next->addMonths(1); |
137
|
|
|
break; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $next; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Détermine si la valeur donnée est dans la plage spécifiée. |
145
|
|
|
* |
146
|
|
|
* @param int|string $value |
147
|
|
|
*/ |
148
|
|
|
protected function isInRange($value, string $range): bool |
149
|
|
|
{ |
150
|
|
|
[$start, $end] = explode('-', $range); |
151
|
|
|
|
152
|
|
|
return $value >= $start && $value <= $end; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Détermine si la valeur donnée est dans la liste de valeurs spécifiée. |
157
|
|
|
* |
158
|
|
|
* @param int|string $value |
159
|
|
|
*/ |
160
|
|
|
protected function isInList($value, string $list): bool |
161
|
|
|
{ |
162
|
|
|
$list = explode(',', $list); |
163
|
|
|
|
164
|
|
|
return in_array(trim($value), $list, true); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Détermine si la valeur $value est l'un des incréments. |
169
|
|
|
* |
170
|
|
|
* @param int|string $value |
171
|
|
|
*/ |
172
|
|
|
protected function isInIncrement($value, string $increment): bool |
173
|
|
|
{ |
174
|
|
|
[$start, $increment] = explode('/', $increment); |
175
|
|
|
|
176
|
|
|
// Autoriser les valeurs de départ vides |
177
|
|
|
if ($start === '' || $start === '*') { |
178
|
|
|
$start = 0; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
// L'intervalle $start doit être le premier à tester |
182
|
|
|
if ($value === $start) { |
183
|
|
|
return true; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
return ($value - $start) > 0 |
187
|
|
|
&& (($value - $start) % $increment) === 0; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Étant donné un paramètre cron pour le jour de la semaine, |
192
|
|
|
* il convertira les paramètres avec les jours de la semaine en texte (lun, mar, etc.) |
193
|
|
|
* en valeurs numériques pour une gestion plus facile. |
194
|
|
|
*/ |
195
|
|
|
protected function convertDOWToNumbers(string $origValue): string |
196
|
|
|
{ |
197
|
|
|
$origValue = strtolower(trim($origValue)); |
198
|
|
|
|
199
|
|
|
// S'il ne contient aucune lettre, renvoyez-le simplement. |
200
|
|
|
preg_match('/\w/', $origValue, $matches); |
201
|
|
|
|
202
|
|
|
if ($matches === []) { |
203
|
|
|
return $origValue; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
$days = [ |
207
|
|
|
'sun' => 0, |
208
|
|
|
'mon' => 1, |
209
|
|
|
'tue' => 2, |
210
|
|
|
'wed' => 3, |
211
|
|
|
'thu' => 4, |
212
|
|
|
'fri' => 5, |
213
|
|
|
'sat' => 6, |
214
|
|
|
]; |
215
|
|
|
|
216
|
|
|
return str_replace(array_keys($days), array_map(fn ($v) => (string) $v, array_values($days)), $origValue); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|