Passed
Push — master ( 1d0e74...3b753b )
by Théo
02:29
created

removeUnnecessaryLineReturns()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 1
nop 1
dl 0
loc 10
ccs 0
cts 0
cp 0
crap 6
rs 10
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\Autoload;
16
17
use Humbug\PhpScoper\Whitelist;
18
use PhpParser\Node\Name\FullyQualified;
19
use function array_map;
20
use function array_unshift;
21
use function chr;
22
use function count;
23
use function explode;
24
use function implode;
25
use function Safe\sprintf;
26
use function str_repeat;
27
use function str_replace;
28
use function strpos;
29
30
final class ScoperAutoloadGenerator
31
{
32
    private const EXPOSED_FUNCTIONS_DOC = <<<'EOF'
33 9
    // Functions whitelisting. For more information see:
34
    // https://github.com/humbug/php-scoper/blob/master/README.md#functions-whitelisting
35 9
    EOF;
36 9
37
    private const EXPOSED_CLASSES_DOC = <<<'EOF'
38
    // Aliases for the whitelisted classes. For more information see:
39 9
    // https://github.com/humbug/php-scoper/blob/master/README.md#class-whitelisting
40
    EOF;
41 9
42
    private static string $eol;
43 9
    
44
    private Whitelist $registry;
45 9
46 9
    public function __construct(Whitelist $registry)
47 9
    {
48 9
        self::$eol = chr(10);
49 9
50
        $this->registry = $registry;
51 9
    }
52 9
53
    public function dump(): string
54 9
    {
55 9
        $exposedFunctions = $this->registry->getRecordedWhitelistedFunctions();
56 9
57 9
        $hasNamespacedFunctions = self::hasNamespacedFunctions($exposedFunctions);
58 9
59
        $statements = implode(
60
                self::$eol,
61
                self::createClassAliasStatementsSection(
62 9
                    $this->registry->getRecordedWhitelistedClasses(),
63
                    $hasNamespacedFunctions,
64
                ),
65
            )
66
            .self::$eol
67
            .self::$eol
68
        ;
69
        $statements .= implode(
70
            self::$eol,
71
            self::createFunctionAliasStatements(
72 3
                $exposedFunctions,
73
                $hasNamespacedFunctions,
74
            ),
75
        );
76
77
        if ($hasNamespacedFunctions) {
78
            $dump = <<<PHP
79
            <?php
80
            
81
            // scoper-autoload.php @generated by PhpScoper
82
            
83
            namespace {
84
                \$loader = require_once __DIR__.'/autoload.php';
85
            }
86
            
87 6
            $statements
88
            
89
            namespace {
90
                return \$loader;
91
            }
92
            
93
            PHP;
94 9
        } else {
95
            $dump = <<<PHP
96 9
            <?php
97
            
98
            // scoper-autoload.php @generated by PhpScoper
99
            
100
            \$loader = require_once __DIR__.'/autoload.php';
101
            
102 9
            $statements
103
            
104 9
            return \$loader;
105
            
106 3
            PHP;
107 3
        }
108 3
109
        return self::removeUnnecessaryLineReturns($dump);
110 9
    }
111 9
112 9
    /**
113 9
     * @param list<array{string, string}> $exposedClasses
0 ignored issues
show
Bug introduced by
The type Humbug\PhpScoper\Autoload\list was not found. Maybe you did not declare it correctly or list all dependencies?

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:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
114
     *
115
     * @return list<string>
116
     */
117 9
    private static function createClassAliasStatementsSection(
118 6
        array $exposedClasses,
119
        bool $hasNamespacedFunctions
120
    ): array
121 3
    {
122 1
        $statements = self::createClassAliasStatements($exposedClasses);
123
124 1
        if (count($statements) === 0) {
125 1
            return $statements;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statements returns the type array which is incompatible with the documented return type Humbug\PhpScoper\Autoload\list.
Loading history...
126 1
        }
127
128
        if ($hasNamespacedFunctions) {
129 1
            $statements = self::wrapStatementsInNamespaceBlock($statements);
130 1
        }
131
132
        array_unshift($statements, self::EXPOSED_CLASSES_DOC);
133 3
134 3
        return $statements;
135
    }
136 3
137
    /**
138
     * @param list<array{string, string}> $exposedClasses
139
     *
140
     * @return list<string>
141 3
     */
142
    private static function createClassAliasStatements(array $exposedClasses): array
143
    {
144
        return array_map(
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_map(functio... */ }, $exposedClasses) returns the type array which is incompatible with the documented return type Humbug\PhpScoper\Autoload\list.
Loading history...
145
            static fn (array $pair) => self::createClassAliasStatement(...$pair),
146
            $exposedClasses
147 9
        );
148
    }
149 9
150
    private static function createClassAliasStatement(
151
        string $original,
152
        string $alias
153
    ): string
154
    {
155 5
        return sprintf(
156
            <<<'PHP'
157 5
            if (!class_exists('%1$s', false) && !interface_exists('%1$s', false) && !trait_exists('%1$s', false)) {
158 5
                spl_autoload_call('%2$s');
159
            }
160 5
            PHP,
161 3
            $original,
162 3
            $alias
163
        );
164 3
    }
165
166 3
    /**
167
     * @param list<string> $statements
168
     *
169
     * @return list<string>
170
     */
171
    private static function wrapStatementsInNamespaceBlock(array $statements): array
172
    {
173
        $indent = str_repeat(' ', 4);
174
        $indentLine = static fn (string $line) => $indent.$line;
175 3
176 3
        $statements = array_map(
177 3
            static function (string $statement) use ($indentLine): string {
178 3
                $parts = explode(self::$eol, $statement);
179 3
180
                if (false === $parts) {
181
                    return $statement;
182
                }
183 2
184
                return implode(
185 2
                    self::$eol,
186
                    array_map($indentLine, $parts),
187
                );
188
            },
189
            $statements,
190
        );
191
192 2
        array_unshift($statements, 'namespace {');
193 2
        $statements[] = '}'.self::$eol;
194 2
195
        return $statements;
196 9
    }
197 9
198
    /**
199
     * @param list<array{string, string}> $exposedFunctions
200 9
     *
201 4
     * @return list<string>
202
     */
203
    private static function createFunctionAliasStatements(
204 5
        array $exposedFunctions,
205 5
        bool $hasNamespacedFunctions
206
    ): array
207 5
    {
208
        $statements = array_map(
209
            static fn (array $pair) => self::createFunctionAliasStatement(
210
                $hasNamespacedFunctions,
211
                ...$pair
212 5
            ),
213
            $exposedFunctions,
214
        );
215 9
216
        if ([] === $statements) {
217 9
            return $statements;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statements returns the type array which is incompatible with the documented return type Humbug\PhpScoper\Autoload\list.
Loading history...
218
        }
219
220
        array_unshift($statements, self::EXPOSED_FUNCTIONS_DOC);
221
222 5
        return $statements;
223 3
    }
224
225
    private static function createFunctionAliasStatement(
226
        bool $hasNamespacedFunctions,
227 6
        string $original,
228
        string $alias
229
    ): string
230 9
    {
231
        if (!$hasNamespacedFunctions) {
232 9
            return sprintf(
233
                <<<'PHP'
234
                if (!function_exists('%1$s')) {
235 9
                    function %1$s(%2$s) {
236 9
                        return \%3$s(...func_get_args());
237 9
                    }
238
                }
239 9
                PHP,
240
                $original,
241
                '__autoload' === $original ? '$className' : '',
242
                $alias,
243
            );
244
        }
245
246
        // When the function is namespaced we need to wrap the function
247
        // declaration within its namespace
248
        // TODO: consider grouping the declarations within the same namespace
249
        //  i.e. that if there is Acme\foo and Acme\bar that only one
250
        //  namespace Acme statement is used
251
252
        $originalFQ = new FullyQualified($original);
253
        $namespace = $originalFQ->slice(0, -1);
254
        $functionName = null === $namespace ? $original : (string) $originalFQ->slice(1);
255
256
        return sprintf(
257
            <<<'PHP'
258
            namespace %s{
259
                if (!function_exists('%s')) {
260
                    function %s(%s) {
261
                        return \%s(...func_get_args());
262
                    }
263
                }
264
            }
265
            PHP,
266
            null === $namespace ? '' : $namespace->toString().' ',
267
            $original,
268
            $functionName,
269
            '__autoload' === $functionName ? '$className' : '',
270
            $alias,
271
        );
272
    }
273
274
    /**
275
     * @param list<array{string, string}> $functions
276
     */
277
    private static function hasNamespacedFunctions(array $functions): bool
278
    {
279
        foreach ($functions as [$original, $alias]) {
280
            $containsBackslash = false !== strpos($original, '\\');
281
282
            if ($containsBackslash) {
283
                return true;
284
            }
285
        }
286
287
        return false;
288
    }
289
290
    private static function removeUnnecessaryLineReturns(string $dump): string
291
    {
292
        $cleanedDump = $dump;
293
294
        do {
295
            $dump = $cleanedDump;
296
            $cleanedDump = str_replace("\n\n\n", "\n\n", $dump);
297
        } while ($cleanedDump !== $dump);
298
299
        return $dump;
300
    }
301
}
302