Skip to content

Instantly share code, notes, and snippets.

@dantleech
Last active May 2, 2022 12:15
Show Gist options
  • Save dantleech/22bc8561bf87d4d8b008d4680488f266 to your computer and use it in GitHub Desktop.
Save dantleech/22bc8561bf87d4d8b008d4680488f266 to your computer and use it in GitHub Desktop.
App\Command\StateMachineDotCommand:
autowire: false
arguments:
$configs: '%sm.configs%'
tags:
- { name: 'console.command', command: 'state-machine:dot' }
<?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