Mercurial > packages > magicforger
changeset 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 | 1a717c7b211f |
| files | src/Generator/Model/ModelGenerator.php src/Generator/Model/snippets/has_many_relation.stub src/Generator/Model/stubs/model.pivot.stub src/Generator/Model/stubs/model.stub src/Helpers/RelationshipNavigator.php |
| diffstat | 5 files changed, 194 insertions(+), 114 deletions(-) [+] |
line wrap: on
line diff
--- a/src/Generator/Model/ModelGenerator.php Fri Apr 11 20:50:20 2025 -0400 +++ b/src/Generator/Model/ModelGenerator.php Tue Apr 22 21:37:44 2025 -0400 @@ -5,6 +5,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Wizard\MagicForger\Generator\BaseGenerator; use Wizard\MagicForger\Helpers\RelationshipNavigator; +use Illuminate\Support\Str; #[AsCommand(name: 'mf:model')] class ModelGenerator extends BaseGenerator @@ -34,15 +35,13 @@ /** * Execute the console command. + * + * @return mixed */ public function handle() { - - $belongs_to_many_relations = RelationshipNavigator::getRelations($this->getTableInput()); - dd($belongs_to_many_relations); - echo $this->renderBelongsToMany($belongs_to_many_relations['hasManyThrough'][0]); - dd('here'); - /* parent::handle(); */ + // Delegate to parent handler (includes replacements and insertions) + return parent::handle(); } /** @@ -89,12 +88,13 @@ protected function getSnippet($snippet_name) { - if (! isset($cached_snippets[$snippet_name])) { - $cached_snippets[$snippet_name] = $this->files->get( - $this->resolveStubPath("/snippets/$snippet_name.stub")); + // Cache snippet contents to avoid re-reading files + if (! isset(self::$cached_snippets[$snippet_name])) { + self::$cached_snippets[$snippet_name] = $this->files->get( + $this->resolveStubPath("/snippets/$snippet_name.stub")); } - return $cached_snippets[$snippet_name]; + return self::$cached_snippets[$snippet_name]; } protected function gatherRelations() { @@ -110,16 +110,19 @@ 'hasMany' => [], 'belongsToMany' => [], ]; - foreach($relations['belongsTo'] as $belongs_to_relation) { - $renders['belongsTo'] = $this->renderBelongsTo($belongs_to_relation); - } + // Render belongsTo relations + foreach (($relations['belongsTo'] ?? []) as $relation) { + $renders['belongsTo'][] = $this->renderBelongsTo($relation); + } - foreach($relations['hasMany'] as $has_many_relation) { - $renders['hasMany'] = $this->renderHasMany($has_many_relation); - } + // Render hasMany relations + foreach (($relations['hasMany'] ?? []) as $relation) { + $renders['hasMany'][] = $this->renderHasMany($relation); + } - foreach($relations['belongsToMany'] as $belongs_to_many_relation) { - $renders['belongsToMany'] = $this->renderBelongsToMany($belongs_to_many_relation); + // Render belongsToMany (many-to-many) via hasManyThrough pivot relations + foreach (($relations['hasManyThrough'] ?? []) as $relation) { + $renders['belongsToMany'][] = $this->renderBelongsToMany($relation); } return $renders; } @@ -133,8 +136,32 @@ // Replace placeholders with actual values $string = str_replace( - ['{{relationName}}', '{{relatedModel}}', '{{pivotTable}}', '{{foreignPivotKey}}', '{{relatedPivotKey}}'], - [$relationName, $relatedModel, $pivotTable, $foreignPivotKey, $relatedPivotKey], + ['{{relationName}}', '{{relatedModel}}', '{{columnName}}'], + [$relationName, $relatedModel, $columnName], + $snippet + ); + + return $string; + } + + /** + * Render a hasMany relation. + * + * @param array $relationship + * @return string + */ + protected function renderHasMany($relationship) + { + $snippet = $this->getSnippet('has_many_relation'); + // Method name uses camel case for plural relation + $relationName = Str::camel($relationship['table']); + $relatedModel = $this->getClassName($relationship['table']); + $columnName = $relationship['column']; + + // Replace placeholders with actual values + $string = str_replace( + ['{{relationName}}', '{{relatedModel}}', '{{columnName}}'], + [$relationName, $relatedModel, $columnName], $snippet ); @@ -159,4 +186,30 @@ return $string; } + /** + * Get available insertions including model relationships. + * + * @return array + */ + public function get_available_inserts(): array + { + // Merge parent insertions (attributes, fillable, etc.) + $inserts = parent::get_available_inserts(); + + // Gather and render relationships for this model + $relations = RelationshipNavigator::getRelations($this->getCurrentTable()); + $rendered = $this->renderRelations($relations); + + // Build code blocks for each relation type + $belongs = !empty($rendered['belongsTo']) ? implode("\n ", $rendered['belongsTo']) : ''; + $hasMany = !empty($rendered['hasMany']) ? implode("\n ", $rendered['hasMany']) : ''; + $belongsMany = !empty($rendered['belongsToMany']) ? implode("\n ", $rendered['belongsToMany']) : ''; + + // Assign to stub placeholders + $inserts['# {{ belongs_to_relationships }}'] = $belongs; + $inserts['# {{ has_many_relationships }}'] = $hasMany; + $inserts['# {{ has_many_through_relationships }}'] = $belongsMany; + + return $inserts; + } }
--- a/src/Generator/Model/snippets/has_many_relation.stub Fri Apr 11 20:50:20 2025 -0400 +++ b/src/Generator/Model/snippets/has_many_relation.stub Tue Apr 22 21:37:44 2025 -0400 @@ -1,4 +1,4 @@ -public function {{ $relationhip_name }}() +public function {{relationName}}() { - return $this->hasMany({{ external_class_name }}::class, '{{ column_name }}'); + return $this->hasMany({{relatedModel}}::class, '{{columnName}}'); }
--- a/src/Generator/Model/stubs/model.pivot.stub Fri Apr 11 20:50:20 2025 -0400 +++ b/src/Generator/Model/stubs/model.pivot.stub Tue Apr 22 21:37:44 2025 -0400 @@ -23,7 +23,7 @@ # {{ belongs_to_relationships }} // HasMany - # {{ has_may_relationships }} + # {{ has_many_relationships }} // HasManyThrough # {{ has_many_through_relationships }}
--- a/src/Generator/Model/stubs/model.stub Fri Apr 11 20:50:20 2025 -0400 +++ b/src/Generator/Model/stubs/model.stub Tue Apr 22 21:37:44 2025 -0400 @@ -52,7 +52,7 @@ # {{ belongs_to_relationships }} // HasMany - # {{ has_may_relationships }} + # {{ has_many_relationships }} // HasManyThrough # {{ has_many_through_relationships }}
--- a/src/Helpers/RelationshipNavigator.php Fri Apr 11 20:50:20 2025 -0400 +++ b/src/Helpers/RelationshipNavigator.php Tue Apr 22 21:37:44 2025 -0400 @@ -4,33 +4,39 @@ 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(function ($table) { - return current((array) $table); - }, $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 => $related_tables) { - echo $relation.": \n"; - foreach ($related_tables as $related_table) { + + foreach ($relations as $relation => $relatedTables) { + echo "$relation: \n"; + foreach ($relatedTables as $relatedTable) { echo "\t"; - foreach ($related_table as $key => $value) { - echo "$key : "; + foreach ($relatedTable as $key => $value) { if (is_array($value)) { - foreach ($related_table as $key => $value) { - echo "\n\t\t"; - echo "$key: $value"; - } + echo "\n\t\t" . implode("\n\t\t", array_map(fn($k, $v) => "$k: $v", array_keys($value), $value)); } else { - echo $value.' '; + echo "$key: $value "; } } echo "\n"; @@ -40,6 +46,12 @@ } } + /** + * 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 = [ @@ -51,11 +63,11 @@ $foreignKeys = DB::select("SHOW KEYS FROM $table WHERE Key_name != 'PRIMARY'"); $referencedTables = self::getAllReferencedTables($table); - // BelongsTo Relationships + // Determine 'belongsTo' Relationships foreach ($foreignKeys as $fk) { $column = $fk->Column_name; - // skip created and updated by + // Skip certain columns if (in_array($column, ['created_by', 'updated_by'])) { continue; } @@ -65,17 +77,16 @@ if ($referencedTable) { $relations['belongsTo'][] = ['column' => $column, 'table' => $referencedTable->REFERENCED_TABLE_NAME]; } - } - // HasMany Relationships + // Determine 'hasMany' Relationships if ($reverseRelation = self::findReverseRelation($table)) { foreach ($reverseRelation as $relatedTable) { $relations['hasMany'][] = $relatedTable; } } - // HasManyThrough Relationships + // Determine 'hasManyThrough' Relationships if ($hasManyThroughRelations = self::findHasManyThroughRelations($table)) { foreach ($hasManyThroughRelations as $relatedTable) { $relations['hasManyThrough'][] = $relatedTable; @@ -85,121 +96,137 @@ 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 TABLE_NAME = '$table' - "); + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = ? + ", [$table]); - $tables = self::re_key_array($results, 'COLUMN_NAME'); - - return (count($tables) > 0) ? $tables : null; + 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 = '$table' + AND REFERENCED_TABLE_NAME = ? AND REFERENCED_COLUMN_NAME = 'id' - AND COLUMN_NAME != 'created_by' - AND COLUMN_NAME != 'updated_by' - "); + AND COLUMN_NAME NOT IN ('created_by', 'updated_by') + ", [$table]); - $relatedTables = array_map(function ($rel) { - return ['table' => $rel->TABLE_NAME, 'column' => $rel->COLUMN_NAME]; - }, $relations); - - return $relatedTables ? $relatedTables : null; + 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 = []; - - // Find potential intermediary tables $intermediaryTables = self::findReverseRelation($table); - if (! is_null($intermediaryTables)) { - + if ($intermediaryTables !== null) { foreach ($intermediaryTables as $intermediary) { - - if ($is_pivot = self::isPivot($intermediary['table'])) { - $is_pivot = current($is_pivot); + if ($isPivot = self::isPivot($intermediary['table'])) { + $isPivot = current($isPivot); - // reformat the table based on the current and external - $potential_tables = array_keys($is_pivot['tables']); - $external_table = $potential_tables[0] == $table ? $is_pivot['tables'][$potential_tables[1]] : $is_pivot['tables'][$potential_tables[0]]; - $internal_table = $potential_tables[0] == $table ? $is_pivot['tables'][$potential_tables[0]] : $is_pivot['tables'][$potential_tables[1]]; + $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' => $external_table['table_name'], - 'through' => [ - 'table' => $is_pivot['table'], - 'external_column' => $external_table['column'], - 'internal_column' => $internal_table['column'] - ] + $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 = []; - // TODO: alsot get the columns that are relevant $pivotTables = DB::select(" - SELECT TABLE_NAME, TABLE_COMMENT - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = '{$table}' - AND TABLE_COMMENT != '' - "); + 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); + if (!is_null($pivotTables) && count($pivotTables) > 0) { + $ref = current($pivotTables); + $pivots = json_decode(str_replace('PIVOT:', '', $ref->TABLE_COMMENT), true); + $tables = []; - $tables = []; - if (count($pivots) > 0) { - //re-key array - $references = self::getAllReferencedTables($table); - $references = self::re_key_array($references, 'REFERENCED_TABLE_NAME'); + if (count($pivots) > 0) { + $references = self::getAllReferencedTables($table); + $references = self::re_key_array($references, 'REFERENCED_TABLE_NAME'); - foreach($pivots as $key => $value) { - if($ref_data = ($references[$value] ?? null)) { - $tables[$value] = [ - 'table_name' => $value, - 'column' => $ref_data->COLUMN_NAME - ]; - } - } - } - $relations[] = ['table' => $ref->TABLE_NAME, 'tables' => $tables]; - } + 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 $relations != [] ? $relations : null; + return !empty($relations) ? $relations : null; } - public static function re_key_array($old_array, $key) { - $new_array = []; - if (count($old_array) > 0) { - foreach ($old_array as $array) { - $new_array[$array->$key] = $array; - } - } - return $new_array; - } + /** + * 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; + } }
