Completed
Push — master ( 77c2d6...01ac29 )
by Théo
17:54 queued 10:00
created

HandleAddPrefix::scopeFiles()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0378

Importance

Changes 0
Metric Value
cc 4
eloc 22
nc 6
nop 7
dl 0
loc 33
ccs 13
cts 15
cp 0.8667
crap 4.0378
rs 8.5806
c 0
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\Handler;
16
17
use Closure;
18
use Humbug\PhpScoper\Autoload\ScoperAutoloadGenerator;
19
use Humbug\PhpScoper\Logger\ConsoleLogger;
20
use Humbug\PhpScoper\Scoper;
21
use Humbug\PhpScoper\Throwable\Exception\ParsingException;
22
use Humbug\PhpScoper\Throwable\Exception\RuntimeException;
23
use SplFileInfo;
24
use Symfony\Component\Filesystem\Filesystem;
25
use Symfony\Component\Finder\Finder;
26
use Throwable;
27
use function Humbug\PhpScoper\get_common_path;
28
29
/**
30
 * @final
31
 */
32
class HandleAddPrefix
33
{
34
    private $fileSystem;
35
    private $scoper;
36
37 10
    public function __construct(Scoper $scoper)
38
    {
39 10
        $this->fileSystem = new Filesystem();
40 10
        $this->scoper = $scoper;
41
    }
42
43
    /**
44
     * Applies prefix to all the code found in the given paths, AKA scope all the files found.
45
     *
46
     * @param string              $prefix                   e.g. 'Foo'
47
     * @param string[]            $paths                    List of files (absolute paths) which will be scoped
48
     * @param string              $output                   Absolute path to the output directory
49
     * @param callable[]          $patchers
50
     * @param string[]            $whitelist                List of classes that will not be scoped
51
     * @param string[]|callable[] $globalNamespaceWhitelist
52
     * @param bool                $stopOnFailure
53
     * @param ConsoleLogger       $logger
54
     */
55 10
    public function __invoke(
56
        string $prefix,
57
        array $paths,
58
        string $output,
59
        array $patchers,
60
        array $whitelist,
61
        array $globalNamespaceWhitelist,
62
        bool $stopOnFailure,
63
        ConsoleLogger $logger
64
    ) {
65 10
        $this->fileSystem->mkdir($output);
66
67
        try {
68 10
            $files = $this->retrieveFiles($paths, $output);
69
70 9
            $globalWhitelister = $this->createGlobalWhitelister($globalNamespaceWhitelist);
71
72 9
            $vendorDir = $this->scopeFiles($files, $prefix, $patchers, $whitelist, $globalWhitelister, $stopOnFailure, $logger);
73
74 7
            if (null !== $vendorDir) {
75
                $autoload = (new ScoperAutoloadGenerator($whitelist))->dump($prefix);
76
77
                $this->fileSystem->dumpFile($vendorDir.'/scoper-autoload.php', $autoload);
78
            }
79 3
        } catch (Throwable $throwable) {
80 3
            $this->fileSystem->remove($output);
81
82 3
            throw $throwable;
83
        }
84
    }
85
86
    /**
87
     * @param string[]|callable[] $globalNamespaceWhitelist
88
     *
89
     * @return Closure
90
     */
91
    private function createGlobalWhitelister(array $globalNamespaceWhitelist): Closure
92
    {
93 9
        return function (string $className) use ($globalNamespaceWhitelist): bool {
94
            foreach ($globalNamespaceWhitelist as $whitelister) {
95
                if (is_string($whitelister)) {
96
                    if ($className === $whitelister) {
97
                        return true;
98
                    } else {
99
                        continue;
100
                    }
101
                }
102
103
                /** @var callable $whitelister */
104
                if (true === $whitelister($className)) {
105
                    return true;
106
                }
107
            }
108
109
            return false;
110 9
        };
111
    }
112
113
    /**
114
     * @param string[] $paths
115
     * @param string   $output
116
     *
117
     * @return string[]
118
     */
119 10
    private function retrieveFiles(array $paths, string $output): array
120
    {
121 10
        $pathsToSearch = [];
122 10
        $filesToAppend = [];
123
124 10
        foreach ($paths as $path) {
125 10
            if (false === file_exists($path)) {
126 1
                throw new RuntimeException(
127 1
                    sprintf(
128 1
                        'Could not find the file "%s".',
129 1
                        $path
130
                    )
131
                );
132
            }
133
134 9
            if (is_dir($path)) {
135 3
                $pathsToSearch[] = $path;
136
            } else {
137 7
                $filesToAppend[] = $path;
138
            }
139
        }
140
141 9
        $finder = new Finder();
142
143 9
        $finder->files()
144 9
            ->in($pathsToSearch)
145 9
            ->append($filesToAppend)
146 9
            ->sortByName()
147
        ;
148
149 9
        $files = array_values(
150 9
            array_map(
151 9
                function (SplFileInfo $fileInfo) {
152 8
                    return $fileInfo->getRealPath();
153 9
                },
154 9
                iterator_to_array($finder)
155
            )
156
        );
157
158 9
        $commonPath = get_common_path($files);
159
160 9
        return array_reduce(
161 9
            $files,
162 9
            function (array $files, string $file) use ($output, $commonPath): array {
163 8
                if (false === file_exists($file)) {
164
                    throw new RuntimeException(
165
                        sprintf(
166
                            'Could not find the file "%s".',
167
                            $file
168
                        )
169
                    );
170
                }
171
172 8
                if (false === is_readable($file)) {
173
                    throw new RuntimeException(
174
                        sprintf(
175
                            'Could not read the file "%s".',
176
                            $file
177
                        )
178
                    );
179
                }
180
181 8
                $files[$file] = $output.str_replace($commonPath, '', $file);
182
183 8
                return $files;
184 9
            },
185 9
            []
186
        );
187
    }
188
189
    /**
190
     * @param string[]      $files
191
     * @param string        $prefix
192
     * @param callable[]    $patchers
193
     * @param string[]      $whitelist
194
     * @param callable      $globalWhitelister
195
     * @param bool          $stopOnFailure
196
     * @param ConsoleLogger $logger
197
     *
198
     * @return string|null
199
     */
200 9
    private function scopeFiles(
201
        array $files,
202
        string $prefix,
203
        array $patchers,
204
        array $whitelist,
205
        callable $globalWhitelister,
206
        bool $stopOnFailure,
207
        ConsoleLogger $logger
208
    ): ?string {
209 9
        $count = count($files);
210 9
        $logger->outputFileCount($count);
211
212 9
        $vendorDirs = [];
213
214 9
        foreach ($files as $inputFilePath => $outputFilePath) {
215 8
            if (preg_match('~((?:.*)\/vendor)\/.*~', $outputFilePath, $matches)) {
216
                $vendorDirs[$matches[1]] = true;
217
            }
218
219 8
            $this->scopeFile($inputFilePath, $outputFilePath, $prefix, $patchers, $whitelist, $globalWhitelister, $stopOnFailure, $logger);
220
        }
221
222 7
        $vendorDirs = array_keys($vendorDirs);
223
224 7
        usort(
225 7
            $vendorDirs,
226 7
            function ($a, $b) {
227
                return strlen($b) <=> strlen($a);
228 7
            }
229
        );
230
231 7
        return (0 === count($vendorDirs)) ? null : $vendorDirs[0];
232
    }
233
234
    /**
235
     * @param string        $inputFilePath
236
     * @param string        $outputFilePath
237
     * @param string        $prefix
238
     * @param callable[]    $patchers
239
     * @param string[]      $whitelist
240
     * @param callable      $globalWhitelister
241
     * @param bool          $stopOnFailure
242
     * @param ConsoleLogger $logger
243
     */
244 8
    private function scopeFile(
245
        string $inputFilePath,
246
        string $outputFilePath,
247
        string $prefix,
248
        array $patchers,
249
        array $whitelist,
250
        callable $globalWhitelister,
251
        bool $stopOnFailure,
252
        ConsoleLogger $logger
253
    ): void {
254
        try {
255 8
            $scoppedContent = $this->scoper->scope($inputFilePath, $prefix, $patchers, $whitelist, $globalWhitelister);
256 3
        } catch (Throwable $error) {
257 3
            $exception = new ParsingException(
258 3
                sprintf(
259 3
                    'Could not parse the file "%s".',
260 3
                    $inputFilePath
261
                ),
262 3
                0,
263 3
                $error
264
            );
265
266 3
            if ($stopOnFailure) {
267 2
                throw $exception;
268
            }
269
270 1
            $logger->outputWarnOfFailure($inputFilePath, $exception);
271
272 1
            $scoppedContent = file_get_contents($inputFilePath);
273
        }
274
275 6
        $this->fileSystem->dumpFile($outputFilePath, $scoppedContent);
276
277 6
        if (false === isset($exception)) {
278 5
            $logger->outputSuccess($inputFilePath);
279
        }
280
    }
281
}
282