33namespace BeyondCode \ErdGenerator ;
44
55use ReflectionClass ;
6- use ReflectionMethod ;
7- use PhpParser \NodeTraverser ;
8- use PhpParser \ParserFactory ;
9- use PhpParser \Node \Stmt \Class_ ;
106use Illuminate \Console \Command ;
117use phpDocumentor \GraphViz \Graph ;
128use Illuminate \Support \Collection ;
13- use Illuminate \Filesystem \Filesystem ;
14- use Illuminate \Database \Eloquent \Model ;
15- use PhpParser \NodeVisitor \NameResolver ;
169use 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
2311class 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}
0 commit comments