comparison src/Helpers/RelationshipNavigator.php @ 34:f65ab84ee47f default

merge with codex
author luka
date Wed, 10 Sep 2025 21:00:47 -0400
parents 31109c61ce02
children
comparison
equal deleted inserted replaced
10:a9ff874afdbd 34:f65ab84ee47f
1 <?php
2
3 namespace Wizard\MagicForger\Helpers;
4
5 use Illuminate\Support\Facades\DB;
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 */
13 class RelationshipNavigator
14 {
15 /**
16 * Handles the retrieval and display of table relationships.
17 *
18 * @return void
19 */
20 public static function handle()
21 {
22 $tables = DB::select('SHOW TABLES');
23 $tableNames = array_map(fn($table) => current((array) $table), $tables);
24
25 foreach ($tableNames as $table) {
26 echo "Table: $table \n";
27
28 $relations = self::getRelations($table);
29 echo "Relationships: \n";
30
31 foreach ($relations as $relation => $relatedTables) {
32 echo "$relation: \n";
33 foreach ($relatedTables as $relatedTable) {
34 echo "\t";
35 foreach ($relatedTable as $key => $value) {
36 if (is_array($value)) {
37 echo "\n\t\t" . implode("\n\t\t", array_map(fn($k, $v) => "$k: $v", array_keys($value), $value));
38 } else {
39 echo "$key: $value ";
40 }
41 }
42 echo "\n";
43 }
44 }
45 echo "\n --- \n";
46 }
47 }
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 */
55 public static function getRelations($table)
56 {
57 $relations = [
58 'belongsTo' => [],
59 'hasMany' => [],
60 'hasManyThrough' => [],
61 ];
62
63 $foreignKeys = DB::select("SHOW KEYS FROM $table WHERE Key_name != 'PRIMARY'");
64 $referencedTables = self::getAllReferencedTables($table);
65
66 // Determine 'belongsTo' Relationships
67 foreach ($foreignKeys as $fk) {
68 $column = $fk->Column_name;
69
70 // Skip certain columns
71 if (in_array($column, ['created_by', 'updated_by'])) {
72 continue;
73 }
74
75 $referencedTable = $referencedTables[$column] ?? null;
76
77 if ($referencedTable) {
78 $relations['belongsTo'][] = ['column' => $column, 'table' => $referencedTable->REFERENCED_TABLE_NAME];
79 }
80 }
81
82 // Determine 'hasMany' Relationships
83 if ($reverseRelation = self::findReverseRelation($table)) {
84 foreach ($reverseRelation as $relatedTable) {
85 $relations['hasMany'][] = $relatedTable;
86 }
87 }
88
89 // Determine 'hasManyThrough' Relationships
90 if ($hasManyThroughRelations = self::findHasManyThroughRelations($table)) {
91 foreach ($hasManyThroughRelations as $relatedTable) {
92 $relations['hasManyThrough'][] = $relatedTable;
93 }
94 }
95
96 return $relations;
97 }
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 */
105 public static function getAllReferencedTables($table)
106 {
107 $results = DB::select("
108 SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
109 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
110 WHERE TABLE_SCHEMA = DATABASE()
111 AND REFERENCED_TABLE_NAME IS NOT NULL
112 AND TABLE_NAME = ?
113 ", [$table]);
114
115 return self::re_key_array($results, 'COLUMN_NAME') ?: null;
116 }
117
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 */
124 public static function findReverseRelation($table)
125 {
126 $relations = DB::select("
127 SELECT TABLE_NAME, COLUMN_NAME
128 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
129 WHERE TABLE_SCHEMA = DATABASE()
130 AND REFERENCED_TABLE_NAME = ?
131 AND REFERENCED_COLUMN_NAME = 'id'
132 AND COLUMN_NAME NOT IN ('created_by', 'updated_by')
133 ", [$table]);
134
135 return array_map(fn($rel) => ['table' => $rel->TABLE_NAME, 'column' => $rel->COLUMN_NAME], $relations) ?: null;
136 }
137
138 /**
139 * Finds 'hasManyThrough' relationships for a given table.
140 *
141 * @param string $table The table name.
142 * @return array An array of 'hasManyThrough' relationships.
143 */
144 public static function findHasManyThroughRelations($table)
145 {
146 $relations = [];
147 $intermediaryTables = self::findReverseRelation($table);
148
149 if ($intermediaryTables !== null) {
150 foreach ($intermediaryTables as $intermediary) {
151 if ($isPivot = self::isPivot($intermediary['table'])) {
152 $isPivot = current($isPivot);
153
154 $potentialTables = array_keys($isPivot['tables']);
155 $externalTable = $potentialTables[0] === $table ? $isPivot['tables'][$potentialTables[1]] : $isPivot['tables'][$potentialTables[0]];
156 $internalTable = $potentialTables[0] === $table ? $isPivot['tables'][$potentialTables[0]] : $isPivot['tables'][$potentialTables[1]];
157
158 $hasManyThrough = [
159 'table' => $externalTable['table_name'],
160 'through' => [
161 'table' => $isPivot['table'],
162 'external_column' => $externalTable['column'],
163 'internal_column' => $internalTable['column'],
164 ],
165 ];
166 $relations[] = $hasManyThrough;
167 }
168 }
169 }
170
171 return $relations;
172 }
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 */
180 public static function isPivot($table)
181 {
182 $relations = [];
183 $pivotTables = DB::select("
184 SELECT TABLE_NAME, TABLE_COMMENT
185 FROM INFORMATION_SCHEMA.TABLES
186 WHERE TABLE_SCHEMA = DATABASE()
187 AND TABLE_NAME = ?
188 AND TABLE_COMMENT != ''
189 ", [$table]);
190
191 if (!is_null($pivotTables) && count($pivotTables) > 0) {
192 $ref = current($pivotTables);
193 $pivots = json_decode(str_replace('PIVOT:', '', $ref->TABLE_COMMENT), true);
194 $tables = [];
195
196 if (count($pivots) > 0) {
197 $references = self::getAllReferencedTables($table);
198 $references = self::re_key_array($references, 'REFERENCED_TABLE_NAME');
199
200 foreach ($pivots as $key => $value) {
201 if ($refData = ($references[$value] ?? null)) {
202 $tables[$value] = [
203 'table_name' => $value,
204 'column' => $refData->COLUMN_NAME,
205 ];
206 }
207 }
208 }
209 $relations[] = ['table' => $ref->TABLE_NAME, 'tables' => $tables];
210 }
211
212 return !empty($relations) ? $relations : null;
213 }
214
215 /**
216 * Re-keys an array of objects using a specific object's property.
217 *
218 * @param array $oldArray The original array of objects.
219 * @param string $key The key to re-index by.
220 * @return array The re-keyed array.
221 */
222 public static function re_key_array($oldArray, $key)
223 {
224 $newArray = [];
225 if (count($oldArray) > 0) {
226 foreach ($oldArray as $array) {
227 $newArray[$array->$key] = $array;
228 }
229 }
230 return $newArray;
231 }
232 }