1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * This file is part of Blitz PHP framework. |
||||
5 | * |
||||
6 | * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]> |
||||
7 | * |
||||
8 | * For the full copyright and license information, please view |
||||
9 | * the LICENSE file that was distributed with this source code. |
||||
10 | */ |
||||
11 | |||||
12 | namespace BlitzPHP\Publisher; |
||||
13 | |||||
14 | use BlitzPHP\Exceptions\PublisherException; |
||||
15 | use BlitzPHP\Filesystem\Files\FileCollection; |
||||
16 | use BlitzPHP\Http\Uri; |
||||
17 | use RuntimeException; |
||||
18 | use Throwable; |
||||
19 | |||||
20 | /** |
||||
21 | * Les éditeurs lisent les chemins d'accès aux fichiers à partir de diverses sources et copient les fichiers vers différentes destinations. |
||||
22 | * Cette classe sert à la fois de base pour les directives de publication individuelles et de mode de découverte pour lesdites instances. |
||||
23 | * Dans cette classe, un "fichier" est un chemin complet vers un fichier vérifié tandis qu'un "chemin" est relatif à sa source ou à sa destination et peut indiquer soit un fichier, soit un répertoire dont l'existence n'est pas confirmée. |
||||
24 | * |
||||
25 | * Les échecs de classe lancent l'exception PublisherException, |
||||
26 | * mais certaines méthodes sous-jacentes peuvent percoler différentes exceptions, |
||||
27 | * comme FileException, FileNotFoundException ou InvalidArgumentException. |
||||
28 | * |
||||
29 | * Les opérations d'écriture intercepteront toutes les erreurs dans le fichier spécifique |
||||
30 | * Propriété $errors pour minimiser l'impact des opérations par lots partielles. |
||||
31 | * |
||||
32 | * @credit <a href="http://codeigniter.com">CodeIgniter 4 - \CodeIgniter\Publisher\Publisher</a> |
||||
33 | */ |
||||
34 | class Publisher extends FileCollection |
||||
35 | { |
||||
36 | /** |
||||
37 | * Tableau des éditeurs découverts. |
||||
38 | * |
||||
39 | * @var array<string, list<self>|null> |
||||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||||
40 | */ |
||||
41 | private static array $discovered = []; |
||||
42 | |||||
43 | /** |
||||
44 | * Répertoire à utiliser pour les méthodes nécessitant un stockage temporaire. |
||||
45 | * Créé à la volée selon les besoins. |
||||
46 | */ |
||||
47 | private ?string $scratch = null; |
||||
48 | |||||
49 | /** |
||||
50 | * Exceptions pour des fichiers spécifiques de la dernière opération d'écriture. |
||||
51 | * |
||||
52 | * @var array<string, Throwable> |
||||
53 | */ |
||||
54 | private array $errors = []; |
||||
55 | |||||
56 | /** |
||||
57 | * Liste des fichiers publiés traitant la dernière opération d'écriture. |
||||
58 | * |
||||
59 | * @var list<string> |
||||
0 ignored issues
–
show
The type
BlitzPHP\Publisher\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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
60 | */ |
||||
61 | private array $published = []; |
||||
62 | |||||
63 | /** |
||||
64 | * Liste des répertoires autorisés et leur regex de fichiers autorisés. |
||||
65 | * Les restrictions sont intentionnellement privées pour éviter qu'elles ne soient dépassées. |
||||
66 | * |
||||
67 | * @var array<string,string> |
||||
68 | */ |
||||
69 | private readonly array $restrictions; |
||||
70 | |||||
71 | private readonly ContentReplacer $replacer; |
||||
72 | |||||
73 | /** |
||||
74 | * Chemin de base à utiliser pour la source. |
||||
75 | */ |
||||
76 | protected string $source = ROOTPATH; |
||||
77 | |||||
78 | /** |
||||
79 | * Chemin de base à utiliser pour la destination. |
||||
80 | */ |
||||
81 | protected string $destination = WEBROOT; |
||||
0 ignored issues
–
show
|
|||||
82 | |||||
83 | // -------------------------------------------------------------------- |
||||
84 | // Méthodes d'assistance |
||||
85 | // -------------------------------------------------------------------- |
||||
86 | |||||
87 | /** |
||||
88 | * Découvre et renvoie tous les éditeurs dans le répertoire d'espace de noms spécifié. |
||||
89 | * |
||||
90 | * @return list<self> |
||||
91 | */ |
||||
92 | final public static function discover(string $directory = 'Publishers', string $namespace = ''): array |
||||
93 | { |
||||
94 | 4 | $key = implode('.', [$directory, $namespace]); |
|||
95 | |||||
96 | if (isset(self::$discovered[$key])) { |
||||
97 | 4 | return self::$discovered[$key]; |
|||
98 | } |
||||
99 | |||||
100 | 4 | self::$discovered[$key] = []; |
|||
101 | |||||
102 | /** @var \BlitzPHP\Contracts\Autoloader\LocatorInterface */ |
||||
103 | 4 | $locator = service('locator'); |
|||
104 | |||||
105 | $files = $namespace === '' |
||||
106 | ? $locator->listFiles($directory) |
||||
107 | 2 | : $locator->listNamespaceFiles($namespace, $directory); |
|||
108 | |||||
109 | if ([] === $files) { |
||||
110 | 2 | return []; |
|||
0 ignored issues
–
show
|
|||||
111 | } |
||||
112 | |||||
113 | // Boucle sur chaque fichier en vérifiant s'il s'agit d'un Publisher |
||||
114 | foreach (array_unique($files) as $file) { |
||||
115 | 4 | $className = $locator->findQualifiedNameFromPath($file); |
|||
116 | |||||
117 | if ($className !== false && class_exists($className) && is_a($className, self::class, true)) { |
||||
118 | 4 | self::$discovered[$key][] = service('factory', $className); |
|||
119 | } |
||||
120 | } |
||||
121 | |||||
122 | 4 | sort(self::$discovered[$key]); |
|||
123 | |||||
124 | 4 | return self::$discovered[$key]; |
|||
125 | } |
||||
126 | |||||
127 | /** |
||||
128 | * Supprime un répertoire et tous ses fichiers et sous-répertoires. |
||||
129 | */ |
||||
130 | private static function wipeDirectory(string $directory): void |
||||
131 | { |
||||
132 | if (is_dir($directory)) { |
||||
133 | // Essayez plusieurs fois en cas de mèches persistantes |
||||
134 | 6 | $attempts = 10; |
|||
135 | |||||
136 | while ((bool) $attempts && ! delete_files($directory, true, false, true)) { |
||||
137 | // @codeCoverageIgnoreStart |
||||
138 | $attempts--; |
||||
139 | usleep(100000); // .1s |
||||
140 | // @codeCoverageIgnoreEnd |
||||
141 | } |
||||
142 | |||||
143 | 6 | @rmdir($directory); |
|||
0 ignored issues
–
show
It seems like you do not handle an error condition for
rmdir() . 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.');
}
![]() |
|||||
144 | } |
||||
145 | } |
||||
146 | |||||
147 | /** |
||||
148 | * Charge l'assistant et vérifie les répertoires source et destination. |
||||
149 | */ |
||||
150 | public function __construct(?string $source = null, ?string $destination = null) |
||||
151 | { |
||||
152 | 12 | helper('filesystem'); |
|||
153 | |||||
154 | 12 | $this->source = self::resolveDirectory($source ?? $this->source); |
|||
155 | 12 | $this->destination = self::resolveDirectory($destination ?? $this->destination); |
|||
156 | |||||
157 | 12 | $this->replacer = new ContentReplacer(); |
|||
0 ignored issues
–
show
|
|||||
158 | |||||
159 | // Les restrictions ne sont intentionnellement pas injectées pour empêcher le dépassement |
||||
160 | 12 | $this->restrictions = config('publisher.restrictions'); |
|||
0 ignored issues
–
show
It seems like
config('publisher.restrictions') can also be of type T or object . However, the property $restrictions is declared as type array<string,string> . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
161 | |||||
162 | // Assurez-vous que la destination est autorisée |
||||
163 | foreach (array_keys($this->restrictions) as $directory) { |
||||
164 | if (str_starts_with($this->destination, $directory)) { |
||||
165 | 12 | return; |
|||
166 | } |
||||
167 | } |
||||
168 | |||||
169 | 2 | throw PublisherException::destinationNotAllowed($this->destination); |
|||
170 | } |
||||
171 | |||||
172 | /** |
||||
173 | * Nettoie tous les fichiers temporaires dans l'espace de travail. |
||||
174 | */ |
||||
175 | public function __destruct() |
||||
176 | { |
||||
177 | if (isset($this->scratch)) { |
||||
178 | 6 | self::wipeDirectory($this->scratch); |
|||
0 ignored issues
–
show
It seems like
$this->scratch can also be of type null ; however, parameter $directory of BlitzPHP\Publisher\Publisher::wipeDirectory() 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
![]() |
|||||
179 | |||||
180 | 6 | $this->scratch = null; |
|||
181 | } |
||||
182 | } |
||||
183 | |||||
184 | /** |
||||
185 | * Lit les fichiers à partir des sources et les copie vers leurs destinations. |
||||
186 | * Cette méthode devrait être réimplémentée par les classes filles destinées à la découverte. |
||||
187 | * |
||||
188 | * @throws RuntimeException |
||||
189 | */ |
||||
190 | public function publish(): bool |
||||
191 | { |
||||
192 | // Protection contre une mauvaise utilisation accidentelle |
||||
193 | if ($this->source === ROOTPATH && $this->destination === WEBROOT) { |
||||
0 ignored issues
–
show
|
|||||
194 | 2 | throw new RuntimeException('Les classes enfants de Publisher doivent fournir leur propre méthode de publication ou une source et une destination.'); |
|||
195 | } |
||||
196 | |||||
197 | 2 | return $this->addPath('/')->merge(true); |
|||
198 | } |
||||
199 | |||||
200 | // -------------------------------------------------------------------- |
||||
201 | // Accesseurs de propriété |
||||
202 | // -------------------------------------------------------------------- |
||||
203 | |||||
204 | /** |
||||
205 | * Renvoie le répertoire source. |
||||
206 | */ |
||||
207 | final public function getSource(): string |
||||
208 | { |
||||
209 | 2 | return $this->source; |
|||
210 | } |
||||
211 | |||||
212 | /** |
||||
213 | * Renvoie le répertoire de destination. |
||||
214 | */ |
||||
215 | final public function getDestination(): string |
||||
216 | { |
||||
217 | 4 | return $this->destination; |
|||
218 | } |
||||
219 | |||||
220 | /** |
||||
221 | * Renvoie l'espace de travail temporaire, en le créant si nécessaire. |
||||
222 | */ |
||||
223 | final public function getScratch(): string |
||||
224 | { |
||||
225 | if ($this->scratch === null) { |
||||
226 | 6 | $this->scratch = rtrim(sys_get_temp_dir(), DS) . DS . bin2hex(random_bytes(6)) . DS; |
|||
227 | 6 | mkdir($this->scratch, 0o700); |
|||
0 ignored issues
–
show
$this->scratch of type null is incompatible with the type string expected by parameter $directory of mkdir() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
228 | $this->scratch = realpath($this->scratch) ? realpath($this->scratch) . DS |
||||
0 ignored issues
–
show
$this->scratch of type null is incompatible with the type string expected by parameter $path of realpath() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
229 | : $this->scratch; |
||||
230 | } |
||||
231 | |||||
232 | 6 | return $this->scratch; |
|||
0 ignored issues
–
show
|
|||||
233 | } |
||||
234 | |||||
235 | /** |
||||
236 | * Renvoie les erreurs de la dernière opération d'écriture, le cas échéant. |
||||
237 | * |
||||
238 | * @return array<string,Throwable> |
||||
239 | */ |
||||
240 | final public function getErrors(): array |
||||
241 | { |
||||
242 | 8 | return $this->errors; |
|||
243 | } |
||||
244 | |||||
245 | /** |
||||
246 | * Renvoie les fichiers publiés par la dernière opération d'écriture. |
||||
247 | * |
||||
248 | * @return list<string> |
||||
249 | */ |
||||
250 | final public function getPublished(): array |
||||
251 | { |
||||
252 | 4 | return $this->published; |
|||
0 ignored issues
–
show
|
|||||
253 | } |
||||
254 | |||||
255 | // -------------------------------------------------------------------- |
||||
256 | // Gestionnaires supplémentaires |
||||
257 | // -------------------------------------------------------------------- |
||||
258 | |||||
259 | /** |
||||
260 | * Vérifie et ajoute des chemins à la liste. |
||||
261 | * |
||||
262 | * @param list<string> $paths |
||||
263 | */ |
||||
264 | final public function addPaths(array $paths, bool $recursive = true): static |
||||
265 | { |
||||
266 | foreach ($paths as $path) { |
||||
267 | 2 | $this->addPath($path, $recursive); |
|||
268 | } |
||||
269 | |||||
270 | 2 | return $this; |
|||
271 | } |
||||
272 | |||||
273 | /** |
||||
274 | * Ajoute un chemin unique à la liste de fichiers. |
||||
275 | */ |
||||
276 | final public function addPath(string $path, bool $recursive = true): static |
||||
277 | { |
||||
278 | 6 | $this->add($this->source . $path, $recursive); |
|||
279 | |||||
280 | 6 | return $this; |
|||
281 | } |
||||
282 | |||||
283 | /** |
||||
284 | * Télécharge et met en scène des fichiers à partir d'un tableau d'URI. |
||||
285 | * |
||||
286 | * @param list<string> $uris |
||||
287 | */ |
||||
288 | final public function addUris(array $uris): static |
||||
289 | { |
||||
290 | foreach ($uris as $uri) { |
||||
291 | 2 | $this->addUri($uri); |
|||
292 | } |
||||
293 | |||||
294 | 2 | return $this; |
|||
295 | } |
||||
296 | |||||
297 | /** |
||||
298 | * Télécharge un fichier à partir de l'URI et l'ajoute à la liste des fichiers. |
||||
299 | * |
||||
300 | * @param string $uri Parce que HTTP\URI est stringable, il sera toujours accepté |
||||
301 | */ |
||||
302 | final public function addUri(string $uri): static |
||||
303 | { |
||||
304 | // Trouvez un bon nom de fichier (en utilisant des requêtes et des fragments de bandes d'URI) |
||||
305 | 2 | $file = $this->getScratch() . basename((new Uri($uri))->getPath()); |
|||
306 | |||||
307 | // Obtenez le contenu et écrivez-le dans l'espace de travail |
||||
308 | 2 | write_file($file, service('httpclient')->get($uri)->body()); |
|||
309 | |||||
310 | 2 | return $this->addFile($file); |
|||
311 | } |
||||
312 | |||||
313 | // -------------------------------------------------------------------- |
||||
314 | // Méthodes d'écriture |
||||
315 | // -------------------------------------------------------------------- |
||||
316 | |||||
317 | /** |
||||
318 | * Supprime la destination et tous ses fichiers et dossiers. |
||||
319 | */ |
||||
320 | final public function wipe(): static |
||||
321 | { |
||||
322 | 2 | self::wipeDirectory($this->destination); |
|||
323 | |||||
324 | 2 | return $this; |
|||
325 | } |
||||
326 | |||||
327 | /** |
||||
328 | * Copie tous les fichiers dans la destination, ne crée pas de structure de répertoire. |
||||
329 | * |
||||
330 | * @param bool $replace S'il faut écraser les fichiers existants. |
||||
331 | * |
||||
332 | * @return bool Si tous les fichiers ont été copiés avec succès |
||||
333 | */ |
||||
334 | final public function copy(bool $replace = true): bool |
||||
335 | { |
||||
336 | 4 | $this->errors = $this->published = []; |
|||
0 ignored issues
–
show
It seems like
array() of type array is incompatible with the declared type BlitzPHP\Publisher\list of property $published .
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.. ![]() |
|||||
337 | |||||
338 | foreach ($this->get() as $file) { |
||||
339 | 4 | $to = $this->destination . basename($file); |
|||
340 | |||||
341 | try { |
||||
342 | 4 | $this->safeCopyFile($file, $to, $replace); |
|||
343 | 2 | $this->published[] = $to; |
|||
344 | } catch (Throwable $e) { |
||||
345 | 4 | $this->errors[$file] = $e; |
|||
346 | } |
||||
347 | } |
||||
348 | |||||
349 | 4 | return $this->errors === []; |
|||
350 | } |
||||
351 | |||||
352 | /** |
||||
353 | * Fusionne tous les fichiers dans la destination. |
||||
354 | * Crée une structure de répertoires en miroir uniquement pour les fichiers de la source. |
||||
355 | * |
||||
356 | * @param bool $replace Indique s'il faut écraser les fichiers existants. |
||||
357 | * |
||||
358 | * @return bool Si tous les fichiers ont été copiés avec succès |
||||
359 | */ |
||||
360 | final public function merge(bool $replace = true): bool |
||||
361 | { |
||||
362 | 4 | $this->errors = $this->published = []; |
|||
0 ignored issues
–
show
It seems like
array() of type array is incompatible with the declared type BlitzPHP\Publisher\list of property $published .
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.. ![]() |
|||||
363 | |||||
364 | // Obtenez les fichiers de la source pour un traitement spécial |
||||
365 | 4 | $sourced = self::filterFiles($this->get(), $this->source); |
|||
366 | |||||
367 | // Obtenez les fichiers de la source pour un traitement spécial |
||||
368 | 4 | $this->files = array_diff($this->files, $sourced); |
|||
369 | 4 | $this->copy($replace); |
|||
370 | |||||
371 | // Copiez chaque fichier source vers sa destination relative |
||||
372 | foreach ($sourced as $file) { |
||||
373 | // Résoudre le chemin de destination |
||||
374 | 2 | $to = $this->destination . substr($file, strlen($this->source)); |
|||
375 | |||||
376 | try { |
||||
377 | 2 | $this->safeCopyFile($file, $to, $replace); |
|||
378 | 2 | $this->published[] = $to; |
|||
379 | } catch (Throwable $e) { |
||||
380 | 2 | $this->errors[$file] = $e; |
|||
381 | } |
||||
382 | } |
||||
383 | |||||
384 | 4 | return $this->errors === []; |
|||
385 | } |
||||
386 | |||||
387 | /** |
||||
388 | * Remplacer le contenu |
||||
389 | * |
||||
390 | * @param array $replaces [search => replace] |
||||
391 | */ |
||||
392 | public function replace(string $file, array $replaces): bool |
||||
393 | { |
||||
394 | 2 | $this->verifyAllowed($file, $file); |
|||
395 | |||||
396 | 2 | $content = file_get_contents($file); |
|||
397 | |||||
398 | 2 | $newContent = $this->replacer->replace($content, $replaces); |
|||
399 | |||||
400 | 2 | $return = file_put_contents($file, $newContent); |
|||
401 | |||||
402 | 2 | return $return !== false; |
|||
403 | } |
||||
404 | |||||
405 | /** |
||||
406 | * Ajouter une ligne après la ligne avec la chaîne |
||||
407 | * |
||||
408 | * @param string $after Chaîne à rechercher. |
||||
409 | */ |
||||
410 | public function addLineAfter(string $file, string $line, string $after): bool |
||||
411 | { |
||||
412 | 2 | $this->verifyAllowed($file, $file); |
|||
413 | |||||
414 | 2 | $content = file_get_contents($file); |
|||
415 | |||||
416 | 2 | $result = $this->replacer->addAfter($content, $line, $after); |
|||
417 | |||||
418 | if ($result !== null) { |
||||
419 | 2 | $return = file_put_contents($file, $result); |
|||
420 | |||||
421 | 2 | return $return !== false; |
|||
422 | } |
||||
423 | |||||
424 | return false; |
||||
425 | } |
||||
426 | |||||
427 | /** |
||||
428 | * Ajouter une ligne avant la ligne avec la chaîne |
||||
429 | * |
||||
430 | * @param string $before String à rechercher. |
||||
431 | */ |
||||
432 | public function addLineBefore(string $file, string $line, string $before): bool |
||||
433 | { |
||||
434 | 2 | $this->verifyAllowed($file, $file); |
|||
435 | |||||
436 | 2 | $content = file_get_contents($file); |
|||
437 | |||||
438 | 2 | $result = $this->replacer->addBefore($content, $line, $before); |
|||
439 | |||||
440 | if ($result !== null) { |
||||
441 | 2 | $return = file_put_contents($file, $result); |
|||
442 | |||||
443 | 2 | return $return !== false; |
|||
444 | } |
||||
445 | |||||
446 | return false; |
||||
447 | } |
||||
448 | |||||
449 | /** |
||||
450 | * Vérifiez qu'il s'agit d'un fichier autorisé pour sa destination |
||||
451 | */ |
||||
452 | private function verifyAllowed(string $from, string $to) |
||||
453 | { |
||||
454 | // Vérifiez qu'il s'agit d'un fichier autorisé pour sa destination |
||||
455 | foreach ($this->restrictions as $directory => $pattern) { |
||||
456 | if (str_starts_with($to, $directory) && self::matchFiles([$to], $pattern) === []) { |
||||
457 | 2 | throw PublisherException::fileNotAllowed($from, $directory, $pattern); |
|||
458 | } |
||||
459 | } |
||||
460 | } |
||||
461 | |||||
462 | /** |
||||
463 | * Copie un fichier avec création de répertoire et reconnaissance de fichier identique. |
||||
464 | * Permet intentionnellement des erreurs. |
||||
465 | * |
||||
466 | * @throws PublisherException Pour les collisions et les violations de restriction |
||||
467 | */ |
||||
468 | private function safeCopyFile(string $from, string $to, bool $replace): void |
||||
469 | { |
||||
470 | // Vérifiez qu'il s'agit d'un fichier autorisé pour sa destination |
||||
471 | 4 | $this->verifyAllowed($from, $to); |
|||
472 | |||||
473 | // Rechercher un fichier existant |
||||
474 | if (file_exists($to)) { |
||||
475 | // S'il n'est pas remplacé ou si les fichiers sont identiques, envisagez de réussir |
||||
476 | if (! $replace || same_file($from, $to)) { |
||||
477 | 2 | return; |
|||
478 | } |
||||
479 | |||||
480 | // S'il s'agit d'un répertoire, n'essayez pas de le supprimer |
||||
481 | if (is_dir($to)) { |
||||
482 | 2 | throw PublisherException::collision($from, $to); |
|||
483 | } |
||||
484 | |||||
485 | // Essayez de supprimer autre chose |
||||
486 | 2 | unlink($to); |
|||
487 | } |
||||
488 | |||||
489 | // Assurez-vous que le répertoire existe |
||||
490 | if (! is_dir($directory = pathinfo($to, PATHINFO_DIRNAME))) { |
||||
0 ignored issues
–
show
It seems like
$directory = pathinfo($t...isher\PATHINFO_DIRNAME) can also be of type array ; however, parameter $filename of is_dir() 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
![]() |
|||||
491 | 2 | mkdir($directory, 0o775, true); |
|||
0 ignored issues
–
show
It seems like
$directory can also be of type array ; however, parameter $directory of mkdir() 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
![]() |
|||||
492 | } |
||||
493 | |||||
494 | // Autoriser copy() à générer des erreurs |
||||
495 | 2 | copy($from, $to); |
|||
496 | } |
||||
497 | } |
||||
498 |