1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | /* |
||||||
6 | * This file is part of the box project. |
||||||
7 | * |
||||||
8 | * (c) Kevin Herrera <[email protected]> |
||||||
9 | * Théo Fidry <[email protected]> |
||||||
10 | * |
||||||
11 | * This source file is subject to the MIT license that is bundled |
||||||
12 | * with this source code in the file LICENSE. |
||||||
13 | */ |
||||||
14 | |||||||
15 | namespace KevinGH\Box\Console; |
||||||
16 | |||||||
17 | use Closure; |
||||||
18 | use DateTimeImmutable; |
||||||
19 | use Fidry\Console\IO; |
||||||
0 ignored issues
–
show
|
|||||||
20 | use KevinGH\Box\Noop; |
||||||
21 | use KevinGH\Box\NotInstantiable; |
||||||
22 | use KevinGH\Box\Phar\CompressionAlgorithm; |
||||||
23 | use KevinGH\Box\Phar\PharInfo; |
||||||
24 | use KevinGH\Box\RequirementChecker\Requirement; |
||||||
25 | use KevinGH\Box\RequirementChecker\Requirements; |
||||||
26 | use KevinGH\Box\RequirementChecker\RequirementType; |
||||||
27 | use KevinGH\Box\RequirementChecker\Throwable\InvalidRequirements; |
||||||
28 | use KevinGH\Box\RequirementChecker\Throwable\NoRequirementsFound; |
||||||
29 | use SplFileInfo; |
||||||
30 | use Symfony\Component\Console\Output\OutputInterface; |
||||||
31 | use Symfony\Component\Filesystem\Path; |
||||||
32 | use function array_filter; |
||||||
33 | use function array_key_last; |
||||||
34 | use function array_map; |
||||||
35 | use function array_reduce; |
||||||
36 | use function array_sum; |
||||||
37 | use function array_values; |
||||||
38 | use function count; |
||||||
39 | use function implode; |
||||||
40 | use function iter\toArray; |
||||||
41 | use function KevinGH\Box\format_size; |
||||||
42 | use function key; |
||||||
43 | use function preg_match; |
||||||
44 | use function round; |
||||||
45 | use function Safe\filesize; |
||||||
46 | use function sprintf; |
||||||
47 | |||||||
48 | /** |
||||||
49 | * Utility to write to the console output various PHAR related pieces of information. |
||||||
50 | * |
||||||
51 | * @private |
||||||
52 | */ |
||||||
53 | final class PharInfoRenderer |
||||||
54 | { |
||||||
55 | use NotInstantiable; |
||||||
56 | |||||||
57 | private const BOX_VERSION_PATTERN = '/ \* Generated by Humbug Box (?<version>.+)\.\s/'; |
||||||
58 | private const INDENT_SIZE = 2; |
||||||
59 | |||||||
60 | public static function renderShortSummary( |
||||||
61 | PharInfo $pharInfo, |
||||||
62 | IO $io, |
||||||
63 | ?Closure $separator = null, |
||||||
64 | ): void { |
||||||
65 | $separator ??= Noop::create(); |
||||||
66 | |||||||
67 | $methods = [ |
||||||
68 | self::renderCompression(...), |
||||||
69 | self::renderSignature(...), |
||||||
70 | self::renderMetadata(...), |
||||||
71 | self::renderTimestamp(...), |
||||||
72 | self::renderRequirementChecker(...), |
||||||
73 | self::renderContentsSummary(...), |
||||||
74 | ]; |
||||||
75 | |||||||
76 | $lastIndex = count($methods) - 1; |
||||||
77 | |||||||
78 | foreach ($methods as $index => $method) { |
||||||
79 | $method($pharInfo, $io); |
||||||
80 | |||||||
81 | if ($index !== $lastIndex) { |
||||||
82 | $separator(); |
||||||
83 | } |
||||||
84 | } |
||||||
85 | } |
||||||
86 | |||||||
87 | public static function renderVersion(PharInfo $pharInfo, IO $io): void |
||||||
88 | { |
||||||
89 | $io->writeln( |
||||||
90 | sprintf( |
||||||
91 | '<comment>API Version:</comment> %s', |
||||||
92 | $pharInfo->getVersion(), |
||||||
93 | ), |
||||||
94 | ); |
||||||
95 | } |
||||||
96 | |||||||
97 | public static function renderBoxVersion(PharInfo $pharInfo, IO $io): void |
||||||
98 | { |
||||||
99 | $version = self::extractBoxVersion($pharInfo); |
||||||
100 | |||||||
101 | if (null === $version) { |
||||||
102 | return; |
||||||
103 | } |
||||||
104 | |||||||
105 | $io->writeln( |
||||||
106 | sprintf( |
||||||
107 | '<comment>Built with Box:</comment> %s', |
||||||
108 | $version, |
||||||
109 | ), |
||||||
110 | ); |
||||||
111 | $io->newLine(); |
||||||
112 | } |
||||||
113 | |||||||
114 | public static function renderCompression(PharInfo $pharInfo, IO $io): void |
||||||
115 | { |
||||||
116 | $io->writeln( |
||||||
117 | sprintf( |
||||||
118 | '<comment>Archive Compression:</comment> %s', |
||||||
119 | self::translateCompressionAlgorithm($pharInfo->getCompression()), |
||||||
120 | ), |
||||||
121 | ); |
||||||
122 | |||||||
123 | $count = $pharInfo->getFilesCompressionCount(); |
||||||
124 | // Rename "none" to "None" |
||||||
125 | $count['None'] = $count[CompressionAlgorithm::NONE->name]; |
||||||
0 ignored issues
–
show
|
|||||||
126 | unset($count[CompressionAlgorithm::NONE->name]); |
||||||
127 | $count = array_filter($count); |
||||||
128 | |||||||
129 | $totalCount = array_sum($count); |
||||||
130 | |||||||
131 | if (1 === count($count)) { |
||||||
132 | $io->writeln( |
||||||
133 | sprintf( |
||||||
134 | '<comment>Files Compression:</comment> %s', |
||||||
135 | key($count), |
||||||
136 | ), |
||||||
137 | ); |
||||||
138 | |||||||
139 | return; |
||||||
140 | } |
||||||
141 | |||||||
142 | $io->writeln('<comment>Files Compression:</comment>'); |
||||||
143 | $lastAlgorithmName = array_key_last($count); |
||||||
144 | |||||||
145 | $totalPercentage = 100; |
||||||
146 | |||||||
147 | foreach ($count as $algorithmName => $nbrOfFiles) { |
||||||
148 | if ($lastAlgorithmName === $algorithmName) { |
||||||
149 | $percentage = $totalPercentage; |
||||||
150 | } else { |
||||||
151 | $percentage = round($nbrOfFiles * 100 / $totalCount, 2); |
||||||
152 | |||||||
153 | $totalPercentage -= $percentage; |
||||||
154 | } |
||||||
155 | |||||||
156 | $io->writeln( |
||||||
157 | sprintf( |
||||||
158 | ' - %s (%0.2f%%)', |
||||||
159 | $algorithmName, |
||||||
160 | $percentage, |
||||||
161 | ), |
||||||
162 | ); |
||||||
163 | } |
||||||
164 | } |
||||||
165 | |||||||
166 | public static function renderSignature(PharInfo $pharInfo, IO $io): void |
||||||
167 | { |
||||||
168 | $signature = $pharInfo->getSignature(); |
||||||
169 | |||||||
170 | if (null === $signature) { |
||||||
171 | $io->writeln('<comment>Signature unreadable</comment>'); |
||||||
172 | |||||||
173 | return; |
||||||
174 | } |
||||||
175 | |||||||
176 | $io->writeln( |
||||||
177 | sprintf( |
||||||
178 | '<comment>Signature:</comment> %s', |
||||||
179 | $signature['hash_type'], |
||||||
180 | ), |
||||||
181 | ); |
||||||
182 | $io->writeln( |
||||||
183 | sprintf( |
||||||
184 | '<comment>Signature Hash:</comment> %s', |
||||||
185 | $signature['hash'], |
||||||
186 | ), |
||||||
187 | ); |
||||||
188 | } |
||||||
189 | |||||||
190 | public static function renderMetadata(PharInfo $pharInfo, IO $io): void |
||||||
191 | { |
||||||
192 | $metadata = $pharInfo->getNormalizedMetadata(); |
||||||
193 | |||||||
194 | if (null === $metadata) { |
||||||
195 | $io->writeln('<comment>Metadata:</comment> None'); |
||||||
196 | } else { |
||||||
197 | $io->writeln('<comment>Metadata:</comment>'); |
||||||
198 | $io->writeln($metadata); |
||||||
199 | } |
||||||
200 | } |
||||||
201 | |||||||
202 | public static function renderTimestamp(PharInfo $pharInfo, IO $io): void |
||||||
203 | { |
||||||
204 | $timestamp = $pharInfo->getTimestamp(); |
||||||
205 | $dateTime = (new DateTimeImmutable())->setTimestamp($timestamp); |
||||||
206 | |||||||
207 | $io->writeln( |
||||||
208 | sprintf( |
||||||
209 | '<comment>Timestamp:</comment> %s (%s)', |
||||||
210 | $timestamp, |
||||||
211 | $dateTime->format(DateTimeImmutable::ATOM), |
||||||
212 | ), |
||||||
213 | ); |
||||||
214 | } |
||||||
215 | |||||||
216 | public static function renderRequirementChecker( |
||||||
217 | PharInfo $pharInfo, |
||||||
218 | IO $io, |
||||||
219 | ): void { |
||||||
220 | try { |
||||||
221 | $requirements = $pharInfo->getRequirements(); |
||||||
222 | } catch (NoRequirementsFound) { |
||||||
223 | $io->writeln('<comment>RequirementChecker:</comment> Not found.'); |
||||||
224 | |||||||
225 | return; |
||||||
226 | } catch (InvalidRequirements) { |
||||||
227 | $io->writeln('<comment>RequirementChecker:</comment> Could not be checked.'); |
||||||
228 | |||||||
229 | return; |
||||||
230 | } |
||||||
231 | |||||||
232 | $io->write('<comment>RequirementChecker:</comment>'); |
||||||
233 | |||||||
234 | if (0 === count($requirements)) { |
||||||
235 | $io->writeln(' No requirement found.'); |
||||||
236 | |||||||
237 | return; |
||||||
238 | } |
||||||
239 | $io->writeln(''); |
||||||
240 | |||||||
241 | [$required, $conflicting] = self::retrieveRequirements($requirements); |
||||||
242 | |||||||
243 | self::renderRequiredSection($required, $io); |
||||||
244 | self::renderConflictingSection($conflicting, $io); |
||||||
245 | } |
||||||
246 | |||||||
247 | public static function renderContentsSummary(PharInfo $pharInfo, IO $io): void |
||||||
248 | { |
||||||
249 | $count = array_filter($pharInfo->getFilesCompressionCount()); |
||||||
250 | $totalCount = array_sum($count); |
||||||
251 | |||||||
252 | $io->writeln( |
||||||
253 | sprintf( |
||||||
254 | '<comment>Contents:</comment>%s (%s)', |
||||||
255 | 1 === $totalCount ? ' 1 file' : " {$totalCount} files", |
||||||
256 | format_size( |
||||||
257 | filesize($pharInfo->getFile()), |
||||||
258 | ), |
||||||
259 | ), |
||||||
260 | ); |
||||||
261 | } |
||||||
262 | |||||||
263 | /** |
||||||
264 | * @param false|positive-int|0 $maxDepth |
||||||
0 ignored issues
–
show
|
|||||||
265 | * @param false|int $indent Nbr of indent or `false` |
||||||
266 | */ |
||||||
267 | public static function renderContent( |
||||||
268 | OutputInterface $output, |
||||||
269 | PharInfo $pharInfo, |
||||||
270 | false|int $maxDepth, |
||||||
271 | bool $indent, |
||||||
272 | ): void { |
||||||
273 | $depth = 0; |
||||||
274 | $renderedDirectories = []; |
||||||
275 | |||||||
276 | foreach ($pharInfo->getFiles() as $splFileInfo) { |
||||||
277 | if (false !== $maxDepth && $depth > $maxDepth) { |
||||||
278 | continue; |
||||||
279 | } |
||||||
280 | |||||||
281 | if ($indent) { |
||||||
282 | self::renderParentDirectoriesIfNecessary( |
||||||
283 | $splFileInfo, |
||||||
284 | $output, |
||||||
285 | $depth, |
||||||
286 | $renderedDirectories, |
||||||
287 | ); |
||||||
288 | } |
||||||
289 | |||||||
290 | [ |
||||||
291 | 'compression' => $compression, |
||||||
292 | 'compressedSize' => $compressionSize, |
||||||
293 | ] = $pharInfo->getFileMeta($splFileInfo->getRelativePathname()); |
||||||
294 | |||||||
295 | $compressionLine = CompressionAlgorithm::NONE === $compression |
||||||
296 | ? '<fg=red>[NONE]</fg=red>' |
||||||
297 | : "<fg=cyan>[{$compression->name}]</fg=cyan>"; |
||||||
298 | |||||||
299 | self::print( |
||||||
300 | $output, |
||||||
301 | sprintf( |
||||||
302 | '%s %s - %s', |
||||||
303 | $indent |
||||||
304 | ? $splFileInfo->getFilename() |
||||||
305 | : $splFileInfo->getRelativePathname(), |
||||||
306 | $compressionLine, |
||||||
307 | format_size($compressionSize), |
||||||
308 | ), |
||||||
309 | $depth, |
||||||
310 | $indent, |
||||||
311 | ); |
||||||
312 | } |
||||||
313 | } |
||||||
314 | |||||||
315 | private static function extractBoxVersion(PharInfo $pharInfo): ?string |
||||||
316 | { |
||||||
317 | $stub = $pharInfo->getStubContent(); |
||||||
318 | |||||||
319 | if (null !== $stub && 1 === preg_match(self::BOX_VERSION_PATTERN, $stub, $matches)) { |
||||||
320 | return $matches['version']; |
||||||
321 | } |
||||||
322 | |||||||
323 | return null; |
||||||
324 | } |
||||||
325 | |||||||
326 | /** |
||||||
327 | * @return array{Requirement[], Requirement[]} |
||||||
0 ignored issues
–
show
|
|||||||
328 | */ |
||||||
329 | private static function retrieveRequirements(Requirements $requirements): array |
||||||
330 | { |
||||||
331 | [$required, $conflicting] = array_reduce( |
||||||
332 | toArray($requirements), |
||||||
333 | static function ($carry, Requirement $requirement): array { |
||||||
334 | $hash = implode( |
||||||
335 | ':', |
||||||
336 | [ |
||||||
337 | $requirement->type->value, |
||||||
338 | $requirement->condition, |
||||||
339 | $requirement->source, |
||||||
340 | ], |
||||||
341 | ); |
||||||
342 | |||||||
343 | if (RequirementType::EXTENSION_CONFLICT === $requirement->type) { |
||||||
344 | $carry[1][$hash] = $requirement; |
||||||
345 | } else { |
||||||
346 | $carry[0][$hash] = $requirement; |
||||||
347 | } |
||||||
348 | |||||||
349 | return $carry; |
||||||
350 | }, |
||||||
351 | [[], []], |
||||||
352 | ); |
||||||
353 | |||||||
354 | return [ |
||||||
355 | array_values($required), |
||||||
356 | array_values($conflicting), |
||||||
357 | ]; |
||||||
358 | } |
||||||
359 | |||||||
360 | /** |
||||||
361 | * @param Requirement[] $required |
||||||
362 | */ |
||||||
363 | private static function renderRequiredSection( |
||||||
364 | array $required, |
||||||
365 | IO $io, |
||||||
366 | ): void { |
||||||
367 | if (0 === count($required)) { |
||||||
368 | return; |
||||||
369 | } |
||||||
370 | |||||||
371 | $io->writeln(' <comment>Required:</comment>'); |
||||||
372 | $io->writeln( |
||||||
373 | array_map( |
||||||
374 | static fn (Requirement $requirement) => match ($requirement->type) { |
||||||
375 | RequirementType::PHP => sprintf( |
||||||
376 | ' - PHP %s (%s)', |
||||||
377 | $requirement->condition, |
||||||
378 | $requirement->source ?? 'root', |
||||||
379 | ), |
||||||
380 | RequirementType::EXTENSION => sprintf( |
||||||
381 | ' - ext-%s (%s)', |
||||||
382 | $requirement->condition, |
||||||
383 | $requirement->source ?? 'root', |
||||||
384 | ), |
||||||
385 | }, |
||||||
386 | $required, |
||||||
387 | ), |
||||||
388 | ); |
||||||
389 | } |
||||||
390 | |||||||
391 | /** |
||||||
392 | * @param Requirement[] $conflicting |
||||||
393 | */ |
||||||
394 | private static function renderConflictingSection( |
||||||
395 | array $conflicting, |
||||||
396 | IO $io, |
||||||
397 | ): void { |
||||||
398 | if (0 === count($conflicting)) { |
||||||
399 | return; |
||||||
400 | } |
||||||
401 | |||||||
402 | $io->writeln(' <comment>Conflict:</comment>'); |
||||||
403 | $io->writeln( |
||||||
404 | array_map( |
||||||
405 | static fn (Requirement $requirement) => sprintf( |
||||||
406 | ' - ext-%s (%s)', |
||||||
407 | $requirement->condition, |
||||||
408 | $requirement->source ?? 'root', |
||||||
409 | ), |
||||||
410 | $conflicting, |
||||||
411 | ), |
||||||
412 | ); |
||||||
413 | } |
||||||
414 | |||||||
415 | private static function renderParentDirectoriesIfNecessary( |
||||||
416 | SplFileInfo $fileInfo, |
||||||
417 | OutputInterface $output, |
||||||
418 | int &$depth, |
||||||
419 | array &$renderedDirectories, |
||||||
420 | ): void { |
||||||
421 | $depth = 0; |
||||||
422 | $relativePath = $fileInfo->getRelativePath(); |
||||||
0 ignored issues
–
show
The method
getRelativePath() does not exist on SplFileInfo . Did you maybe mean getRealPath() ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||||
423 | |||||||
424 | if ('' === $relativePath) { |
||||||
425 | // No parent directory: there is nothing to do. |
||||||
426 | return; |
||||||
427 | } |
||||||
428 | |||||||
429 | $parentDirectories = explode( |
||||||
430 | '/', |
||||||
431 | Path::normalize($relativePath), |
||||||
432 | ); |
||||||
433 | |||||||
434 | foreach ($parentDirectories as $index => $parentDirectory) { |
||||||
435 | if (array_key_exists($parentDirectory, $renderedDirectories)) { |
||||||
436 | ++$depth; |
||||||
437 | |||||||
438 | continue; |
||||||
439 | } |
||||||
440 | |||||||
441 | self::print( |
||||||
442 | $output, |
||||||
443 | "<info>{$parentDirectory}/</info>", |
||||||
444 | $index, |
||||||
445 | true, |
||||||
446 | ); |
||||||
447 | |||||||
448 | $renderedDirectories[$parentDirectory] = true; |
||||||
449 | ++$depth; |
||||||
450 | } |
||||||
451 | |||||||
452 | $depth = count($parentDirectories); |
||||||
453 | } |
||||||
454 | |||||||
455 | private static function print( |
||||||
456 | OutputInterface $output, |
||||||
457 | string $message, |
||||||
458 | int $depth, |
||||||
459 | bool $indent, |
||||||
460 | ): void { |
||||||
461 | if ($indent) { |
||||||
462 | $output->write(str_repeat(' ', $depth * self::INDENT_SIZE)); |
||||||
463 | } |
||||||
464 | |||||||
465 | $output->writeln($message); |
||||||
466 | } |
||||||
467 | |||||||
468 | private static function translateCompressionAlgorithm(CompressionAlgorithm $algorithm): string |
||||||
469 | { |
||||||
470 | return CompressionAlgorithm::NONE === $algorithm ? 'None' : $algorithm->name; |
||||||
0 ignored issues
–
show
|
|||||||
471 | } |
||||||
472 | } |
||||||
473 |
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