Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhemphill committed Dec 17, 2024
1 parent 979e5ec commit b616699
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 48 deletions.
88 changes: 87 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,87 @@
# Machinery
# HEMP Machinery 📠

Machinery is tiny state machine package for Laravel. It allows you
to manage a state machine for an attribute on one of your Eloquent
models using PHP enumerations.

Machinery gives your backed enumeration superpowers by allowing you
to specify the valid transitions between states.

## Installation

You can install the package via composer:

```bash
composer require hemp/machinery
```

## Usage

First, create an enumeration that represents the states for your Eloquent model and the valid transitions between them:

```php
use Hemp\Machinery\MachineState;
use Hemp\Machinery\MachineStateTrait;

enum OrderStatus: string implements MachineState
{
use MachineStateTrait;

case Processing : 'processing';
case Shipped : 'shipped';
case Delivered : 'delivered';

public static function transitions(): array
{
return [
self::Processing->value => [
self::Shipped
],
self::Shipped->value => [
self::Delivered
],
self::Delivered->value => [
// This is the final state...
]
];
}
}
```

Next, add a column to your Eloquent model's `casts` to store the state:

```php
use Hemp\Machinery\Machinable;
use Hemp\Machinery\Machinery;
use Illuminate\Database\Eloquent\Model;

class Order extends Model implements Machinable
{
use Machinery;

protected $casts = [
'status' => OrderStatus::class
];
}
```

Now you can use the `OrderStatus` enumeration to manage the state of your `Order` model:

```php
$order = Order::create([
'status' => OrderStatus::Processing
]);

$order->status->is(OrderStatus::Processing); // true
$order->status->canTransitionTo(OrderStatus::Shipped); // true

$order->status->transitionTo('status', OrderStatus::Shipped, function () {
// Perform any actions that need to be done when the state changes...
});

$order->status->is(OrderStatus::Shipped); // true

$order->status->canTransitionTo('status', OrderStatus::Processing); // false

$order->status->transitionTo('status', OrderStatus::Delivered); // Throws an exception...
```
19 changes: 11 additions & 8 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="vendor/autoload.php"
Expand All @@ -8,18 +9,20 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Package Tests">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<php>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
</php>
</phpunit>
</phpunit>
8 changes: 0 additions & 8 deletions src/Machinable.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/MachineState.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ public function transitionTo(MachineState $state, callable $sideEffect): Machine

public static function transitions(): array;

public function isValidTransition(MachineState $state): bool;
public function canTransitionTo(MachineState $state): bool;
}
9 changes: 7 additions & 2 deletions src/MachineStateTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function transitionTo(MachineState $state, ?callable $sideEffect = null):
{
$callback = $sideEffect ?? fn () => null;

if (! $this->isValidTransition($state)) {
if (! $this->canTransitionTo($state)) {
throw new InvalidStateTransition("Cannot transition from state [{$this->value}] to state [{$state->value}].");
}

Expand All @@ -23,8 +23,13 @@ public function transitionTo(MachineState $state, ?callable $sideEffect = null):
return $state;
}

public function isValidTransition(MachineState $state): bool
public function canTransitionTo(MachineState $state): bool
{
return in_array($state, self::transitions()[$this->value]);
}

public function is(MachineState $state): bool
{
return $this->value === $state->value;
}
}
10 changes: 5 additions & 5 deletions src/Machinery.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@

trait Machinery
{
public function transition(): PendingTransition
{
return new PendingTransition($this);
}

public function transitionTo($machineKey, MachineState $state, ?callable $sideEffect = null): MachineState
{
$sideEffect = $sideEffect ?? fn () => null;
Expand All @@ -20,4 +15,9 @@ public function transitionTo($machineKey, MachineState $state, ?callable $sideEf

return $this->{$machineKey}->transitionTo($state, $callable);
}

public function canTransitionTo($machineKey, MachineState $state): bool
{
return $this->{$machineKey}->canTransitionTo($state);
}
}
22 changes: 0 additions & 22 deletions src/PendingTransition.php

This file was deleted.

2 changes: 1 addition & 1 deletion tests/Fixtures/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Hemp\Machinery\Machinery;
use Illuminate\Database\Eloquent\Model;

class User extends Model implements Machinable
class User extends Model
{
use Machinery;

Expand Down
3 changes: 3 additions & 0 deletions tests/MachineryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public function testModelStateCanBeTransitionedUsingTransitionTo()
$user->transitionTo('status', Status::Inactive);

$this->assertEquals(Status::Inactive, $user->status);
$this->assertTrue($user->status->is(Status::Inactive));
}

public function testCannotTransitionToAnInvalidState()
Expand All @@ -29,6 +30,8 @@ public function testCannotTransitionToAnInvalidState()
'status' => Status::Banned
]);

$this->assertFalse($user->canTransitionTo('status', Status::Active));

$this->expectException(InvalidStateTransition::class);

$user->transitionTo('status', Status::Active);
Expand Down
35 changes: 35 additions & 0 deletions tests/MachineryUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Hemp\Machinery\Tests;

use Hemp\Machinery\InvalidStateTransition;
use Hemp\Machinery\Tests\Fixtures\Status;
use PHPUnit\Framework\TestCase;

class MachineryUnitTest extends TestCase
{
public function testModelStateCanBeTransitionedUsingTransitionTo()
{
$this->assertEquals(Status::Inactive, Status::Active->transitionTo(Status::Inactive));
}

public function testCannotTransitionToAnInvalidState()
{
$this->assertFalse(Status::Banned->canTransitionTo(Status::Active));

$this->expectException(InvalidStateTransition::class);

Status::Banned->transitionTo(Status::Active);
}

public function testSideEffectsAreExecuted()
{
$affected = false;

Status::Active->transitionTo(Status::Inactive, function () use (&$affected) {
$affected = true;
});

$this->assertTrue($affected);
}
}

0 comments on commit b616699

Please sign in to comment.