10
10
namespace Nette \PhpGenerator ;
11
11
12
12
use Nette ;
13
+ use PhpParser ;
14
+ use PhpParser \Node ;
15
+ use PhpParser \ParserFactory ;
13
16
14
17
15
18
/**
@@ -19,7 +22,7 @@ final class Factory
19
22
{
20
23
use Nette \SmartObject;
21
24
22
- public function fromClassReflection (\ReflectionClass $ from ): ClassType
25
+ public function fromClassReflection (\ReflectionClass $ from, bool $ withBodies = false ): ClassType
23
26
{
24
27
$ class = $ from ->isAnonymous ()
25
28
? new ClassType
@@ -48,9 +51,14 @@ public function fromClassReflection(\ReflectionClass $from): ClassType
48
51
}
49
52
}
50
53
$ class ->setProperties ($ props );
54
+
55
+ $ bodies = $ withBodies ? $ this ->loadMethodBodies ($ from ) : [];
51
56
foreach ($ from ->getMethods () as $ method ) {
52
57
if ($ method ->getDeclaringClass ()->name === $ from ->name ) {
53
- $ methods [] = $ this ->fromMethodReflection ($ method );
58
+ $ methods [] = $ m = $ this ->fromMethodReflection ($ method );
59
+ if (isset ($ bodies [$ method ->name ])) {
60
+ $ m ->setBody ($ bodies [$ method ->name ]);
61
+ }
54
62
}
55
63
}
56
64
$ class ->setMethods ($ methods );
@@ -91,7 +99,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method
91
99
92
100
93
101
/** @return GlobalFunction|Closure */
94
- public function fromFunctionReflection (\ReflectionFunction $ from )
102
+ public function fromFunctionReflection (\ReflectionFunction $ from, bool $ withBody = false )
95
103
{
96
104
$ function = $ from ->isClosure () ? new Closure : new GlobalFunction ($ from ->name );
97
105
$ function ->setParameters (array_map ([$ this , 'fromParameterReflection ' ], $ from ->getParameters ()));
@@ -104,6 +112,7 @@ public function fromFunctionReflection(\ReflectionFunction $from)
104
112
$ function ->setReturnType ($ from ->getReturnType ()->getName ());
105
113
$ function ->setReturnNullable ($ from ->getReturnType ()->allowsNull ());
106
114
}
115
+ $ function ->setBody ($ withBody ? $ this ->loadFunctionBody ($ from ) : '' );
107
116
return $ function ;
108
117
}
109
118
@@ -165,4 +174,70 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property
165
174
$ prop ->setComment (Helpers::unformatDocComment ((string ) $ from ->getDocComment ()));
166
175
return $ prop ;
167
176
}
177
+
178
+
179
+ private function loadMethodBodies (\ReflectionClass $ from ): array
180
+ {
181
+ if ($ from ->isAnonymous ()) {
182
+ throw new Nette \NotSupportedException ('Anonymous classes are not supported. ' );
183
+ }
184
+
185
+ [$ code , $ stmts ] = $ this ->parse ($ from );
186
+ $ nodeFinder = new PhpParser \NodeFinder ;
187
+ $ class = $ nodeFinder ->findFirst ($ stmts , function (Node $ node ) use ($ from ) {
188
+ return ($ node instanceof Node \Stmt \Class_ || $ node instanceof Node \Stmt \Trait_) && $ node ->namespacedName ->toString () === $ from ->name ;
189
+ });
190
+
191
+ $ bodies = [];
192
+ foreach ($ nodeFinder ->findInstanceOf ($ class , Node \Stmt \ClassMethod::class) as $ method ) {
193
+ /** @var Node\Stmt\ClassMethod $method */
194
+ if ($ method ->stmts ) {
195
+ $ start = $ method ->stmts [0 ]->getAttribute ('startFilePos ' );
196
+ $ body = substr ($ code , $ start , end ($ method ->stmts )->getAttribute ('endFilePos ' ) - $ start + 1 );
197
+ $ bodies [$ method ->name ->toString ()] = Helpers::indentPhp ($ body , -2 );
198
+ }
199
+ }
200
+ return $ bodies ;
201
+ }
202
+
203
+
204
+ private function loadFunctionBody (\ReflectionFunction $ from ): string
205
+ {
206
+ if ($ from ->isClosure ()) {
207
+ throw new Nette \NotSupportedException ('Closures are not supported. ' );
208
+ }
209
+
210
+ [$ code , $ stmts ] = $ this ->parse ($ from );
211
+ /** @var Node\Stmt\Function_ $function */
212
+ $ function = (new PhpParser \NodeFinder )->findFirst ($ stmts , function (Node $ node ) use ($ from ) {
213
+ return $ node instanceof Node \Stmt \Function_ && $ node ->namespacedName ->toString () === $ from ->name ;
214
+ });
215
+
216
+ $ start = $ function ->stmts [0 ]->getAttribute ('startFilePos ' );
217
+ $ body = substr ($ code , $ start , end ($ function ->stmts )->getAttribute ('endFilePos ' ) - $ start + 1 );
218
+ return Helpers::indentPhp ($ body , -1 );
219
+ }
220
+
221
+
222
+ private function parse ($ from ): array
223
+ {
224
+ $ file = $ from ->getFileName ();
225
+ if (!class_exists (ParserFactory::class)) {
226
+ throw new Nette \NotSupportedException ("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'. " );
227
+ } elseif (!$ file ) {
228
+ throw new Nette \InvalidStateException ("Source code of $ from ->name not found. " );
229
+ }
230
+
231
+ $ lexer = new PhpParser \Lexer (['usedAttributes ' => ['startFilePos ' , 'endFilePos ' ]]);
232
+ $ parser = (new ParserFactory )->create (ParserFactory::ONLY_PHP7 , $ lexer );
233
+ $ code = file_get_contents ($ file );
234
+ $ code = str_replace ("\r\n" , "\n" , $ code );
235
+ $ stmts = $ parser ->parse ($ code );
236
+
237
+ $ traverser = new PhpParser \NodeTraverser ;
238
+ $ traverser ->addVisitor (new PhpParser \NodeVisitor \NameResolver );
239
+ $ stmts = $ traverser ->traverse ($ stmts );
240
+
241
+ return [$ code , $ stmts ];
242
+ }
168
243
}
0 commit comments