Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/hollodotme/state-machine-generator

PHP code generator for OOP state machines
https://github.com/hollodotme/state-machine-generator

code-generator oop php state-machine

Last synced: 3 days ago
JSON representation

PHP code generator for OOP state machines

Awesome Lists containing this project

README

        

[![Build Status](https://travis-ci.org/hollodotme/state-machine-generator.svg?branch=master)](https://travis-ci.org/hollodotme/state-machine-generator)
[![Coverage Status](https://coveralls.io/repos/github/hollodotme/state-machine-generator/badge.svg?branch=master)](https://coveralls.io/github/hollodotme/state-machine-generator?branch=master)

# State machine generator

A PHP code generator for OOP state machines.

This package is based on the work of [Sebastian Bergmann and his state repository](https://github.com/sebastianbergmann/state).

## Requirements

* PHP >= 7.1
* PHPUnit >= 6.0 (for running the generated tests)

## Features

* Command based CLI API
* Support for namespaces
* States can be represented as and reconstituted from strings
* Configurable output paths
* Configurable meta data
* Template specification file creation
* Generator available as a PHAR file

## Installation

Download the current stable PHAR file from the [releases section](https://github.com/hollodotme/state-machine-generator/releases/).

wget https://github.com/hollodotme/state-machine-generator/releases/download/vX.X.X/state-machine-generator.phar

If you like verify it with its GPG signature:

wget https://github.com/hollodotme/state-machine-generator/releases/download/vX.X.X/state-machine-generator.phar.asc

## Usage

### Create a sample specification file

```bash
$ php state-machine-generator.phar create:specfile /path/to/specification-file.xml
```

### Generate state machine classes incl. exceptions and unit tests

```bash
$ php state-machine-generator.phar generate:machine /path/to/specification-file.xml
```

## Example

### Specification file

```xml







































```

### Generated files

```
- /src
|- /States
| |- /Exceptions
| | |- IllegalStateTransition
| | `- InvalidStateString
| |- /Interfaces
| | `- DoorState.php
| |- AbstractDoorState.php
| |- ClosedDoorState.php
| |- LockedDoorState.php
| `- OpenDoorState.php
`- Door.php
- /tests
|- ClosedDoorStateTest.php
|- LockedDoorStateTest.php
`- OpenDoorStateTest.php
```

#### Interface `DoorState`

```php

*/

namespace YourVendor\YourProject\States\Interfaces;

/**
* Interface DoorState
* @package YourVendor\YourProject\States\Interfaces
*/
interface DoorState
{
public function open() : DoorState;

public function close() : DoorState;

public function lock() : DoorState;

public function unlock() : DoorState;

public function toString() : string;

public function __toString() : string;
}
```

### Class `AbstractDoorState`

```php

*/

namespace YourVendor\YourProject\States;

use YourVendor\YourProject\States\Interfaces\DoorState;
use YourVendor\YourProject\States\Exceptions\IllegalStateTransition;
use YourVendor\YourProject\States\Exceptions\InvalidStateString;

/**
* Class AbstractDoorState
* @package YourVendor\YourProject\States
*/
abstract class AbstractDoorState implements DoorState
{
protected const OPEN = 'open';

protected const CLOSED = 'closed';

protected const LOCKED = 'locked';

public function open() : DoorState
{
throw (new IllegalStateTransition())->withDesiredState(self::OPEN);
}

public function close() : DoorState
{
throw (new IllegalStateTransition())->withDesiredState(self::CLOSED);
}

public function lock() : DoorState
{
throw (new IllegalStateTransition())->withDesiredState(self::LOCKED);
}

public function unlock() : DoorState
{
throw (new IllegalStateTransition())->withDesiredState(self::CLOSED);
}

public function __toString() : string
{
return $this->toString();
}

/**
* @param string $stateString
* @throws InvalidStateString
* @return DoorState
*/
public static function fromString(string $stateString) : DoorState
{
switch($stateString)
{
case self::OPEN:
return new OpenDoorState();

case self::CLOSED:
return new ClosedDoorState();

case self::LOCKED:
return new LockedDoorState();

default:
throw (new InvalidStateString())->withStateString($stateString);
}
}
}
```

#### Class `ClosedDoorState`

```php

*/

namespace YourVendor\YourProject\States;

use YourVendor\YourProject\States\Interfaces\DoorState;

/**
* Class ClosedDoorState
* @package YourVendor\YourProject\States
*/
final class ClosedDoorState extends AbstractDoorState
{
public function open() : DoorState
{
return new OpenDoorState();
}

public function lock() : DoorState
{
return new LockedDoorState();
}

public function toString() : string
{
return self::CLOSED;
}
}
```

#### Class `LockedDoorState`

```php

*/

namespace YourVendor\YourProject\States;

use YourVendor\YourProject\States\Interfaces\DoorState;

/**
* Class LockedDoorState
* @package YourVendor\YourProject\States
*/
final class LockedDoorState extends AbstractDoorState
{
public function unlock() : DoorState
{
return new ClosedDoorState();
}

public function toString() : string
{
return self::LOCKED;
}
}
```

#### Class `OpenDoorState`

```php

*/

namespace YourVendor\YourProject\States;

use YourVendor\YourProject\States\Interfaces\DoorState;

/**
* Class OpenDoorState
* @package YourVendor\YourProject\States
*/
final class OpenDoorState extends AbstractDoorState
{
public function close() : DoorState
{
return new ClosedDoorState();
}

public function toString() : string
{
return self::OPEN;
}
}
```

#### Class `Door`

```php

*/

namespace YourVendor\YourProject;

use YourVendor\YourProject\States\Interfaces\DoorState;
use YourVendor\YourProject\States\Exceptions\IllegalStateTransition;
use YourVendor\YourProject\States\OpenDoorState;
use YourVendor\YourProject\States\ClosedDoorState;
use YourVendor\YourProject\States\LockedDoorState;

/**
* Class Door
* @package YourVendor\YourProject
*/
class Door
{
/** @var DoorState */
private $state;

public function __construct(DoorState $state)
{
$this->setState($state);
}

private function setState(DoorState $state)
{
$this->state = $state;
}

/**
* @throws IllegalStateTransition
*/
public function open()
{
$this->setState($this->state->open());
}

/**
* @throws IllegalStateTransition
*/
public function close()
{
$this->setState($this->state->close());
}

/**
* @throws IllegalStateTransition
*/
public function lock()
{
$this->setState($this->state->lock());
}

/**
* @throws IllegalStateTransition
*/
public function unlock()
{
$this->setState($this->state->unlock());
}

public function isOpen() : bool
{
return ($this->state instanceof OpenDoorState);
}

public function isClosed() : bool
{
return ($this->state instanceof ClosedDoorState);
}

public function isLocked() : bool
{
return ($this->state instanceof LockedDoorState);
}
}
```

#### Test class `ClosedDoorStateTest`

```php

*/

namespace YourVendor\YourProject\Tests\Unit;

use YourVendor\YourProject\Door;
use YourVendor\YourProject\States\AbstractDoorState;
use YourVendor\YourProject\States\Exceptions\InvalidStateString;
use YourVendor\YourProject\States\Exceptions\IllegalStateTransition;
use YourVendor\YourProject\States\ClosedDoorState;

/**
* Class ClosedDoorStateTest
* @package YourVendor\YourProject\Tests\Unit
*/
final class ClosedDoorStateTest extends \PHPUnit\Framework\TestCase
{
/** @var Door */
private $door;

protected function setUp()
{
$this->door = new Door(new ClosedDoorState());
}

public function testIsNotOpen()
{
$this->assertFalse($this->door->isOpen());
}

public function testIsClosed()
{
$this->assertTrue($this->door->isClosed());
}

public function testIsNotLocked()
{
$this->assertFalse($this->door->isLocked());
}

public function testCanBeOpened()
{
$this->door->open();

$this->assertTrue($this->door->isOpen());
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeClosed()
{
try
{
$this->door->close();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('closed', $e->getDesiredState());

throw $e;
}
}

public function testCanBeLocked()
{
$this->door->lock();

$this->assertTrue($this->door->isLocked());
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeUnlocked()
{
try
{
$this->door->unlock();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('closed', $e->getDesiredState());

throw $e;
}
}

public function testStateCanBeRepresentedAsString()
{
$state = new ClosedDoorState();

$this->assertEquals('closed', (string)$state);
$this->assertEquals('closed', $state->toString());
}

public function testCanBuildStateFromStringRepresentation()
{
$state = AbstractDoorState::fromString('closed');

$this->assertInstanceOf(ClosedDoorState::class, $state);
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\InvalidStateString
*/
public function testInvalidStateStringThrowsException()
{
try
{
AbstractDoorState::fromString('closed.invalid');
}
catch (InvalidStateString $e)
{
$this->assertEquals('closed.invalid', $e->getStateString());

throw $e;
}
}
}
```

#### Test class `LockedDoorStateTest`

```php

*/

namespace YourVendor\YourProject\Tests\Unit;

use YourVendor\YourProject\Door;
use YourVendor\YourProject\States\AbstractDoorState;
use YourVendor\YourProject\States\Exceptions\InvalidStateString;
use YourVendor\YourProject\States\Exceptions\IllegalStateTransition;
use YourVendor\YourProject\States\LockedDoorState;

/**
* Class LockedDoorStateTest
* @package YourVendor\YourProject\Tests\Unit
*/
final class LockedDoorStateTest extends \PHPUnit\Framework\TestCase
{
/** @var Door */
private $door;

protected function setUp()
{
$this->door = new Door(new LockedDoorState());
}

public function testIsNotOpen()
{
$this->assertFalse($this->door->isOpen());
}

public function testIsNotClosed()
{
$this->assertFalse($this->door->isClosed());
}

public function testIsLocked()
{
$this->assertTrue($this->door->isLocked());
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeOpened()
{
try
{
$this->door->open();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('open', $e->getDesiredState());

throw $e;
}
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeClosed()
{
try
{
$this->door->close();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('closed', $e->getDesiredState());

throw $e;
}
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeLocked()
{
try
{
$this->door->lock();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('locked', $e->getDesiredState());

throw $e;
}
}

public function testCanBeUnlocked()
{
$this->door->unlock();

$this->assertTrue($this->door->isClosed());
}

public function testStateCanBeRepresentedAsString()
{
$state = new LockedDoorState();

$this->assertEquals('locked', (string)$state);
$this->assertEquals('locked', $state->toString());
}

public function testCanBuildStateFromStringRepresentation()
{
$state = AbstractDoorState::fromString('locked');

$this->assertInstanceOf(LockedDoorState::class, $state);
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\InvalidStateString
*/
public function testInvalidStateStringThrowsException()
{
try
{
AbstractDoorState::fromString('locked.invalid');
}
catch (InvalidStateString $e)
{
$this->assertEquals('locked.invalid', $e->getStateString());

throw $e;
}
}
}
```

#### Test class `OpenDoorStateTest`

```php

*/

namespace YourVendor\YourProject\Tests\Unit;

use YourVendor\YourProject\Door;
use YourVendor\YourProject\States\AbstractDoorState;
use YourVendor\YourProject\States\Exceptions\InvalidStateString;
use YourVendor\YourProject\States\Exceptions\IllegalStateTransition;
use YourVendor\YourProject\States\OpenDoorState;

/**
* Class OpenDoorStateTest
* @package YourVendor\YourProject\Tests\Unit
*/
final class OpenDoorStateTest extends \PHPUnit\Framework\TestCase
{
/** @var Door */
private $door;

protected function setUp()
{
$this->door = new Door(new OpenDoorState());
}

public function testIsOpen()
{
$this->assertTrue($this->door->isOpen());
}

public function testIsNotClosed()
{
$this->assertFalse($this->door->isClosed());
}

public function testIsNotLocked()
{
$this->assertFalse($this->door->isLocked());
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeOpened()
{
try
{
$this->door->open();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('open', $e->getDesiredState());

throw $e;
}
}

public function testCanBeClosed()
{
$this->door->close();

$this->assertTrue($this->door->isClosed());
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeLocked()
{
try
{
$this->door->lock();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('locked', $e->getDesiredState());

throw $e;
}
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\IllegalStateTransition
*/
public function testCannotBeUnlocked()
{
try
{
$this->door->unlock();
}
catch(IllegalStateTransition $e)
{
$this->assertEquals('closed', $e->getDesiredState());

throw $e;
}
}

public function testStateCanBeRepresentedAsString()
{
$state = new OpenDoorState();

$this->assertEquals('open', (string)$state);
$this->assertEquals('open', $state->toString());
}

public function testCanBuildStateFromStringRepresentation()
{
$state = AbstractDoorState::fromString('open');

$this->assertInstanceOf(OpenDoorState::class, $state);
}

/**
* @expectedException \YourVendor\YourProject\States\Exceptions\InvalidStateString
*/
public function testInvalidStateStringThrowsException()
{
try
{
AbstractDoorState::fromString('open.invalid');
}
catch (InvalidStateString $e)
{
$this->assertEquals('open.invalid', $e->getStateString());

throw $e;
}
}
}
```

### Test execution result with TestDox and text coverage summary

```
PHPUnit 6.0.6 by Sebastian Bergmann and contributors.

Runtime: PHP 7.1.1 with Xdebug 2.5.0
Configuration: /Users/hollodotme/Sites/StateMachineGenerator/build/phpunit.xml

YourVendor\YourProject\Tests\Unit\ClosedDoorState
[x] Is not open
[x] Is closed
[x] Is not locked
[x] Can be opened
[x] Cannot be closed
[x] Can be locked
[x] Cannot be unlocked
[x] State can be represented as string
[x] Can build state from string representation
[x] Invalid state string throws exception

YourVendor\YourProject\Tests\Unit\LockedDoorState
[x] Is not open
[x] Is not closed
[x] Is locked
[x] Cannot be opened
[x] Cannot be closed
[x] Cannot be locked
[x] Can be unlocked
[x] State can be represented as string
[x] Can build state from string representation
[x] Invalid state string throws exception

YourVendor\YourProject\Tests\Unit\OpenDoorState
[x] Is open
[x] Is not closed
[x] Is not locked
[x] Cannot be opened
[x] Can be closed
[x] Cannot be locked
[x] Cannot be unlocked
[x] State can be represented as string
[x] Can build state from string representation
[x] Invalid state string throws exception

Code Coverage Report Summary:
Classes: 100.00% (7/7)
Methods: 100.00% (26/26)
Lines: 100.00% (42/42)
```