State Machine Properties

The state machine properties represent data that the state machine stores. Usually, each property corresponds with a column in an assigned SQL table. However, some properties may be calculated (e.g., using a subselect rather than a column), or generated by the state machine reference (e.g., a relation to another state machine).

To define the properties, we will need two things: a DTO object to carry the data, and an interface that could the state machine reference implement.

For our Task state machine, we will use the TaskDataImmutable object, which implements the TaskData interface. However, we will not write these on our own — we will use the CodeCooker component to generate them.

Code Cooker & DTO Generator

The CodeCooker is a code generator which scans for annotations in our classes and then generates other classes according to registered recipes. One of the recipes is a DtoRecipe, which utilizes the DtoGenerator class to generate boilerplate code for our DTO objects.

Note that this generated boilerplate is not specific to Smalldb state machines.

The input class for the DtoGenerator will be the TaskProperties class — an abstract class with a few protected properties (member variables) only, and this will be the only class we write by hand here:

<?php // src/StateMachine/TaskProperties.php
declare(strict_types = 1);

namespace App\StateMachine;

use Smalldb\CodeCooker\Annotation\GenerateDTO;
use Smalldb\StateMachine\SqlExtension\Annotation\SQL;


/**
 * @GenerateDTO("TaskData")
 * @SQL\Table("Tasks")
 */
abstract class TaskProperties
{

	/**
	 * @SQL\Id
	 */
	protected int $id;


	/**
	 * @SQL\Column
	 */
	protected string $description;

	/**
	 * @SQL\Column
	 */
	protected ?\DateTimeImmutable $completedAt;

}

The @GenerateDTO("TaskData") annotation triggers the DtoRecipe to generate TaskData classes (in the current namespace).

The @SQL annotations define a mapping between SQL table Tasks and the properties of this DTO object. These will be used by repository data source and query builder in the next chapter.

This TaskProperties class becomes a common ancestor of the generated classes, which will implement various accessor methods. The DtoGenerator generates the following classes.

Generated DTO Classes

TaskData interface

TaskData interface defines getters for each protected property of TaskProperties class:

interface TaskData {
    public function getId(): int;
    public function getDescription(): string;
    public function getComletedAt(): ?\DateTimeImmutable;
}

TaskDataImmutable class

TaskDataImmutable class is an immutable DTO that implements the getters from TaskData interface and the with* methods to create a modified clone. There are also some helper methods to access a property by name (the get() method), or to construct the immutable object from various inputs.

The constructor semantics is inspired by C++ copy constructors. The new object will contain data from the $source. Unlike the clone operator, the copy constructor accepts any objects that implement TaskData interface and always produces TaskDataImmutable.

class TaskDataImmutable extends TaskProperties implements TaskData {
    public function __construct(?TaskData $source = null) {/*...*/}
    public static function fromArray(?array $source, ?TaskData $sourceObj = null): ?self {/*...*/}
    public static function fromIterable(?TaskData $sourceObj, iterable $source): self {/*...*/}

    public function getId(): int {/*...*/}
    public function getDescription(): string {/*...*/}
    public function getCompletedAt(): ?DateTimeImmutable {/*...*/}

    public static function get(TaskData $source, string $propertyName) {/*...*/}

    public function withId(int $id): self {/*...*/}
    public function withDescription(string $description): self {/*...*/}
    public function withCompletedAt(?DateTimeImmutable $completedAt): self {/*...*/}
}

TaskDataMutable class

TaskDataMutable class is a mutable variant of TaskDataImmutable class. Instead of the with* methods, it has traditional setters that mutate the data in-place.

Both classes implement the TaskData interface, and both extend the abstract TaskProperties class. They are mostly interchangeable except for the mutable/immutable semantics.

class TaskDataMutable extends TaskProperties implements TaskData {
    public function __construct(?TaskData $source = null) {/*...*/}
    public static function fromArray(?array $source, ?TaskData $sourceObj = null): ?self {/*...*/}
    public static function fromIterable(?TaskData $sourceObj, iterable $source): self {/*...*/}

    public function getId(): int {/*...*/}
    public function getDescription(): string {/*...*/}
    public function getCompletedAt(): ?DateTimeImmutable {/*...*/}

    public static function get(TaskData $source, string $propertyName) {/*...*/}

    public function setId(int $id): void {/*...*/}
    public function setDescription(string $description): void {/*...*/}
    public function setCompletedAt(?DateTimeImmutable $completedAt): void {/*...*/}
}

We can also use the mutable class as a builder to construct the immutable object:

$builder = new TaskDataMutable();
$builder->setDescription($description);
$builder->setCompletedAt(new DateTimeImmutable());
$immutableObject = new TaskDataImmutable($builder);

DTO Without a Generator

Smalldb generates implementations of abstract methods in the reference classes automatically. The generated class uses ReferenceTrait to implement most of the state machine API, the rest of the abstract methods are either transitions or property getters.

A property getter will be implemented automatically when:

Therefore, if you want to use a custom hand-written DTO object, you need to create an interface wish the getters you want to use from the reference and pass it to the reference object.

/**
 * @StateMachine("your-custom-machine")
 * @WrapDto(YourCustomDTO::class)
 */
abstract class YourCustomMachine implements ReferenceInterface, YourCustomDtoGetters {
    /* ... */
}

class YourCustomDto implements YourCustomDtoGetters {
    public function getSomething() {/* ... */}
    public function setSomething($value) {/* ... */}
}

interface YourCustomDtoGetters {
    public function getSomething();
}

You may skip the generator, the mutable/immutable stuff, and keep it this simple. You may also inline the getters directly into the reference class; however, the use of the interface helps to keep the classes consistent.