Passed
Pull Request — master (#390)
by Théo
02:53
created

Extract::renderContents()   C

Complexity

Conditions 12
Paths 32

Size

Total Lines 65
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 37
dl 0
loc 65
rs 6.9666
c 0
b 0
f 0
cc 12
nc 32
nop 8

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Command;
16
17
use function array_filter;
18
use function array_flip;
19
use Assert\Assertion;
20
use DirectoryIterator;
21
use function file_get_contents;
22
use function getcwd;
23
use function is_array;
24
use KevinGH\Box\Box;
25
use KevinGH\Box\Console\FileDescriptorManipulator;
26
use KevinGH\Box\Console\PharInfoRenderer;
27
use function KevinGH\Box\FileSystem\remove;
28
use function KevinGH\Box\format_size;
29
use function KevinGH\Box\get_phar_compression_algorithms;
30
use KevinGH\Box\PharInfo\PharInfo;
31
use Phar;
32
use PharData;
33
use PharFileInfo;
34
use function realpath;
35
use RuntimeException;
36
use function sprintf;
37
use function str_repeat;
38
use function str_replace;
39
use Symfony\Component\Console\Input\InputArgument;
40
use Symfony\Component\Console\Input\InputInterface;
41
use Symfony\Component\Console\Input\InputOption;
42
use Symfony\Component\Console\Output\OutputInterface;
43
use Symfony\Component\Console\Style\SymfonyStyle;
44
use Throwable;
45
use function var_dump;
46
47
/**
48
 * @private
49
 */
50
final class Extract extends Command
51
{
52
    use CreateTemporaryPharFile;
53
54
    private const PHAR_ARG = 'phar';
55
    private const OUTPUT_ARG = 'output';
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    protected function configure(): void
61
    {
62
        $this->setName('extract');
63
        $this->setDescription(
64
            '🚚  Extracts a given PHAR into a directory'
65
        );
66
        $this->addArgument(
67
            self::PHAR_ARG,
68
            InputArgument::REQUIRED,
69
            'The PHAR file.'
70
        );
71
        $this->addArgument(
72
            self::OUTPUT_ARG,
73
            InputArgument::REQUIRED,
74
            'The output directory'
75
        );
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    public function execute(InputInterface $input, OutputInterface $output): int
82
    {
83
        $io = new SymfonyStyle($input, $output);
84
        $io->newLine();
85
86
        $file = realpath($input->getArgument(self::PHAR_ARG));
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument(self::PHAR_ARG) can also be of type string[]; however, parameter $path of realpath() 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

86
        $file = realpath(/** @scrutinizer ignore-type */ $input->getArgument(self::PHAR_ARG));
Loading history...
87
88
        if (false === $file) {
89
            $io->error(
90
                sprintf(
91
                    'The file "%s" could not be found.',
92
                    $input->getArgument(self::PHAR_ARG)
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument(self::PHAR_ARG) can also be of type string[]; however, parameter $args of sprintf() 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

92
                    /** @scrutinizer ignore-type */ $input->getArgument(self::PHAR_ARG)
Loading history...
93
                )
94
            );
95
96
            return 1;
97
        }
98
99
        $tmpFile = $this->createTemporaryPhar($file);
100
101
        $box = Box::create($tmpFile);
102
103
        $restoreLimit = FileDescriptorManipulator::bumpOpenFileDescriptorLimit(count($box), $io);
104
105
        try {
106
            $box->compress(Phar::NONE);
107
        } catch (RuntimeException $exception) {
108
            $io->error($exception->getMessage());
109
110
            return 1;
111
        } finally {
112
            $restoreLimit();
113
114
            remove($tmpFile);
115
        }
116
117
        $outputDir = $input->getArgument(self::OUTPUT_ARG);
118
119
        $box->getPhar()->decompressFiles();
120
121
        unset($box);
122
123
        Box::create($tmpFile)->getPhar()->extractTo($outputDir, null, true);
0 ignored issues
show
Bug introduced by
It seems like $outputDir can also be of type string[]; however, parameter $pathto of Phar::extractTo() 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

123
        Box::create($tmpFile)->getPhar()->extractTo(/** @scrutinizer ignore-type */ $outputDir, null, true);
Loading history...
124
125
        return 0;
126
    }
127
128
    public function showInfo(string $file, string $originalFile, InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
129
    {
130
        $depth = (int) $input->getOption(self::DEPTH_OPT);
0 ignored issues
show
Bug introduced by
The constant KevinGH\Box\Console\Command\Extract::DEPTH_OPT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
131
132
        Assertion::greaterOrEqualThan($depth, -1, 'Expected the depth to be a positive integer or -1, got "%d"');
133
134
        try {
135
            $pharInfo = new PharInfo($file);
136
137
            return $this->showPharInfo(
138
                $pharInfo,
139
                $input->getOption(self::OUTPUT_ARG),
140
                $depth,
141
                'indent' === $input->getOption(self::MODE_OPT),
0 ignored issues
show
Bug introduced by
The constant KevinGH\Box\Console\Command\Extract::MODE_OPT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
142
                $output,
143
                $io
144
            );
145
        } catch (Throwable $throwable) {
146
            if ($output->isDebug()) {
147
                throw $throwable;
148
            }
149
150
            $io->error(
151
                sprintf(
152
                    'Could not read the file "%s".',
153
                    $originalFile
154
                )
155
            );
156
157
            return 1;
158
        }
159
    }
160
161
    private function showGlobalInfo(OutputInterface $output, SymfonyStyle $io): int
0 ignored issues
show
Unused Code introduced by
The method showGlobalInfo() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
162
    {
163
        $this->render(
164
            $output,
165
            [
166
                'API Version' => Phar::apiVersion(),
167
                'Supported Compression' => Phar::getSupportedCompression(),
168
                'Supported Signatures' => Phar::getSupportedSignatures(),
169
            ]
170
        );
171
172
        $io->newLine();
173
        $io->comment('Get a PHAR details by giving its path as an argument.');
174
175
        return 0;
176
    }
177
178
    private function showPharInfo(
179
        PharInfo $pharInfo,
180
        bool $content,
181
        int $depth,
182
        bool $indent,
183
        OutputInterface $output,
184
        SymfonyStyle $io
185
    ): int {
186
        $this->showPharMeta($pharInfo, $io);
187
188
        if ($content) {
189
            $this->renderContents(
190
                $output,
191
                $pharInfo->getPhar(),
192
                0,
193
                $depth,
194
                $indent ? 0 : false,
195
                $pharInfo->getRoot(),
196
                $pharInfo->getPhar(),
197
                $pharInfo->getRoot()
198
            );
199
        } else {
200
            $io->comment('Use the <info>--list|-l</info> option to list the content of the PHAR.');
201
        }
202
203
        return 0;
204
    }
205
206
    private function showPharMeta(PharInfo $pharInfo, SymfonyStyle $io): void
207
    {
208
        $io->writeln(
209
            sprintf(
210
                '<comment>API Version:</comment> %s',
211
                $pharInfo->getVersion()
212
            )
213
        );
214
215
        $io->newLine();
216
217
        PharInfoRenderer::renderCompression($pharInfo, $io);
218
219
        $io->newLine();
220
221
        PharInfoRenderer::renderSignature($pharInfo, $io);
222
223
        $io->newLine();
224
225
        PharInfoRenderer::renderMetadata($pharInfo, $io);
226
227
        $io->newLine();
228
229
        PharInfoRenderer::renderContentsSummary($pharInfo, $io);
230
    }
231
232
    private function render(OutputInterface $output, array $attributes): void
233
    {
234
        $out = false;
235
236
        foreach ($attributes as $name => $value) {
237
            if ($out) {
238
                $output->writeln('');
239
            }
240
241
            $output->write("<comment>$name:</comment>");
242
243
            if (is_array($value)) {
244
                $output->writeln('');
245
246
                foreach ($value as $v) {
247
                    $output->writeln("  - $v");
248
                }
249
            } else {
250
                $output->writeln(" $value");
251
            }
252
253
            $out = true;
254
        }
255
    }
256
257
    /**
258
     * @param iterable|PharFileInfo[] $list
259
     * @param false|int               $indent Nbr of indent or `false`
260
     * @param Phar|PharData           $phar
261
     */
262
    private function renderContents(
263
        OutputInterface $output,
264
        iterable $list,
265
        int $depth,
266
        int $maxDepth,
267
        $indent,
268
        string $base,
269
        $phar,
270
        string $root
271
    ): void {
272
        if (-1 !== $maxDepth && $depth > $maxDepth) {
273
            return;
274
        }
275
276
        foreach ($list as $item) {
277
            $item = $phar[str_replace($root, '', $item->getPathname())];
278
279
            if (false !== $indent) {
280
                $output->write(str_repeat(' ', $indent));
281
282
                $path = $item->getFilename();
283
284
                if ($item->isDir()) {
285
                    $path .= '/';
286
                }
287
            } else {
288
                $path = str_replace($base, '', $item->getPathname());
289
            }
290
291
            if ($item->isDir()) {
292
                if (false !== $indent) {
293
                    $output->writeln("<info>$path</info>");
294
                }
295
            } else {
296
                $compression = '<fg=red>[NONE]</fg=red>';
297
298
                foreach (self::$FILE_ALGORITHMS as $code => $name) {
0 ignored issues
show
Bug Best Practice introduced by
The property FILE_ALGORITHMS does not exist on KevinGH\Box\Console\Command\Extract. Did you maybe forget to declare it?
Loading history...
299
                    if ($item->isCompressed($code)) {
300
                        $compression = "<fg=cyan>[$name]</fg=cyan>";
301
                        break;
302
                    }
303
                }
304
305
                $fileSize = format_size($item->getCompressedSize());
306
307
                $output->writeln(
308
                    sprintf(
309
                        '%s %s - %s',
310
                        $path,
311
                        $compression,
312
                        $fileSize
313
                    )
314
                );
315
            }
316
317
            if ($item->isDir()) {
318
                $this->renderContents(
319
                    $output,
320
                    new DirectoryIterator($item->getPathname()),
321
                    $depth + 1,
322
                    $maxDepth,
323
                    false === $indent ? $indent : $indent + 2,
324
                    $base,
325
                    $phar,
326
                    $root
327
                );
328
            }
329
        }
330
    }
331
}
332