Passed
Pull Request — 1.x (#13)
by
unknown
26:54 queued 11:55
created

MermaidRenderer   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 146
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 23
eloc 71
c 1
b 0
f 0
dl 0
loc 146
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A formatTypecast() 0 21 5
F render() 0 104 17
A getClassShortName() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\Schema\Renderer\MermaidRenderer;
6
7
use Cycle\ORM\Relation;
8
use Cycle\ORM\SchemaInterface;
9
use Cycle\Schema\Renderer\MermaidRenderer\Entity\EntityArrow;
10
use Cycle\Schema\Renderer\MermaidRenderer\Entity\EntityTable;
11
use Cycle\Schema\Renderer\MermaidRenderer\Schema\ClassDiagram;
12
use Cycle\Schema\Renderer\SchemaRenderer;
13
use ReflectionException;
14
15
final class MermaidRenderer implements SchemaRenderer
16
{
17
    private const METHOD_FORMAT = '%s: %s';
18
19
    public function render(array $schema): string
20
    {
21
        $class = new ClassDiagram();
22
        $relationMapper = new RelationMapper();
23
24
        $aliases = [];
25
26
        foreach ($schema as $key => $value) {
27
            $role = $key;
28
29
            if (\class_exists($role)) {
30
                $role = $value[SchemaInterface::ROLE];
31
            }
32
33
            $aliases[$key] = $role;
34
        }
35
36
        $aliasCollection = new AliasCollection($aliases);
37
38
        foreach ($schema as $key => $value) {
39
            if (!isset($value[SchemaInterface::COLUMNS])) {
40
                continue;
41
            }
42
43
            $role = $aliasCollection->getAlias($key);
44
45
            $table = new EntityTable($role);
46
            $arrow = new EntityArrow();
47
48
            if (\strpos($key, ':') !== false) {
49
                $table->addAnnotation($key);
50
            }
51
52
            foreach ($value[SchemaInterface::COLUMNS] as $column) {
53
                $typecast = $this->formatTypecast($value[SchemaInterface::TYPECAST][$column] ?? 'string');
54
55
                $table->addRow($typecast, $column);
56
            }
57
58
            foreach ($value[SchemaInterface::RELATIONS] ?? [] as $relationKey => $relation) {
59
                if (!isset($relation[Relation::TARGET], $relation[Relation::SCHEMA])) {
60
                    continue;
61
                }
62
63
                $target = $aliasCollection->getAlias($relation[Relation::TARGET]);
64
65
                if (\class_exists($target)) {
66
                    $target = \lcfirst($this->getClassShortName($target));
67
                }
68
69
                $isNullable = $relation[Relation::SCHEMA][Relation::NULLABLE] ?? false;
70
71
                $mappedRelation = $relationMapper->mapWithNode($relation[Relation::TYPE], $isNullable);
72
73
                switch ($relation[Relation::TYPE]) {
74
                    case Relation::MANY_TO_MANY:
75
                        $throughEntity = $relation[Relation::SCHEMA][Relation::THROUGH_ENTITY] ?? null;
76
77
                        if ($throughEntity) {
78
                            if (\class_exists($throughEntity)) {
79
                                $throughEntity = \lcfirst($this->getClassShortName($throughEntity));
80
                            }
81
82
                            $table->addMethod($relationKey, \sprintf(self::METHOD_FORMAT, $mappedRelation[0], $target));
83
84
                            // tag --* post : posts
85
                            $arrow->addArrow($role, $target, $relationKey, $mappedRelation[1]);
86
                            // postTag ..> tag : tag.posts
87
                            $arrow->addArrow($throughEntity, $role, "$role.$relationKey", '..>');
88
                            // postTag ..> post : tag.posts
89
                            $arrow->addArrow($throughEntity, $target, "$role.$relationKey", '..>');
90
                        }
91
                        break;
92
                    case Relation::EMBEDDED:
93
                        $methodTarget = explode('_', $target);
94
95
                        $prop = isset($methodTarget[2]) ? $methodTarget[2] . '_prop' : $target;
96
97
                        $table->addMethod(
98
                            $prop,
99
                            \sprintf(self::METHOD_FORMAT, $mappedRelation[0], $relation[Relation::TARGET])
100
                        );
101
102
                        $arrow->addArrow($role, $target, $prop, $mappedRelation[1]);
103
                        break;
104
                    default:
105
                        $table->addMethod($relationKey, \sprintf(self::METHOD_FORMAT, $mappedRelation[0], $target));
106
                        $arrow->addArrow($role, $target, $relationKey, $mappedRelation[1]);
107
                }
108
            }
109
110
            foreach ($value[SchemaInterface::CHILDREN] ?? [] as $children) {
111
                $arrow->addArrow($role, $children, 'STI', '--|>');
112
            }
113
114
            if (isset($value[SchemaInterface::PARENT])) {
115
                $arrow->addArrow($value[SchemaInterface::PARENT], $role, 'JTI', '--|>');
116
            }
117
118
            $class->addEntity($table);
119
            $class->addEntity($arrow);
120
        }
121
122
        return (string)$class;
123
    }
124
125
    /**
126
     * @param class-string|object $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|object at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|object.
Loading history...
127
     * @return string
128
     * @throws ReflectionException
129
     */
130
    private function getClassShortName($class): string
131
    {
132
        $ref = new \ReflectionClass($class);
133
134
        return $ref->getShortName();
135
    }
136
137
    /**
138
     * @psalm-suppress MissingParamType
139
     */
140
    private function formatTypecast($typecast): string
141
    {
142
        if (\is_array($typecast)) {
143
            $typecast[0] = $this->getClassShortName($typecast[0]);
144
145
            return \sprintf("[%s, '%s']", "$typecast[0]::class", $typecast[1]);
146
        }
147
148
        if ($typecast instanceof \Closure) {
149
            return 'Closure';
150
        }
151
152
        if ($typecast === 'datetime') {
153
            return $typecast;
154
        }
155
156
        if (\class_exists($typecast)) {
157
            return $this->getClassShortName($typecast) . '::class';
158
        }
159
160
        return \htmlentities($typecast);
161
    }
162
}
163