Passed
Pull Request — master (#483)
by Théo
110:16
created

ConsoleScoper::getFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 12
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Humbug\PhpScoper\Console;
6
7
use Fidry\Console\Application\Application;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Humbug\PhpScoper\Console\Application. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use Fidry\Console\IO;
9
use Humbug\PhpScoper\Autoload\ScoperAutoloadGenerator;
10
use Humbug\PhpScoper\Configuration;
11
use Humbug\PhpScoper\Scoper;
12
use Humbug\PhpScoper\Throwable\Exception\ParsingException;
13
use Humbug\PhpScoper\Whitelist;
14
use Symfony\Component\Filesystem\Filesystem;
15
use Throwable;
16
use function array_column;
17
use function array_keys;
18
use function array_map;
19
use function count;
20
use function Humbug\PhpScoper\get_common_path;
21
use function preg_match as native_preg_match;
22
use function Safe\file_get_contents;
23
use function Safe\sprintf;
24
use function Safe\usort;
25
use function str_replace;
26
use function strlen;
27
use const DIRECTORY_SEPARATOR;
28
29
/**
30
 * @private
31
 */
32
final class ConsoleScoper
33
{
34
    private const VENDOR_DIR_PATTERN = '~((?:.*)\\'.DIRECTORY_SEPARATOR.'vendor)\\'.DIRECTORY_SEPARATOR.'.*~';
35
36
    private Filesystem $fileSystem;
37
    private Application $application;
38
    private Scoper $scoper;
39
40
    public function __construct(
41
        Filesystem $fileSystem,
42
        Application $application,
43
        Scoper $scoper
44
    )
45
    {
46
        $this->fileSystem = $fileSystem;
47
        $this->application = $application;
48
        $this->scoper = $scoper;
49
    }
50
51
    public function scope(
52
        IO $io,
53
        Configuration $config,
54
        array $paths,
55
        string $outputDir,
56
        bool $stopOnFailure
57
    ): void
58
    {
59
        $logger = new ScoperLogger(
60
            $this->application,
61
            $io,
62
        );
63
64
        $logger->outputScopingStart(
65
            $config->getPrefix(),
0 ignored issues
show
Bug introduced by
It seems like $config->getPrefix() can also be of type null; however, parameter $prefix of Humbug\PhpScoper\Console...r::outputScopingStart() 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

65
            /** @scrutinizer ignore-type */ $config->getPrefix(),
Loading history...
66
            $paths,
67
        );
68
69
        try {
70
            $this->scopeFiles(
71
                $config,
72
                $outputDir,
73
                $stopOnFailure,
74
                $logger,
75
            );
76
        } catch (Throwable $throwable) {
77
            $this->fileSystem->remove($outputDir);
78
79
            $logger->outputScopingEndWithFailure();
80
81
            throw $throwable;
82
        }
83
84
        $logger->outputScopingEnd();
85
    }
86
87
    private function scopeFiles(
88
        Configuration $config,
89
        string $outputDir,
90
        bool $stopOnFailure,
91
        ScoperLogger $logger
92
    ): void {
93
        // Creates output directory if does not already exist
94
        $this->fileSystem->mkdir($outputDir);
95
96
        $files = self::getFiles($config, $outputDir);
97
98
        $logger->outputFileCount(count($files));
99
100
        foreach ($files as [$inputFilePath, $inputContents, $outputFilePath]) {
101
            $this->scopeFile(
102
                $inputFilePath,
103
                $inputContents,
104
                $outputFilePath,
105
                $config,
106
                $stopOnFailure,
107
                $logger,
108
            );
109
        }
110
111
        $vendorDir = self::findVendorDir(array_column($files, 2));
112
113
        if (null !== $vendorDir) {
114
            $autoload = (new ScoperAutoloadGenerator($config->getWhitelist()))->dump();
115
116
            $this->fileSystem->dumpFile(
117
                $vendorDir.DIRECTORY_SEPARATOR.'scoper-autoload.php',
118
                $autoload,
119
            );
120
        }
121
    }
122
123
    /**
124
     * @return array<array{string, string, string}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array{string, string, string}> at position 4 could not be parsed: Expected ':' at position 4, but found 'string'.
Loading history...
125
     */
126
    private static function getFiles(Configuration $config, string $outputDir): array
127
    {
128
        $filesWithContent = $config->getFilesWithContents();
129
        $commonPath = get_common_path(array_keys($filesWithContent));
130
131
        return array_map(
132
            static fn (array $inputFileTuple) => [
133
                $inputFileTuple[0],
134
                $inputFileTuple[1],
135
                $outputDir.str_replace($commonPath, '', $inputFileTuple[0]),
136
            ],
137
            $filesWithContent,
138
        );
139
    }
140
141
    private static function findVendorDir(array $outputFilePaths): ?string
142
    {
143
        $vendorDirsAsKeys = [];
144
145
        foreach ($outputFilePaths as $filePath) {
146
            if (native_preg_match(self::VENDOR_DIR_PATTERN, $filePath, $matches)) {
147
                $vendorDirsAsKeys[$matches[1]] = true;
148
            }
149
        }
150
151
        $vendorDirs = array_keys($vendorDirsAsKeys);
152
153
        usort(
154
            $vendorDirs,
155
            static fn ($a, $b) => strlen($b) <=> strlen($a),
156
        );
157
158
        return (0 === count($vendorDirs)) ? null : $vendorDirs[0];
159
    }
160
161
    /**
162
     * @param callable[] $patchers
163
     */
164
    private function scopeFile(
165
        string $inputFilePath,
166
        string $inputContents,
167
        string $outputFilePath,
168
        Configuration $config,
169
        bool $stopOnFailure,
170
        ScoperLogger $logger
171
    ): void {
172
        try {
173
            $scoppedContent = $this->scoper->scope(
174
                $inputFilePath,
175
                $inputContents,
176
                $config->getPrefix(),
0 ignored issues
show
Bug introduced by
It seems like $config->getPrefix() can also be of type null; however, parameter $prefix of Humbug\PhpScoper\Scoper::scope() 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

176
                /** @scrutinizer ignore-type */ $config->getPrefix(),
Loading history...
177
                $config->getPatchers(),
178
                $config->getWhitelist(),
179
            );
180
        } catch (Throwable $throwable) {
181
            $exception = new ParsingException(
182
                sprintf(
183
                    'Could not parse the file "%s".',
184
                    $inputFilePath
185
                ),
186
                0,
187
                $throwable,
188
            );
189
190
            if ($stopOnFailure) {
191
                throw $exception;
192
            }
193
194
            $logger->outputWarnOfFailure($inputFilePath, $exception);
195
196
            // Fallback on unchanged content
197
            $scoppedContent = file_get_contents($inputFilePath);
198
        }
199
200
        $this->fileSystem->dumpFile($outputFilePath, $scoppedContent);
201
202
        if (false === isset($exception)) {
203
            $logger->outputSuccess($inputFilePath);
204
        }
205
    }
206
}
207