Created
March 2, 2023 15:11
-
-
Save Nex-Otaku/d4efa31059831dc864c7fe8bf58c0dc0 to your computer and use it in GitHub Desktop.
Вычисляем связи в PHP классе с помощью парсера Никиты Попова и визуализируем в Orb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
require './../vendor/autoload.php'; | |
use NexOtaku\MinimalFilesystem\Filesystem; | |
use PhpParser\ParserFactory; | |
use PhpParser\{Node, NodeTraverser, NodeVisitorAbstract}; | |
class CallMap | |
{ | |
private array $nodes = []; | |
private array $nodeNames = []; | |
private array $nodeIdsByName = []; | |
private int $nodeCount = 0; | |
private array $edges = []; | |
private int $edgeCount = 0; | |
public function addNode(string $name): void | |
{ | |
if (in_array($name, $this->nodeNames)) { | |
return; | |
} | |
$this->nodeNames []= $name; | |
$nodeId = $this->nodeCount + 1; | |
$this->nodeIdsByName[$name] = $nodeId; | |
if ( | |
str_starts_with($name, '$this->get') | |
|| str_starts_with($name, '$this->is') | |
|| str_starts_with($name, '$this->has') | |
|| str_starts_with($name, '$this->query') | |
) { | |
$sideEffect = 'query'; | |
} else if ( | |
str_starts_with($name, '$this->update') | |
|| str_starts_with($name, '$this->set') | |
|| str_starts_with($name, '$this->clear') | |
|| str_starts_with($name, '$this->command') | |
|| str_starts_with($name, '$this->switch') | |
) { | |
$sideEffect = 'command'; | |
} else { | |
$sideEffect = 'default'; | |
} | |
if ( | |
str_starts_with($name, '$this->query') | |
|| str_starts_with($name, '$this->command') | |
) { | |
$visibility = 'public'; | |
} else { | |
$visibility = 'private'; | |
} | |
$this->nodes []= [ | |
'id' => $nodeId, | |
'name' => $name, | |
'sideEffect' => $sideEffect, | |
'visibility' => $visibility, | |
]; | |
$this->nodeCount++; | |
} | |
public function addEdge(string $from, string $to): void | |
{ | |
$fromId = $this->getNodeId($from); | |
$toId = $this->getNodeId($to); | |
$edgeId = $this->edgeCount + 1; | |
$this->edges []= [ | |
'id' => $edgeId, | |
'start' => $fromId, | |
'end' => $toId, | |
]; | |
$this->edgeCount++; | |
} | |
private function getNodeId(string $name): int | |
{ | |
if (!array_key_exists($name, $this->nodeIdsByName)) { | |
$this->addNode($name); | |
} | |
return $this->nodeIdsByName[$name]; | |
} | |
public function getNodes(): array | |
{ | |
return $this->nodes; | |
} | |
public function getEdges(): array | |
{ | |
return $this->edges; | |
} | |
} | |
function getAst() | |
{ | |
$fs = new Filesystem(); | |
$code = $fs->readFile('./../app/Module/Demo/Xo/Xo.php'); | |
try { | |
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); | |
$stmts = $parser->parse($code); | |
$json = json_encode($stmts, JSON_PRETTY_PRINT); | |
$fs->writeFile('./xo-ast.json', $json); | |
echo $json, "\n"; | |
} catch (PhpParser\Error $e) { | |
die('Parse Error: ' . $e->getMessage()); | |
} | |
return $stmts; | |
} | |
class MethodCallsVisitor extends NodeVisitorAbstract | |
{ | |
private string $method = ''; | |
private CallMap $callMap; | |
public function __construct() | |
{ | |
$this->callMap = new CallMap(); | |
} | |
public function enterNode(Node $node) | |
{ | |
if ($node->getType() === 'Stmt_ClassMethod') { | |
$this->method = '$this->' . $node->name->name; | |
$this->callMap->addNode($this->method); | |
} | |
return null; | |
} | |
public function leaveNode(Node $node) { | |
if ($node->getType() === 'Expr_MethodCall') { | |
$this->callMap->addEdge($this->method, '$' . $node->var->name . '->' . $node->name->name); | |
} | |
return null; | |
} | |
public function getMap(): CallMap | |
{ | |
return $this->callMap; | |
} | |
} | |
function writeJson(array $jsonData, string $filename): void | |
{ | |
$json = json_encode($jsonData, JSON_PRETTY_PRINT); | |
$fs = new Filesystem(); | |
$fs->writeFile('./' . $filename, $json); | |
} | |
$visitor = new MethodCallsVisitor(); | |
$traverser = new NodeTraverser; | |
$traverser->addVisitor($visitor); | |
$stmts = getAst(); | |
$traverser->traverse($stmts); | |
$map = $visitor->getMap(); | |
writeJson($map->getNodes(), 'nodes.json'); | |
writeJson($map->getEdges(), 'edges.json'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment