Issues (29)

src/HooksTrait.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\Contracts\Mail\MailerInterface;
18
use BlitzPHP\Utilities\Iterable\Arr;
19
use BlitzPHP\Utilities\String\Stringable;
20
use Closure;
21
use LogicException;
22
use Throwable;
23
24
trait HooksTrait
25
{
26
    /**
27
     * L'emplacement où la sortie doit être envoyée.
28
     */
29
    public ?string $location = null;
30
31
    /**
32
     * Code de sortie de la tache
33
     */
34
    protected ?int $exitCode = null;
35
36
    /**
37
     * Exception levée lors de l'exécution de la tâche.
38
     */
39
    protected ?Throwable $exception = null;
40
41
    /**
42
     * Indique si la sortie doit être ajoutée.
43
     */
44
    public bool $shouldAppendOutput = false;
45
46
    /**
47
     * Tableau de rappels à exécuter avant l'execution de la tâche.
48
     *
49
     * @var list<Closure>
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...
50
     */
51
    protected array $beforeCallbacks = [];
52
53
    /**
54
     * Tableau de rappels à exécuter après l'execution de la tâche.
55
     *
56
     * @var list<Closure>
57
     */
58
    protected $afterCallbacks = [];
59
60
    /**
61
     * Met la sortie de la tâche dans un fichier donné.
62
     */
63
    public function sendOutputTo(string $location, bool $append = false): self
64
    {
65
        $this->location           = $location;
66
        $this->shouldAppendOutput = $append;
67
68
        return $this;
69
    }
70
71
    /**
72
     * Ajoute la sortie de la tâche à la fin d'un fichier donné.
73
     */
74
    public function appendOutputTo(string $location): self
75
    {
76
        return $this->sendOutputTo($location, true);
77
    }
78
79
    /**
80
     * Envoi le resultat de l'execution de la tache par mail.
81
     *
82
     * @param array|mixed $addresses
83
     *
84
     * @throws LogicException
85
     */
86
    public function emailOutputTo($addresses, bool $onlyIfOutputExists = false): self
87
    {
88
        $this->ensureOutputIsBeingCaptured();
89
90
        $addresses = Arr::wrap($addresses);
91
92
        return $this->then(function (MailerInterface $mailer) use ($addresses, $onlyIfOutputExists) {
93
            $this->emailOutput($mailer, $addresses, $onlyIfOutputExists);
94
        });
95
    }
96
97
    /**
98
     * Envoi le resultat de l'execution de la tache par mail si un resultat existe dans la sortie.
99
     *
100
     * @param array|mixed $addresses
101
     *
102
     * @throws LogicException
103
     */
104
    public function emailWrittenOutputTo($addresses): self
105
    {
106
        return $this->emailOutputTo($addresses, true);
107
    }
108
109
    /**
110
     * Envoi le resultat de l'execution de la tache par mail si l'operation a echouée.
111
     *
112
     * @param array|mixed $addresses
113
     */
114
    public function emailOutputOnFailure($addresses): self
115
    {
116
        $this->ensureOutputIsBeingCaptured();
117
118
        $addresses = Arr::wrap($addresses);
119
120
        return $this->onFailure(function (MailerInterface $mailer) use ($addresses) {
121
            $this->emailOutput($mailer, $addresses, false);
122
        });
123
    }
124
125
    /**
126
     * Enregistre un callback à appeler avant l'opération.
127
     */
128
    public function before(Closure $callback): self
129
    {
130
        $this->beforeCallbacks[] = $callback;
131
132
        return $this;
133
    }
134
135
    /**
136
     * Enregistre un callback à appeler apres l'opération.
137
     */
138
    public function after(Closure $callback): self
139
    {
140
        return $this->then($callback);
141
    }
142
143
    /**
144
     * Enregistre un callback à appeler apres l'opération.
145
     */
146
    public function then(Closure $callback): self
147
    {
148
        $this->afterCallbacks[] = $callback;
149
150
        return $this;
151
    }
152
153
    /**
154
     * Enregistre un callback à appeler si l'opération se deroulle avec succes.
155
     */
156
    public function onSuccess(Closure $callback): self
157
    {
158
        return $this->then(function (ContainerInterface $container) use ($callback) {
159
            if ($this->exitCode === EXIT_SUCCESS) {
160
                $container->call($callback);
161
            }
162
        });
163
    }
164
165
    /**
166
     * Enregistre un callback à appeler si l'opération ne se deroulle pas correctement.
167
     */
168
    public function onFailure(Closure $callback): self
169
    {
170
        return $this->then(function (ContainerInterface $container) use ($callback) {
171
            if ($this->exitCode !== EXIT_SUCCESS) {
172
                $container->call($callback, array_filter([$this->exception]));
173
            }
174
        });
175
    }
176
177
    /**
178
     * Procede a l'execution de la tache
179
     */
180
    protected function process(ContainerInterface $container, string $method): mixed
181
    {
182
        ob_start();
183
184
        $result = $this->start($this->container, $method);
185
186
        $result = $this->finish($this->container, $result);
187
188
        ob_end_flush();
189
190
        return $result;
191
    }
192
193
    /**
194
     * Demarre l'execution de la tache
195
     *
196
     * @return mixed Le resultat de l'execution de la tache
197
     *
198
     * @throws Throwable
199
     */
200
    protected function start(ContainerInterface $container, string $runMethod)
201
    {
202
        try {
203
            $this->callBeforeCallbacks($container);
204
205
            return $this->execute($container, $runMethod);
206
        } catch (Throwable $e) {
207
            $this->registerException($e);
208
        }
209
    }
210
211
    /**
212
     * Execute la tache.
213
     *
214
     * @return mixed Le resultat de l'execution de la tache
215
     */
216
    protected function execute(ContainerInterface $container, string $runMethod): mixed
217
    {
218
        try {
219
            $result = $this->{$runMethod}();
220
221
            if (is_int($result)) {
222
                $this->exitCode = $result;
223
            } else {
224
                $this->exitCode = EXIT_SUCCESS;
225
            }
226
        } catch (Throwable $e) {
227
            $this->registerException($e);
228
        }
229
230
        return $result ?? null;
231
    }
232
233
    /**
234
     * Marque l'execution de la tache comme terminée et lance les callbacks/nettoyages.
235
     */
236
    protected function finish(ContainerInterface $container, mixed $result): mixed
237
    {
238
        try {
239
            $output = $this->callAfterCallbacks($container, $result);
240
        } finally {
241
            if (isset($output) && $output !== '' && $this->location !== null) {
242
                @file_put_contents($this->location, $output, $this->shouldAppendOutput ? FILE_APPEND : 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for file_put_contents(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

242
                /** @scrutinizer ignore-unhandled */ @file_put_contents($this->location, $output, $this->shouldAppendOutput ? FILE_APPEND : 0);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
243
            }
244
        }
245
246
        return $result;
247
    }
248
249
    /**
250
     * S'assurer que les résultats de la tâche sont capturés.
251
     */
252
    protected function ensureOutputIsBeingCaptured(): void
253
    {
254
        if (null === $this->location) {
255
            $this->sendOutputTo(storage_path('logs/task-' . sha1($this->name) . '.log'));
256
        }
257
    }
258
259
    /**
260
     * Envoie du résultat de l'execution de la tache par mail aux destinataires.
261
     *
262
     * @param list<string> $addresses Liste des addresses a qui le mail sera envoyer
263
     */
264
    protected function emailOutput(MailerInterface $mailer, array $addresses, bool $onlyIfOutputExists = false): void
265
    {
266
        $text = is_file($this->location) ? file_get_contents($this->location) : '';
0 ignored issues
show
It seems like $this->location can also be of type null; however, parameter $filename of is_file() 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

266
        $text = is_file(/** @scrutinizer ignore-type */ $this->location) ? file_get_contents($this->location) : '';
Loading history...
It seems like $this->location can also be of type null; however, parameter $filename of file_get_contents() 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

266
        $text = is_file($this->location) ? file_get_contents(/** @scrutinizer ignore-type */ $this->location) : '';
Loading history...
267
268
        if ($onlyIfOutputExists && empty($text)) {
269
            return;
270
        }
271
272
        $mailer->to($addresses)->subject($this->getEmailSubject())->text($text)->send();
273
    }
274
275
    /**
276
     * Objet de l'e-mail pour les résultats de sortie.
277
     */
278
    protected function getEmailSubject(): string
279
    {
280
        return "Sortie de la tâche planifiée pour [{$this->name}]";
281
    }
282
283
    /**
284
     * Appelle tous les callbacks qui doivent être lancer "avant" l'exécution de la tâche.
285
     */
286
    protected function callBeforeCallbacks(ContainerInterface $container): void
287
    {
288
        foreach ($this->beforeCallbacks as $callback) {
289
            $container->call($callback);
290
        }
291
    }
292
293
    /**
294
     * Appelle tous les callbacks qui doivent être lancer "apres" l'exécution de la tâche.
295
     */
296
    protected function callAfterCallbacks(ContainerInterface $container, mixed $result = null): string
297
    {
298
        $parameters = ['result' => $result];
299
300
        if ('' !== $output = ob_get_contents() ?: '') {
301
            $parameters['output'] = new Stringable($output);
302
        }
303
304
        foreach ($this->afterCallbacks as $callback) {
305
            $container->call($callback, $parameters);
306
        }
307
308
        return $output;
309
    }
310
311
    /**
312
     * Marque l'exception en cours et définit le code de sortie à EXIT_ERROR.
313
     */
314
    protected function registerException(Throwable $e): void
315
    {
316
        $this->exception = $e;
317
        $this->exitCode  = EXIT_ERROR;
318
    }
319
}
320