Issues (29)

src/CronExpression.php (3 issues)

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\Tasks\Exceptions\TasksException;
17
use BlitzPHP\Utilities\Date;
18
use Exception;
19
20
/**
21
 * @credit <a href="https://tasks.codeigniter.com">CodeIgniter4 - CodeIgniter\Tasks\CronExpression</a>
22
 */
23
class CronExpression
24
{
25
    /**
26
     * Le fuseau horaire dans lequel cela doit être pris en compte.
27
     */
28
    protected string $timezone;
29
30
    /**
31
     * Le fuseau horaire global à utiliser.
32
     */
33
    private string $globalTimezone;
34
35
    /**
36
     * La date/heure actuelle. Utilisée pour les tests.
37
     */
38
    protected ?Date $testTime = null;
39
40
    /**
41
     * La chaîne d'expression Cron actuelle à traiter
42
     */
43
    private ?string $currentExpression = null;
44
45
    /**
46
     * Nous permet de définir le fuseau horaire global pour toutes les tâches de construction
47
     *
48
     * @param string $timezone Le fuseau horaire global pour toutes les tâches
49
     *
50
     * @return void
51
     */
52
    public function __construct(?string $timezone = null)
53
    {
54
        if (null === $globalTimezone = config('tasks.timezone')) {
55
            $globalTimezone = config('app.timezone');
56
        }
57
58
        $this->globalTimezone = $globalTimezone;
59
60
        $this->setTimezone($timezone);
61
    }
62
63
    /**
64
     * Définit le fuseau horaire global pour toutes les tâches de construction.
65
     */
66
    public function setTimezone(?string $timezone = null): self
67
    {
68
        $this->timezone = $timezone ?? $this->globalTimezone;
69
70
        return $this;
71
    }
72
73
    /**
74
     * Vérifie si l'expression cron doit être exécutée. Permet d'utiliser un fuseau horaire personnalisé pour une tâche spécifique
75
     *
76
     * @param string $expression L'expression Cron à évaluer
77
     */
78
    public function shouldRun(string $expression): bool
79
    {
80
        $this->setTime();
81
82
        $this->currentExpression = $expression;
83
84
        // Diviser l'expression en parties distinctes
85
        [
86
            $min,
87
            $hour,
88
            $monthDay,
89
            $month,
90
            $weekDay,
91
        ] = explode(' ', $expression);
92
93
        return $this->checkMinute($min)
94
            && $this->checkHour($hour)
95
            && $this->checkMonthDay($monthDay)
96
            && $this->checkMonth($month)
97
            && $this->checkWeekDay($weekDay);
98
    }
99
100
    /**
101
     * Renvoie une instance Date représentant la prochaine date/heure à laquelle cette expression serait exécutée.
102
     */
103
    public function nextRun(string $expression): Date
104
    {
105
        $this->setTime();
106
107
        return (new RunResolver())->nextRun($expression, clone $this->testTime);
108
    }
109
110
    /**
111
     * Renvoie une instance Date représentant la dernière date/heure à laquelle cette expression aurait été exécutée.
112
     */
113
    public function lastRun(string $expression): Date
0 ignored issues
show
The parameter $expression is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

113
    public function lastRun(/** @scrutinizer ignore-unused */ string $expression): Date

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
114
    {
115
        return new Date();
116
    }
117
118
    /**
119
     * Définit une date/heure qui sera utilisée à la place de l'heure actuelle pour faciliter les tests.
120
     *
121
     * @throws Exception
122
     */
123
    public function testTime(string $dateTime): self
124
    {
125
        $this->testTime = Date::parse($dateTime, $this->timezone);
126
127
        return $this;
128
    }
129
130
    private function checkMinute(string $time): bool
131
    {
132
        return $this->checkTime($time, 'i');
133
    }
134
135
    private function checkHour(string $time): bool
136
    {
137
        return $this->checkTime($time, 'G');
138
    }
139
140
    private function checkMonthDay(string $time): bool
141
    {
142
        return $this->checkTime($time, 'j');
143
    }
144
145
    private function checkMonth(string $time): bool
146
    {
147
        return $this->checkTime($time, 'n');
148
    }
149
150
    private function checkWeekDay(string $time): bool
151
    {
152
        return $this->checkTime($time, 'w');
153
    }
154
155
    private function checkTime(string $time, string $format): bool
156
    {
157
        if ($time === '*') {
158
            return true;
159
        }
160
161
        $currentTime = $this->testTime->format($format);
0 ignored issues
show
The method format() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

161
        /** @scrutinizer ignore-call */ 
162
        $currentTime = $this->testTime->format($format);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
162
        assert(ctype_digit($currentTime));
163
164
        // Gérer les temps répétitifs (c'est-à-dire /5 ou */5 pour toutes les 5 minutes)
165
        if (str_contains($time, '/')) {
166
            $period = substr($time, strpos($time, '/') + 1) ?: '';
167
168
            if ($period === '' || ! ctype_digit($period)) {
169
                throw TasksException::invalidCronExpression($this->currentExpression);
0 ignored issues
show
It seems like $this->currentExpression can also be of type null; however, parameter $string of BlitzPHP\Tasks\Exception...invalidCronExpression() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
                throw TasksException::invalidCronExpression(/** @scrutinizer ignore-type */ $this->currentExpression);
Loading history...
170
            }
171
172
            return ($currentTime % $period) === 0;
173
        }
174
175
        // Gere les plages (1-5)
176
        if (str_contains($time, '-')) {
177
            $items         = [];
178
            [$start, $end] = explode('-', $time);
179
180
            for ($i = $start; $i <= $end; $i++) {
181
                $items[] = $i;
182
            }
183
        }
184
        // Gérer plusieurs jours
185
        else {
186
            $items = explode(',', $time);
187
        }
188
189
        return in_array($currentTime, $items, false);
190
    }
191
192
    /**
193
     * Définit l'heure actuelle si elle n'a pas déjà été définie.
194
     *
195
     * @throws Exception
196
     */
197
    private function setTime(): void
198
    {
199
        // Définir notre heure actuelle
200
        if ($this->testTime instanceof Date) {
201
            return;
202
        }
203
204
        $this->testTime = Date::now($this->timezone);
205
    }
206
}
207