Skip to content

Commit

Permalink
Merge pull request #167 from yohang/feature/dumpers
Browse files Browse the repository at this point in the history
feat: Add state machine dumpers
  • Loading branch information
yohang authored Jan 15, 2025
2 parents dd07204 + 418010c commit 5829fa8
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 10 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"symfony/stopwatch": ">=5.4,<8",
"symfony/twig-bundle": ">=5.4,<8",
"symfony/browser-kit": ">=5.4,<8",
"symfony/css-selector": ">=5.4,<8"
"symfony/css-selector": ">=5.4,<8",
"symfony/console": ">=5.4,<8"
},
"autoload": {
"psr-4": {
Expand Down
13 changes: 13 additions & 0 deletions src/Dumper/Dumper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Finite\Dumper;

use Finite\State;

interface Dumper
{
/**
* @param enum-string<State> $stateEnum
*/
public function dump(string $stateEnum): string;
}
34 changes: 34 additions & 0 deletions src/Dumper/MermaidDumper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Finite\Dumper;

use Finite\State;

class MermaidDumper implements Dumper
{
/**
* @param enum-string<State> $stateEnum
*/
public function dump(string $stateEnum): string
{
$output = [
'---',
'title: ' . $stateEnum,
'---',
'stateDiagram-v2',
];

foreach ($stateEnum::getTransitions() as $transition) {
foreach ($transition->getSourceStates() as $state) {
$output[] = sprintf(
' %s --> %s: %s',
$state->value,
$transition->getTargetState()->value,
$transition->getName()
);
}
}

return implode(PHP_EOL, $output);
}
}
6 changes: 6 additions & 0 deletions src/Extension/Symfony/Bundle/FiniteBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@

namespace Finite\Extension\Symfony\Bundle;

use Finite\Extension\Symfony\Command\DumpStateMachineCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\HttpKernel\Bundle\Bundle;

final class FiniteBundle extends Bundle
{
public function registerCommands(Application $application): void
{
$application->add(new DumpStateMachineCommand);
}
}
53 changes: 53 additions & 0 deletions src/Extension/Symfony/Command/DumpStateMachineCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Finite\Extension\Symfony\Command;

use Finite\Dumper\MermaidDumper;
use Finite\State;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class DumpStateMachineCommand extends Command
{
private const FORMAT_MERMAID = 'mermaid';
private const FORMATS = [self::FORMAT_MERMAID];

protected function configure(): void
{
$this
->setName('finite:state-machine:dump')
->setDescription('Dump the state machine graph into requested format')
->addArgument('state_enum', InputArgument::REQUIRED, 'The state enum to use')
->addArgument('format', InputArgument::REQUIRED, 'The format to dump the graph in');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$stateEnum = (string)$input->getArgument('state_enum');

if (!(enum_exists($stateEnum) && is_subclass_of($stateEnum, State::class))) {
$io->error('The state enum "' . $stateEnum . '" does not exist.');

return self::FAILURE;
}

$format = (string)$input->getArgument('format');
switch ($format) {
case self::FORMAT_MERMAID:
/** @psalm-suppress ArgumentTypeCoercion Type is enforced upper but not detected by psalm */
$output->writeln((new MermaidDumper)->dump($stateEnum));

break;
default:
$output->writeln('Unknown format "' . $format . '". Supported formats are: ' . implode(', ', self::FORMATS));

return self::FAILURE;
}

return self::SUCCESS;
}
}
6 changes: 3 additions & 3 deletions src/Transition/Transition.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class Transition implements TransitionInterface
{
public function __construct(
public readonly string $name,
/** @var State[] */
/** @var array<int,State&\BackedEnum> */
public readonly array $sourceStates,
public readonly State $targetState,
public readonly State&\BackedEnum $targetState,
/** @var array<string, string> */
public readonly array $properties = []
)
Expand All @@ -28,7 +28,7 @@ public function getSourceStates(): array
return $this->sourceStates;
}

public function getTargetState(): State
public function getTargetState(): State&\BackedEnum
{
return $this->targetState;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Transition/TransitionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
interface TransitionInterface
{
/**
* @return State[]
* @return array<int, \BackedEnum&State>
*/
public function getSourceStates(): array;

public function getTargetState(): State;
public function getTargetState(): State&\BackedEnum;

public function process(object $object): void;

Expand Down
29 changes: 29 additions & 0 deletions tests/Dumper/MermaidDumperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Finite\Tests\Dumper;

use Finite\Dumper\MermaidDumper;
use Finite\Tests\E2E\SimpleArticleState;
use PHPUnit\Framework\TestCase;

class MermaidDumperTest extends TestCase
{
public function test_it_dumps(): void
{
$this->assertSame(
<<<MERMAID
---
title: Finite\Tests\E2E\SimpleArticleState
---
stateDiagram-v2
draft --> published: publish
reported --> published: clear
disabled --> published: clear
published --> reported: report
reported --> disabled: disable
published --> disabled: disable
MERMAID,
(new MermaidDumper)->dump(SimpleArticleState::class)
);
}
}
59 changes: 59 additions & 0 deletions tests/Extension/Symfony/Command/DumpStateMachineCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Finite\Tests\Extension\Symfony\Command;

use Finite\Tests\Extension\Symfony\Fixtures\State\DocumentState;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;

class DumpStateMachineCommandTest extends KernelTestCase
{
private ?CommandTester $commandTester = null;

protected function setUp(): void
{
parent::setUp();

$kernel = self::bootKernel();
$application = new Application($kernel);

$command = $application->find('finite:state-machine:dump');
$this->commandTester = new CommandTester($command);
}

public function test_it_returns_mermaid_dump(): void
{
$this->commandTester->execute([
'state_enum' => DocumentState::class,
'format' => 'mermaid',
]);

$this->commandTester->assertCommandIsSuccessful();
}

public function test_it_fails_with_unknown_state_enum(): void
{
$this->commandTester->execute([
'state_enum' => 'UnknownStateEnum',
'format' => 'mermaid',
]);

$this->assertSame(1, $this->commandTester->getStatusCode());
}

public function test_it_fails_with_unknown_format(): void
{
$this->commandTester->execute([
'state_enum' => DocumentState::class,
'format' => 'blobfish',
]);

$this->assertSame(1, $this->commandTester->getStatusCode());
}

protected static function getKernelClass(): string
{
return \AppKernel::class;
}
}
7 changes: 3 additions & 4 deletions tests/Transition/TransitionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Finite\Tests\Transition;

use Finite\State;
use Finite\Tests\E2E\SimpleArticleState;
use Finite\Transition\Transition;
use PHPUnit\Framework\TestCase;

Expand All @@ -13,12 +14,10 @@ class TransitionTest extends TestCase

protected function setUp(): void
{
$targetState = $this->createMock(State::class);

$this->object = new Transition(
'name',
['source'],
$targetState,
[SimpleArticleState::DRAFT],
SimpleArticleState::PUBLISHED,
['property' => 'value', 'property2' => 'value2'],
);
}
Expand Down

0 comments on commit 5829fa8

Please sign in to comment.