Skip to content
21 changes: 21 additions & 0 deletions spec/SM/DummyEnumState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace spec\SM;

if (version_compare(PHP_VERSION, '8.1', '>=')) {
enum DummyEnumState: string
{
case Checkout = 'checkout';
case Pending = 'pending';
case Confirmed = 'confirmed';
case Cancelled = 'cancelled';
}
} else {
class DummyEnumState
{
const Checkout = 'checkout';
const Pending = 'pending';
const Confirmed = 'confirmed';
const Cancelled = 'cancelled';
}
}
44 changes: 44 additions & 0 deletions spec/SM/StateMachine/StateMachineSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace spec\SM\StateMachine;

use PhpSpec\Exception\Example\SkippingException;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use SM\Callback\CallbackFactoryInterface;
Expand Down Expand Up @@ -208,4 +209,47 @@ function it_returns_possible_transitions($object, $callbackFactory, CallbackInte

$this->getPossibleTransitions()->shouldReturn(array('create', 'confirm'));
}



function it_should_return_back_enum_value($object)
{
if (version_compare(PHP_VERSION, '8.1', '<')) {
return;
}

$object->getState()->shouldBeCalled()->willReturn(\spec\SM\DummyEnumState::Checkout);
$this->setEnumClass(\spec\SM\DummyEnumState::class);
$state = \spec\SM\DummyEnumState::Checkout;

if (property_exists($state, 'value')) {
$this->getState()->shouldReturn($state->value);
}
}

function it_throws_exeception_if_enum_class_in_not_backed($object)
{
if (version_compare(PHP_VERSION, '8.1', '<')) {
return;
}

$this->shouldThrow(SMException::class)->during('setEnumClass', array('dummy_string'));
}

function it_not_throws_an_exception_during_apply_when_state_is_back_enum($object, $callbackFactory, CallbackInterface $guard, $dispatcher)
{
$this->checkPhpMinimumVersion('8.1');

$object->getState()->shouldBeCalled()->willReturn(\spec\SM\DummyEnumState::Checkout);
$this->setEnumClass(\spec\SM\DummyEnumState::class);
$dispatcher->dispatch(Argument::any())->shouldNotBeCalled();
$this->shouldNotThrow(SMException::class)->during('apply', array(\spec\SM\DummyEnumState::Pending));
}

private function checkPhpMinimumVersion($version)
{
if (version_compare(PHP_VERSION, $version, '<')) {
throw new SkippingException(sprintf("Minimum php version: %s", $version));
}
}
}
33 changes: 32 additions & 1 deletion src/SM/StateMachine/StateMachine.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class StateMachine implements StateMachineInterface
/** @var CallbackFactoryInterface */
protected $callbackFactory;

/** @var ?string */
protected $enumClass;

/**
* @param object $object Underlying object for the state machine
* @param array $config Config array of the graph
Expand All @@ -59,6 +62,8 @@ public function __construct(

$this->config = $config;

$this->setEnumClass($config['enumClass'] ?? null);

// Test if the given object has the given state property path
if (!$this->hasObjectProperty($object, $config['property_path'])) {
throw new SMException(sprintf(
Expand Down Expand Up @@ -147,7 +152,13 @@ public function apply(string $transition, $soft = false): bool
public function getState(): string
{
$accessor = new PropertyAccessor();
return $accessor->getValue($this->object, $this->config['property_path']);
$state = $accessor->getValue($this->object, $this->config['property_path']);

if (($enumClass = $this->getEnumClass()) && is_a($state, $enumClass, true)) {
return (string) $state->value;
}

return $state;
}

/**
Expand Down Expand Up @@ -195,6 +206,10 @@ protected function setState($state): void
));
}

if ($enumClass = $this->getEnumClass()) {
$state = $enumClass::from($state);
}

$accessor = new PropertyAccessor();
$accessor->setValue($this->object, $this->config['property_path'], $state);
}
Expand Down Expand Up @@ -223,4 +238,20 @@ protected function hasObjectProperty($object, string $property): bool
{
return (new PropertyAccessor())->isReadable($object, $property);
}

public function getEnumClass(): ?string
{
return $this->enumClass;
}

public function setEnumClass(?string $class): self
{
if ($class && !is_a($class, \BackedEnum::class, true)) {
throw new SMException('Enum class must be a BackedEnum');
}

$this->enumClass = $class;

return $this;
}
}