Completed
Push — master ( c5a4d2...cbdcc2 )
by Akihito
26s queued 13s
created

DrawDiagram::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Koriym\AppStateDiagram;
6
7
use JetBrains\PhpStorm\Immutable;
8
use Koriym\AppStateDiagram\Exception\InvalidHrefException;
9
use Koriym\AppStateDiagram\Exception\SharpMissingInHrefException;
10
use stdClass;
11
12
use function assert;
13
use function in_array;
14
use function is_int;
15
use function is_string;
16
use function property_exists;
17
use function sprintf;
18
use function strpos;
19
use function substr;
20
21
use const PHP_EOL;
22
23
/**
24
 * @psalm-immutable
25
 */
26
#[Immutable]
27
final class DrawDiagram
28
{
29
    public function __invoke(AbstractProfile $profile, ?LabelNameInterface $labelName, ?TaggedProfile $taggedProfile = null, ?string $color = null): string
30
    {
31
        $transNodes = $this->getTransNodes($profile);
32
        $labelName = $labelName ?? new LabelName();
33
        $descriptors = $profile->descriptors;
34
        [$filterIds, $nodes] = $this->getNodes($transNodes, $labelName, $descriptors, $taggedProfile, $color);
35
        $edge = new Edge($profile, $taggedProfile, $color);
36
        $graph = (string) $edge;
37
        $appSateWithNoLink = (string) (new AppState($profile->links, $profile->descriptors, $labelName, $taggedProfile, $color, $filterIds));
0 ignored issues
show
Bug introduced by
$filterIds of type Koriym\AppStateDiagram\list is incompatible with the type array expected by parameter $filterIds of Koriym\AppStateDiagram\AppState::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

37
        $appSateWithNoLink = (string) (new AppState($profile->links, $profile->descriptors, $labelName, $taggedProfile, $color, /** @scrutinizer ignore-type */ $filterIds));
Loading history...
38
        $template = <<<'EOT'
39
digraph application_state_diagram {
40
  graph [
41
    labelloc="t";
42
    fontname="Helvetica"
43
    label="%s";
44
    URL="index.html" target="_parent"
45
  ];
46
  node [shape = box, style = "bold,filled" fillcolor="lightgray"];
47
48
%s
49
%s
50
%s
51
}
52
EOT;
53
54
        return sprintf($template, $profile->title, $nodes, $graph, $appSateWithNoLink);
55
    }
56
57
    /**
58
     * @param list<string>                      $transNodes
0 ignored issues
show
Bug introduced by
The type Koriym\AppStateDiagram\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
59
     * @param array<string, AbstractDescriptor> $descriptors
60
     *
61
     * @return array{0: list<string>, 1: string}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{0: list<string>, 1: string} at position 4 could not be parsed: Expected '}' at position 4, but found 'list'.
Loading history...
62
     */
63
    public function getNodes(array $transNodes, LabelNameInterface $labelName, array $descriptors, ?TaggedProfile $taggedProfile, ?string $color): array
64
    {
65
        /** @var list<string> $ids */
66
        $ids = [];
67
        $dot = '';
68
        foreach ($descriptors as $descriptor) {
69
            if (! in_array($descriptor->id, $transNodes)) {
70
                continue;
71
            }
72
73
            [$id, $deltaDot] = $this->getNode($descriptor, $labelName, $descriptors, $taggedProfile, $color);
74
            $dot .= $deltaDot;
75
            if ($id) {
76
                $ids[] = $id;
77
            }
78
        }
79
80
        return [$ids, $dot];
81
    }
82
83
    /**
84
     * @return list<string>
85
     */
86
    private function getTransNodes(AbstractProfile $profile): array
87
    {
88
        $transNodes = [];
89
        foreach ($profile->links as $link) {
90
            if (! in_array($link->from, $transNodes)) {
91
                $transNodes[] = $link->from;
92
            }
93
94
            if (! in_array($link->to, $transNodes)) {
95
                $transNodes[] = $link->to;
96
            }
97
        }
98
99
        return $transNodes;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $transNodes returns the type array|string[] which is incompatible with the documented return type Koriym\AppStateDiagram\list.
Loading history...
100
    }
101
102
    /**
103
     * @param array<string, AbstractDescriptor> $descriptors
104
     *
105
     * @return array{0: ?string, 1: string}
106
     */
107
    private function getNode(AbstractDescriptor $descriptor, LabelNameInterface $labelName, array $descriptors, ?TaggedProfile $taggedProfile, ?string $color): array
108
    {
109
        $hasDescriptor = $descriptor instanceof SemanticDescriptor && isset($descriptor->descriptor); // @phpstan-ignore-line
110
        if (! $hasDescriptor) {
111
            return [null, ''];
112
        }
113
114
        $props = $this->getNodeProps($descriptor, $labelName, $descriptors);
115
        if ($props === []) {
116
            return [null, ''];
117
        }
118
119
        $inlineDescriptors = '';
120
        foreach ($props as $prop) {
121
            $inlineDescriptors .= sprintf('(%s)<br />', $prop);
122
        }
123
124
        return [$descriptor->id, $this->template($descriptor, $inlineDescriptors, $labelName, $taggedProfile, $color)];
125
    }
126
127
    /**
128
     * @param array<string, AbstractDescriptor> $descriptors
129
     *
130
     * @return list<string>
131
     */
132
    private function getNodeProps(SemanticDescriptor $descriptor, LabelNameInterface $labelName, array $descriptors): array
133
    {
134
        $props = [];
135
        foreach ($descriptor->descriptor as $item) {
136
            if ($this->isSemanticHref($item, $descriptors)) {
137
                assert(is_string($item->href));
138
                $descriptor =  $this->getHref($item->href, $descriptors);
139
                assert($descriptor instanceof SemanticDescriptor);
140
                $props[] = $labelName->getNodeLabel($descriptor);
141
            }
142
143
            $isSemantic = isset($item->type) && $item->type === 'semantic';
144
            if ($isSemantic) {
145
                $props[] = (string) $item->id;
146
            }
147
        }
148
149
        return $props;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $props returns the type array|string[] which is incompatible with the documented return type Koriym\AppStateDiagram\list.
Loading history...
150
    }
151
152
    /**
153
     * @param array<string, AbstractDescriptor> $descriptors
154
     */
155
    private function getHref(string $href, array $descriptors): AbstractDescriptor
156
    {
157
        $pos = strpos($href, '#');
158
        assert(is_int($pos));
159
        $index = substr($href, $pos + 1);
160
161
        return $descriptors[$index];
162
    }
163
164
    /**
165
     * @param array<string, AbstractDescriptor> $descriptors
166
     */
167
    private function isSemanticHref(stdClass $item, array $descriptors): bool
168
    {
169
        if (! property_exists($item, 'href')) {
170
            return false;
171
        }
172
173
        assert(is_string($item->href));
174
175
        $pos = strpos($item->href, '#');
176
        if ($pos === false) {
177
            throw new SharpMissingInHrefException($item->href);
178
        }
179
180
        $id = substr($item->href, $pos + 1);
181
        if (! isset($descriptors[$id])) {
182
            throw new InvalidHrefException($item->href);
183
        }
184
185
        $descriptor = $descriptors[$id];
186
187
        return $descriptor instanceof SemanticDescriptor;
188
    }
189
190
    private function template(AbstractDescriptor $descriptor, string $props, LabelNameInterface $labelName, ?TaggedProfile $taggedProfile, ?string $color): string
191
    {
192
        $base = <<<'EOT'
193
    %s [margin=0.02, label=<<table cellspacing="0" cellpadding="5" border="0"><tr><td>%s<br />%s</td></tr></table>>,shape=box URL="%s" target="_parent"
194
EOT;
195
196
        $url = sprintf('docs/%s.%s.html', $descriptor->type, $descriptor->id);
197
        assert($descriptor instanceof SemanticDescriptor);
198
199
        if (isset($color, $taggedProfile) && in_array($descriptor, $taggedProfile->descriptors)) {
200
            return sprintf($base . ' color="%s"]' . PHP_EOL, $descriptor->id, $labelName->getNodeLabel($descriptor), $props, $url, $color);
201
        }
202
203
        return sprintf($base . ']' . PHP_EOL, $descriptor->id, $labelName->getNodeLabel($descriptor), $props, $url);
204
    }
205
}
206