Passed
Pull Request — master (#120)
by
unknown
11:13
created

GraphBuilder::connectNodes()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
c 1
b 0
f 0
nc 4
nop 3
dl 0
loc 17
rs 9.9332
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;
0 ignored issues
show
Bug introduced by
The type \Illuminate\Database\Eloquent\Model 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...
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
    /**
34
     * Generate a structured text representation of the ER diagram
35
     * 
36
     * @param Collection $models
37
     * @return string
38
     */
39
    public function generateStructuredTextRepresentation(Collection $models) : string
40
    {
41
        $output = "# Entity Relationship Diagram\n\n";
42
        
43
        // First list all models/entities with their attributes
44
        $output .= "## Entities\n\n";
45
        
46
        foreach ($models as $model) {
47
            /** @var Model $model */
48
            $eloquentModel = app($model->getModel());
49
            $output .= "### " . $model->getLabel() . " (`" . $model->getModel() . "`)\n\n";
50
            
51
            // Add table columns if available
52
            if (config('erd-generator.use_db_schema')) {
53
                $columns = $this->getTableColumnsFromModel($eloquentModel);
54
                if (count($columns) > 0) {
55
                    $output .= "#### Attributes:\n\n";
56
                    foreach ($columns as $column) {
57
                        $columnType = config('erd-generator.use_column_types') ? ' (' . $column->getType()->getName() . ')' : '';
58
                        $output .= "- `" . $column->getName() . "`" . $columnType . "\n";
59
                    }
60
                    $output .= "\n";
61
                }
62
            }
63
        }
64
        
65
        // Then list all relationships
66
        $output .= "## Relationships\n\n";
67
        
68
        foreach ($models as $model) {
69
            /** @var Model $model */
70
            if (count($model->getRelations()) > 0) {
71
                $output .= "### " . $model->getLabel() . " Relationships\n\n";
72
                
73
                foreach ($model->getRelations() as $relation) {
74
                    /** @var ModelRelation $relation */
75
                    $relatedModel = $models->first(function ($m) use ($relation) {
76
                        return $m->getModel() === $relation->getRelatedModel();
0 ignored issues
show
Bug introduced by
The method getRelatedModel() does not exist on BeyondCode\ErdGenerator\ModelRelation. ( Ignorable by Annotation )

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

76
                        return $m->getModel() === $relation->/** @scrutinizer ignore-call */ getRelatedModel();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
77
                    });
78
                    
79
                    if ($relatedModel) {
80
                        $output .= "- **" . $relation->getType() . "** `" . $relation->getName() . "` to " . 
81
                                  $relatedModel->getLabel() . " (Local Key: `" . $relation->getLocalKey() . 
0 ignored issues
show
Bug introduced by
Are you sure the usage of $relation->getLocalKey() targeting BeyondCode\ErdGenerator\...Relation::getLocalKey() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
82
                                  "`, Foreign Key: `" . $relation->getForeignKey() . "`)\n";
83
                    }
84
                }
85
                
86
                $output .= "\n";
87
            }
88
        }
89
        
90
        return $output;
91
    }
92
93
    protected function getTableColumnsFromModel(EloquentModel $model)
94
    {
95
        try {
96
97
            $table = $model->getConnection()->getTablePrefix() . $model->getTable();
98
            $schema = $model->getConnection()->getDoctrineSchemaManager($table);
99
            $databasePlatform = $schema->getDatabasePlatform();
100
            $databasePlatform->registerDoctrineTypeMapping('enum', 'string');
101
102
            $database = null;
103
104
            if (strpos($table, '.')) {
105
                list($database, $table) = explode('.', $table);
106
            }
107
108
            return $schema->listTableColumns($table, $database);
109
        } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
110
        }
111
112
        return [];
113
    }
114
115
    protected function getModelLabel(EloquentModel $model, string $label)
116
    {
117
118
        $table = '<<table width="100%" height="100%" border="0" margin="0" cellborder="1" cellspacing="0" cellpadding="10">' . PHP_EOL;
119
        $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;
120
121
        if (config('erd-generator.use_db_schema')) {
122
            $columns = $this->getTableColumnsFromModel($model);
123
            foreach ($columns as $column) {
124
                $label = $column->getName();
125
                if (config('erd-generator.use_column_types')) {
126
                    $label .= ' ('.$column->getType()->getName().')';
127
                }
128
                $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;
129
            }
130
        }
131
132
        $table .= '</table>>';
133
134
        return $table;
135
    }
136
137
    protected function addModelsToGraph(Collection $models)
138
    {
139
        // Add models to graph
140
        $models->map(function (Model $model) {
141
            $eloquentModel = app($model->getModel());
142
            $this->addNodeToGraph($eloquentModel, $model->getNodeName(), $model->getLabel());
143
        });
144
145
        // Create relations
146
        $models->map(function ($model) {
147
            $this->addRelationToGraph($model);
148
        });
149
    }
150
151
    protected function addNodeToGraph(EloquentModel $eloquentModel, string $nodeName, string $label)
152
    {
153
        $node = Node::create($nodeName);
154
        $node->setLabel($this->getModelLabel($eloquentModel, $label));
155
156
        foreach (config('erd-generator.node') as $key => $value) {
157
            $node->{"set{$key}"}($value);
158
        }
159
160
        $this->graph->setNode($node);
161
    }
162
163
    protected function addRelationToGraph(Model $model)
164
    {
165
166
        $modelNode = $this->graph->findNode($model->getNodeName());
167
168
        /** @var ModelRelation $relation */
169
        foreach ($model->getRelations() as $relation) {
170
            $relatedModelNode = $this->graph->findNode($relation->getModelNodeName());
171
172
            if ($relatedModelNode !== null) {
173
                $this->connectByRelation($model, $relation, $modelNode, $relatedModelNode);
174
            }
175
        }
176
    }
177
178
    /**
179
     * @param Node $modelNode
180
     * @param Node $relatedModelNode
181
     * @param ModelRelation $relation
182
     */
183
    protected function connectNodes(Node $modelNode, Node $relatedModelNode, ModelRelation $relation): void
184
    {
185
        $edge = Edge::create($modelNode, $relatedModelNode);
186
        $edge->setFromPort($relation->getLocalKey());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $relation->getLocalKey() targeting BeyondCode\ErdGenerator\...Relation::getLocalKey() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
187
        $edge->setToPort($relation->getForeignKey());
188
        $edge->setLabel(' ');
189
        $edge->setXLabel($relation->getType() . PHP_EOL . $relation->getName());
190
191
        foreach (config('erd-generator.edge') as $key => $value) {
192
            $edge->{"set{$key}"}($value);
193
        }
194
195
        foreach (config('erd-generator.relations.' . $relation->getType(), []) as $key => $value) {
196
            $edge->{"set{$key}"}($value);
197
        }
198
199
        $this->graph->link($edge);
200
    }
201
202
    /**
203
     * @param Model $model
204
     * @param ModelRelation $relation
205
     * @param Node $modelNode
206
     * @param Node $relatedModelNode
207
     * @return void
208
     */
209
    protected function connectBelongsToMany(
210
        Model $model,
211
        ModelRelation $relation,
212
        Node $modelNode,
213
        Node $relatedModelNode
214
    ): void {
215
        $relationName = $relation->getName();
216
        $eloquentModel = app($model->getModel());
217
218
        /** @var BelongsToMany $eloquentRelation */
219
        $eloquentRelation = $eloquentModel->$relationName();
220
221
        if (!$eloquentRelation instanceof BelongsToMany) {
0 ignored issues
show
introduced by
$eloquentRelation is always a sub-type of Illuminate\Database\Eloq...Relations\BelongsToMany.
Loading history...
222
            return;
223
        }
224
225
        $pivotClass = $eloquentRelation->getPivotClass();
226
227
        try {
228
            /** @var EloquentModel $relationModel */
229
            $pivotModel = app($pivotClass);
230
            $pivotModel->setTable($eloquentRelation->getTable());
231
            $label = (new \ReflectionClass($pivotClass))->getShortName();
232
            $pivotTable = $eloquentRelation->getTable();
233
            $this->addNodeToGraph($pivotModel, $pivotTable, $label);
234
235
            $pivotModelNode = $this->graph->findNode($pivotTable);
236
237
            $relation = new ModelRelation(
238
                $relationName,
239
                'BelongsToMany',
240
                $model->getModel(),
241
                $eloquentRelation->getParent()->getKeyName(),
242
                $eloquentRelation->getForeignPivotKeyName()
243
            );
244
245
            $this->connectNodes($modelNode, $pivotModelNode, $relation);
246
247
            $relation = new ModelRelation(
248
                $relationName,
249
                'BelongsToMany',
250
                $model->getModel(),
251
                $eloquentRelation->getRelatedPivotKeyName(),
252
                $eloquentRelation->getRelated()->getKeyName()
253
            );
254
255
            $this->connectNodes($pivotModelNode, $relatedModelNode, $relation);
256
        } catch (\ReflectionException $e){}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
257
    }
258
259
    /**
260
     * @param Model $model
261
     * @param ModelRelation $relation
262
     * @param Node $modelNode
263
     * @param Node $relatedModelNode
264
     */
265
    protected function connectByRelation(
266
        Model $model,
267
        ModelRelation $relation,
268
        Node $modelNode,
269
        Node $relatedModelNode
270
    ): void {
271
272
        if ($relation->getType() === 'BelongsToMany') {
273
            $this->connectBelongsToMany($model, $relation, $modelNode, $relatedModelNode);
274
            return;
275
        }
276
277
        $this->connectNodes($modelNode, $relatedModelNode, $relation);
278
    }
279
}
280