Skip to content

Commit

Permalink
feat(symfony): Connected StateMachine to Symfony EventDispatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
yohang committed Jan 13, 2025
1 parent ec6f7ef commit e8e37a5
Show file tree
Hide file tree
Showing 17 changed files with 107 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
tests/Extension/Symfony/fixtures/app/var/
tests/Extension/Symfony/Fixtures/app/var/
vendor/
.phpunit.result.cache
composer.lock
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
tests/Extension/Symfony/fixtures/app/var/
tests/Extension/Symfony/Fixtures/app/var/
vendor/
.phpunit.result.cache
composer.lock
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ARG DEPENDENCIES=highest
RUN set -eux; \
apk add --no-cache acl libzip; \
apk add --no-cache --virtual .build-deps ${PHPIZE_DEPS} zlib-dev libzip-dev; \
docker-php-ext-install zip; \
docker-php-ext-install zip opcache; \
pecl install pcov;\
docker-php-ext-enable pcov; \
apk del .build-deps;
Expand Down
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@
],
"require": {
"php": ">=8.1",
"symfony/property-access": ">=5.4,<8"
"symfony/property-access": ">=5.4,<8",
"psr/event-dispatcher": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^10.5.40",
"symfony/var-dumper": ">=5.4,<8",
"twig/twig": "^3.4",
"vimeo/psalm": "dev-master@dev",
"symfony/http-kernel": ">=5.4,<8",
"symfony/framework-bundle": ">=5.4,<8"
"symfony/framework-bundle": ">=5.4,<8",
"symfony/event-dispatcher": ">=5.4,<8",
"symfony/stopwatch": ">=5.4,<8"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
colors="true"
bootstrap="tests/Extension/Symfony/fixtures/app/bootstrap.php"
bootstrap="tests/Extension/Symfony/Fixtures/app/bootstrap.php"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd">
<source>
<include>
Expand Down
2 changes: 1 addition & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
Expand Down
4 changes: 3 additions & 1 deletion src/Event/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Finite\Event;

abstract class Event
use Psr\EventDispatcher\StoppableEventInterface;

abstract class Event implements StoppableEventInterface
{
private bool $propagationStopped = false;

Expand Down
9 changes: 6 additions & 3 deletions src/Event/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace Finite\Event;

class EventDispatcher
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\StoppableEventInterface;

class EventDispatcher implements EventDispatcherInterface
{
/**
* @var array<string,array<callable>>
Expand All @@ -18,7 +21,7 @@ public function addEventListener(string $eventClass, callable $listener): void
$this->listeners[$eventClass][] = $listener;
}

public function dispatch(Event $event): void
public function dispatch(object $event): void
{
if (!isset($this->listeners[get_class($event)])) {
return;
Expand All @@ -27,7 +30,7 @@ public function dispatch(Event $event): void
foreach ($this->listeners[get_class($event)] as $listener) {
$listener($event);

if ($event->isPropagationStopped()) {
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Reference;

final class FiniteExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container): void
{
$container->addDefinitions(
[
StateMachine::class => (new Definition(StateMachine::class))->setPublic(true),
StateMachine::class => (new Definition(StateMachine::class))
->setArgument('$dispatcher', new Reference('event_dispatcher'))
->setPublic(true),
TwigExtension::class => new Definition(TwigExtension::class),
]
);
Expand Down
10 changes: 8 additions & 2 deletions src/StateMachine.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
use Finite\Event\PostTransitionEvent;
use Finite\Event\PreTransitionEvent;
use Finite\Transition\TransitionInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;

class StateMachine
{
public function __construct(
private readonly EventDispatcher $dispatcher = new EventDispatcher,
private readonly EventDispatcherInterface $dispatcher = new EventDispatcher,
)
{

}

/**
Expand Down Expand Up @@ -85,13 +85,19 @@ public function getReachablesTransitions(object $object, ?string $stateClass = n
);
}

public function getDispatcher(): EventDispatcherInterface
{
return $this->dispatcher;
}

/**
* @param class-string|null $stateClass
*/
private function extractState(object $object, ?string $stateClass = null): State
{
$property = $this->extractStateProperty($object, $stateClass);

/** @psalm-suppress MixedReturnStatement */
return PropertyAccess::createPropertyAccessor()->getValue($object, $property->getName());
}

Expand Down
2 changes: 2 additions & 0 deletions src/Transition/Transition.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ class Transition implements TransitionInterface
{
public function __construct(
public readonly string $name,
/** @var State[] */
public readonly array $sourceStates,
public readonly State $targetState,
/** @var array<string, string> */
public readonly array $properties = []
)
{
Expand Down
11 changes: 11 additions & 0 deletions tests/Extension/Symfony/Fixtures/Model/Document.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);

namespace Finite\Tests\Extension\Symfony\Fixtures\Model;

use Finite\Tests\Extension\Symfony\Fixtures\State\DocumentState;

class Document
{
public DocumentState $state = DocumentState::DRAFT;
}
25 changes: 25 additions & 0 deletions tests/Extension/Symfony/Fixtures/State/DocumentState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);

namespace Finite\Tests\Extension\Symfony\Fixtures\State;

use Finite\State;
use Finite\Transition\Transition;

enum DocumentState: string implements State
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case REPORTED = 'reported';
case DISABLED = 'disabled';

public static function getTransitions(): array
{
return [
new Transition('publish', [self::DRAFT], self::PUBLISHED),
new Transition('clear', [self::REPORTED, self::DISABLED], self::PUBLISHED),
new Transition('report', [self::PUBLISHED], self::REPORTED),
new Transition('disable', [self::REPORTED, self::PUBLISHED], self::DISABLED),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public function registerBundles(): iterable

protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader): void
{
$c->prependExtensionConfig('framework', ['test' => true]);
$c->setParameter('kernel.debug', true);
$c->prependExtensionConfig('framework', ['test' => true, 'profiler' => true]);
}

public function getProjectDir(): string
Expand Down
22 changes: 19 additions & 3 deletions tests/Extension/Symfony/ServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,35 @@

namespace Finite\Tests\Extension\Symfony;

use Finite\Extension\Twig\FiniteExtension;
use Finite\Event\CanTransitionEvent;
use Finite\StateMachine;
use Finite\Tests\Extension\Symfony\Fixtures\Model\Document;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Bundle\FrameworkBundle\Test\TestContainer;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class ServiceTest extends KernelTestCase
{
public function test_services_are_registered(): void
{
/** @var TestContainer $container */
$container = static::getContainer();

$this->assertInstanceOf(StateMachine::class, $container->get(StateMachine::class));
$this->assertInstanceOf(EventDispatcherInterface::class, $container->get(StateMachine::class)->getDispatcher());
}

public function test_it_uses_the_symfony_dispatcher(): void
{
$container = static::getContainer();

/** @var StateMachine $stateMachine */
$stateMachine = $container->get(StateMachine::class);
$stateMachine->can(new Document, 'publish');

/** @var TraceableEventDispatcher $debugDispatcher */
$debugDispatcher = $container->get('debug.event_dispatcher');

$this->assertSame(CanTransitionEvent::class, $debugDispatcher->getOrphanedEvents()[0]);
}

protected static function getKernelClass(): string
Expand Down
21 changes: 17 additions & 4 deletions tests/StateMachineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,28 @@
use Finite\Tests\E2E\Article;
use Finite\Tests\E2E\SimpleArticleState;
use PHPUnit\Framework\TestCase;
use Psr\EventDispatcher\EventDispatcherInterface;

class StateMachineTest extends TestCase
{
public function test_it_instantiate_event_dispatcher(): void
{
$this->assertInstanceOf(EventDispatcher::class, (new StateMachine)->getDispatcher());
$this->assertInstanceOf(EventDispatcherInterface::class, (new StateMachine)->getDispatcher());
}

public function test_it_use_constructor_event_dispatcher(): void
{
$eventDispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock();

$this->assertSame($eventDispatcher, (new StateMachine($eventDispatcher))->getDispatcher());
}

public function test_it_can_transition(): void
{
$object = new Article('Hi !');

$eventDispatcher = $this->getMockBuilder(EventDispatcher::class)->getMock();
$eventDispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock();
$eventDispatcher
->expects($this->once())
->method('dispatch')
Expand All @@ -29,7 +43,6 @@ public function test_it_can_transition(): void
}),
);


$stateMachine = new StateMachine($eventDispatcher);

$this->assertTrue($stateMachine->can($object, SimpleArticleState::PUBLISH));
Expand All @@ -39,7 +52,7 @@ public function test_it_blocks_transition(): void
{
$object = new Article('Hi !');

$eventDispatcher = $this->getMockBuilder(EventDispatcher::class)->getMock();
$eventDispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock();
$eventDispatcher
->expects($this->once())
->method('dispatch')
Expand All @@ -56,7 +69,7 @@ public function test_it_applies_transition(): void
{
$object = new Article('Hi !');

$eventDispatcher = $this->getMockBuilder(EventDispatcher::class)->getMock();
$eventDispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock();

$matcher = $this->exactly(6);
$eventDispatcher
Expand Down

0 comments on commit e8e37a5

Please sign in to comment.