ConsoleScoper::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the humbug/php-scoper package.
7
 *
8
 * Copyright (c) 2017 Théo FIDRY <[email protected]>,
9
 *                    Pádraic Brady <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Humbug\PhpScoper\Console;
16
17
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...
18
use Fidry\Console\Input\IO;
19
use Humbug\PhpScoper\Autoload\ScoperAutoloadGenerator;
20
use Humbug\PhpScoper\Configuration\Configuration;
21
use Humbug\PhpScoper\Scoper\Scoper;
22
use Humbug\PhpScoper\Scoper\ScoperFactory;
23
use Humbug\PhpScoper\Symbol\SymbolsRegistry;
24
use Humbug\PhpScoper\Throwable\Exception\ParsingException;
25
use Symfony\Component\Filesystem\Filesystem;
26
use Throwable;
27
use function array_column;
28
use function array_keys;
29
use function array_map;
30
use function count;
31
use function Humbug\PhpScoper\get_common_path;
32
use function preg_match as native_preg_match;
33
use function Safe\file_get_contents;
34
use function Safe\sprintf;
35
use function Safe\usort;
36
use function str_replace;
37
use function strlen;
38
use const DIRECTORY_SEPARATOR;
39
40
/**
41
 * @private
42
 */
43
final class ConsoleScoper
44
{
45
    private const VENDOR_DIR_PATTERN = '~((?:.*)\\'.DIRECTORY_SEPARATOR.'vendor)\\'.DIRECTORY_SEPARATOR.'.*~';
46
47
    private Filesystem $fileSystem;
48
    private Application $application;
49
    private ScoperFactory $scoperFactory;
50
51
    public function __construct(
52
        Filesystem $fileSystem,
53
        Application $application,
54
        ScoperFactory $scoperFactory
55
    ) {
56
        $this->fileSystem = $fileSystem;
57
        $this->application = $application;
58
        $this->scoperFactory = $scoperFactory;
59
    }
60
61
    /**
62
     * @param list<non-empty-string> $paths
63
     * @param non-empty-string       $outputDir
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
64
     */
65
    public function scope(
66
        IO $io,
67
        Configuration $config,
68
        array $paths,
69
        string $outputDir,
70
        bool $stopOnFailure
71
    ): void {
72
        $logger = new ScoperLogger(
73
            $this->application,
74
            $io,
75
        );
76
77
        $logger->outputScopingStart(
78
            $config->getPrefix(),
79
            $paths,
80
        );
81
82
        try {
83
            $this->scopeFiles(
84
                $config,
85
                $outputDir,
86
                $stopOnFailure,
87
                $logger,
88
            );
89
        } catch (Throwable $throwable) {
90
            $this->fileSystem->remove($outputDir);
91
92
            $logger->outputScopingEndWithFailure();
93
94
            throw $throwable;
95
        }
96
97
        $logger->outputScopingEnd();
98
    }
99
100
    private function scopeFiles(
101
        Configuration $config,
102
        string $outputDir,
103
        bool $stopOnFailure,
104
        ScoperLogger $logger
105
    ): void {
106
        // Creates output directory if does not already exist
107
        $this->fileSystem->mkdir($outputDir);
108
109
        [$files, $whitelistedFiles] = self::getFiles($config, $outputDir);
110
111
        $logger->outputFileCount(count($files));
112
113
        $symbolsRegistry = new SymbolsRegistry();
114
115
        $scoper = $this->scoperFactory->createScoper(
116
            $config,
117
            $symbolsRegistry,
118
        );
119
120
        foreach ($files as [$inputFilePath, $inputContents, $outputFilePath]) {
121
            $this->scopeFile(
122
                $scoper,
123
                $inputFilePath,
124
                $inputContents,
125
                $outputFilePath,
126
                $stopOnFailure,
127
                $logger,
128
            );
129
        }
130
131
        foreach ($whitelistedFiles as [$inputFilePath, $inputContents, $outputFilePath]) {
132
            $this->fileSystem->dumpFile($outputFilePath, $inputContents);
133
        }
134
135
        $vendorDir = self::findVendorDir(
136
            [
137
                ...array_column($files, 2),
138
                ...array_column($whitelistedFiles, 2),
139
            ],
140
        );
141
142
        if (null !== $vendorDir) {
143
            $autoload = (new ScoperAutoloadGenerator($symbolsRegistry))->dump();
144
145
            $this->fileSystem->dumpFile(
146
                $vendorDir.DIRECTORY_SEPARATOR.'scoper-autoload.php',
147
                $autoload,
148
            );
149
        }
150
    }
151
152
    /**
153
     * @return array{array<array{string, string, string}>, array<array{string, string, string}>}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{array<array{string...ring, string, string}>} at position 2 could not be parsed: Expected ':' at position 2, but found 'array'.
Loading history...
154
     */
155
    private static function getFiles(Configuration $config, string $outputDir): array
156
    {
157
        $filesWithContent = $config->getFilesWithContents();
158
        $excludedFilesWithContents = $config->getExcludedFilesWithContents();
159
160
        $commonPath = get_common_path(
161
            [
162
                ...array_keys($filesWithContent),
163
                ...array_keys($excludedFilesWithContents),
164
            ],
165
        );
166
167
        $mapFiles = static fn (array $inputFileTuple) => [
168
            $inputFileTuple[0],
169
            $inputFileTuple[1],
170
            $outputDir.str_replace($commonPath, '', $inputFileTuple[0]),
171
        ];
172
173
        return [
174
            array_map(
175
                $mapFiles,
176
                $filesWithContent,
177
            ),
178
            array_map(
179
                $mapFiles,
180
                $excludedFilesWithContents,
181
            ),
182
        ];
183
    }
184
185
    private static function findVendorDir(array $outputFilePaths): ?string
186
    {
187
        $vendorDirsAsKeys = [];
188
189
        foreach ($outputFilePaths as $filePath) {
190
            if (native_preg_match(self::VENDOR_DIR_PATTERN, $filePath, $matches)) {
191
                $vendorDirsAsKeys[$matches[1]] = true;
192
            }
193
        }
194
195
        $vendorDirs = array_keys($vendorDirsAsKeys);
196
197
        usort(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\usort() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

197
        /** @scrutinizer ignore-deprecated */ usort(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
198
            $vendorDirs,
199
            static fn ($a, $b) => strlen($a) <=> strlen($b),
200
        );
201
202
        return (0 === count($vendorDirs)) ? null : $vendorDirs[0];
203
    }
204
205
    private function scopeFile(
206
        Scoper $scoper,
207
        string $inputFilePath,
208
        string $inputContents,
209
        string $outputFilePath,
210
        bool $stopOnFailure,
211
        ScoperLogger $logger
212
    ): void {
213
        try {
214
            $scoppedContent = $scoper->scope(
215
                $inputFilePath,
216
                $inputContents,
217
            );
218
        } catch (Throwable $throwable) {
219
            $exception = new ParsingException(
220
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

220
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
221
                    'Could not parse the file "%s".',
222
                    $inputFilePath,
223
                ),
224
                0,
225
                $throwable,
226
            );
227
228
            if ($stopOnFailure) {
229
                throw $exception;
230
            }
231
232
            $logger->outputWarnOfFailure($inputFilePath, $exception);
233
234
            // Fallback on unchanged content
235
            $scoppedContent = file_get_contents($inputFilePath);
236
        }
237
238
        $this->fileSystem->dumpFile($outputFilePath, $scoppedContent);
239
240
        if (!isset($exception)) {
241
            $logger->outputSuccess($inputFilePath);
242
        }
243
    }
244
}
245