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
![]() |
|||||
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
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
|
|||||
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
|
|||||
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
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
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. ![]() |
|||||
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
|
|||||
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
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
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. ![]() |
|||||
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
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
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. ![]() |
|||||
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 |