Issues (29)

src/Task.php (4 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\Contracts\Container\ContainerInterface;
17
use BlitzPHP\Tasks\Exceptions\TasksException;
18
use BlitzPHP\Utilities\Date;
19
use InvalidArgumentException;
20
use ReflectionException;
21
use ReflectionFunction;
22
use SplFileObject;
23
24
/**
25
 * Représente une tâche unique qui doit être planifiée et exécutée périodiquement.
26
 *
27
 * @property mixed        $action
28
 * @property list<string> $environments
29
 * @property string       $name
30
 * @property string       $type
31
 * @property list<string> $types
32
 *
33
 * @credit <a href="https://tasks.codeigniter.com">CodeIgniter4 - CodeIgniter\Tasks\Task</a>
34
 */
35
class Task
36
{
37
    use FrequenciesTrait;
38
    use HooksTrait;
39
40
    /**
41
     * Types d'action supportés
42
     *
43
     * @var list<string>
0 ignored issues
show
The type BlitzPHP\Tasks\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
44
     */
45
    protected array $types = [
46
        'command',
47
        'shell',
48
        'closure',
49
        'event',
50
        'url',
51
    ];
52
53
    /**
54
     * S'il n'est pas vide, liste les environnements autorisés dans lesquels le programme peut être exécuté.
55
     *
56
     * @var list<string>
57
     */
58
    protected array $environments = [];
59
60
    /**
61
     * Timezone dans lequel la tâche doit être traitée.
62
     */
63
    protected ?string $timezone = null;
64
65
    /**
66
     * Proprietés magiques emulées
67
     *
68
     * @var array<string,mixed>
69
     */
70
    protected array $attributes = [];
71
72
    protected ContainerInterface $container;
73
74
    /**
75
     * @param string      $type       Type de l'action en cours.
76
     * @param mixed       $action     Le contenu actuel qu'on souhaite executer.
77
     * @param list<mixed> $parameters Parametres eventuels de l'action
78
     *
79
     * @throws TasksException
80
     */
81
    public function __construct(protected string $type, protected mixed $action, protected array $parameters = [])
82
    {
83
        if (! in_array($type, $this->types, true)) {
0 ignored issues
show
$this->types of type BlitzPHP\Tasks\list is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

83
        if (! in_array($type, /** @scrutinizer ignore-type */ $this->types, true)) {
Loading history...
84
            throw TasksException::invalidTaskType($type);
85
        }
86
87
        $this->container = service('container');
88
    }
89
90
    /**
91
     * Définissez le nom par lequel sera référencé cette tâche
92
     */
93
    public function named(string $name): self
94
    {
95
        $this->attributes['name'] = $name;
96
97
        return $this;
98
    }
99
100
    /**
101
     * Renvoie le type de la tache.
102
     */
103
    public function getType(): string
104
    {
105
        return $this->type;
106
    }
107
108
    /**
109
     * Renvoie l'action enregistrée.
110
     */
111
    public function getAction(): mixed
112
    {
113
        return $this->action;
114
    }
115
116
    /**
117
     * Exécute l'action de cette tâche.
118
     *
119
     * @return mixed
120
     *
121
     * @throws TasksException
122
     */
123
    public function run()
124
    {
125
        $method = 'run' . ucfirst($this->type);
126
        if (! method_exists($this, $method)) {
127
            throw TasksException::invalidTaskType($this->type);
128
        }
129
130
        return $this->process($this->container, $method);
131
    }
132
133
    /**
134
     * Détermine si cette tâche doit être exécutée maintenant en fonction de sa planification et de son environnement.
135
     */
136
    public function shouldRun(?string $testTime = null): bool
137
    {
138
        $cron = service('cronExpression')->setTimezone($this->timezone);
139
140
        // Autoriser le réglage des heures pendant les tests
141
        if (! empty($testTime)) {
142
            $cron->testTime($testTime);
143
        }
144
145
        // Sommes-nous limités aux environnements?
146
        if (! $this->runsInEnvironment(environment())) {
147
            return false;
148
        }
149
150
        return $cron->shouldRun($this->getExpression());
151
    }
152
153
    /**
154
     * Limite l'exécution de cette tâche uniquement dans des environnements spécifiés.
155
     */
156
    public function environments(string ...$environments): self
157
    {
158
        $this->environments = $environments;
0 ignored issues
show
Documentation Bug introduced by
It seems like $environments of type array<integer,string> is incompatible with the declared type BlitzPHP\Tasks\list of property $environments.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
159
160
        return $this;
161
    }
162
163
    /**
164
     * Définit le fuseau horaire pour l'exécution de la tâche.
165
     *
166
     * @param string $timezone L'identifiant du fuseau horaire à utiliser pour la tâche.
167
     *                         Il doit s'agir d'une chaîne de caractères PHP valide (par exemple, 'America/New_York', 'Europe/Paris').
168
     */
169
    public function timezone(string $timezone): self
170
    {
171
        $this->timezone = $timezone;
172
173
        return $this;
174
    }
175
176
    /**
177
     * Renvoie la date à laquelle cette tâche a été exécutée pour la dernière fois.
178
     *
179
     * @return Date|string
180
     */
181
    public function lastRun()
182
    {
183
        if (parametre('tasks.log_performance') === false) {
184
            return '--';
185
        }
186
187
        // Recupere les logs
188
        $logs = parametre("tasks.log-{$this->name}");
189
190
        if (empty($logs)) {
191
            return '--';
192
        }
193
194
        $log = array_shift($logs);
195
196
        return Date::parse($log['start']);
197
    }
198
199
    /**
200
     * Vérifie s'il peut s'exécute dans l'environnement spécifié.
201
     */
202
    protected function runsInEnvironment(string $environment): bool
203
    {
204
        return empty($this->environments) || in_array($environment, $this->environments, true);
0 ignored issues
show
$this->environments of type BlitzPHP\Tasks\list is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

204
        return empty($this->environments) || in_array($environment, /** @scrutinizer ignore-type */ $this->environments, true);
Loading history...
205
    }
206
207
    /**
208
     * Execute une commande Klinge.
209
     *
210
     * @return string Sortie tamponnée de la commande
211
     *
212
     * @throws InvalidArgumentException
213
     */
214
    protected function runCommand(): string
215
    {
216
        if (class_exists($command = $this->getAction())) {
217
        }
218
219
        return command($command);
220
    }
221
222
    /**
223
     * Execute un script shell.
224
     *
225
     * @return list<string> Lignes de la sortie de l'execution
226
     */
227
    protected function runShell(): array
228
    {
229
        exec($this->getAction(), $output);
230
231
        return $output;
232
    }
233
234
    /**
235
     * Execute un Closure.
236
     *
237
     * @return mixed Le resultat de la closure
238
     */
239
    protected function runClosure(): mixed
240
    {
241
        return $this->container->call($this->getAction(), $this->parameters);
242
    }
243
244
    /**
245
     * Declanche un evenement.
246
     *
247
     * @return bool Resultat du declanchement
248
     */
249
    protected function runEvent(): bool
250
    {
251
        return ! (false === service('event')->emit($this->getAction()));
252
    }
253
254
    /**
255
     * Interroge une URL.
256
     *
257
     * @return string Corps de la response
258
     */
259
    protected function runUrl(): string
260
    {
261
        $response = service('httpclient')->get($this->getAction());
262
263
        return $response->body();
264
    }
265
266
    /**
267
     * Crée un nom unique pour la tâche.
268
     * Utilisé lorsqu'un nom existant n'existe pas.
269
     *
270
     * @throws ReflectionException
271
     */
272
    protected function buildName(): string
273
    {
274
        // Obtenez un hachage basé sur l'action
275
        // Les closure ne peuvent pas être sérialisées, alors faites-le à la dure
276
        if ($this->getType() === 'closure') {
277
            $ref  = new ReflectionFunction($this->getAction());
278
            $file = new SplFileObject($ref->getFileName());
279
            $file->seek($ref->getStartLine() - 1);
280
            $content = '';
281
282
            while ($file->key() < $ref->getEndLine()) {
283
                $content .= $file->current();
284
                $file->next();
285
            }
286
            $actionString = json_encode([
287
                $content,
288
                $ref->getStaticVariables(),
289
            ]);
290
        } else {
291
            $actionString = serialize($this->getAction());
292
        }
293
294
        // Obtenir un hachage basé sur l'expression
295
        $expHash = $this->getExpression();
296
297
        return $this->getType() . '_' . md5($actionString . '_' . $expHash);
298
    }
299
300
    /**
301
     * Getter magique
302
     *
303
     * @return mixed
304
     */
305
    public function __get(string $key)
306
    {
307
        if ($key === 'name' && empty($this->attributes['name'])) {
308
            return $this->buildName();
309
        }
310
311
        if (property_exists($this, $key)) {
312
            return $this->{$key};
313
        }
314
315
        return $this->attributes[$key] ?? null;
316
    }
317
318
    /**
319
     * Setter magique
320
     */
321
    public function __set(string $name, mixed $value): void
322
    {
323
        $this->attributes[$name] = $value;
324
    }
325
}
326