Issues (57)

src/DrawDiagram.php (5 issues)

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\MissingHashSignInHrefException;
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
/** @psalm-immutable */
24
#[Immutable]
25
final class DrawDiagram
26
{
27
    public function __invoke(AbstractProfile $profile, ?LabelNameInterface $labelName, ?TaggedProfile $taggedProfile = null, ?string $color = null): string
28
    {
29
        $transNodes = $this->getTransNodes($profile);
30
        $labelName = $labelName ?? new LabelName();
31
        $descriptors = $profile->descriptors;
32
        [$filterIds, $nodes] = $this->getNodes($transNodes, $labelName, $descriptors, $taggedProfile, $color);
33
        $edge = new Edge($profile, $taggedProfile, $color);
34
        $graph = (string) $edge;
35
        $appSateWithNoLink = (string) (new AppState($profile->links, $profile->descriptors, $labelName, $taggedProfile, $color, $filterIds));
0 ignored issues
show
$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

35
        $appSateWithNoLink = (string) (new AppState($profile->links, $profile->descriptors, $labelName, $taggedProfile, $color, /** @scrutinizer ignore-type */ $filterIds));
Loading history...
36
        $template = <<<'EOT'
37
digraph application_state_diagram {
38
  graph [
39
    labelloc="t";
40
    fontname="Helvetica"
41
    label="%s";
42
    URL="index.html" target="_parent"
43
  ];
44
  node [shape = box, style = "bold,filled" fillcolor="lightgray"];
45
46
%s
47
%s
48
%s
49
}
50
EOT;
51
52
        return sprintf($template, $profile->title, $nodes, $graph, $appSateWithNoLink);
53
    }
54
55
    /**
56
     * @param list<string>                      $transNodes
0 ignored issues
show
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...
57
     * @param array<string, AbstractDescriptor> $descriptors
58
     *
59
     * @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...
60
     */
61
    public function getNodes(array $transNodes, LabelNameInterface $labelName, array $descriptors, ?TaggedProfile $taggedProfile, ?string $color): array
62
    {
63
        /** @var list<string> $ids */
64
        $ids = [];
65
        $dot = '';
66
        foreach ($descriptors as $descriptor) {
67
            if (! in_array($descriptor->id, $transNodes)) {
68
                continue;
69
            }
70
71
            [$id, $deltaDot] = $this->getNode($descriptor, $labelName, $descriptors, $taggedProfile, $color);
72
            $dot .= $deltaDot;
73
            if ($id) {
74
                $ids[] = $id;
75
            }
76
        }
77
78
        return [$ids, $dot];
79
    }
80
81
    /** @return list<string> */
82
    private function getTransNodes(AbstractProfile $profile): array
83
    {
84
        $transNodes = [];
85
        foreach ($profile->links as $link) {
86
            if (! in_array($link->from, $transNodes)) {
87
                $transNodes[] = $link->from;
88
            }
89
90
            if (! in_array($link->to, $transNodes)) {
91
                $transNodes[] = $link->to;
92
            }
93
        }
94
95
        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...
96
    }
97
98
    /**
99
     * @param array<string, AbstractDescriptor> $descriptors
100
     *
101
     * @return array{0: ?string, 1: string}
102
     */
103
    private function getNode(AbstractDescriptor $descriptor, LabelNameInterface $labelName, array $descriptors, ?TaggedProfile $taggedProfile, ?string $color): array
104
    {
105
        $hasDescriptor = $descriptor instanceof SemanticDescriptor && $descriptor->descriptor !== [];
106
        if (! $hasDescriptor) {
107
            return [null, ''];
108
        }
109
110
        $props = $this->getNodeProps($descriptor, $labelName, $descriptors);
111
        if ($props === []) {
112
            return [null, ''];
113
        }
114
115
        $inlineDescriptors = '';
116
        foreach ($props as $prop) {
117
            $inlineDescriptors .= sprintf('(%s)<br />', $prop);
118
        }
119
120
        return [$descriptor->id, $this->template($descriptor, $inlineDescriptors, $labelName, $taggedProfile, $color)];
121
    }
122
123
    /**
124
     * @param array<string, AbstractDescriptor> $descriptors
125
     *
126
     * @return list<string>
127
     */
128
    private function getNodeProps(SemanticDescriptor $descriptor, LabelNameInterface $labelName, array $descriptors): array
129
    {
130
        $props = [];
131
        foreach ($descriptor->descriptor as $item) {
132
            if ($this->isSemanticHref($item, $descriptors)) {
133
                assert(is_string($item->href));
134
                $descriptor =  $this->getHref($item->href, $descriptors);
135
                assert($descriptor instanceof SemanticDescriptor);
136
                $props[] = $labelName->getNodeLabel($descriptor);
137
            }
138
139
            $isSemantic = isset($item->type) && $item->type === 'semantic';
140
            if ($isSemantic) {
141
                $props[] = (string) $item->id;
142
            }
143
        }
144
145
        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...
146
    }
147
148
    /** @param array<string, AbstractDescriptor> $descriptors */
149
    private function getHref(string $href, array $descriptors): AbstractDescriptor
150
    {
151
        $pos = strpos($href, '#');
152
        assert(is_int($pos));
153
        $index = substr($href, $pos + 1);
154
155
        return $descriptors[$index];
156
    }
157
158
    /** @param array<string, AbstractDescriptor> $descriptors */
159
    private function isSemanticHref(stdClass $item, array $descriptors): bool
160
    {
161
        if (! property_exists($item, 'href')) {
162
            return false;
163
        }
164
165
        assert(is_string($item->href));
166
167
        $pos = strpos($item->href, '#');
168
        if ($pos === false) {
169
            throw new MissingHashSignInHrefException($item->href); // @codeCoverageIgnore
170
        }
171
172
        $id = substr($item->href, $pos + 1);
173
        if (! isset($descriptors[$id])) {
174
            throw new InvalidHrefException($item->href); // @codeCoverageIgnore
175
        }
176
177
        $descriptor = $descriptors[$id];
178
179
        return $descriptor instanceof SemanticDescriptor;
180
    }
181
182
    private function template(AbstractDescriptor $descriptor, string $props, LabelNameInterface $labelName, ?TaggedProfile $taggedProfile, ?string $color): string
183
    {
184
        $base = <<<'EOT'
185
    %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"
186
EOT;
187
188
        $url = sprintf('docs/%s.%s.html', $descriptor->type, $descriptor->id);
189
        assert($descriptor instanceof SemanticDescriptor);
190
191
        if (isset($color, $taggedProfile) && in_array($descriptor, $taggedProfile->descriptors)) {
192
            return sprintf($base . ' color="%s"]' . PHP_EOL, $descriptor->id, $labelName->getNodeLabel($descriptor), $props, $url, $color);
193
        }
194
195
        return sprintf($base . ']' . PHP_EOL, $descriptor->id, $labelName->getNodeLabel($descriptor), $props, $url);
196
    }
197
}
198