1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace BeyondCode\ErdGenerator; |
4
|
|
|
|
5
|
|
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany; |
6
|
|
|
use phpDocumentor\GraphViz\Graph; |
7
|
|
|
use Illuminate\Support\Collection; |
8
|
|
|
use phpDocumentor\GraphViz\Node; |
9
|
|
|
use \Illuminate\Database\Eloquent\Model as EloquentModel; |
10
|
|
|
|
11
|
|
|
class GraphBuilder |
12
|
|
|
{ |
13
|
|
|
/** @var Graph */ |
14
|
|
|
private $graph; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @param $models |
18
|
|
|
* @return Graph |
19
|
|
|
*/ |
20
|
|
|
public function buildGraph(Collection $models) : Graph |
21
|
|
|
{ |
22
|
|
|
$this->graph = new Graph(); |
23
|
|
|
|
24
|
|
|
foreach (config('erd-generator.graph') as $key => $value) { |
25
|
|
|
$this->graph->{"set${key}"}($value); |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
$this->addModelsToGraph($models); |
29
|
|
|
|
30
|
|
|
return $this->graph; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
protected function getTableColumnsFromModel(EloquentModel $model) |
34
|
|
|
{ |
35
|
|
|
try { |
36
|
|
|
|
37
|
|
|
$table = $model->getConnection()->getTablePrefix() . $model->getTable(); |
38
|
|
|
$schema = $model->getConnection()->getDoctrineSchemaManager($table); |
|
|
|
|
39
|
|
|
$databasePlatform = $schema->getDatabasePlatform(); |
40
|
|
|
$databasePlatform->registerDoctrineTypeMapping('enum', 'string'); |
41
|
|
|
|
42
|
|
|
$database = null; |
43
|
|
|
|
44
|
|
|
if (strpos($table, '.')) { |
45
|
|
|
list($database, $table) = explode('.', $table); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
return $schema->listTableColumns($table, $database); |
49
|
|
|
} catch (\Throwable $e) { |
|
|
|
|
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
return []; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
protected function getModelLabel(EloquentModel $model, string $label) |
56
|
|
|
{ |
57
|
|
|
|
58
|
|
|
$table = '<<table width="100%" height="100%" border="0" margin="0" cellborder="1" cellspacing="0" cellpadding="10">' . PHP_EOL; |
59
|
|
|
$table .= '<tr width="100%"><td width="100%" bgcolor="'.config('erd-generator.table.header_background_color').'"><font color="'.config('erd-generator.table.header_font_color').'">' . $label . '</font></td></tr>' . PHP_EOL; |
60
|
|
|
|
61
|
|
|
if (config('erd-generator.use_db_schema')) { |
62
|
|
|
$columns = $this->getTableColumnsFromModel($model); |
63
|
|
|
foreach ($columns as $column) { |
64
|
|
|
$label = $column->getName(); |
65
|
|
|
if (config('erd-generator.use_column_types')) { |
66
|
|
|
$label .= ' ('.$column->getType()->getName().')'; |
67
|
|
|
} |
68
|
|
|
$table .= '<tr width="100%"><td port="' . $column->getName() . '" align="left" width="100%" bgcolor="'.config('erd-generator.table.row_background_color').'"><font color="'.config('erd-generator.table.row_font_color').'" >' . $label . '</font></td></tr>' . PHP_EOL; |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
$table .= '</table>>'; |
73
|
|
|
|
74
|
|
|
return $table; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
protected function addModelsToGraph(Collection $models) |
78
|
|
|
{ |
79
|
|
|
// Add models to graph |
80
|
|
|
$models->map(function (Model $model) { |
81
|
|
|
$eloquentModel = app($model->getModel()); |
82
|
|
|
$this->addNodeToGraph($eloquentModel, $model->getNodeName(), $model->getLabel()); |
83
|
|
|
}); |
84
|
|
|
|
85
|
|
|
// Create relations |
86
|
|
|
$models->map(function ($model) { |
87
|
|
|
$this->addRelationToGraph($model); |
88
|
|
|
}); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
protected function addNodeToGraph(EloquentModel $eloquentModel, string $nodeName, string $label) |
92
|
|
|
{ |
93
|
|
|
$node = Node::create($nodeName); |
94
|
|
|
$node->setLabel($this->getModelLabel($eloquentModel, $label)); |
95
|
|
|
|
96
|
|
|
foreach (config('erd-generator.node') as $key => $value) { |
97
|
|
|
$node->{"set${key}"}($value); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
$this->graph->setNode($node); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
protected function addRelationToGraph(Model $model) |
104
|
|
|
{ |
105
|
|
|
|
106
|
|
|
$modelNode = $this->graph->findNode($model->getNodeName()); |
107
|
|
|
|
108
|
|
|
/** @var ModelRelation $relation */ |
109
|
|
|
foreach ($model->getRelations() as $relation) { |
110
|
|
|
$relatedModelNode = $this->graph->findNode($relation->getModelNodeName()); |
111
|
|
|
|
112
|
|
|
if ($relatedModelNode !== null) { |
113
|
|
|
$this->connectByRelation($model, $relation, $modelNode, $relatedModelNode); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* @param Node $modelNode |
120
|
|
|
* @param Node $relatedModelNode |
121
|
|
|
* @param ModelRelation $relation |
122
|
|
|
*/ |
123
|
|
|
protected function connectNodes(Node $modelNode, Node $relatedModelNode, ModelRelation $relation): void |
124
|
|
|
{ |
125
|
|
|
$edge = Edge::create($modelNode, $relatedModelNode); |
126
|
|
|
$edge->setFromPort($relation->getLocalKey()); |
127
|
|
|
$edge->setToPort($relation->getForeignKey()); |
128
|
|
|
$edge->setLabel(' '); |
129
|
|
|
$edge->setXLabel($relation->getType() . PHP_EOL . $relation->getName()); |
130
|
|
|
|
131
|
|
|
foreach (config('erd-generator.edge') as $key => $value) { |
132
|
|
|
$edge->{"set${key}"}($value); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
foreach (config('erd-generator.relations.' . $relation->getType(), []) as $key => $value) { |
136
|
|
|
$edge->{"set${key}"}($value); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
$this->graph->link($edge); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @param Model $model |
144
|
|
|
* @param ModelRelation $relation |
145
|
|
|
* @param Node $modelNode |
146
|
|
|
* @param Node $relatedModelNode |
147
|
|
|
* @return void |
148
|
|
|
*/ |
149
|
|
|
protected function connectBelongsToMany( |
150
|
|
|
Model $model, |
151
|
|
|
ModelRelation $relation, |
152
|
|
|
Node $modelNode, |
153
|
|
|
Node $relatedModelNode |
154
|
|
|
): void { |
155
|
|
|
$relationName = $relation->getName(); |
156
|
|
|
$eloquentModel = app($model->getModel()); |
157
|
|
|
|
158
|
|
|
/** @var BelongsToMany $eloquentRelation */ |
159
|
|
|
$eloquentRelation = $eloquentModel->$relationName(); |
160
|
|
|
|
161
|
|
|
if (!$eloquentRelation instanceof BelongsToMany) { |
162
|
|
|
return; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
$pivotClass = $eloquentRelation->getPivotClass(); |
166
|
|
|
|
167
|
|
|
try { |
168
|
|
|
/** @var EloquentModel $relationModel */ |
169
|
|
|
$pivotModel = app($pivotClass); |
170
|
|
|
$pivotModel->setTable($eloquentRelation->getTable()); |
171
|
|
|
$label = (new \ReflectionClass($pivotClass))->getShortName(); |
172
|
|
|
$pivotTable = $eloquentRelation->getTable(); |
173
|
|
|
$this->addNodeToGraph($pivotModel, $pivotTable, $label); |
174
|
|
|
|
175
|
|
|
$pivotModelNode = $this->graph->findNode($pivotTable); |
176
|
|
|
|
177
|
|
|
$relation = new ModelRelation( |
178
|
|
|
$relationName, |
179
|
|
|
'BelongsToMany', |
180
|
|
|
$model->getModel(), |
181
|
|
|
$eloquentRelation->getParent()->getKeyName(), |
182
|
|
|
$eloquentRelation->getForeignPivotKeyName() |
183
|
|
|
); |
184
|
|
|
|
185
|
|
|
$this->connectNodes($modelNode, $pivotModelNode, $relation); |
186
|
|
|
|
187
|
|
|
$relation = new ModelRelation( |
188
|
|
|
$relationName, |
189
|
|
|
'BelongsToMany', |
190
|
|
|
$model->getModel(), |
191
|
|
|
$eloquentRelation->getRelatedPivotKeyName(), |
192
|
|
|
$eloquentRelation->getRelated()->getKeyName() |
193
|
|
|
); |
194
|
|
|
|
195
|
|
|
$this->connectNodes($pivotModelNode, $relatedModelNode, $relation); |
196
|
|
|
} catch (\ReflectionException $e){} |
|
|
|
|
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* @param Model $model |
201
|
|
|
* @param ModelRelation $relation |
202
|
|
|
* @param Node $modelNode |
203
|
|
|
* @param Node $relatedModelNode |
204
|
|
|
*/ |
205
|
|
|
protected function connectByRelation( |
206
|
|
|
Model $model, |
207
|
|
|
ModelRelation $relation, |
208
|
|
|
Node $modelNode, |
209
|
|
|
Node $relatedModelNode |
210
|
|
|
): void { |
211
|
|
|
|
212
|
|
|
if ($relation->getType() === 'BelongsToMany') { |
213
|
|
|
$this->connectBelongsToMany($model, $relation, $modelNode, $relatedModelNode); |
214
|
|
|
return; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
$this->connectNodes($modelNode, $relatedModelNode, $relation); |
218
|
|
|
} |
219
|
|
|
} |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.