Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
norotaro committed Jul 22, 2023
0 parents commit e08c537
Show file tree
Hide file tree
Showing 25 changed files with 898 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml,json}]
indent_size = 2
12 changes: 12 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
* text=auto

*.md diff=markdown
*.php diff=php

/.github export-ignore
/tests export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
CHANGELOG.md export-ignore
phpunit.xml export-ignore
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/vendor
/.idea
/.vscode
composer.lock
169 changes: 169 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Enumaton

State Machines for Eloquent models using Enums.

## Description

This package helps to implement State Machines to Eloquent models in an easy way using Enum files to represent all possible states and also to configure transitions.

## Instalation

```bash
composer require norotaro/enumaton
```

## Usage

Having a model with two status fields:

```php
$order->status; // 'pending', 'approved', 'declined' or 'processed'

$order->fulfillment; // null, 'pending', 'completed'
```

We need to create a `enum` file with the State Definitions for each field. We can do that with the `make:model-state` command:

```bash
php artisan make:model-state OrderStatus
```

```bash
php artisan make:model-state OrderFulfillment --nullable
```
> Because the `fulfillment` attribute can be null, we use the `--nullable` option to generate a more appropriate file.
### OrderStatus definition
The command will create a default file that we can addapt to fulfill our requirements:

```php
namespace App\Models;

use Norotaro\Enumaton\Contracts\Nullable;
use Norotaro\Enumaton\Contracts\StateDefinitions;

enum OrderStatus implements StateDefinitions
{
case Pending;
case Approved;
case Declined;
case Processed;

public function allowedTransitions(): array
{
return match ($this) {
self::Pending => [
self::Approved,
self::Delined,
],
self::Approved => [
self::Processed,
]
};
}

public static function default(): self
{
return self::Pending;
}
}
```

### OrderFulfillment definition

And this is the state definitions for the `fulfillment` attribute that can be null:

```php
namespace App\Models;

use Norotaro\Enumaton\Contracts\Nullable;
use Norotaro\Enumaton\Contracts\StateDefinitions;

enum OrderFulfillment implements StateDefinitions, Nullable
{
case Pending;
case Completed;

public function allowedTransitions(): array
{
return match ($this) {
self::Pending => [
self::Completed,
]
};
}

public static function default(): ?self
{
return null;
}

public static function validInitialStates(): array
{
return [
self::Pending,
];
}
}
```
> `validInitialStates()` returns the list of valid states when the field is null.
### Configuring the model

In the model we need to register the `HasStateMachine` trait and then each `enum` to the `$casts` property:

```php
use Norotaro\Enumaton\Traits\HasStateMachine;

class Order extends Model
{
use HasStateMachine;

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

That's it! Now we can transition the states using the State Machines.

## Access the current state

If we access the attributes, Laravel will return the `enum` object with the current state:

```php
$model = new Order;
$model->save();

$model->status; // App\Model\OrderStatus{name: "Pending"}
$model->fulfillment; // null
```

## Access the State Machine

To access the State Machine we only need to add parentheses to the attribute names:

```php
$model->status(); // Norotaro\Enumaton\StateMachine
$model->fulfillment(); // Norotaro\Enumaton\StateMachine
```

## Using the State Machine

### Transitioning

We can transition between states with the `transitionTo($state)` method:

```php
$model->status()->transitionTo(OrderStatus::Approved);
```

### Checking available transitions

```php
$model->status; // App\Model\OrderStatus{name: "Pending"}

$model->status()->canBe(OrderStatus::Approved); // true
$model->status()->canBe(OrderStatus::Processed); // false
```
53 changes: 53 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "norotaro/enumaton",
"description": "State machines for Eloquent models with Enums",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"Norotaro\\Enumaton\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Norotaro\\Enumaton\\Tests\\": "tests"
}
},
"authors": [
{
"name": "Nelson Otazo",
"email": "[email protected]",
"homepage": "https://nelson.otazo.com.py"
}
],
"require": {
"php": "^8.1",
"illuminate/support": "^9.0|^10.0",
"javoscript/laravel-macroable-models": "^1.0"
},
"require-dev": {
"laravel/pint": "^1.10",
"mockery/mockery": "^1.6",
"orchestra/testbench": "^8.5",
"pestphp/pest": "^2.9",
"phpstan/phpstan": "^1.10"
},
"config": {
"sort-packages": true,
"preferred-install": "dist",
"optimize-autoloader": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"scripts": {
"test": "./vendor/bin/pest"
},
"extra": {
"laravel": {
"providers": [
"Norotaro\\Enumaton\\Providers\\EnumatonServiceProvider"
]
}
}
}
5 changes: 5 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
level: 9

paths:
- src
15 changes: 15 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" stopOnFailure="true">
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage/>
<source>
<include>
<!-- <directory suffix=".php">./app</directory> -->
<directory suffix=".php">./src</directory>
</include>
</source>
</phpunit>
3 changes: 3 additions & 0 deletions pint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"preset": "psr12"
}
43 changes: 43 additions & 0 deletions src/Console/Commands/ModelStateMakeCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Norotaro\Enumaton\Console\Commands;

use Illuminate\Console\GeneratorCommand;

final class ModelStateMakeCommand extends GeneratorCommand
{
/**
* @var string
*/
protected $signature = 'make:model-state {name : The Model State Name} {--N|nullable : If the State is nullable}';

/**
* @var string
*/
protected $description = 'Create a new Model State class';

/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'class';

protected function getStub()
{
$file = $this->option('nullable') ? 'nullable.stub' : 'states.stub';

return __DIR__ . "/../../../stubs/$file";
}

/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return "{$rootNamespace}\\Models";
}
}
8 changes: 8 additions & 0 deletions src/Contracts/Nullable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Norotaro\Enumaton\Contracts;

interface Nullable
{
public static function validInitialStates(): array;
}
10 changes: 10 additions & 0 deletions src/Contracts/StateDefinitions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Norotaro\Enumaton\Contracts;

interface StateDefinitions
{
public function allowedTransitions(): array;

public static function default(): ?self;
}
22 changes: 22 additions & 0 deletions src/Contracts/StateMachine.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Norotaro\Enumaton\Contracts;

use Illuminate\Database\Eloquent\Model;
use UnitEnum;

interface StateMachine
{
public function __construct(Model $model, string $field);

/**
* Return current state
*
* @return null|StateDefinitions&UnitEnum
*/
public function currentState();

public function canBe(StateDefinitions&UnitEnum $status): bool;

public function transitionTo(StateDefinitions&UnitEnum $state): void;
}
Loading

0 comments on commit e08c537

Please sign in to comment.