Jak si vygenerovat graf funkce?

Řešil jsem před nedávnem (při vytváření videa o cyklomatické komplexitě), jak nejlépe PHP funkci zobrazit ve formě grafu (respektive CFG – content flow graph).

Ideální nástroj, kde bych vložit PHP kód a získal ihned graf, jsem bohužel nenašel. Popíši tedy postup, který mi přišel jako ideální bez toho, abych si psal vlastní knihovnu.

Narazil jsem na ircmaxell/php-cfg, které je takovým mezikrokem za cestou k vytouženému grafu. Schroustá PHP kód a vyplivne kód ve formátu pro GraphViz.

Pokud mám tento PHP kód:

<?php

function index($a, $b) {
    $value = 1;

    for ($i=$a;$i<$b;$i++) {
        $value++;
    }

    return $value;
}

Proženu jej knihovnou PHP-CFG pomocí skriptu:

<?php

include "vendor/autoload.php";

$parser = new PHPCfg\Parser(
    (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7)
);

$file = "index.php";
$script = $parser->parse(file_get_contents($file), $file);

$dumper = new PHPCfg\Printer\GraphViz();
echo $dumper->printFunc($script->functions[0]);

A získám DOT kód:

digraph "cfg" {
"block_1" -> "block_2" [
label="target"
]
"block_2" -> "block_3" [
label="if"
]
"block_2" -> "block_4" [
label="else"
]
"block_3" -> "block_5" [
label="target"
]
"block_4" -> "block_6" [
label="target"
]
"block_5" -> "block_2" [
label="target"
]
"block_1" [
label="\l        Expr_Param\l            name: LITERAL(\'a\')\l            result: Var#1<$a>\l        Expr_Param\l            name: LITERAL(\'b\')\l            result: Var#2<$b>\l        Expr_Assign\l            var: Var#3<$value>\l            expr: LITERAL(1)\l            result: Var#4\l        Expr_Assign\l            var: Var#5<$i>\l            expr: Var#1<$a>\l            result: Var#6\l        Stmt_Jump"
shape="rect"
]
"block_2" [
label="\l        Var#7<$i> = Phi(Var#5<$i>, Var#8<$i>)\l        Var#9<$b> = Phi(Var#2<$b>)\l        Var#10<$value> = Phi(Var#3<$value>, Var#11<$value>)\l        Expr_BinaryOp_Smaller\l            left: Var#7<$i>\l            right: Var#9<$b>\l            result: Var#12\l        Stmt_JumpIf\l            cond: Var#12"
shape="rect"
]
"block_3" [
label="\l        Var#13<$value> = Phi(Var#10<$value>)\l        Expr_BinaryOp_Plus\l            left: Var#13<$value>\l            right: LITERAL(1)\l            result: Var#14\l        Expr_Assign\l            var: Var#11<$value>\l            expr: Var#14\l            result: Var#15\l        Stmt_Jump"
shape="rect"
]
"block_4" [
label="\l        Stmt_Jump"
shape="rect"
]
"block_5" [
label="\l        Var#16<$i> = Phi(Var#7<$i>)\l        Expr_BinaryOp_Plus\l            left: Var#16<$i>\l            right: LITERAL(1)\l            result: Var#17\l        Expr_Assign\l            var: Var#8<$i>\l            expr: Var#17\l            result: Var#18\l        Stmt_Jump"
shape="rect"
]
"block_6" [
label="\l        Var#19<$value> = Phi(Var#10<$value>)\l        Terminal_Return\l            expr: Var#19<$value>"
shape="rect"
]
}

Na tento kód jsem si stáhl GraphViz - www.graphviz.org. Díky tomu je přes příkazovou řádku dostupný příkaz "dot" a vygenerujete si například PNG.

dot file.dot -Tpng -o image.png

A konečně ve výsledku dostanete graf :)

Sdílením článku mi pomůžete a uděláte mi velikou radost :)

Komentáře