33namespace Toramanlis \ImplicitMigrations \Generator ;
44
55use Illuminate \Database \Schema \Blueprint ;
6- use Toramanlis \ImplicitMigrations \Blueprint \BlueprintDiff ;
76use Toramanlis \ImplicitMigrations \Blueprint \Exporters \TableDiffExporter ;
87use Toramanlis \ImplicitMigrations \Blueprint \Exporters \TableExporter ;
98use Toramanlis \ImplicitMigrations \Blueprint \Manager ;
109use Toramanlis \ImplicitMigrations \Blueprint \SimplifyingBlueprint ;
1110use Illuminate \Database \Migrations \Migration ;
1211use Illuminate \Support \Facades \App ;
12+ use Toramanlis \ImplicitMigrations \Blueprint \BlueprintDiff ;
13+ use Toramanlis \ImplicitMigrations \Blueprint \Migratable ;
1314
1415/** @package Toramanlis\ImplicitMigrations\Generator */
1516class MigrationGenerator
@@ -73,11 +74,12 @@ public function generate(array $modelNames): array
7374 $ sourceMap [$ tableName ] = $ sourceMap [$ tableName ] ?? $ relationship ->getSource ();
7475 }
7576
77+ $ migratables = [];
7678 foreach ($ blueprintManager ->getBlueprints () as $ table => $ blueprint ) {
7779 $ blueprint ->removeDuplicatePrimaries ();
7880 $ source = $ sourceMap [$ table ];
7981 if (!isset ($ this ->existingBlueprints [$ source ])) {
80- $ migrationData [$ table ] = $ this -> getMigrationItem ( $ source , $ blueprint) ;
82+ $ migratables [$ table ] = $ blueprint ;
8183 continue ;
8284 }
8385
@@ -87,22 +89,183 @@ public function generate(array $modelNames): array
8789 continue ;
8890 }
8991
90- $ migrationData [$ this ->existingBlueprints [$ source ]->getTable ()] = $ this ->getMigrationItem (
91- $ source ,
92- $ diff ,
93- MigrationMode::Update
94- );
92+ $ migratables [$ table ] = $ diff ;
93+ }
94+
95+ $ migratables = $ this ->sortMigrations ($ migratables );
96+
97+ foreach ($ migratables as $ table => $ migratable ) {
98+ $ source = $ sourceMap [$ table ];
99+
100+ if ($ migratable instanceof SimplifyingBlueprint) {
101+ $ migrationData [$ table ] = $ this ->getMigrationItem ($ source , $ migratable );
102+ } else {
103+ /** @var BlueprintDiff $migratable */
104+ $ migratable ->applyColumnIndexes ();
105+ $ migratable ->applyColumnIndexes (true );
106+ $ migrationData [$ this ->existingBlueprints [$ source ]->getTable ()] = $ this ->getMigrationItem (
107+ $ source ,
108+ $ migratable ,
109+ );
110+ }
95111 }
96112
97113 return $ migrationData ;
98114 }
99115
116+ /**
117+ * @param array<Migratable> $migratables
118+ * @return array<Migratable> $migratables
119+ */
120+ protected function sortMigrations (array $ migratables ): array
121+ {
122+ $ extraMigratables = $ this ->separateCodependents ($ migratables );
123+
124+ $ sorted = [];
125+
126+ while (count ($ migratables )) {
127+ $ dependencyMap = $ this ->getDependencyMap ($ migratables );
128+
129+ foreach (array_keys ($ migratables ) as $ table ) {
130+ $ dependencies = $ dependencyMap [$ table ] ?? [];
131+
132+ $ counts = array_map (fn ($ item ) => count ($ item ), $ dependencies );
133+ if (0 !== array_sum ($ counts )) {
134+ continue ;
135+ }
136+
137+ $ sorted [$ table ] = $ migratables [$ table ];
138+ unset($ migratables [$ table ]);
139+ }
140+ }
141+
142+ return array_merge ($ sorted , $ extraMigratables );
143+ }
144+
145+ protected function getDependencyMap ($ migratables )
146+ {
147+ $ addedColumns = [];
148+
149+ foreach ($ migratables as $ table => $ migratable ) {
150+ foreach ($ migratable ->getAddedColumnNames () as $ addedColumn ) {
151+ $ addedColumns ["{$ table }. {$ addedColumn }" ] = $ table ;
152+ }
153+ }
154+
155+ $ dependencyMap = [];
156+ foreach (array_keys ($ migratables ) as $ table ) {
157+ $ dependencyMap [$ table ] = $ this ->getDependencies ($ table , $ migratables , $ addedColumns );
158+ }
159+
160+ return $ dependencyMap ;
161+ }
162+
163+
164+ protected function separateCodependents ($ migratables ): array
165+ {
166+ $ extraMigratables = [];
167+
168+ while (count ($ codependents = $ this ->getCodependents ($ migratables ))) {
169+ $ biggestDependent = null ;
170+ $ mostDependencies = 0 ;
171+
172+ foreach ($ codependents as $ table => $ dependencies ) {
173+ foreach ($ dependencies as $ column => $ columnDependencies ) {
174+ if (count ($ columnDependencies ) <= $ mostDependencies ) {
175+ continue ;
176+ }
177+
178+ $ mostDependencies = count ($ columnDependencies );
179+ $ biggestDependent = ['table ' => $ table , 'column ' => $ column ];
180+ }
181+ }
182+
183+ [$ on , $ shortColumn ] = explode ('. ' , $ biggestDependent ['column ' ]);
184+ $ extraMigratable = App::make (BlueprintDiff::class, [
185+ 'from ' => App::make (SimplifyingBlueprint::class, ['tableName ' => $ biggestDependent ['table ' ]]),
186+ 'to ' => App::make (SimplifyingBlueprint::class, ['tableName ' => $ biggestDependent ['table ' ]]),
187+ 'modifiedColumns ' => [],
188+ 'droppedColumns ' => [],
189+ 'addedColumns ' => [],
190+ 'droppedIndexes ' => [],
191+ 'renamedIndexes ' => [],
192+ 'addedIndexes ' => [],
193+ 'addedIndexes ' => [$ migratables [$ biggestDependent ['table ' ]]->extractForeignKey ($ on , $ shortColumn )],
194+ ]);
195+ $ extraMigratables ['_ ' . $ biggestDependent ['table ' ]] = $ extraMigratable ;
196+ }
197+
198+
199+ return $ extraMigratables ;
200+ }
201+
202+ protected function getCodependents ($ migratables )
203+ {
204+ $ dependencyMap = $ this ->getDependencyMap ($ migratables );
205+
206+ $ codependents = [];
207+ foreach ($ dependencyMap as $ table => $ dependencies ) {
208+ foreach ($ dependencies as $ dependedColumn => $ columnDependencies ) {
209+ foreach ($ columnDependencies as $ dependency ) {
210+ foreach ($ dependencyMap [$ dependency ] as $ counterColumn => $ subdependencies ) {
211+ if (!in_array ($ table , $ subdependencies )) {
212+ continue ;
213+ }
214+
215+ $ codependents [$ table ] ??= [];
216+ $ codependents [$ table ][$ dependedColumn ] ??= [];
217+ if (!in_array ($ dependency , $ codependents [$ table ][$ dependedColumn ])) {
218+ $ codependents [$ table ][$ dependedColumn ][] = $ dependency ;
219+ }
220+
221+ $ codependents [$ dependency ] ??= [];
222+ $ codependents [$ dependency ][$ counterColumn ] ??= [];
223+ if (!in_array ($ table , $ codependents [$ dependency ][$ counterColumn ])) {
224+ $ codependents [$ dependency ][$ counterColumn ][] = $ table ;
225+ }
226+ }
227+ }
228+ }
229+ }
230+
231+ return $ codependents ;
232+ }
233+
234+ protected function getDependencies ($ table , $ migratables , $ addedColumns , $ chain = []): array
235+ {
236+ if (in_array ($ table , $ chain )) {
237+ return [];
238+ }
239+
240+ $ chain = array_merge ($ chain , [$ table ]);
241+
242+ $ dependencies = [];
243+ $ migratable = $ migratables [$ table ];
244+ foreach ($ migratable ->getDependedColumnNames () as $ dependedColumn ) {
245+ if (!isset ($ addedColumns [$ dependedColumn ])) {
246+ continue ;
247+ }
248+
249+ $ dependency = $ addedColumns [$ dependedColumn ];
250+ $ subDependencies = $ this ->getDependencies ($ dependency , $ migratables , $ addedColumns , $ chain );
251+ $ merged = array_unique (array_merge ([$ dependency ], $ subDependencies ));
252+
253+ if (1 === count ($ chain )) {
254+ $ dependencies [$ dependedColumn ] = $ merged ;
255+ } else {
256+ $ dependencies = $ merged ;
257+ }
258+ }
259+
260+ return $ dependencies ;
261+ }
262+
100263 protected function getMigrationItem (
101264 string $ source ,
102- Blueprint |BlueprintDiff $ definition ,
103- MigrationMode $ mode = MigrationMode::Create
265+ Migratable $ definition ,
104266 ) {
105267 $ modelName = explode (':: ' , $ source )[0 ];
268+ $ mode = $ definition instanceof SimplifyingBlueprint ? MigrationMode::Create : MigrationMode::Update;
106269
107270 $ exporter = match ($ mode ) {
108271 MigrationMode::Create => TableExporter::class,
0 commit comments