RunResolver   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 30
eloc 81
c 1
b 0
f 0
dl 0
loc 196
ccs 0
cts 40
cp 0
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A increment() 0 22 6
A convertDOWToNumbers() 0 22 2
A isInIncrement() 0 16 5
C nextRun() 0 79 14
A isInList() 0 5 1
A isInRange() 0 5 2
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