Completed
Pull Request — master (#69)
by reallyli
06:58
created

GenerateDiagramCommand::getOutputFileName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace BeyondCode\ErdGenerator;
4
5
use BeyondCode\ErdGenerator\Model as GraphModel;
6
use Illuminate\Console\Command;
7
use Illuminate\Support\Collection;
8
use phpDocumentor\GraphViz\Graph;
9
use ReflectionClass;
10
use Illuminate\Support\Arr;
11
use Illuminate\Support\Str;
12
13
class GenerateDiagramCommand extends Command
14
{
15
    const FORMAT_TEXT = 'text';
16
17
    const DEFAULT_FILENAME = 'graph';
18
19
    /**
20
     * @var string
21
     */
22
    protected $modelNamespace = 'Modules\{#}\Entities\{#}';
23
24
    /**
25
     * @var string
26
     */
27
    protected $modulePath = 'Modules/{#}';
28
29
    /**
30
     * The console command name.
31
     *
32
     * @var string
33
     */
34
    protected $signature = 'generate:model-erd {models} {filename?} {--format=png}';
35
36
    /**
37
     * The console command description.
38
     *
39
     * @var string
40
     */
41
    protected $description = 'Generate ER diagram.';
42
43
    /** @var ModelFinder */
44
    protected $modelFinder;
45
46
    /** @var RelationFinder */
47
    protected $relationFinder;
48
49
    /** @var Graph */
50
    protected $graph;
51
52
    /** @var GraphBuilder */
53
    protected $graphBuilder;
54
55
    /**
56
     * GenerateDiagramCommand constructor.
57
     *
58
     * @param ModelFinder $modelFinder
59
     * @param RelationFinder $relationFinder
60
     * @param GraphBuilder $graphBuilder
61
     */
62
    public function __construct(ModelFinder $modelFinder, RelationFinder $relationFinder, GraphBuilder $graphBuilder)
63
    {
64
        parent::__construct();
65
66
        $this->relationFinder = $relationFinder;
67
        $this->modelFinder = $modelFinder;
68
        $this->graphBuilder = $graphBuilder;
69
    }
70
71
    /**
72
     * @throws \phpDocumentor\GraphViz\Exception
73
     */
74
    public function handle()
75
    {
76
        $models = $this->getModelsThatShouldBeInspected();
77
78
        $this->info("Found {$models->count()} models.");
79
        $this->info("Inspecting model relations.");
80
81
        $bar = $this->output->createProgressBar($models->count());
82
83
        $models->transform(function ($model) use ($bar) {
84
            $bar->advance();
85
            return new GraphModel(
86
                $model,
87
                (new ReflectionClass($model))->getShortName(),
88
                $this->relationFinder->getModelRelations($model)
89
            );
90
        });
91
92
        $graph = $this->graphBuilder->buildGraph($models);
93
94
        if ($this->option('format') === self::FORMAT_TEXT) {
95
            $this->info($graph->__toString());
96
            return;
97
        }
98
99
        $graph->export($this->option('format'), $this->getOutputFileName());
100
101
        $this->info(PHP_EOL);
102
        $this->info('Wrote diagram to ' . $this->getOutputFileName());
103
    }
104
105
    /**
106
     * @return string
107
     */
108
    protected function getOutputFileName(): string
109
    {
110
        return $this->argument('filename') ?:
111
            static::DEFAULT_FILENAME . '.' . $this->option('format');
112
    }
113
114
    /**
115
     * @param array $directories
116
     * @return Collection
117
     */
118
    protected function getAllModelsFromEachDirectory(array $directories): Collection
119
    {
120
        return collect($directories)
121
            ->map(function ($directory) {
122
                return $this->modelFinder->getModelsInDirectory($directory)->all();
123
            })
124
            ->flatten();
125
    }
126
127
    /**
128
     * @return Collection
129
     */
130
    protected function getModelsThatShouldBeInspected(): Collection
131
    {
132
        $models = explode(',', $this->argument('models'));
133
134
        $modulePaths = [];
135
        $modelNamespaces = [];
136
        foreach ($models as $model) {
137
            $path = explode('::', $model);
138
            $modelNamespaces[] = Str::replaceArray('{#}', $path, $this->modelNamespace);
139
            $modulePaths[] = Str::replaceArray('{#}', $path, $this->modulePath);
140
        }
141
142
        $directories = array_values(array_filter($modulePaths));
143
        $modelsFromDirectories = $this->getAllModelsFromEachDirectory($directories)->intersect($modelNamespaces)
144
            ->values();
145
146
        foreach ($modelsFromDirectories as $modelsFromDirectory) {
147
            $reflector = new \ReflectionClass($modelsFromDirectory);
148
            $catFile = file_get_contents($reflector->getFileName());
149
            preg_match_all('/(\w+)::class/', $catFile, $classRelations);
150
            preg_match_all('/Modules\\\\(\w+)\\\\Entities\\\\(\w+)/', $catFile, $namespaceRelations);
151
            $relationsFromNamespaces = Arr::last($namespaceRelations);
0 ignored issues
show
Bug introduced by
It seems like $namespaceRelations can also be of type null; however, Illuminate\Support\Arr::last() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
152
            $namespaces = array_combine($relationsFromNamespaces, Arr::first($namespaceRelations));
0 ignored issues
show
Bug introduced by
It seems like $namespaceRelations can also be of type null; however, Illuminate\Support\Arr::first() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
153
            $relations = Arr::last($classRelations);
0 ignored issues
show
Bug introduced by
It seems like $classRelations can also be of type null; however, Illuminate\Support\Arr::last() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
154
155
            foreach ($relations as $relation) {
156
                if (! in_array($relation, $relationsFromNamespaces)) {
157
                    $baseDir = substr($modelsFromDirectory, 0, strrpos($modelsFromDirectory, "\\"));
158
                    $modelsFromDirectories->push($baseDir .'\\' . $relation);
159
                }
160
            }
161
162
            foreach ($namespaces as $name => $namespace) {
163
                if (in_array($name, $relations)) {
164
                    $modelsFromDirectories->push($namespace);
165
                }
166
            }
167
        }
168
169
        return $modelsFromDirectories;
170
    }
171
}
172