Issues (163)

src/Autoload/ScoperAutoloadGenerator.php (8 issues)

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\Symbol\SymbolsRegistry;
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
    // TODO: aliasing functions could be done via a single function to reduce boiler-template.
33 9
34
    private const EXPOSED_FUNCTIONS_DOC = <<<'EOF'
35 9
        // Exposed functions. For more information see:
36 9
        // https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#exposing-functions
37
        EOF;
38
39 9
    private const EXPOSED_CLASSES_DOC = <<<'EOF'
40
        // Exposed classes. For more information see:
41 9
        // https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#exposing-classes
42
        EOF;
43 9
44
    /** @var non-empty-string */
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...
45 9
    private static string $eol;
46 9
47 9
    private SymbolsRegistry $registry;
48 9
49 9
    public function __construct(SymbolsRegistry $registry)
50
    {
51 9
        self::$eol = chr(10);
52 9
53
        $this->registry = $registry;
54 9
    }
55 9
56 9
    public function dump(): string
57 9
    {
58 9
        $exposedFunctions = $this->registry->getRecordedFunctions();
59
60
        $hasNamespacedFunctions = self::hasNamespacedFunctions($exposedFunctions);
61
62 9
        $statements = implode(
63
            self::$eol,
64
            self::createClassAliasStatementsSection(
65
                $this->registry->getRecordedClasses(),
66
                $hasNamespacedFunctions,
67
            ),
68
        )
69
            .self::$eol
70
            .self::$eol;
71
        $statements .= implode(
72 3
            self::$eol,
73
            self::createFunctionAliasStatements(
74
                $exposedFunctions,
75
                $hasNamespacedFunctions,
76
            ),
77
        );
78
79
        if ($hasNamespacedFunctions) {
80
            $dump = <<<PHP
81
                <?php
82
83
                // scoper-autoload.php @generated by PhpScoper
84
85
                namespace {
86
                    \$loader = require_once __DIR__.'/autoload.php';
87 6
                }
88
89
                {$statements}
90
91
                namespace {
92
                    return \$loader;
93
                }
94 9
95
                PHP;
96 9
        } else {
97
            $dump = <<<PHP
98
                <?php
99
100
                // scoper-autoload.php @generated by PhpScoper
101
102 9
                \$loader = require_once __DIR__.'/autoload.php';
103
104 9
                {$statements}
105
106 3
                return \$loader;
107 3
108 3
                PHP;
109
        }
110 9
111 9
        return self::removeUnnecessaryLineReturns($dump);
112 9
    }
113 9
114
    /**
115
     * @param list<array{string, string}> $exposedClasses
0 ignored issues
show
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...
116
     *
117 9
     * @return list<string>
118 6
     */
119
    private static function createClassAliasStatementsSection(
120
        array $exposedClasses,
121 3
        bool $hasNamespacedFunctions
122 1
    ): array {
123
        $statements = self::createClassAliasStatements($exposedClasses);
124 1
125 1
        if (count($statements) === 0) {
126 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...
127
        }
128
129 1
        if ($hasNamespacedFunctions) {
130 1
            $statements = self::wrapStatementsInNamespaceBlock($statements);
131
        }
132
133 3
        array_unshift($statements, self::EXPOSED_CLASSES_DOC);
134 3
135
        return $statements;
136 3
    }
137
138
    /**
139
     * @param list<array{string, string}> $exposedClasses
140
     *
141 3
     * @return list<string>
142
     */
143
    private static function createClassAliasStatements(array $exposedClasses): array
144
    {
145
        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...
146
            static fn (array $pair) => self::createClassAliasStatement(...$pair),
147 9
            $exposedClasses,
148
        );
149 9
    }
150
151
    private static function createClassAliasStatement(
152
        string $original,
153
        string $alias
154
    ): string {
155 5
        return 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

155
        return /** @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...
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
                return implode(
181
                    self::$eol,
182
                    array_map($indentLine, $parts),
183 2
                );
184
            },
185 2
            $statements,
186
        );
187
188
        array_unshift($statements, 'namespace {');
189
        $statements[] = '}'.self::$eol;
190
191
        return $statements;
192 2
    }
193 2
194 2
    /**
195
     * @param list<array{string, string}> $exposedFunctions
196 9
     *
197 9
     * @return list<string>
198
     */
199
    private static function createFunctionAliasStatements(
200 9
        array $exposedFunctions,
201 4
        bool $hasNamespacedFunctions
202
    ): array {
203
        $statements = array_map(
204 5
            static fn (array $pair) => self::createFunctionAliasStatement(
205 5
                $hasNamespacedFunctions,
206
                ...$pair,
207 5
            ),
208
            $exposedFunctions,
209
        );
210
211
        if ([] === $statements) {
212 5
            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...
213
        }
214
215 9
        array_unshift($statements, self::EXPOSED_FUNCTIONS_DOC);
216
217 9
        return $statements;
218
    }
219
220
    private static function createFunctionAliasStatement(
221
        bool $hasNamespacedFunctions,
222 5
        string $original,
223 3
        string $alias
224
    ): string {
225
        if (!$hasNamespacedFunctions) {
226
            return 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

226
            return /** @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...
227 6
                <<<'PHP'
228
                    if (!function_exists('%1$s')) {
229
                        function %1$s(%2$s) {
230 9
                            return \%3$s(...func_get_args());
231
                        }
232 9
                    }
233
                    PHP,
234
                $original,
235 9
                '__autoload' === $original ? '$className' : '',
236 9
                $alias,
237 9
            );
238
        }
239 9
240
        // When the function is namespaced we need to wrap the function
241
        // declaration within its namespace
242
        // TODO: consider grouping the declarations within the same namespace
243
        //  i.e. that if there is Acme\foo and Acme\bar that only one
244
        //  namespace Acme statement is used
245
246
        $originalFQ = new FullyQualified($original);
247
        $namespace = $originalFQ->slice(0, -1);
248
        $functionName = null === $namespace ? $original : (string) $originalFQ->slice(1);
249
250
        return 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

250
        return /** @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...
251
            <<<'PHP'
252
                namespace %s{
253
                    if (!function_exists('%s')) {
254
                        function %s(%s) {
255
                            return \%s(...func_get_args());
256
                        }
257
                    }
258
                }
259
                PHP,
260
            null === $namespace ? '' : $namespace->toString().' ',
261
            $original,
262
            $functionName,
263
            '__autoload' === $functionName ? '$className' : '',
264
            $alias,
265
        );
266
    }
267
268
    /**
269
     * @param list<array{string, string}> $functions
270
     */
271
    private static function hasNamespacedFunctions(array $functions): bool
272
    {
273
        foreach ($functions as [$original, $alias]) {
274
            $containsBackslash = false !== strpos($original, '\\');
275
276
            if ($containsBackslash) {
277
                return true;
278
            }
279
        }
280
281
        return false;
282
    }
283
284
    private static function removeUnnecessaryLineReturns(string $dump): string
285
    {
286
        $cleanedDump = $dump;
287
288
        do {
289
            $dump = $cleanedDump;
290
            $cleanedDump = str_replace("\n\n\n", "\n\n", $dump);
291
        } while ($cleanedDump !== $dump);
292
293
        return $dump;
294
    }
295
}
296