10
10
namespace Nette \PhpGenerator ;
11
11
12
12
use Nette ;
13
- use PhpParser ;
14
- use PhpParser \Node ;
15
- use PhpParser \ParserFactory ;
16
13
17
14
18
15
/**
@@ -24,6 +21,10 @@ final class Factory
24
21
25
22
public function fromClassReflection (\ReflectionClass $ from , bool $ withBodies = false ): ClassType
26
23
{
24
+ if ($ withBodies && $ from ->isAnonymous ()) {
25
+ throw new Nette \NotSupportedException ('The $withBodies parameter cannot be used for anonymous functions. ' );
26
+ }
27
+
27
28
$ class = $ from ->isAnonymous ()
28
29
? new ClassType
29
30
: new ClassType ($ from ->getShortName (), new PhpNamespace ($ from ->getNamespaceName ()));
@@ -80,8 +81,8 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f
80
81
$ methods [] = $ m = $ this ->fromMethodReflection ($ method );
81
82
if ($ withBodies ) {
82
83
$ srcMethod = Nette \Utils \Reflection::getMethodDeclaringMethod ($ method );
83
- $ srcClass = $ srcMethod ->getDeclaringClass ()-> name ;
84
- $ b = $ bodies [$ srcClass ] = $ bodies [$ srcClass ] ?? $ this ->loadMethodBodies ( $ srcMethod -> getDeclaringClass () );
84
+ $ srcClass = $ srcMethod ->getDeclaringClass ();
85
+ $ b = $ bodies [$ srcClass-> name ] = $ bodies [$ srcClass-> name ] ?? $ this ->getExtractor ( $ srcClass )-> extractMethodBodies ( $ srcClass -> name );
85
86
if (isset ($ b [$ srcMethod ->name ])) {
86
87
$ m ->setBody ($ b [$ srcMethod ->name ]);
87
88
}
@@ -152,7 +153,12 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody
152
153
) {
153
154
$ function ->setReturnType ((string ) $ from ->getReturnType ());
154
155
}
155
- $ function ->setBody ($ withBody ? $ this ->loadFunctionBody ($ from ) : '' );
156
+ if ($ withBody ) {
157
+ if ($ from ->isClosure ()) {
158
+ throw new Nette \NotSupportedException ('The $withBody parameter cannot be used for closures. ' );
159
+ }
160
+ $ function ->setBody ($ this ->getExtractor ($ from )->extractFunctionBody ($ from ->name ));
161
+ }
156
162
return $ function ;
157
163
}
158
164
@@ -262,144 +268,12 @@ private function getVisibility($from): string
262
268
}
263
269
264
270
265
- private function loadMethodBodies (\ReflectionClass $ from ): array
266
- {
267
- if ($ from ->isAnonymous ()) {
268
- throw new Nette \NotSupportedException ('Anonymous classes are not supported. ' );
269
- }
270
-
271
- [$ code , $ stmts ] = $ this ->parse ($ from );
272
- $ nodeFinder = new PhpParser \NodeFinder ;
273
- $ class = $ nodeFinder ->findFirst ($ stmts , function (Node $ node ) use ($ from ) {
274
- return ($ node instanceof Node \Stmt \Class_ || $ node instanceof Node \Stmt \Trait_) && $ node ->namespacedName ->toString () === $ from ->name ;
275
- });
276
-
277
- $ bodies = [];
278
- foreach ($ nodeFinder ->findInstanceOf ($ class , Node \Stmt \ClassMethod::class) as $ method ) {
279
- /** @var Node\Stmt\ClassMethod $method */
280
- if ($ method ->stmts ) {
281
- $ body = $ this ->extractBody ($ nodeFinder , $ code , $ method ->stmts );
282
- $ bodies [$ method ->name ->toString ()] = Helpers::unindent ($ body , 2 );
283
- }
284
- }
285
- return $ bodies ;
286
- }
287
-
288
-
289
- private function loadFunctionBody (\ReflectionFunction $ from ): string
290
- {
291
- if ($ from ->isClosure ()) {
292
- throw new Nette \NotSupportedException ('Closures are not supported. ' );
293
- }
294
-
295
- [$ code , $ stmts ] = $ this ->parse ($ from );
296
-
297
- $ nodeFinder = new PhpParser \NodeFinder ;
298
- /** @var Node\Stmt\Function_ $function */
299
- $ function = $ nodeFinder ->findFirst ($ stmts , function (Node $ node ) use ($ from ) {
300
- return $ node instanceof Node \Stmt \Function_ && $ node ->namespacedName ->toString () === $ from ->name ;
301
- });
302
-
303
- $ body = $ this ->extractBody ($ nodeFinder , $ code , $ function ->stmts );
304
- return Helpers::unindent ($ body , 1 );
305
- }
306
-
307
-
308
- /**
309
- * @param Node[] $statements
310
- */
311
- private function extractBody (PhpParser \NodeFinder $ nodeFinder , string $ originalCode , array $ statements ): string
312
- {
313
- $ start = $ statements [0 ]->getAttribute ('startFilePos ' );
314
- $ body = substr ($ originalCode , $ start , end ($ statements )->getAttribute ('endFilePos ' ) - $ start + 1 );
315
-
316
- $ replacements = [];
317
- // name-nodes => resolved fully-qualified name
318
- foreach ($ nodeFinder ->findInstanceOf ($ statements , Node \Name::class) as $ node ) {
319
- if ($ node ->hasAttribute ('resolvedName ' )
320
- && $ node ->getAttribute ('resolvedName ' ) instanceof Node \Name \FullyQualified
321
- ) {
322
- $ replacements [] = [
323
- $ node ->getStartFilePos (),
324
- $ node ->getEndFilePos (),
325
- $ node ->getAttribute ('resolvedName ' )->toCodeString (),
326
- ];
327
- }
328
- }
329
-
330
- // multi-line strings => singleline
331
- foreach (array_merge (
332
- $ nodeFinder ->findInstanceOf ($ statements , Node \Scalar \String_::class),
333
- $ nodeFinder ->findInstanceOf ($ statements , Node \Scalar \EncapsedStringPart::class)
334
- ) as $ node ) {
335
- /** @var Node\Scalar\String_|Node\Scalar\EncapsedStringPart $node */
336
- $ token = substr ($ body , $ node ->getStartFilePos () - $ start , $ node ->getEndFilePos () - $ node ->getStartFilePos () + 1 );
337
- if (strpos ($ token , "\n" ) !== false ) {
338
- $ quote = $ node instanceof Node \Scalar \String_ ? '" ' : '' ;
339
- $ replacements [] = [
340
- $ node ->getStartFilePos (),
341
- $ node ->getEndFilePos (),
342
- $ quote . addcslashes ($ node ->value , "\x00.. \x1F" ) . $ quote ,
343
- ];
344
- }
345
- }
346
-
347
- // HEREDOC => "string"
348
- foreach ($ nodeFinder ->findInstanceOf ($ statements , Node \Scalar \Encapsed::class) as $ node ) {
349
- /** @var Node\Scalar\Encapsed $node */
350
- if ($ node ->getAttribute ('kind ' ) === Node \Scalar \String_::KIND_HEREDOC ) {
351
- $ replacements [] = [
352
- $ node ->getStartFilePos (),
353
- $ node ->parts [0 ]->getStartFilePos () - 1 ,
354
- '" ' ,
355
- ];
356
- $ replacements [] = [
357
- end ($ node ->parts )->getEndFilePos () + 1 ,
358
- $ node ->getEndFilePos (),
359
- '" ' ,
360
- ];
361
- }
362
- }
363
-
364
- //sort collected resolved names by position in file
365
- usort ($ replacements , function ($ a , $ b ) {
366
- return $ a [0 ] <=> $ b [0 ];
367
- });
368
- $ correctiveOffset = -$ start ;
369
- //replace changes body length so we need correct offset
370
- foreach ($ replacements as [$ startPos , $ endPos , $ replacement ]) {
371
- $ replacingStringLength = $ endPos - $ startPos + 1 ;
372
- $ body = substr_replace (
373
- $ body ,
374
- $ replacement ,
375
- $ correctiveOffset + $ startPos ,
376
- $ replacingStringLength
377
- );
378
- $ correctiveOffset += strlen ($ replacement ) - $ replacingStringLength ;
379
- }
380
- return $ body ;
381
- }
382
-
383
-
384
- private function parse ($ from ): array
271
+ private function getExtractor ($ from ): Extractor
385
272
{
386
273
$ file = $ from ->getFileName ();
387
- if (!class_exists (ParserFactory::class)) {
388
- throw new Nette \NotSupportedException ("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'. " );
389
- } elseif (!$ file ) {
274
+ if (!$ file ) {
390
275
throw new Nette \InvalidStateException ("Source code of $ from ->name not found. " );
391
276
}
392
-
393
- $ lexer = new PhpParser \Lexer (['usedAttributes ' => ['startFilePos ' , 'endFilePos ' ]]);
394
- $ parser = (new ParserFactory )->create (ParserFactory::ONLY_PHP7 , $ lexer );
395
- $ code = file_get_contents ($ file );
396
- $ code = str_replace ("\r\n" , "\n" , $ code );
397
- $ stmts = $ parser ->parse ($ code );
398
-
399
- $ traverser = new PhpParser \NodeTraverser ;
400
- $ traverser ->addVisitor (new PhpParser \NodeVisitor \NameResolver (null , ['replaceNodes ' => false ]));
401
- $ stmts = $ traverser ->traverse ($ stmts );
402
-
403
- return [$ code , $ stmts ];
277
+ return new Extractor (file_get_contents ($ file ));
404
278
}
405
279
}
0 commit comments