Issues (57)

src/CreateDescriptor.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Koriym\AppStateDiagram;
6
7
use Koriym\AppStateDiagram\Exception\DescriptorIsNotArrayException;
8
use Koriym\AppStateDiagram\Exception\InvalidDescriptorException;
9
use Koriym\AppStateDiagram\Exception\InvalidTypeException;
10
use stdClass;
11
12
use function assert;
13
use function in_array;
14
use function is_array;
15
use function is_string;
16
use function json_encode;
17
use function ksort;
18
use function property_exists;
19
20
final class CreateDescriptor
21
{
22
    private const VALID_TYPES = ['semantic', 'safe', 'unsafe', 'idempotent'];
23
24
    /**
25
     * @param array<string, stdClass> $descriptorsArray
26
     *
27
     * @return array<string, AbstractDescriptor>
28
     */
29
    public function __invoke(array $descriptorsArray, ?stdClass $parentDescriptor = null): array
30
    {
31
        $descriptors = [];
32
        foreach ($descriptorsArray as $descriptor) {
33
            /** @var array<string, AbstractDescriptor> $descriptors */
34
            $descriptors = $this->scan($descriptor, $descriptors, $parentDescriptor);
35
        }
36
37
        ksort($descriptors);
38
39
        return $descriptors;
40
    }
41
42
    /**
43
     * @param array<string, AbstractDescriptor> $descriptors
44
     *
45
     * @return array<AbstractDescriptor>
46
     */
47
    private function scan(stdClass $descriptor, array $descriptors, ?stdClass $parentDescriptor): array
48
    {
49
        $this->defaultSemantic($descriptor);
50
        $this->validateDescriptor($descriptor);
51
52
        if (isset($descriptor->id) && is_string($descriptor->id) && $descriptor->type === 'semantic') {
53
            $descriptors[$descriptor->id] = new SemanticDescriptor($descriptor, $parentDescriptor);
54
        }
55
56
        $isTransDescriptor = isset($descriptor->type) && in_array($descriptor->type, ['safe', 'unsafe', 'idempotent'], true);
57
        if ($isTransDescriptor) {
58
            $parent = $parentDescriptor ?? new NullDescriptor();
59
            assert(is_string($descriptor->id));
60
61
            $descriptors[$descriptor->id] = new TransDescriptor($descriptor, new SemanticDescriptor($parent));
62
        }
63
64
        if (property_exists($descriptor, 'descriptor')) {
65
            $descriptors = $this->scanInlineDescriptor($descriptor, $descriptors);
66
        }
67
68
        return $descriptors;
69
    }
70
71
    private function defaultSemantic(stdClass $descriptor): void
72
    {
73
        if (isset($descriptor->id) && ! isset($descriptor->type)) {
74
            $descriptor->type = 'semantic';
75
        }
76
    }
77
78
    /**
79
     * @param array<AbstractDescriptor> $descriptors
80
     *
81
     * @return array<array-key, AbstractDescriptor>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, AbstractDescriptor> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, AbstractDescriptor>.
Loading history...
82
     */
83
    private function scanInlineDescriptor(stdClass $descriptor, array $descriptors): array
84
    {
85
        if (! is_array($descriptor->descriptor)) {
86
            $msg = is_string($descriptor->descriptor) ? $descriptor->descriptor : json_encode($descriptor);
87
88
            throw new DescriptorIsNotArrayException((string) $msg);
89
        }
90
91
        /** @psalm-suppress MixedArgumentTypeCoercion */
92
        $inLineSemantics = ($this)($descriptor->descriptor, $descriptor);
93
        /** @psalm-suppress MixedArgumentTypeCoercion */
94
        if ($inLineSemantics !== []) {
95
            $descriptors = $this->addInlineSemantics($descriptors, $inLineSemantics);
96
        }
97
98
        return $descriptors;
99
    }
100
101
    /**
102
     * @param array<string, AbstractDescriptor> $descriptors
103
     * @param array<string, AbstractDescriptor> $inLineSemantics
104
     *
105
     * @return array<string, AbstractDescriptor>
106
     */
107
    private function addInlineSemantics(array $descriptors, array $inLineSemantics): array
108
    {
109
        foreach ($inLineSemantics as $inLineSemantic) {
110
            if ($inLineSemantic instanceof SemanticDescriptor) {
111
                $descriptors[$inLineSemantic->id] = $inLineSemantic;
112
            }
113
        }
114
115
        return $descriptors;
116
    }
117
118
    private function validateDescriptor(stdClass $descriptor): void
119
    {
120
        $hasNoId = ! isset($descriptor->href) && ! isset($descriptor->id);
121
        if ($hasNoId) {
122
            throw new InvalidDescriptorException((string) json_encode($descriptor));
123
        }
124
125
        if (isset($descriptor->type) && ! in_array($descriptor->type, self::VALID_TYPES)) {
126
            throw new InvalidTypeException((string) $descriptor->type);
127
        }
128
    }
129
}
130