1010namespace Nette \PhpGenerator ;
1111
1212use Nette ;
13+ use PhpParser ;
14+ use PhpParser \Node ;
15+ use PhpParser \ParserFactory ;
1316
1417
1518/**
@@ -19,7 +22,7 @@ final class Factory
1922{
2023 use Nette \SmartObject;
2124
22- public function fromClassReflection (\ReflectionClass $ from ): ClassType
25+ public function fromClassReflection (\ReflectionClass $ from, bool $ withBodies = false ): ClassType
2326 {
2427 $ class = $ from ->isAnonymous ()
2528 ? new ClassType
@@ -48,9 +51,14 @@ public function fromClassReflection(\ReflectionClass $from): ClassType
4851 }
4952 }
5053 $ class ->setProperties ($ props );
54+
55+ $ bodies = $ withBodies ? $ this ->loadMethodBodies ($ from ) : [];
5156 foreach ($ from ->getMethods () as $ method ) {
5257 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+ }
5462 }
5563 }
5664 $ class ->setMethods ($ methods );
@@ -91,7 +99,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method
9199
92100
93101 /** @return GlobalFunction|Closure */
94- public function fromFunctionReflection (\ReflectionFunction $ from )
102+ public function fromFunctionReflection (\ReflectionFunction $ from, bool $ withBody = false )
95103 {
96104 $ function = $ from ->isClosure () ? new Closure : new GlobalFunction ($ from ->name );
97105 $ function ->setParameters (array_map ([$ this , 'fromParameterReflection ' ], $ from ->getParameters ()));
@@ -104,6 +112,7 @@ public function fromFunctionReflection(\ReflectionFunction $from)
104112 $ function ->setReturnType ($ from ->getReturnType ()->getName ());
105113 $ function ->setReturnNullable ($ from ->getReturnType ()->allowsNull ());
106114 }
115+ $ function ->setBody ($ withBody ? $ this ->loadFunctionBody ($ from ) : '' );
107116 return $ function ;
108117 }
109118
@@ -165,4 +174,70 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property
165174 $ prop ->setComment (Helpers::unformatDocComment ((string ) $ from ->getDocComment ()));
166175 return $ prop ;
167176 }
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+ }
168243}
0 commit comments