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

merge with codex
author luka
date Wed, 10 Sep 2025 21:00:47 -0400
parents 31109c61ce02
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Helpers/RelationshipNavigator.php	Wed Sep 10 21:00:47 2025 -0400
@@ -0,0 +1,232 @@
+<?php
+
+namespace Wizard\MagicForger\Helpers;
+
+use Illuminate\Support\Facades\DB;
+
+/**
+ * Class RelationshipNavigator
+ *
+ * This class is responsible for navigating database table relationships, specifically 
+ * identifying and categorizing 'belongsTo', 'hasMany', and 'hasManyThrough' relationships.
+ */
+class RelationshipNavigator
+{
+    /**
+     * Handles the retrieval and display of table relationships.
+     *
+     * @return void
+     */
+    public static function handle()
+    {
+        $tables = DB::select('SHOW TABLES');
+        $tableNames = array_map(fn($table) => current((array) $table), $tables);
+
+        foreach ($tableNames as $table) {
+            echo "Table: $table \n";
+
+            $relations = self::getRelations($table);
+            echo "Relationships: \n";
+
+            foreach ($relations as $relation => $relatedTables) {
+                echo "$relation: \n";
+                foreach ($relatedTables as $relatedTable) {
+                    echo "\t";
+                    foreach ($relatedTable as $key => $value) {
+                        if (is_array($value)) {
+                            echo "\n\t\t" . implode("\n\t\t", array_map(fn($k, $v) => "$k: $v", array_keys($value), $value));
+                        } else {
+                            echo "$key: $value ";
+                        }
+                    }
+                    echo "\n";
+                }
+            }
+            echo "\n --- \n";
+        }
+    }
+
+    /**
+     * Retrieves relationships of a specific table.
+     *
+     * @param string $table The table name.
+     * @return array An array containing 'belongsTo', 'hasMany', and 'hasManyThrough' relations.
+     */
+    public static function getRelations($table)
+    {
+        $relations = [
+            'belongsTo' => [],
+            'hasMany' => [],
+            'hasManyThrough' => [],
+        ];
+
+        $foreignKeys = DB::select("SHOW KEYS FROM $table WHERE Key_name != 'PRIMARY'");
+        $referencedTables = self::getAllReferencedTables($table);
+
+        // Determine 'belongsTo' Relationships
+        foreach ($foreignKeys as $fk) {
+            $column = $fk->Column_name;
+
+            // Skip certain columns
+            if (in_array($column, ['created_by', 'updated_by'])) {
+                continue;
+            }
+
+            $referencedTable = $referencedTables[$column] ?? null;
+
+            if ($referencedTable) {
+                $relations['belongsTo'][] = ['column' => $column, 'table' => $referencedTable->REFERENCED_TABLE_NAME];
+            }
+        }
+
+        // Determine 'hasMany' Relationships
+        if ($reverseRelation = self::findReverseRelation($table)) {
+            foreach ($reverseRelation as $relatedTable) {
+                $relations['hasMany'][] = $relatedTable;
+            }
+        }
+
+        // Determine 'hasManyThrough' Relationships
+        if ($hasManyThroughRelations = self::findHasManyThroughRelations($table)) {
+            foreach ($hasManyThroughRelations as $relatedTable) {
+                $relations['hasManyThrough'][] = $relatedTable;
+            }
+        }
+
+        return $relations;
+    }
+
+    /**
+     * Retrieves all referenced tables for a given table.
+     *
+     * @param string $table The table name.
+     * @return array|null An associative array of referenced tables, keyed by column name.
+     */
+    public static function getAllReferencedTables($table)
+    {
+        $results = DB::select("
+            SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME 
+            FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 
+            WHERE TABLE_SCHEMA = DATABASE() 
+            AND REFERENCED_TABLE_NAME IS NOT NULL
+            AND TABLE_NAME = ?
+        ", [$table]);
+
+        return self::re_key_array($results, 'COLUMN_NAME') ?: null;
+    }
+
+    /**
+     * Finds 'hasMany' inverse relationships for a given table.
+     *
+     * @param string $table The table name.
+     * @return array|null An array of related tables with column names.
+     */
+    public static function findReverseRelation($table)
+    {
+        $relations = DB::select("
+            SELECT TABLE_NAME, COLUMN_NAME
+            FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 
+            WHERE TABLE_SCHEMA = DATABASE() 
+            AND REFERENCED_TABLE_NAME = ? 
+            AND REFERENCED_COLUMN_NAME = 'id'
+            AND COLUMN_NAME NOT IN ('created_by', 'updated_by')
+        ", [$table]);
+
+        return array_map(fn($rel) => ['table' => $rel->TABLE_NAME, 'column' => $rel->COLUMN_NAME], $relations) ?: null;
+    }
+    
+    /**
+     * Finds 'hasManyThrough' relationships for a given table.
+     *
+     * @param string $table The table name.
+     * @return array An array of 'hasManyThrough' relationships.
+     */
+    public static function findHasManyThroughRelations($table)
+    {
+        $relations = [];
+        $intermediaryTables = self::findReverseRelation($table);
+
+        if ($intermediaryTables !== null) {
+            foreach ($intermediaryTables as $intermediary) {
+                if ($isPivot = self::isPivot($intermediary['table'])) {
+                    $isPivot = current($isPivot);
+
+                    $potentialTables = array_keys($isPivot['tables']);
+                    $externalTable = $potentialTables[0] === $table ? $isPivot['tables'][$potentialTables[1]] : $isPivot['tables'][$potentialTables[0]];
+                    $internalTable = $potentialTables[0] === $table ? $isPivot['tables'][$potentialTables[0]] : $isPivot['tables'][$potentialTables[1]];
+
+                    $hasManyThrough = [
+                        'table' => $externalTable['table_name'],
+                        'through' => [
+                            'table' => $isPivot['table'],
+                            'external_column' => $externalTable['column'],
+                            'internal_column' => $internalTable['column'],
+                        ],
+                    ];
+                    $relations[] = $hasManyThrough;
+                }
+            }
+        }
+
+        return $relations;
+    }
+
+    /**
+     * Determines if a table is a pivot table.
+     *
+     * @param string $table The table name.
+     * @return array|null An array with pivot details or null if not a pivot.
+     */
+    public static function isPivot($table)
+    {
+        $relations = [];
+        $pivotTables = DB::select("
+            SELECT TABLE_NAME, TABLE_COMMENT
+            FROM INFORMATION_SCHEMA.TABLES 
+            WHERE TABLE_SCHEMA = DATABASE() 
+            AND TABLE_NAME = ?
+            AND TABLE_COMMENT != ''
+        ", [$table]);
+
+        if (!is_null($pivotTables) && count($pivotTables) > 0) {
+            $ref = current($pivotTables);
+            $pivots = json_decode(str_replace('PIVOT:', '', $ref->TABLE_COMMENT), true);
+            $tables = [];
+
+            if (count($pivots) > 0) {
+                $references = self::getAllReferencedTables($table);
+                $references = self::re_key_array($references, 'REFERENCED_TABLE_NAME');
+
+                foreach ($pivots as $key => $value) {
+                    if ($refData = ($references[$value] ?? null)) {
+                        $tables[$value] = [
+                            'table_name' => $value,
+                            'column' => $refData->COLUMN_NAME,
+                        ];
+                    }
+                }
+            }
+            $relations[] = ['table' => $ref->TABLE_NAME, 'tables' => $tables];
+        }
+
+        return !empty($relations) ? $relations : null;
+    }
+
+    /**
+     * Re-keys an array of objects using a specific object's property.
+     *
+     * @param array $oldArray The original array of objects.
+     * @param string $key The key to re-index by.
+     * @return array The re-keyed array.
+     */
+    public static function re_key_array($oldArray, $key)
+    {
+        $newArray = [];
+        if (count($oldArray) > 0) {
+            foreach ($oldArray as $array) {
+                $newArray[$array->$key] = $array;
+            }
+        }
+        return $newArray;
+    }
+}