Passed
Pull Request — 1.x (#13)
by
unknown
27:11 queued 12:09
created

MermaidRenderer   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 21
eloc 66
c 1
b 0
f 0
dl 0
loc 132
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A formatTypecast() 0 21 5
F render() 0 95 15
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\Exception\RelationNotFoundException;
12
use Cycle\Schema\Renderer\MermaidRenderer\Schema\ClassDiagram;
13
use Cycle\Schema\Renderer\SchemaRenderer;
14
15
final class MermaidRenderer implements SchemaRenderer
16
{
17
    private const METHOD_FORMAT = '%s: %s';
18
19
    /**
20
     * @throws RelationNotFoundException
21
     */
22
    public function render(array $schema): string
23
    {
24
        $class = new ClassDiagram();
25
        $relationMapper = new RelationMapper();
26
27
        foreach ($schema as $key => $value) {
28
            if (!isset($value[SchemaInterface::COLUMNS])) {
29
                continue;
30
            }
31
32
            $role = $key;
33
34
            if (class_exists($role)) {
35
                $role = $value[SchemaInterface::ROLE];
36
            }
37
38
            if (strpos($role, ':') !== false) {
39
                $role = $key;
40
            }
41
42
            $table = new EntityTable($role);
43
            $arrow = new EntityArrow();
44
45
            foreach ($value[SchemaInterface::COLUMNS] as $column) {
46
                $typecast = $this->formatTypecast($value[SchemaInterface::TYPECAST][$column] ?? 'string');
47
48
                $table->addRow($typecast, $column);
49
            }
50
51
            foreach ($value[SchemaInterface::RELATIONS] ?? [] as $relationKey => $relation) {
52
                if (!isset($relation[Relation::TARGET], $relation[Relation::SCHEMA])) {
53
                    continue;
54
                }
55
56
                $target = $relation[Relation::TARGET];
57
58
                if (class_exists($target)) {
59
                    $target = lcfirst($this->getClassShortName($target));
60
                }
61
62
                $isNullable = $relation[Relation::SCHEMA][Relation::NULLABLE] ?? false;
63
64
                $mappedRelation = $relationMapper->mapWithNode($relation[Relation::TYPE], $isNullable);
65
66
                switch ($relation[Relation::TYPE]) {
67
                    case Relation::MANY_TO_MANY:
68
                        $throughEntity = $relation[Relation::SCHEMA][Relation::THROUGH_ENTITY] ?? null;
69
70
                        if ($throughEntity) {
71
                            if (class_exists($throughEntity)) {
72
                                $throughEntity = lcfirst($this->getClassShortName($throughEntity));
73
                            }
74
75
                            $table->addMethod($relationKey, sprintf(self::METHOD_FORMAT, $mappedRelation[0], $target));
76
77
                            // tag --* post : posts
78
                            $arrow->addArrow($role, $target, $relationKey, $mappedRelation[1]);
79
                            // postTag ..> tag : tag.posts
80
                            $arrow->addArrow($throughEntity, $role, "$role.$relationKey", '..>');
81
                            // postTag ..> post : tag.posts
82
                            $arrow->addArrow($throughEntity, $target, "$role.$relationKey", '..>');
83
                        }
84
                        break;
85
                    case Relation::EMBEDDED:
86
                        // explode string like user:credentials
87
                        $methodTarget = explode(':', $target);
88
89
                        $table->addMethod(
90
                            $methodTarget[1] ?? $target,
91
                            sprintf(self::METHOD_FORMAT, $mappedRelation[0], $relationKey)
92
                        );
93
94
                        $arrowTarget = str_replace(':', '&#58', $target);
95
96
                        $arrow->addArrow($role, $relationKey, $arrowTarget, $mappedRelation[1]);
97
                        break;
98
                    default:
99
                        $table->addMethod($relationKey, sprintf(self::METHOD_FORMAT, $mappedRelation[0], $target));
100
                        $arrow->addArrow($role, $target, $relationKey, $mappedRelation[1]);
101
                }
102
            }
103
104
            foreach ($value[SchemaInterface::CHILDREN] ?? [] as $children) {
105
                $arrow->addArrow($role, $children, 'STI', '--|>');
106
            }
107
108
            if (isset($value[SchemaInterface::PARENT])) {
109
                $arrow->addArrow($value[SchemaInterface::PARENT], $role, 'JTI', '--|>');
110
            }
111
112
            $class->addEntity($table);
113
            $class->addEntity($arrow);
114
        }
115
116
        return (string)$class;
117
    }
118
119
    private function getClassShortName($class): string
120
    {
121
        $ref = new \ReflectionClass($class);
122
123
        return $ref->getShortName();
124
    }
125
126
    private function formatTypecast($typecast): string
127
    {
128
        if (is_array($typecast)) {
129
            $typecast[0] = $this->getClassShortName($typecast[0]);
130
131
            return sprintf("[%s, '%s']", "$typecast[0]::class", $typecast[1]);
132
        }
133
134
        if ($typecast instanceof \Closure) {
135
            return 'Closure';
136
        }
137
138
        if (class_exists($typecast)) {
139
            if ($typecast === 'datetime') {
140
                return $typecast;
141
            }
142
143
            return $this->getClassShortName($typecast) . '::class';
144
        }
145
146
        return \htmlentities($typecast);
147
    }
148
}
149