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
|
|||||||
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
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
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.');
}
![]() |
|||||||
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
![]() 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
![]() |
|||||||
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 |
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths