Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
31 / 31 |
CRAP | |
100.00% |
117 / 117 |
StateMachineDefinitionBuilder | |
100.00% |
1 / 1 |
|
100.00% |
31 / 31 |
66 | |
100.00% |
117 / 117 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
addPreprocessorPass | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
runPreprocessor | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
build | |
100.00% |
1 / 1 |
8 | |
100.00% |
31 / 31 |
|||
buildStateDefinition | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
buildActionDefinition | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
buildTransitionDefinition | |
100.00% |
1 / 1 |
4 | |
100.00% |
10 / 10 |
|||
buildPropertyDefinition | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
sortPlaceholders | |
100.00% |
1 / 1 |
2 | |
100.00% |
6 / 6 |
|||
getMTime | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
setMTime | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
|||
addMTime | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
getState | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
addState | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
getAction | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
addAction | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
getTransition | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
addTransition | |
100.00% |
1 / 1 |
4 | |
100.00% |
10 / 10 |
|||
getProperty | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
addProperty | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
getMachineType | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
setMachineType | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
addError | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getErrors | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
hasErrors | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getReferenceClass | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
setReferenceClass | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
|||
getRepositoryClass | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
setRepositoryClass | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
|||
getTransitionsClass | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
setTransitionsClass | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
<?php declare(strict_types = 1); | |
/* | |
* Copyright (c) 2019, Josef Kufner <josef@kufner.cz> | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
*/ | |
namespace Smalldb\StateMachine\Definition\Builder; | |
use Smalldb\StateMachine\Definition\ActionDefinition; | |
use Smalldb\StateMachine\Definition\DefinitionErrorInterface; | |
use Smalldb\StateMachine\Definition\PropertyDefinition; | |
use Smalldb\StateMachine\Definition\StateDefinition; | |
use Smalldb\StateMachine\Definition\StateMachineDefinition; | |
use Smalldb\StateMachine\Definition\TransitionDefinition; | |
use Smalldb\StateMachine\Definition\UndefinedStateException; | |
use Smalldb\StateMachine\InvalidArgumentException; | |
class StateMachineDefinitionBuilder extends ExtensiblePlaceholder | |
{ | |
private PreprocessorList $preprocessorList; | |
private ?int $mtime = null; | |
/** @var PreprocessorPass[] */ | |
private array $preprocessorQueue = []; | |
/** @var StatePlaceholder[] */ | |
private array $states = []; | |
/** @var ActionPlaceholder[] */ | |
private array $actions = []; | |
/** @var TransitionPlaceholder[] */ | |
private array $transitions = []; | |
/** @var TransitionPlaceholder[][] */ | |
private array $transitionsByState = []; | |
/** @var PropertyPlaceholder[] */ | |
private array $properties = []; | |
private ?string $machineType = null; | |
/** @var DefinitionErrorInterface[] */ | |
private array $errors = []; | |
private ?string $referenceClass = null; | |
private ?string $repositoryClass = null; | |
private ?string $transitionsClass = null; | |
public function __construct(PreprocessorList $preprocessorList) | |
{ | |
parent::__construct([]); | |
$this->preprocessorList = $preprocessorList; | |
} | |
public function addPreprocessorPass(PreprocessorPass $preprocessorPass): void | |
{ | |
$this->preprocessorQueue[] = $preprocessorPass; | |
} | |
public function runPreprocessor(): void | |
{ | |
while (($preprocessorPass = array_shift($this->preprocessorQueue))) { | |
$this->preprocessorList->preprocessDefinition($this, $preprocessorPass); | |
} | |
} | |
public function build(): StateMachineDefinition | |
{ | |
$this->runPreprocessor(); | |
if (empty($this->machineType)) { | |
throw new StateMachineBuilderException("Machine type not set."); | |
} | |
// Check if we have the Not Exists state | |
if (!isset($this->states[''])) { | |
$this->addState(''); | |
} | |
try { | |
/** @var StateDefinition[] $states */ | |
$states = []; | |
foreach ($this->states as $statePlaceholder) { | |
$state = $this->buildStateDefinition($statePlaceholder); | |
$states[$state->getName()] = $state; | |
} | |
/** @var TransitionDefinition[] $transitions */ | |
$transitions = []; | |
$transitionsByAction = []; | |
foreach ($this->transitions as $transitionPlaceholder) { | |
$transition = $this->buildTransitionDefinition($transitionPlaceholder, $states); | |
$transitions[] = $transition; | |
$transitionName = $transition->getName(); | |
$sourceStateName = $transition->getSourceState()->getName(); | |
$transitionsByAction[$transitionName][$sourceStateName] = $transition; | |
} | |
/** @var ActionDefinition[] $actions */ | |
$actions = []; | |
foreach ($this->actions as $actionPlaceholder) { | |
$action = $this->buildActionDefinition($actionPlaceholder, $transitionsByAction[$actionPlaceholder->name] ?? []); | |
$actions[$action->getName()] = $action; | |
} | |
/** @var PropertyDefinition[] $properties */ | |
$properties = []; | |
foreach ($this->properties as $propertyPlaceholder) { | |
$property = $this->buildPropertyDefinition($propertyPlaceholder); | |
$properties[$property->getName()] = $property; | |
} | |
return new StateMachineDefinition($this->machineType, $this->mtime ?? time(), | |
$states, $actions, $transitions, $properties, $this->errors, | |
$this->referenceClass, $this->transitionsClass, $this->repositoryClass, | |
$this->buildExtensions()); | |
} | |
catch (\Exception $ex) { | |
throw new StateMachineBuilderException("Failed to build '$this->machineType' state machine definition: " . $ex->getMessage(), $ex->getCode(), $ex); | |
} | |
} | |
protected function buildStateDefinition(StatePlaceholder $statePlaceholder): StateDefinition | |
{ | |
return $statePlaceholder->buildStateDefinition(); | |
} | |
protected function buildActionDefinition(ActionPlaceholder $actionPlaceholder, array $transitions): ActionDefinition | |
{ | |
return $actionPlaceholder->buildActionDefinition($transitions); | |
} | |
protected function buildTransitionDefinition(TransitionPlaceholder $transitionPlaceholder, array $stateDefinitions): TransitionDefinition | |
{ | |
$sourceState = $stateDefinitions[$transitionPlaceholder->sourceState] ?? null; | |
if ($sourceState === null) { | |
throw new UndefinedStateException("Undefined source state \"$transitionPlaceholder->sourceState\" in transition \"$transitionPlaceholder->name\"."); | |
} | |
$targetStates = []; | |
foreach ($transitionPlaceholder->targetStates as $targetStateName) { | |
$targetState = $stateDefinitions[$targetStateName] ?? null; | |
if ($targetState === null) { | |
throw new UndefinedStateException("Undefined target state \"$targetStateName\" in transition \"$transitionPlaceholder->name\"."); | |
} | |
$targetStates[$targetStateName] = $targetState; | |
} | |
return $transitionPlaceholder->buildTransitionDefinition($sourceState, $targetStates); | |
} | |
protected function buildPropertyDefinition(PropertyPlaceholder $propertyPlaceholder): PropertyDefinition | |
{ | |
return $propertyPlaceholder->buildPropertyDefinition(); | |
} | |
/** | |
* Sort everything to make changes in generated definitions more stable when the source changes. | |
*/ | |
public function sortPlaceholders() | |
{ | |
ksort($this->states); | |
ksort($this->actions); | |
ksort($this->transitionsByState); | |
foreach ($this->transitionsByState as & $t) { | |
ksort($t); | |
} | |
// TODO: Sort properties too? | |
//ksort($this->properties); | |
} | |
public function getMTime(): ?int | |
{ | |
return $this->mtime; | |
} | |
public function setMTime(?int $mtime): void | |
{ | |
$this->mtime = $mtime; | |
} | |
/** | |
* Set mtime of the definition if the new value is later, i.e., `max($mtime, $this->mtime)`. | |
*/ | |
public function addMTime(int $mtime): void | |
{ | |
if ($mtime > $this->mtime) { | |
$this->mtime = $mtime; | |
} | |
} | |
public function getState(string $name): StatePlaceholder | |
{ | |
return $this->states[$name] ?? ($this->states[$name] = new StatePlaceholder($name)); | |
} | |
public function addState(string $name): StatePlaceholder | |
{ | |
if (isset($this->states[$name])) { | |
throw new DuplicateStateException("State already exists: $name"); | |
} else { | |
return ($this->states[$name] = new StatePlaceholder($name)); | |
} | |
} | |
public function getAction(string $name): ActionPlaceholder | |
{ | |
if ($name === '') { | |
throw new InvalidArgumentException("Empty action name."); | |
} | |
return $this->actions[$name] ?? ($this->actions[$name] = new ActionPlaceholder($name)); | |
} | |
public function addAction(string $name): ActionPlaceholder | |
{ | |
if ($name === '') { | |
throw new InvalidArgumentException("Empty action name."); | |
} | |
if (isset($this->actions[$name])) { | |
throw new DuplicateActionException("Action already exists: $name"); | |
} else { | |
return ($this->actions[$name] = new ActionPlaceholder($name)); | |
} | |
} | |
public function getTransition(string $transitionName, string $sourceStateName): TransitionPlaceholder | |
{ | |
if ($transitionName === '') { | |
throw new InvalidArgumentException("Empty transition name."); | |
} | |
if (isset($this->transitionsByState[$sourceStateName][$transitionName])) { | |
return $this->transitionsByState[$sourceStateName][$transitionName]; | |
} else { | |
return $this->addTransition($transitionName, $sourceStateName, []); | |
} | |
} | |
public function addTransition(string $transitionName, string $sourceStateName, array $targetStateNames): TransitionPlaceholder | |
{ | |
if ($transitionName === '') { | |
throw new InvalidArgumentException("Empty transition name."); | |
} | |
if (isset($this->transitionsByState[$sourceStateName][$transitionName])) { | |
throw new DuplicateTransitionException("Transition \"$transitionName\" already exists in state \"$sourceStateName\"."); | |
} else { | |
if (empty($this->actions[$transitionName])) { | |
$this->addAction($transitionName); | |
} | |
$placeholder = new TransitionPlaceholder($transitionName, $sourceStateName, $targetStateNames); | |
$this->transitionsByState[$sourceStateName][$transitionName] = $placeholder; | |
$this->transitions[] = $placeholder; | |
return $placeholder; | |
} | |
} | |
public function getProperty(string $name): PropertyPlaceholder | |
{ | |
if ($name === '') { | |
throw new InvalidArgumentException("Empty property name."); | |
} | |
if (isset($this->properties[$name])) { | |
return $this->properties[$name]; | |
} else { | |
return $this->addProperty($name); | |
} | |
} | |
public function addProperty(string $name, string $type = null, bool $isNullable = null): PropertyPlaceholder | |
{ | |
if ($name === '') { | |
throw new InvalidArgumentException("Empty property name."); | |
} | |
if (isset($this->properties[$name])) { | |
throw new DuplicatePropertyException("Property already exists: $name"); | |
} else { | |
return ($this->properties[$name] = new PropertyPlaceholder($name, $type, $isNullable)); | |
} | |
} | |
public function getMachineType(): ?string | |
{ | |
return $this->machineType; | |
} | |
public function setMachineType(string $machineType) | |
{ | |
$this->machineType = $machineType; | |
} | |
public function addError(DefinitionErrorInterface $error): void | |
{ | |
$this->errors[] = $error; | |
} | |
/** | |
* @return DefinitionErrorInterface[] | |
*/ | |
public function getErrors(): array | |
{ | |
return $this->errors; | |
} | |
public function hasErrors(): bool | |
{ | |
return !empty($this->errors); | |
} | |
public function getReferenceClass(): ?string | |
{ | |
return $this->referenceClass; | |
} | |
public function setReferenceClass(?string $referenceClass): void | |
{ | |
$this->referenceClass = $referenceClass; | |
} | |
public function getRepositoryClass(): ?string | |
{ | |
return $this->repositoryClass; | |
} | |
public function setRepositoryClass(?string $repositoryClass): void | |
{ | |
$this->repositoryClass = $repositoryClass; | |
} | |
public function getTransitionsClass(): ?string | |
{ | |
return $this->transitionsClass; | |
} | |
public function setTransitionsClass(?string $transitionsClass): void | |
{ | |
$this->transitionsClass = $transitionsClass; | |
} | |
} |