33namespace SymfonyTools \CodeBlockChecker \Service \CodeValidator ;
44
55use Doctrine \RST \Nodes \CodeNode ;
6- use Symfony \Component \Process \Process ;
6+ use PhpParser \ErrorHandler ;
7+ use PhpParser \Parser ;
8+ use PhpParser \ParserFactory ;
79use SymfonyTools \CodeBlockChecker \Issue \Issue ;
810use SymfonyTools \CodeBlockChecker \Issue \IssueCollection ;
911
1012class PhpValidator implements Validator
1113{
14+ private ?Parser $ parser = null ;
15+
1216 public function validate (CodeNode $ node , IssueCollection $ issues ): void
1317 {
1418 $ language = $ node ->getLanguage () ?? ($ node ->isRaw () ? null : 'php ' );
1519 if (!in_array ($ language , ['php ' , 'php-symfony ' , 'php-standalone ' , 'php-annotations ' , 'html+php ' ])) {
1620 return ;
1721 }
1822
19- $ file = sys_get_temp_dir (). ' / ' . uniqid ( ' doc_builder ' , true ). ' .php ' ;
20-
21- file_put_contents ( $ file , $ this ->getContents ( $ node , $ language ));
23+ $ linesPrepended = 0 ;
24+ $ code = ' html+php ' === $ language ? $ node -> getValue () : $ this -> getContents ( $ node , $ linesPrepended );
25+ $ this ->getParser ()-> parse ( $ code , $ errorHandler = new ErrorHandler \ Collecting ( ));
2226
23- $ process = new Process (['php ' , '-l ' , $ file ]);
24- $ process ->run ();
25- if ($ process ->isSuccessful ()) {
26- return ;
27+ foreach ($ errorHandler ->getErrors () as $ error ) {
28+ $ issues ->addIssue (new Issue ($ node , $ error ->getRawMessage (), 'PHP syntax ' , $ node ->getEnvironment ()->getCurrentFileName (), $ error ->getStartLine () - $ linesPrepended ));
2729 }
30+ }
2831
29- $ line = 0 ;
30- $ text = str_replace ($ file , 'example.php ' , $ process ->getErrorOutput ());
31- if (preg_match ('| in example.php on line ([0-9]+)|s ' , $ text , $ matches )) {
32- $ text = str_replace ($ matches [0 ], '' , $ text );
33- $ line = ((int ) $ matches [1 ]) - 1 ; // we added "<?php"
32+ private function getParser (): Parser
33+ {
34+ if (null === $ this ->parser ) {
35+ $ this ->parser = (new ParserFactory ())->create (ParserFactory::ONLY_PHP7 );
3436 }
35- $ issues ->addIssue (new Issue ($ node , $ text , 'PHP syntax ' , $ node ->getEnvironment ()->getCurrentFileName (), $ line ));
37+
38+ return $ this ->parser ;
3639 }
3740
38- private function getContents (CodeNode $ node , string $ language ): string
41+ private function getContents (CodeNode $ node , & $ linesPrepended = null ): string
3942 {
4043 $ contents = $ node ->getValue ();
41- if ('html+php ' === $ language ) {
42- return $ contents ;
43- }
44-
4544 if (!preg_match ('#(class|interface) [a-zA-Z]+#s ' , $ contents ) && preg_match ('#(public|protected|private)( static)? (\$[a-z]+|function)#s ' , $ contents )) {
4645 $ contents = 'class Foobar { ' .$ contents .'} ' ;
4746 }
@@ -52,6 +51,7 @@ private function getContents(CodeNode $node, string $language): string
5251 $ lines = explode ("\n" , $ contents );
5352 if (!str_contains ($ lines [0 ] ?? '' , '<?php ' ) && !str_contains ($ lines [1 ] ?? '' , '<?php ' ) && !str_contains ($ lines [2 ] ?? '' , '<?php ' )) {
5453 $ contents = '<?php ' ."\n" .$ contents ;
54+ $ linesPrepended = 1 ;
5555 }
5656
5757 return $ contents ;
0 commit comments