comparison src/Helpers/RelationshipNavigator.php @ 24:31109c61ce02 codex

Refactor RelationshipNavigator formatting and implement belongsTo, hasMany, belongsToMany injection in ModelGenerator
author Luka Sitas <sitas.luka.97@gmail.com>
date Tue, 22 Apr 2025 21:37:44 -0400
parents 827efbf4d73c
children
comparison
equal deleted inserted replaced
23:827efbf4d73c 24:31109c61ce02
2 2
3 namespace Wizard\MagicForger\Helpers; 3 namespace Wizard\MagicForger\Helpers;
4 4
5 use Illuminate\Support\Facades\DB; 5 use Illuminate\Support\Facades\DB;
6 6
7 /**
8 * Class RelationshipNavigator
9 *
10 * This class is responsible for navigating database table relationships, specifically
11 * identifying and categorizing 'belongsTo', 'hasMany', and 'hasManyThrough' relationships.
12 */
7 class RelationshipNavigator 13 class RelationshipNavigator
8 { 14 {
15 /**
16 * Handles the retrieval and display of table relationships.
17 *
18 * @return void
19 */
9 public static function handle() 20 public static function handle()
10 { 21 {
11 $tables = DB::select('SHOW TABLES'); 22 $tables = DB::select('SHOW TABLES');
12 $tableNames = array_map(function ($table) { 23 $tableNames = array_map(fn($table) => current((array) $table), $tables);
13 return current((array) $table);
14 }, $tables);
15 24
16 foreach ($tableNames as $table) { 25 foreach ($tableNames as $table) {
17 echo "Table: $table \n"; 26 echo "Table: $table \n";
18 27
19 $relations = self::getRelations($table); 28 $relations = self::getRelations($table);
20 echo "Relationships: \n"; 29 echo "Relationships: \n";
21 foreach ($relations as $relation => $related_tables) { 30
22 echo $relation.": \n"; 31 foreach ($relations as $relation => $relatedTables) {
23 foreach ($related_tables as $related_table) { 32 echo "$relation: \n";
33 foreach ($relatedTables as $relatedTable) {
24 echo "\t"; 34 echo "\t";
25 foreach ($related_table as $key => $value) { 35 foreach ($relatedTable as $key => $value) {
26 echo "$key : ";
27 if (is_array($value)) { 36 if (is_array($value)) {
28 foreach ($related_table as $key => $value) { 37 echo "\n\t\t" . implode("\n\t\t", array_map(fn($k, $v) => "$k: $v", array_keys($value), $value));
29 echo "\n\t\t";
30 echo "$key: $value";
31 }
32 } else { 38 } else {
33 echo $value.' '; 39 echo "$key: $value ";
34 } 40 }
35 } 41 }
36 echo "\n"; 42 echo "\n";
37 } 43 }
38 } 44 }
39 echo "\n --- \n"; 45 echo "\n --- \n";
40 } 46 }
41 } 47 }
42 48
49 /**
50 * Retrieves relationships of a specific table.
51 *
52 * @param string $table The table name.
53 * @return array An array containing 'belongsTo', 'hasMany', and 'hasManyThrough' relations.
54 */
43 public static function getRelations($table) 55 public static function getRelations($table)
44 { 56 {
45 $relations = [ 57 $relations = [
46 'belongsTo' => [], 58 'belongsTo' => [],
47 'hasMany' => [], 59 'hasMany' => [],
49 ]; 61 ];
50 62
51 $foreignKeys = DB::select("SHOW KEYS FROM $table WHERE Key_name != 'PRIMARY'"); 63 $foreignKeys = DB::select("SHOW KEYS FROM $table WHERE Key_name != 'PRIMARY'");
52 $referencedTables = self::getAllReferencedTables($table); 64 $referencedTables = self::getAllReferencedTables($table);
53 65
54 // BelongsTo Relationships 66 // Determine 'belongsTo' Relationships
55 foreach ($foreignKeys as $fk) { 67 foreach ($foreignKeys as $fk) {
56 $column = $fk->Column_name; 68 $column = $fk->Column_name;
57 69
58 // skip created and updated by 70 // Skip certain columns
59 if (in_array($column, ['created_by', 'updated_by'])) { 71 if (in_array($column, ['created_by', 'updated_by'])) {
60 continue; 72 continue;
61 } 73 }
62 74
63 $referencedTable = $referencedTables[$column] ?? null; 75 $referencedTable = $referencedTables[$column] ?? null;
64 76
65 if ($referencedTable) { 77 if ($referencedTable) {
66 $relations['belongsTo'][] = ['column' => $column, 'table' => $referencedTable->REFERENCED_TABLE_NAME]; 78 $relations['belongsTo'][] = ['column' => $column, 'table' => $referencedTable->REFERENCED_TABLE_NAME];
67 } 79 }
68 80 }
69 } 81
70 82 // Determine 'hasMany' Relationships
71 // HasMany Relationships
72 if ($reverseRelation = self::findReverseRelation($table)) { 83 if ($reverseRelation = self::findReverseRelation($table)) {
73 foreach ($reverseRelation as $relatedTable) { 84 foreach ($reverseRelation as $relatedTable) {
74 $relations['hasMany'][] = $relatedTable; 85 $relations['hasMany'][] = $relatedTable;
75 } 86 }
76 } 87 }
77 88
78 // HasManyThrough Relationships 89 // Determine 'hasManyThrough' Relationships
79 if ($hasManyThroughRelations = self::findHasManyThroughRelations($table)) { 90 if ($hasManyThroughRelations = self::findHasManyThroughRelations($table)) {
80 foreach ($hasManyThroughRelations as $relatedTable) { 91 foreach ($hasManyThroughRelations as $relatedTable) {
81 $relations['hasManyThrough'][] = $relatedTable; 92 $relations['hasManyThrough'][] = $relatedTable;
82 } 93 }
83 } 94 }
84 95
85 return $relations; 96 return $relations;
86 } 97 }
87 98
99 /**
100 * Retrieves all referenced tables for a given table.
101 *
102 * @param string $table The table name.
103 * @return array|null An associative array of referenced tables, keyed by column name.
104 */
88 public static function getAllReferencedTables($table) 105 public static function getAllReferencedTables($table)
89 { 106 {
90 $results = DB::select(" 107 $results = DB::select("
91 SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME 108 SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
92 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 109 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
93 WHERE TABLE_SCHEMA = DATABASE() 110 WHERE TABLE_SCHEMA = DATABASE()
94 AND TABLE_NAME = '$table' 111 AND REFERENCED_TABLE_NAME IS NOT NULL
95 "); 112 AND TABLE_NAME = ?
96 113 ", [$table]);
97 $tables = self::re_key_array($results, 'COLUMN_NAME'); 114
98 115 return self::re_key_array($results, 'COLUMN_NAME') ?: null;
99 return (count($tables) > 0) ? $tables : null; 116 }
100 } 117
101 118 /**
119 * Finds 'hasMany' inverse relationships for a given table.
120 *
121 * @param string $table The table name.
122 * @return array|null An array of related tables with column names.
123 */
102 public static function findReverseRelation($table) 124 public static function findReverseRelation($table)
103 { 125 {
104 $relations = DB::select(" 126 $relations = DB::select("
105 SELECT TABLE_NAME, COLUMN_NAME 127 SELECT TABLE_NAME, COLUMN_NAME
106 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 128 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
107 WHERE TABLE_SCHEMA = DATABASE() 129 WHERE TABLE_SCHEMA = DATABASE()
108 AND REFERENCED_TABLE_NAME = '$table' 130 AND REFERENCED_TABLE_NAME = ?
109 AND REFERENCED_COLUMN_NAME = 'id' 131 AND REFERENCED_COLUMN_NAME = 'id'
110 AND COLUMN_NAME != 'created_by' 132 AND COLUMN_NAME NOT IN ('created_by', 'updated_by')
111 AND COLUMN_NAME != 'updated_by' 133 ", [$table]);
112 "); 134
113 135 return array_map(fn($rel) => ['table' => $rel->TABLE_NAME, 'column' => $rel->COLUMN_NAME], $relations) ?: null;
114 $relatedTables = array_map(function ($rel) { 136 }
115 return ['table' => $rel->TABLE_NAME, 'column' => $rel->COLUMN_NAME]; 137
116 }, $relations); 138 /**
117 139 * Finds 'hasManyThrough' relationships for a given table.
118 return $relatedTables ? $relatedTables : null; 140 *
119 } 141 * @param string $table The table name.
120 142 * @return array An array of 'hasManyThrough' relationships.
143 */
121 public static function findHasManyThroughRelations($table) 144 public static function findHasManyThroughRelations($table)
122 { 145 {
123 $relations = []; 146 $relations = [];
124
125 // Find potential intermediary tables
126 $intermediaryTables = self::findReverseRelation($table); 147 $intermediaryTables = self::findReverseRelation($table);
127 148
128 if (! is_null($intermediaryTables)) { 149 if ($intermediaryTables !== null) {
129
130 foreach ($intermediaryTables as $intermediary) { 150 foreach ($intermediaryTables as $intermediary) {
131 151 if ($isPivot = self::isPivot($intermediary['table'])) {
132 if ($is_pivot = self::isPivot($intermediary['table'])) { 152 $isPivot = current($isPivot);
133 $is_pivot = current($is_pivot); 153
134 154 $potentialTables = array_keys($isPivot['tables']);
135 // reformat the table based on the current and external 155 $externalTable = $potentialTables[0] === $table ? $isPivot['tables'][$potentialTables[1]] : $isPivot['tables'][$potentialTables[0]];
136 $potential_tables = array_keys($is_pivot['tables']); 156 $internalTable = $potentialTables[0] === $table ? $isPivot['tables'][$potentialTables[0]] : $isPivot['tables'][$potentialTables[1]];
137 $external_table = $potential_tables[0] == $table ? $is_pivot['tables'][$potential_tables[1]] : $is_pivot['tables'][$potential_tables[0]]; 157
138 $internal_table = $potential_tables[0] == $table ? $is_pivot['tables'][$potential_tables[0]] : $is_pivot['tables'][$potential_tables[1]]; 158 $hasManyThrough = [
139 159 'table' => $externalTable['table_name'],
140 $hasManyThrough = [ 160 'through' => [
141 'table' => $external_table['table_name'], 161 'table' => $isPivot['table'],
142 'through' => [ 162 'external_column' => $externalTable['column'],
143 'table' => $is_pivot['table'], 163 'internal_column' => $internalTable['column'],
144 'external_column' => $external_table['column'], 164 ],
145 'internal_column' => $internal_table['column']
146 ]
147 ]; 165 ];
148 $relations[] = $hasManyThrough; 166 $relations[] = $hasManyThrough;
149 } 167 }
150 } 168 }
151
152 } 169 }
153 170
154 return $relations; 171 return $relations;
155 } 172 }
156 173
174 /**
175 * Determines if a table is a pivot table.
176 *
177 * @param string $table The table name.
178 * @return array|null An array with pivot details or null if not a pivot.
179 */
157 public static function isPivot($table) 180 public static function isPivot($table)
158 { 181 {
159
160 $relations = []; 182 $relations = [];
161 // TODO: alsot get the columns that are relevant
162 $pivotTables = DB::select(" 183 $pivotTables = DB::select("
163 SELECT TABLE_NAME, TABLE_COMMENT 184 SELECT TABLE_NAME, TABLE_COMMENT
164 FROM INFORMATION_SCHEMA.TABLES 185 FROM INFORMATION_SCHEMA.TABLES
165 WHERE TABLE_SCHEMA = DATABASE() 186 WHERE TABLE_SCHEMA = DATABASE()
166 AND TABLE_NAME = '{$table}' 187 AND TABLE_NAME = ?
167 AND TABLE_COMMENT != '' 188 AND TABLE_COMMENT != ''
168 "); 189 ", [$table]);
169 190
170 if(!is_null($pivotTables) && count($pivotTables) > 0) { 191 if (!is_null($pivotTables) && count($pivotTables) > 0) {
171 $ref = current($pivotTables); 192 $ref = current($pivotTables);
172 193 $pivots = json_decode(str_replace('PIVOT:', '', $ref->TABLE_COMMENT), true);
173 $pivots = json_decode(str_replace('PIVOT:', '', $ref->TABLE_COMMENT), true); 194 $tables = [];
174 195
175 $tables = []; 196 if (count($pivots) > 0) {
176 if (count($pivots) > 0) { 197 $references = self::getAllReferencedTables($table);
177 //re-key array 198 $references = self::re_key_array($references, 'REFERENCED_TABLE_NAME');
178 $references = self::getAllReferencedTables($table); 199
179 $references = self::re_key_array($references, 'REFERENCED_TABLE_NAME'); 200 foreach ($pivots as $key => $value) {
180 201 if ($refData = ($references[$value] ?? null)) {
181 foreach($pivots as $key => $value) { 202 $tables[$value] = [
182 if($ref_data = ($references[$value] ?? null)) { 203 'table_name' => $value,
183 $tables[$value] = [ 204 'column' => $refData->COLUMN_NAME,
184 'table_name' => $value, 205 ];
185 'column' => $ref_data->COLUMN_NAME 206 }
186 ]; 207 }
187 } 208 }
188 } 209 $relations[] = ['table' => $ref->TABLE_NAME, 'tables' => $tables];
189 } 210 }
190 $relations[] = ['table' => $ref->TABLE_NAME, 'tables' => $tables]; 211
191 } 212 return !empty($relations) ? $relations : null;
192 213 }
193 return $relations != [] ? $relations : null; 214
194 } 215 /**
195 216 * Re-keys an array of objects using a specific object's property.
196 public static function re_key_array($old_array, $key) { 217 *
197 $new_array = []; 218 * @param array $oldArray The original array of objects.
198 if (count($old_array) > 0) { 219 * @param string $key The key to re-index by.
199 foreach ($old_array as $array) { 220 * @return array The re-keyed array.
200 $new_array[$array->$key] = $array; 221 */
201 } 222 public static function re_key_array($oldArray, $key)
202 } 223 {
203 return $new_array; 224 $newArray = [];
204 } 225 if (count($oldArray) > 0) {
226 foreach ($oldArray as $array) {
227 $newArray[$array->$key] = $array;
228 }
229 }
230 return $newArray;
231 }
205 } 232 }