Skip to content

Commit 975d64e

Browse files
committed
Split up model and relation finder classes
1 parent 908779e commit 975d64e

File tree

5 files changed

+168
-148
lines changed

5 files changed

+168
-148
lines changed

src/GenerateDiagramCommand.php

Lines changed: 10 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,10 @@
33
namespace BeyondCode\ErdGenerator;
44

55
use ReflectionClass;
6-
use ReflectionMethod;
7-
use PhpParser\NodeTraverser;
8-
use PhpParser\ParserFactory;
9-
use PhpParser\Node\Stmt\Class_;
106
use Illuminate\Console\Command;
117
use phpDocumentor\GraphViz\Graph;
128
use Illuminate\Support\Collection;
13-
use Illuminate\Filesystem\Filesystem;
14-
use Illuminate\Database\Eloquent\Model;
15-
use PhpParser\NodeVisitor\NameResolver;
169
use BeyondCode\ErdGenerator\Model as GraphModel;
17-
use Illuminate\Database\Eloquent\Relations\Relation;
18-
use Illuminate\Database\Eloquent\Relations\BelongsTo;
19-
use Illuminate\Database\Eloquent\Relations\MorphToMany;
20-
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
21-
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
2210

2311
class GenerateDiagramCommand extends Command
2412
{
@@ -38,20 +26,24 @@ class GenerateDiagramCommand extends Command
3826
*/
3927
protected $description = 'Generate ER diagram.';
4028

41-
/** @var Filesystem */
42-
protected $filesystem;
29+
/** @var ModelFinder */
30+
protected $modelFinder;
31+
32+
/** @var RelationFinder */
33+
protected $relationFinder;
4334

4435
/** @var Graph */
4536
protected $graph;
4637

4738
/** @var GraphBuilder */
4839
protected $graphBuilder;
4940

50-
public function __construct(Filesystem $filesystem, GraphBuilder $graphBuilder)
41+
public function __construct(ModelFinder $modelFinder, RelationFinder $relationFinder, GraphBuilder $graphBuilder)
5142
{
5243
parent::__construct();
5344

54-
$this->filesystem = $filesystem;
45+
$this->relationFinder = $relationFinder;
46+
$this->modelFinder = $modelFinder;
5547
$this->graphBuilder = $graphBuilder;
5648
}
5749

@@ -69,7 +61,7 @@ public function handle()
6961
return new GraphModel(
7062
$model,
7163
(new ReflectionClass($model))->getShortName(),
72-
$this->getModelRelations($model)
64+
$this->relationFinder->getModelRelations($model)
7365
);
7466
});
7567

@@ -99,114 +91,8 @@ protected function getAllModelsFromEachDirectory(array $directories): Collection
9991
{
10092
return collect($directories)
10193
->map(function ($directory) {
102-
return $this->getModelInstancesInDirectory($directory)->all();
94+
return $this->modelFinder->getModelsInDirectory($directory)->all();
10395
})
10496
->flatten();
10597
}
106-
107-
protected function getModelInstancesInDirectory(string $directory): Collection
108-
{
109-
return collect($this->filesystem->files($directory))->map(function ($path) {
110-
return $this->getFullyQualifiedClassNameFromFile($path);
111-
})->filter(function (string $className) {
112-
return !empty($className);
113-
})->filter(function (string $className) {
114-
return is_subclass_of($className, Model::class);
115-
});
116-
}
117-
118-
protected function getFullyQualifiedClassNameFromFile(string $path): string
119-
{
120-
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
121-
122-
$traverser = new NodeTraverser();
123-
$traverser->addVisitor(new NameResolver());
124-
125-
$code = file_get_contents($path);
126-
127-
$statements = $parser->parse($code);
128-
129-
$statements = $traverser->traverse($statements);
130-
131-
return collect($statements[0]->stmts)
132-
->filter(function ($statement) {
133-
return $statement instanceof Class_;
134-
})
135-
->map(function (Class_ $statement) {
136-
return $statement->namespacedName->toString();
137-
})
138-
->first() ?? '';
139-
}
140-
141-
protected function getModelRelations($model)
142-
{
143-
$class = new ReflectionClass($model);
144-
145-
$traitMethods = Collection::make($class->getTraits())->map(function ($trait) {
146-
return Collection::make($trait->getMethods(ReflectionMethod::IS_PUBLIC));
147-
})->flatten();
148-
149-
$methods = Collection::make($class->getMethods(ReflectionMethod::IS_PUBLIC))
150-
->merge($traitMethods)
151-
->reject(function (ReflectionMethod $method) use ($model) {
152-
return $method->class !== $model;
153-
})->reject(function (ReflectionMethod $method) use ($model) {
154-
return $method->getNumberOfParameters() > 0;
155-
});
156-
157-
$relations = Collection::make();
158-
159-
$methods->map(function (ReflectionMethod $method) use ($model, &$relations) {
160-
$relations = $relations->merge($this->getRelationshipFromMethodAndModel($method, $model));
161-
});
162-
163-
$relations = $relations->filter();
164-
165-
return $relations;
166-
}
167-
168-
protected function getParentKey(string $qualifiedKeyName)
169-
{
170-
$segments = explode('.', $qualifiedKeyName);
171-
172-
return end($segments);
173-
}
174-
175-
protected function getRelationshipFromMethodAndModel(ReflectionMethod $method, string $model)
176-
{
177-
try {
178-
$return = $method->invoke(app($model));
179-
180-
if ($return instanceof Relation) {
181-
$localKey = null;
182-
$foreignKey = null;
183-
184-
if ($return instanceof HasOneOrMany) {
185-
$localKey = $this->getParentKey($return->getQualifiedParentKeyName());
186-
$foreignKey = $return->getForeignKeyName();
187-
}
188-
189-
if ($return instanceof BelongsTo) {
190-
$foreignKey = $this->getParentKey($return->getQualifiedOwnerKeyName());
191-
$localKey = $return->getForeignKey();
192-
}
193-
194-
if ($return instanceof BelongsToMany && ! $return instanceof MorphToMany) {
195-
$foreignKey = $this->getParentKey($return->getQualifiedOwnerKeyName());
196-
$localKey = $return->getForeignKey();
197-
}
198-
199-
return [
200-
$method->getName() => new ModelRelation(
201-
$method->getShortName(),
202-
(new ReflectionClass($return))->getShortName(),
203-
(new ReflectionClass($return->getRelated()))->getName(),
204-
$localKey,
205-
$foreignKey
206-
)
207-
];
208-
}
209-
} catch (\Throwable $e) {}
210-
return null;
211-
}
21298
}

src/ModelFinder.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace BeyondCode\ErdGenerator;
4+
5+
use PhpParser\NodeTraverser;
6+
use PhpParser\ParserFactory;
7+
use PhpParser\Node\Stmt\Class_;
8+
use Illuminate\Support\Collection;
9+
use Illuminate\Filesystem\Filesystem;
10+
use PhpParser\NodeVisitor\NameResolver;
11+
use Illuminate\Database\Eloquent\Model as EloquentModel;
12+
13+
class ModelFinder
14+
{
15+
16+
/** @var Filesystem */
17+
protected $filesystem;
18+
19+
public function __construct(Filesystem $filesystem)
20+
{
21+
$this->filesystem = $filesystem;
22+
}
23+
24+
public function getModelsInDirectory(string $directory): Collection
25+
{
26+
return Collection::make($this->filesystem->files($directory))->map(function ($path) {
27+
return $this->getFullyQualifiedClassNameFromFile($path);
28+
})->filter(function (string $className) {
29+
return !empty($className);
30+
})->filter(function (string $className) {
31+
return is_subclass_of($className, EloquentModel::class);
32+
});
33+
}
34+
35+
protected function getFullyQualifiedClassNameFromFile(string $path): string
36+
{
37+
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
38+
39+
$traverser = new NodeTraverser();
40+
$traverser->addVisitor(new NameResolver());
41+
42+
$code = file_get_contents($path);
43+
44+
$statements = $parser->parse($code);
45+
46+
$statements = $traverser->traverse($statements);
47+
48+
return collect($statements[0]->stmts)
49+
->filter(function ($statement) {
50+
return $statement instanceof Class_;
51+
})
52+
->map(function (Class_ $statement) {
53+
return $statement->namespacedName->toString();
54+
})
55+
->first() ?? '';
56+
}
57+
58+
}

src/RelationFinder.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
namespace BeyondCode\ErdGenerator;
4+
5+
use ReflectionClass;
6+
use ReflectionMethod;
7+
use Illuminate\Support\Collection;
8+
use Illuminate\Database\Eloquent\Relations\Relation;
9+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
10+
use Illuminate\Database\Eloquent\Relations\MorphToMany;
11+
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
12+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
13+
14+
class RelationFinder
15+
{
16+
/**
17+
* Return all relations from a fully qualified model class name.
18+
*
19+
* @param string $model
20+
* @return Collection
21+
*/
22+
public function getModelRelations(string $model)
23+
{
24+
$class = new ReflectionClass($model);
25+
26+
$traitMethods = Collection::make($class->getTraits())->map(function ($trait) {
27+
return Collection::make($trait->getMethods(ReflectionMethod::IS_PUBLIC));
28+
})->flatten();
29+
30+
$methods = Collection::make($class->getMethods(ReflectionMethod::IS_PUBLIC))
31+
->merge($traitMethods)
32+
->reject(function (ReflectionMethod $method) use ($model) {
33+
return $method->class !== $model;
34+
})->reject(function (ReflectionMethod $method) use ($model) {
35+
return $method->getNumberOfParameters() > 0;
36+
});
37+
38+
$relations = Collection::make();
39+
40+
$methods->map(function (ReflectionMethod $method) use ($model, &$relations) {
41+
$relations = $relations->merge($this->getRelationshipFromMethodAndModel($method, $model));
42+
});
43+
44+
$relations = $relations->filter();
45+
46+
return $relations;
47+
}
48+
49+
protected function getParentKey(string $qualifiedKeyName)
50+
{
51+
$segments = explode('.', $qualifiedKeyName);
52+
53+
return end($segments);
54+
}
55+
56+
protected function getRelationshipFromMethodAndModel(ReflectionMethod $method, string $model)
57+
{
58+
try {
59+
$return = $method->invoke(app($model));
60+
61+
if ($return instanceof Relation) {
62+
$localKey = null;
63+
$foreignKey = null;
64+
65+
if ($return instanceof HasOneOrMany) {
66+
$localKey = $this->getParentKey($return->getQualifiedParentKeyName());
67+
$foreignKey = $return->getForeignKeyName();
68+
}
69+
70+
if ($return instanceof BelongsTo) {
71+
$foreignKey = $this->getParentKey($return->getQualifiedOwnerKeyName());
72+
$localKey = $return->getForeignKey();
73+
}
74+
75+
if ($return instanceof BelongsToMany && ! $return instanceof MorphToMany) {
76+
$foreignKey = $this->getParentKey($return->getQualifiedOwnerKeyName());
77+
$localKey = $return->getForeignKey();
78+
}
79+
80+
return [
81+
$method->getName() => new ModelRelation(
82+
$method->getShortName(),
83+
(new ReflectionClass($return))->getShortName(),
84+
(new ReflectionClass($return->getRelated()))->getName(),
85+
$localKey,
86+
$foreignKey
87+
)
88+
];
89+
}
90+
} catch (\Throwable $e) {}
91+
return null;
92+
}
93+
94+
}

tests/FindModelsFromConfigTest.php

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace BeyondCode\ErdGenerator\Tests;
44

55
use BeyondCode\ErdGenerator\GraphBuilder;
6+
use BeyondCode\ErdGenerator\ModelFinder;
67
use BeyondCode\ErdGenerator\Tests\Models\Avatar;
78
use BeyondCode\ErdGenerator\GenerateDiagramCommand;
89

@@ -12,21 +13,11 @@ class FindModelsFromConfigTest extends TestCase
1213
/** @test */
1314
public function it_can_find_class_names_from_directory()
1415
{
15-
$method = self::getMethod(GenerateDiagramCommand::class, 'getModelInstancesInDirectory');
16+
$finder = new ModelFinder(app()->make('files'));
1617

17-
$cmd = new GenerateDiagramCommand(app()->make('files'), new GraphBuilder());
18-
19-
$classNames = $method->invokeArgs($cmd, ["./tests/Models"]);
18+
$classNames = $finder->getModelsInDirectory("./tests/Models");
2019

2120
$this->assertCount(4, $classNames);
2221
$this->assertSame(Avatar::class, $classNames->first());
2322
}
24-
25-
protected static function getMethod($class, $name)
26-
{
27-
$class = new \ReflectionClass($class);
28-
$method = $class->getMethod($name);
29-
$method->setAccessible(true);
30-
return $method;
31-
}
3223
}

0 commit comments

Comments
 (0)