Last active
May 2, 2022 12:15
-
-
Save dantleech/22bc8561bf87d4d8b008d4680488f266 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
App\Command\StateMachineDotCommand: | |
autowire: false | |
arguments: | |
$configs: '%sm.configs%' | |
tags: | |
- { name: 'console.command', command: 'state-machine:dot' } |
This file contains hidden or 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 | |
namespace App\Command; | |
use RuntimeException; | |
use Symfony\Component\Console\Command\Command; | |
use Symfony\Component\Console\Input\InputArgument; | |
use Symfony\Component\Console\Input\InputInterface; | |
use Symfony\Component\Console\Output\OutputInterface; | |
class StateMachineDotCommand extends Command | |
{ | |
public const ARG_NAME = 'state-machine'; | |
public const OPT_RENDER = 'render'; | |
public const ARG_OUT_PATH = 'path'; | |
/** | |
* @var array<string,mixed> | |
*/ | |
private array $configs; | |
/** | |
* @param array<string,mixed> $configs | |
*/ | |
public function __construct(array $configs) | |
{ | |
parent::__construct(); | |
$this->configs = $configs; | |
} | |
protected function configure(): void | |
{ | |
$this->setDescription('Generate a dot file (diagram) for a given state machine'); | |
$this->addArgument(self::ARG_NAME, InputArgument::REQUIRED, 'Name of state machine'); | |
$this->addArgument(self::ARG_OUT_PATH, InputArgument::REQUIRED, 'File to write dot file to'); | |
} | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$name = $input->getArgument(self::ARG_NAME); | |
$outPath = $input->getArgument(self::ARG_OUT_PATH); | |
if (!isset($this->configs[$name])) { | |
throw new RuntimeException(sprintf('Unknown state machine "%s", known state machines "%s"', $name, implode('", "', array_keys($this->configs)))); | |
} | |
$config = $this->configs[$name]; | |
$generatedFile = $this->buildGraph($config); | |
file_put_contents($outPath, $generatedFile); | |
$output->writeln(sprintf('<comment>Written dot file to:</> %s', $outPath)); | |
$output->writeln(sprintf('<info>Generate a PNG (requires graphviz) with:</> dot %s -Tpng -o out.png', $outPath)); | |
return 0; | |
} | |
/** | |
* @param array<string,mixed> $config | |
*/ | |
private function buildGraph(array $config): string | |
{ | |
$out = []; | |
$out[] = 'digraph {'; | |
foreach ($config['transitions'] ?? [] as $name => $transition) { | |
foreach ((array) $transition['from'] as $from) { | |
$label = [ | |
sprintf('<i>%s</i>', $name), | |
]; | |
foreach ($this->callbacks($config, 'after', $name) as $callbackName => $callback) { | |
$label[] = sprintf('<font point-size="8" color="darkgreen">after: %s</font>', $callbackName); | |
} | |
foreach ($this->callbacks($config, 'before', $name) as $callbackName => $callback) { | |
$label[] = sprintf('<font point-size="8" color="darkblue">before: %s</font>', $callbackName); | |
} | |
$out[] = sprintf(' "%s"->"%s" [label=<%s>]', $from, $transition['to'], implode('<br/>', $label)); | |
} | |
} | |
$out[] = '}'; | |
return implode("\n", $out); | |
} | |
/** | |
* @param array<string,mixed> $config | |
* | |
* @return array<string,mixed> | |
*/ | |
private function callbacks(array $config, string $type, string $transition): array | |
{ | |
$callbacks = []; | |
foreach ($config['callbacks'][$type] ?? [] as $name => $callback) { | |
if (!isset($callback['on'])) { | |
continue; | |
} | |
if (in_array($transition, (array) $callback['on'])) { | |
$callbacks[(string) $name] = $callback; | |
} | |
} | |
return $callbacks; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment