diff --git a/.gitignore b/.gitignore index 089088d..09b94df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -.idea -vendor +.idea/ +.php-cs-fixer.cache +.phpunit.result.cache +.psalm-cache/ composer.lock -.phpunit.result.cache \ No newline at end of file +vendor/ diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..5a8dc48 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,95 @@ +exclude('tests/integration/data') + ->ignoreDotFiles(true) + ->ignoreVCS(true) + ->in([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->name('*.php'); +; + +$config = new PhpCsFixer\Config(); +$config + ->setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + '@PSR2' => true, + //'@PhpCsFixer:risky' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + + 'array_syntax' => [ + 'syntax' => 'short', + ], + + 'binary_operator_spaces' => [ + 'default' => 'align_single_space_minimal', + ], + + 'blank_line_after_namespace' => true, + + 'blank_line_before_statement' => [ + 'statements' => [ + //'break', + //'continue', + 'declare', + 'return', + //'throw', + 'switch', + 'try', + ], + ], + + 'clean_namespace' => true, + + 'concat_space' => [ + 'spacing' => 'one', + ], + + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + 'keep_multiple_spaces_after_comma' => true, + ], + + 'no_break_comment' => false, + 'no_empty_comment' => true, + 'no_superfluous_phpdoc_tags' => false, + 'no_unused_imports' => true, + 'not_operator_with_successor_space' => true, + + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + ], + + 'phpdoc_align' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => false, + 'phpdoc_var_without_name' => true, + + 'phpdoc_types_order' => [ + 'null_adjustment' => 'always_last', + 'sort_algorithm' => 'none', + ], + + //'single_line_comment_spacing' => true, + //'single_line_comment_style' => true, + + 'trailing_comma_in_multiline' => [ + 'elements' => [ + 'arrays', + ], + ], + + 'unary_operator_spaces' => true, + ]) + ->setFinder($finder); + +return $config; diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ccd55..e0ce784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ CHANGELOG ========= + +2025-01-28, v2.0.3 +----------------- +* Do not connect to database in command constructors + 2023-11-25, v2.0.2 ----------------- * Add Laravel 10 support diff --git a/README.md b/README.md index 92fe816..524b9f3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +(forked from [krlove/eloquent-model-generator](https://github.com/krlove/eloquent-model-generator)) + # Eloquent Model Generator Eloquent Model Generator generates Eloquent models using database schema as a source. @@ -8,7 +10,7 @@ Version 2.0.0 has been released. Checkout [this discussion](https://github.com/k ## Installation Step 1. Add Eloquent Model Generator to your project: ``` -composer require krlove/eloquent-model-generator --dev +composer require --dev magentron/eloquent-model-generator ``` Step 2. Register `GeneratorServiceProvider`: ```php diff --git a/composer.json b/composer.json index 5a449ac..78135fe 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { - "name": "krlove/eloquent-model-generator", - "description": "Eloquent Model Generator", - "version": "2.0.2", + "name": "magentron/eloquent-model-generator", + "description": "Eloquent Model Generator (forked from krlove/eloquent-model-generator", "license": "MIT", "autoload": { "psr-4": { @@ -13,18 +12,30 @@ "Krlove\\Tests\\Integration\\": "tests/integration" } }, + "replace": { + "krlove/eloquent-model-generator": "<=2.0.2" + }, "require": { "php": "^8.0", - "illuminate/database": "^9.0 || ^10.0", - "illuminate/support": "^9.0 || ^10.0", - "illuminate/config": "^9.0 || ^10.0", - "illuminate/console": "^9.0 || ^10.0", + "illuminate/database": "^11.0", + "illuminate/support": "^11.0", + "illuminate/config": "^11.0", + "illuminate/console": "^11.0", "doctrine/dbal": "^3.0", "krlove/code-generator": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "friendsofphp/php-cs-fixer": "^3.70", + "larastan/larastan": "^3.1", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11", + "symfony/var-dumper": "^7.2", + "vimeo/psalm": "~6.4.0" }, + "config": { + "optimize-autoloader": true, + "sort-packages": true + }, "extra": { "laravel": { "providers": [ diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..0d9ea8e --- /dev/null +++ b/psalm.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Command/GenerateCommandTrait.php b/src/Command/GenerateCommandTrait.php index 7b4f0a7..321884e 100644 --- a/src/Command/GenerateCommandTrait.php +++ b/src/Command/GenerateCommandTrait.php @@ -2,6 +2,7 @@ namespace Krlove\EloquentModelGenerator\Command; +use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Model; use Krlove\EloquentModelGenerator\Config\Config; use Krlove\EloquentModelGenerator\Exception\GeneratorException; @@ -26,7 +27,7 @@ protected function saveModel(EloquentModel $model): void $content = $model->render(); $outputFilepath = $this->resolveOutputPath() . '/' . $model->getName()->getName() . '.php'; - if (!$this->option('no-backup') && file_exists($outputFilepath)) { + if (! $this->option('no-backup') && file_exists($outputFilepath)) { rename($outputFilepath, $outputFilepath . '~'); } file_put_contents($outputFilepath, $content); @@ -37,17 +38,17 @@ protected function resolveOutputPath(): string $path = $this->option('output-path'); if ($path === null) { $path = app()->path('Models'); - } elseif (!str_starts_with($path, '/')) { + } elseif (! str_starts_with($path, '/')) { $path = app()->path($path); } - if (!is_dir($path)) { - if (!mkdir($path, 0777, true)) { + if (! is_dir($path)) { + if (! mkdir($path, 0777, true)) { throw new GeneratorException(sprintf('Could not create directory %s', $path)); } } - if (!is_writeable($path)) { + if (! is_writeable($path)) { throw new GeneratorException(sprintf('%s is not writeable', $path)); } @@ -67,4 +68,9 @@ protected function getCommonOptions(): array ['no-backup', 'b', InputOption::VALUE_OPTIONAL, 'Backup existing model', config('eloquent_model_generator.no_backup', false)], ]; } -} \ No newline at end of file + + protected function resolve($abstract) + { + return Container::getInstance()->make($abstract); + } +} diff --git a/src/Command/GenerateModelCommand.php b/src/Command/GenerateModelCommand.php index 2a3d7bf..49c791e 100644 --- a/src/Command/GenerateModelCommand.php +++ b/src/Command/GenerateModelCommand.php @@ -14,18 +14,16 @@ class GenerateModelCommand extends Command protected $name = 'krlove:generate:model'; - public function __construct(private Generator $generator, private DatabaseManager $databaseManager) - { - parent::__construct(); - } - public function handle() { + $generator = $this->resolve(Generator::class); + $databaseManager = $this->resolve(DatabaseManager::class); + $config = $this->createConfig(); $config->setClassName($this->argument('class-name')); - Prefix::setPrefix($this->databaseManager->connection($config->getConnection())->getTablePrefix()); + Prefix::setPrefix($databaseManager->connection($config->getConnection())->getTablePrefix()); - $model = $this->generator->generateModel($config); + $model = $generator->generateModel($config); $this->saveModel($model); $this->output->writeln(sprintf('Model %s generated', $model->getName()->getName())); diff --git a/src/Command/GenerateModelsCommand.php b/src/Command/GenerateModelsCommand.php index fc3656d..c01bbd0 100644 --- a/src/Command/GenerateModelsCommand.php +++ b/src/Command/GenerateModelsCommand.php @@ -2,7 +2,6 @@ namespace Krlove\EloquentModelGenerator\Command; -use Illuminate\Config\Repository as AppConfig; use Illuminate\Console\Command; use Illuminate\Database\DatabaseManager; use Krlove\EloquentModelGenerator\Generator; @@ -16,19 +15,20 @@ class GenerateModelsCommand extends Command protected $name = 'krlove:generate:models'; - public function __construct(private Generator $generator, private DatabaseManager $databaseManager) - { - parent::__construct(); - } - public function handle() { + $generator = $this->resolve(Generator::class); + $databaseManager = $this->resolve(DatabaseManager::class); + $config = $this->createConfig(); - Prefix::setPrefix($this->databaseManager->connection($config->getConnection())->getTablePrefix()); + Prefix::setPrefix($databaseManager->connection($config->getConnection())->getTablePrefix()); + + $connection = $databaseManager->connection($config->getConnection()); + $schemaBuilder = $connection->getSchemaBuilder(); - $schemaManager = $this->databaseManager->connection($config->getConnection())->getDoctrineSchemaManager(); - $tables = $schemaManager->listTables(); + $tables = $schemaBuilder->getTableListing(); $skipTables = $this->option('skip-table'); + foreach ($tables as $table) { $tableName = Prefix::remove($table->getName()); if (in_array($tableName, $skipTables)) { @@ -36,7 +36,7 @@ public function handle() } $config->setClassName(EmgHelper::getClassNameByTableName($tableName)); - $model = $this->generator->generateModel($config); + $model = $generator->generateModel($config); $this->saveModel($model); $this->output->writeln(sprintf('Model %s generated', $model->getName()->getName())); @@ -52,4 +52,4 @@ protected function getOptions() ], ); } -} \ No newline at end of file +} diff --git a/src/Config/Config.php b/src/Config/Config.php index fd22eb5..88bd577 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -4,13 +4,13 @@ class Config { - private ?string $className = null; - private ?string $tableName = null; - private ?string $namespace = null; + private ?string $className = null; + private ?string $tableName = null; + private ?string $namespace = null; private ?string $baseClassName = null; - private ?bool $noTimestamps = null; - private ?string $dateFormat = null; - private ?string $connection = null; + private ?bool $noTimestamps = null; + private ?string $dateFormat = null; + private ?string $connection = null; public function getClassName(): ?string { diff --git a/src/EventListener/GenerateCommandEventListener.php b/src/EventListener/GenerateCommandEventListener.php index 36f071b..f89c8c2 100644 --- a/src/EventListener/GenerateCommandEventListener.php +++ b/src/EventListener/GenerateCommandEventListener.php @@ -3,6 +3,7 @@ namespace Krlove\EloquentModelGenerator\EventListener; use Illuminate\Console\Events\CommandStarting; +use Illuminate\Container\Container; use Krlove\EloquentModelGenerator\TypeRegistry; class GenerateCommandEventListener @@ -12,17 +13,18 @@ class GenerateCommandEventListener 'krlove:generate:models', ]; - public function __construct(private TypeRegistry $typeRegistry) {} - public function handle(CommandStarting $event): void { - if (!in_array($event->command, self::SUPPORTED_COMMANDS)) { + if (! in_array($event->command, self::SUPPORTED_COMMANDS)) { return; } + /** @var TypeRegistry $typeRegistry */ + $typeRegistry = Container::getInstance()->make(TypeRegistry::class); + $userTypes = config('eloquent_model_generator.db_types', []); foreach ($userTypes as $type => $value) { - $this->typeRegistry->registerType($type, $value); + $typeRegistry->registerType($type, $value); } } -} \ No newline at end of file +} diff --git a/src/Exception/GeneratorException.php b/src/Exception/GeneratorException.php index f83a112..3850434 100644 --- a/src/Exception/GeneratorException.php +++ b/src/Exception/GeneratorException.php @@ -2,6 +2,8 @@ namespace Krlove\EloquentModelGenerator\Exception; -class GeneratorException extends \Exception +use Exception; + +class GeneratorException extends Exception { } diff --git a/src/Helper/EmgHelper.php b/src/Helper/EmgHelper.php index 405d54c..f839aa2 100644 --- a/src/Helper/EmgHelper.php +++ b/src/Helper/EmgHelper.php @@ -2,7 +2,6 @@ namespace Krlove\EloquentModelGenerator\Helper; -use Doctrine\DBAL\Schema\Table; use Illuminate\Support\Str; class EmgHelper @@ -39,15 +38,15 @@ public static function getDefaultJoinTableName(string $tableNameOne, string $tab return implode('_', $tables); } - public static function isColumnUnique(Table $table, string $column): bool + public static function isColumnUniqueIndex(array $indexes, string $column): bool { - foreach ($table->getIndexes() as $index) { - $indexColumns = $index->getColumns(); + foreach ($indexes as $index) { + $indexColumns = $index['columns']; if (count($indexColumns) !== 1) { continue; } $indexColumn = $indexColumns[0]; - if ($indexColumn === $column && $index->isUnique()) { + if ($indexColumn === $column && $index['unique']) { return true; } } diff --git a/src/Helper/Prefix.php b/src/Helper/Prefix.php index 7691cec..02b8963 100644 --- a/src/Helper/Prefix.php +++ b/src/Helper/Prefix.php @@ -22,4 +22,4 @@ public static function remove(string $tableName): string return preg_replace("/^$prefix/", '', $tableName); } -} \ No newline at end of file +} diff --git a/src/Model/EloquentModel.php b/src/Model/EloquentModel.php index 81fba5a..e04a6e4 100644 --- a/src/Model/EloquentModel.php +++ b/src/Model/EloquentModel.php @@ -13,6 +13,7 @@ use Krlove\CodeGenerator\Model\VirtualPropertyModel; use Krlove\EloquentModelGenerator\Exception\GeneratorException; use Krlove\EloquentModelGenerator\Helper\EmgHelper; +use ReflectionObject; class EloquentModel extends ClassModel { @@ -34,22 +35,22 @@ public function addRelation(Relation $relation): void { $relationClass = EmgHelper::getClassNameByTableName($relation->getTableName()); if ($relation instanceof HasOne) { - $name = Str::singular(Str::camel($relation->getTableName())); + $name = Str::singular(Str::camel($relation->getTableName())); $docBlock = sprintf('@return \%s', EloquentHasOne::class); $virtualPropertyType = $relationClass; } elseif ($relation instanceof HasMany) { - $name = Str::plural(Str::camel($relation->getTableName())); + $name = Str::plural(Str::camel($relation->getTableName())); $docBlock = sprintf('@return \%s', EloquentHasMany::class); $virtualPropertyType = sprintf('%s[]', $relationClass); } elseif ($relation instanceof BelongsTo) { - $name = Str::singular(Str::camel($relation->getTableName())); + $name = Str::singular(Str::camel($relation->getTableName())); $docBlock = sprintf('@return \%s', EloquentBelongsTo::class); $virtualPropertyType = $relationClass; } elseif ($relation instanceof BelongsToMany) { - $name = Str::plural(Str::camel($relation->getTableName())); + $name = Str::plural(Str::camel($relation->getTableName())); $docBlock = sprintf('@return \%s', EloquentBelongsToMany::class); $virtualPropertyType = sprintf('%s[]', $relationClass); @@ -67,11 +68,11 @@ public function addRelation(Relation $relation): void protected function createRelationMethodBody(Relation $relation): string { - $reflectionObject = new \ReflectionObject($relation); - $name = Str::camel($reflectionObject->getShortName()); + $reflectionObject = new ReflectionObject($relation); + $name = Str::camel($reflectionObject->getShortName()); $arguments = [ - $this->getNamespace()->getNamespace() . '\\' . EmgHelper::getClassNameByTableName($relation->getTableName()) + $this->getNamespace()->getNamespace() . '\\' . EmgHelper::getClassNameByTableName($relation->getTableName()), ]; if ($relation instanceof BelongsToMany) { @@ -117,11 +118,12 @@ protected function createRelationMethodBody(Relation $relation): string protected function createRelationMethodArguments(array $array): string { - $array = array_reverse($array); + $array = array_reverse($array); $milestone = false; + foreach ($array as $key => &$item) { - if (!$milestone) { - if (!is_string($item)) { + if (! $milestone) { + if (! is_string($item)) { unset($array[$key]); } else { $milestone = true; diff --git a/src/Processor/CustomPrimaryKeyProcessor.php b/src/Processor/CustomPrimaryKeyProcessor.php index 29697e1..81c05bf 100644 --- a/src/Processor/CustomPrimaryKeyProcessor.php +++ b/src/Processor/CustomPrimaryKeyProcessor.php @@ -3,6 +3,8 @@ namespace Krlove\EloquentModelGenerator\Processor; use Illuminate\Database\DatabaseManager; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\BlueprintState; use Krlove\CodeGenerator\Model\DocBlockModel; use Krlove\CodeGenerator\Model\PropertyModel; use Krlove\EloquentModelGenerator\Config\Config; @@ -12,37 +14,51 @@ class CustomPrimaryKeyProcessor implements ProcessorInterface { - public function __construct(private DatabaseManager $databaseManager, private TypeRegistry $typeRegistry) {} + public function __construct( + private DatabaseManager $databaseManager, + private TypeRegistry $typeRegistry, + ) { + } public function process(EloquentModel $model, Config $config): void { - $schemaManager = $this->databaseManager->connection($config->getConnection())->getDoctrineSchemaManager(); + $connection = $this->databaseManager->connection($config->getConnection()); + $schemaGrammar = $connection->getSchemaGrammar(); + + $tableName = Prefix::add($model->getTableName()); + $blueprint = new Blueprint($tableName); + $blueprintState = new BlueprintState($blueprint, $connection, $schemaGrammar); - $tableDetails = $schemaManager->listTableDetails(Prefix::add($model->getTableName())); - $primaryKey = $tableDetails->getPrimaryKey(); + $primaryKey = $blueprintState->getPrimaryKey(); if ($primaryKey === null) { return; } - $columns = $primaryKey->getColumns(); - if (count($columns) !== 1) { + $primaryKeyColumns = $primaryKey->columns; + if (count($primaryKeyColumns) !== 1) { return; } - $column = $tableDetails->getColumn($columns[0]); - if ($column->getName() !== 'id') { - $primaryKeyProperty = new PropertyModel('primaryKey', 'protected', $column->getName()); + $columns = $blueprintState->getColumns(); + $column = collect($columns)->filter(fn ($column) => $column->name === $primaryKeyColumns[0])->first(); + $columnName = $column->name; + + if ($columnName !== 'id') { + $primaryKeyProperty = new PropertyModel('primaryKey', 'protected', $columnName); $primaryKeyProperty->setDocBlock( new DocBlockModel('The primary key for the model.', '', '@var string') ); $model->addProperty($primaryKeyProperty); } - if ($column->getType()->getName() !== 'integer') { + $columnTypeName = $column->type; + if ($columnTypeName !== 'integer') { + $keyType = $this->typeRegistry->resolveType($columnTypeName); + $keyTypeProperty = new PropertyModel( 'keyType', 'protected', - $this->typeRegistry->resolveType($column->getType()->getName()) + $keyType, ); $keyTypeProperty->setDocBlock( new DocBlockModel('The "type" of the auto-incrementing ID.', '', '@var string') @@ -50,7 +66,7 @@ public function process(EloquentModel $model, Config $config): void $model->addProperty($keyTypeProperty); } - if (!$column->getAutoincrement()) { + if (! $column->autoincrement()) { $autoincrementProperty = new PropertyModel('incrementing', 'public', false); $autoincrementProperty->setDocBlock( new DocBlockModel('Indicates if the IDs are auto-incrementing.', '', '@var bool') diff --git a/src/Processor/FieldProcessor.php b/src/Processor/FieldProcessor.php index 705f0e7..9743c69 100644 --- a/src/Processor/FieldProcessor.php +++ b/src/Processor/FieldProcessor.php @@ -3,6 +3,8 @@ namespace Krlove\EloquentModelGenerator\Processor; use Illuminate\Database\DatabaseManager; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\BlueprintState; use Krlove\CodeGenerator\Model\DocBlockModel; use Krlove\CodeGenerator\Model\PropertyModel; use Krlove\CodeGenerator\Model\VirtualPropertyModel; @@ -13,24 +15,35 @@ class FieldProcessor implements ProcessorInterface { - public function __construct(private DatabaseManager $databaseManager, private TypeRegistry $typeRegistry) {} - + public function __construct( + private DatabaseManager $databaseManager, + private TypeRegistry $typeRegistry, + ) { + } + public function process(EloquentModel $model, Config $config): void { - $schemaManager = $this->databaseManager->connection($config->getConnection())->getDoctrineSchemaManager(); + $connection = $this->databaseManager->connection($config->getConnection()); + $schemaGrammar = $connection->getSchemaGrammar(); - $tableDetails = $schemaManager->listTableDetails(Prefix::add($model->getTableName())); - $primaryColumnNames = $tableDetails->getPrimaryKey() ? $tableDetails->getPrimaryKey()->getColumns() : []; + $tableName = Prefix::add($model->getTableName()); + $blueprint = new Blueprint($tableName); + $blueprintState = new BlueprintState($blueprint, $connection, $schemaGrammar); + $columns = $blueprintState->getColumns(); + + $primaryKey = $blueprintState->getPrimaryKey(); + $primaryColumnNames = $primaryKey->columns; $columnNames = []; - foreach ($tableDetails->getColumns() as $column) { + + foreach ($columns as $column) { $model->addProperty(new VirtualPropertyModel( - $column->getName(), - $this->typeRegistry->resolveType($column->getType()->getName()) + $column->name, + $this->typeRegistry->resolveType($column->type) )); - if (!in_array($column->getName(), $primaryColumnNames)) { - $columnNames[] = $column->getName(); + if (! in_array($column->name, $primaryColumnNames)) { + $columnNames[] = $column->name; } } diff --git a/src/Processor/RelationProcessor.php b/src/Processor/RelationProcessor.php index 47d3623..560af09 100644 --- a/src/Processor/RelationProcessor.php +++ b/src/Processor/RelationProcessor.php @@ -3,6 +3,8 @@ namespace Krlove\EloquentModelGenerator\Processor; use Illuminate\Database\DatabaseManager; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\BlueprintState; use Krlove\EloquentModelGenerator\Config\Config; use Krlove\EloquentModelGenerator\Helper\EmgHelper; use Krlove\EloquentModelGenerator\Helper\Prefix; @@ -14,51 +16,67 @@ class RelationProcessor implements ProcessorInterface { - public function __construct(private DatabaseManager $databaseManager) {} + public function __construct( + private DatabaseManager $databaseManager, + ) { + } public function process(EloquentModel $model, Config $config): void { - $schemaManager = $this->databaseManager->connection($config->getConnection())->getDoctrineSchemaManager(); + $connection = $this->databaseManager->connection($config->getConnection()); + $schemaBuilder = $connection->getSchemaBuilder(); + $schemaGrammar = $connection->getSchemaGrammar(); $prefixedTableName = Prefix::add($model->getTableName()); - $tables = $schemaManager->listTables(); + $tables = $schemaBuilder->getTableListing(); + foreach ($tables as $table) { - $foreignKeys = $schemaManager->listTableForeignKeys($table->getName()); - foreach ($foreignKeys as $name => $foreignKey) { - $localColumns = $foreignKey->getLocalColumns(); + $tableName = $table; + $blueprint = new Blueprint($tableName); + $blueprintState = new BlueprintState($blueprint, $connection, $schemaGrammar); + $columns = $blueprintState->getColumns(); + $indexes = $schemaBuilder->getIndexes($tableName); + + $foreignKeys = $schemaBuilder->getForeignKeys($tableName); + foreach ($foreignKeys as $index => $foreignKey) { + $name = $foreignKey['name']; + $localColumns = $foreignKey['columns']; + if (count($localColumns) !== 1) { continue; } - if ($table->getName() === $prefixedTableName) { + $foreignTableName = $foreignKey['foreign_table']; + $foreignTableColumns = $foreignKey['foreign_columns']; + + if ($tableName === $prefixedTableName) { $relation = new BelongsTo( - Prefix::remove($foreignKey->getForeignTableName()), - $foreignKey->getLocalColumns()[0], - $foreignKey->getForeignColumns()[0] + Prefix::remove($foreignTableName), + $localColumns[0], + $foreignTableColumns[0] ); $model->addRelation($relation); - } elseif ($foreignKey->getForeignTableName() === $prefixedTableName) { - if (count($foreignKeys) === 2 && count($table->getColumns()) === 2) { - $keys = array_keys($foreignKeys); - $key = array_search($name, $keys) === 0 ? 1 : 0; - $secondForeignKey = $foreignKeys[$keys[$key]]; - $secondForeignTable = Prefix::remove($secondForeignKey->getForeignTableName()); + } elseif ($foreignTableName === $prefixedTableName) { + if (count($foreignKeys) === 2 && count($columns) === 2) { + $keyIndex = 1 - $index; + $secondForeignKey = $foreignKeys[$keyIndex]; + $secondForeignTable = Prefix::remove($secondForeignKey['foreign_table']); $relation = new BelongsToMany( $secondForeignTable, - Prefix::remove($table->getName()), + Prefix::remove($tableName), $localColumns[0], - $secondForeignKey->getLocalColumns()[0] + $secondForeignKey['columns'][0] ); $model->addRelation($relation); break; } else { - $tableName = Prefix::remove($table->getName()); + $tableName = Prefix::remove($tableName); $foreignColumn = $localColumns[0]; - $localColumn = $foreignKey->getForeignColumns()[0]; + $localColumn = $foreignTableColumns[0]; - if (EmgHelper::isColumnUnique($table, $foreignColumn)) { + if (EmgHelper::isColumnUniqueIndex($indexes, $foreignColumn)) { $relation = new HasOne($tableName, $foreignColumn, $localColumn); } else { $relation = new HasMany($tableName, $foreignColumn, $localColumn); diff --git a/src/Processor/TableNameProcessor.php b/src/Processor/TableNameProcessor.php index 020665f..36f0b7e 100644 --- a/src/Processor/TableNameProcessor.php +++ b/src/Processor/TableNameProcessor.php @@ -15,17 +15,21 @@ class TableNameProcessor implements ProcessorInterface { - public function __construct(private DatabaseManager $databaseManager) {} + public function __construct( + private DatabaseManager $databaseManager, + ) { + } public function process(EloquentModel $model, Config $config): void { - $className = $config->getClassName(); + $className = $config->getClassName(); $baseClassName = $config->getBaseClassName(); - $tableName = $config->getTableName() ?: EmgHelper::getTableNameByClassName($className); + $tableName = $config->getTableName() ?: EmgHelper::getTableNameByClassName($className); - $schemaManager = $this->databaseManager->connection($config->getConnection())->getDoctrineSchemaManager(); + $schemaManager = $this->databaseManager->connection($config->getConnection())->getSchemaBuilder(); $prefixedTableName = Prefix::add($tableName); - if (!$schemaManager->tablesExist($prefixedTableName)) { + + if (! $schemaManager->hasTable($prefixedTableName)) { throw new GeneratorException(sprintf('Table %s does not exist', $prefixedTableName)); } diff --git a/src/TypeRegistry.php b/src/TypeRegistry.php index 6eef201..56bc773 100644 --- a/src/TypeRegistry.php +++ b/src/TypeRegistry.php @@ -2,55 +2,52 @@ namespace Krlove\EloquentModelGenerator; -use Illuminate\Database\DatabaseManager; - class TypeRegistry { protected array $types = [ 'array' => 'array', - 'simple_array' => 'array', - 'json_array' => 'string', 'bigint' => 'integer', + 'binary' => 'string', + 'blob' => 'string', 'boolean' => 'boolean', + 'date' => 'string', 'datetime' => 'string', 'datetimetz' => 'string', - 'date' => 'string', - 'time' => 'string', 'decimal' => 'float', + 'enum' => 'string', + 'float' => 'float', + 'guid' => 'string', 'integer' => 'integer', + 'json_array' => 'string', 'object' => 'object', 'smallint' => 'integer', + 'simple_array' => 'array', 'string' => 'string', 'text' => 'string', - 'binary' => 'string', - 'blob' => 'string', - 'float' => 'float', - 'guid' => 'string', - 'enum' => 'string', + 'time' => 'string', + 'varchar' => 'string', ]; - public function __construct(private DatabaseManager $databaseManager) + public function __construct() { - foreach ($this->types as $sqlType => $phpType) { - $this->registerDoctrineTypeMapping($sqlType, $phpType); - } } public function registerType(string $sqlType, string $phpType, string $connection = null): void { $this->types[$sqlType] = $phpType; - - $this->registerDoctrineTypeMapping($sqlType, $phpType, $connection); } public function resolveType(string $type): string { - return array_key_exists($type, $this->types) ? $this->types[$type] : 'mixed'; - } + if (array_key_exists($type, $this->types)) { + return $this->types[$type]; + } - private function registerDoctrineTypeMapping(string $sqlType, string $phpType, string $connection = null): void - { - $manager = $this->databaseManager->connection($connection)->getDoctrineSchemaManager(); - $manager->getDatabasePlatform()->registerDoctrineTypeMapping($sqlType, $phpType); + $type = strtok($type, ' '); + if (array_key_exists($type, $this->types)) { + return $this->types[$type]; + } + + return 'mixed'; } } diff --git a/tests/integration/GeneratorTest.php b/tests/integration/GeneratorTest.php index 8d96b69..6e34115 100644 --- a/tests/integration/GeneratorTest.php +++ b/tests/integration/GeneratorTest.php @@ -8,7 +8,6 @@ use Illuminate\Database\SQLiteConnection; use Krlove\EloquentModelGenerator\Config\Config; use Krlove\EloquentModelGenerator\Generator; -use Krlove\EloquentModelGenerator\Helper\EmgHelper; use Krlove\EloquentModelGenerator\Processor\CustomPrimaryKeyProcessor; use Krlove\EloquentModelGenerator\Processor\CustomPropertyProcessor; use Krlove\EloquentModelGenerator\Processor\FieldProcessor; @@ -16,6 +15,7 @@ use Krlove\EloquentModelGenerator\Processor\RelationProcessor; use Krlove\EloquentModelGenerator\Processor\TableNameProcessor; use Krlove\EloquentModelGenerator\TypeRegistry; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class GeneratorTest extends TestCase @@ -26,8 +26,9 @@ class GeneratorTest extends TestCase public static function setUpBeforeClass(): void { $connector = new SQLiteConnector(); + $pdo = $connector->connect([ - 'database' => ':memory:', + 'database' => ':memory:', 'foreign_key_constraints' => true, ]); self::$connection = new SQLiteConnection($pdo); @@ -57,9 +58,7 @@ protected function setUp(): void ]); } - /** - * @dataProvider modelNameProvider - */ + #[DataProvider('modelNameProvider')] public function testGeneratedModel(string $modelName): void { $config = (new Config()) @@ -71,7 +70,7 @@ public function testGeneratedModel(string $modelName): void $this->assertEquals(file_get_contents(__DIR__ . '/resources/' . $modelName . '.php.generated'), $model->render()); } - public function modelNameProvider(): array + public static function modelNameProvider(): array { return [ [ diff --git a/tests/integration/resources/Avatar.php.generated b/tests/integration/resources/Avatar.php.generated index 3ec8ff5..e446ab0 100644 --- a/tests/integration/resources/Avatar.php.generated +++ b/tests/integration/resources/Avatar.php.generated @@ -6,8 +6,8 @@ use Illuminate\Database\Eloquent\Model; /** * @property integer $id - * @property integer $user_id * @property string $path + * @property integer $user_id * @property User $user */ class Avatar extends Model @@ -15,7 +15,7 @@ class Avatar extends Model /** * @var array */ - protected $fillable = ['user_id', 'path']; + protected $fillable = ['path', 'user_id']; /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo diff --git a/tests/integration/resources/Post.php.generated b/tests/integration/resources/Post.php.generated index fce0621..e47dfcd 100644 --- a/tests/integration/resources/Post.php.generated +++ b/tests/integration/resources/Post.php.generated @@ -6,9 +6,9 @@ use Illuminate\Database\Eloquent\Model; /** * @property integer $id - * @property integer $author_id * @property string $title * @property string $content + * @property integer $author_id * @property User $user */ class Post extends Model @@ -16,7 +16,7 @@ class Post extends Model /** * @var array */ - protected $fillable = ['author_id', 'title', 'content']; + protected $fillable = ['title', 'content', 'author_id']; /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo diff --git a/tests/integration/resources/User-with-params.php.generated b/tests/integration/resources/User-with-params.php.generated index 9bb7bdf..0c2017e 100644 --- a/tests/integration/resources/User-with-params.php.generated +++ b/tests/integration/resources/User-with-params.php.generated @@ -6,9 +6,9 @@ use Base\ClassName; /** * @property integer $id - * @property integer $organization_id * @property string $username * @property integer $is_active + * @property integer $organization_id * @property Avatar $avatar * @property Post[] $posts * @property Role[] $roles @@ -33,7 +33,7 @@ class User extends ClassName /** * @var array */ - protected $fillable = ['organization_id', 'username', 'is_active']; + protected $fillable = ['username', 'is_active', 'organization_id']; /** * @return \Illuminate\Database\Eloquent\Relations\HasOne diff --git a/tests/integration/resources/User.php.generated b/tests/integration/resources/User.php.generated index f388aae..dff3bc2 100644 --- a/tests/integration/resources/User.php.generated +++ b/tests/integration/resources/User.php.generated @@ -6,9 +6,9 @@ use Illuminate\Database\Eloquent\Model; /** * @property integer $id - * @property integer $organization_id * @property string $username * @property integer $is_active + * @property integer $organization_id * @property Avatar $avatar * @property Post[] $posts * @property Role[] $roles @@ -19,7 +19,7 @@ class User extends Model /** * @var array */ - protected $fillable = ['organization_id', 'username', 'is_active']; + protected $fillable = ['username', 'is_active', 'organization_id']; /** * @return \Illuminate\Database\Eloquent\Relations\HasOne diff --git a/tests/unit/Helper/EmgHelperTest.php b/tests/unit/Helper/EmgHelperTest.php index 6ae2ae9..a5107e3 100644 --- a/tests/unit/Helper/EmgHelperTest.php +++ b/tests/unit/Helper/EmgHelperTest.php @@ -2,160 +2,142 @@ namespace unit\Helper; -use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Schema\Table; use Illuminate\Database\Eloquent\Model; use Krlove\EloquentModelGenerator\Helper\EmgHelper; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class EmgHelperTest extends TestCase { - /** - * @dataProvider fqcnProvider - */ + #[DataProvider('fqcnProvider')] public function testGetShortClassName(string $fqcn, string $expected): void { $this->assertEquals($expected, EmgHelper::getShortClassName($fqcn)); } - public function fqcnProvider(): array + public static function fqcnProvider(): array { return [ - ['fqcn' => Model::class, 'expected' => 'Model'], + ['fqcn' => Model::class, 'expected' => 'Model'], ['fqcn' => 'Custom\Name', 'expected' => 'Name'], - ['fqcn' => 'ShortName', 'expected' => 'ShortName'], + ['fqcn' => 'ShortName', 'expected' => 'ShortName'], ]; } - /** - * @dataProvider classNameProvider - */ + #[DataProvider('classNameProvider')] public function testGetTableNameByClassName(string $className, string $expected): void { $this->assertEquals($expected, EmgHelper::getTableNameByClassName($className)); } - public function classNameProvider(): array + public static function classNameProvider(): array { return [ - ['className' => 'User', 'expected' => 'users'], + ['className' => 'User', 'expected' => 'users'], ['className' => 'ServiceAccount', 'expected' => 'service_accounts'], - ['className' => 'Mouse', 'expected' => 'mice'], - ['className' => 'D', 'expected' => 'ds'], + ['className' => 'Mouse', 'expected' => 'mice'], + ['className' => 'D', 'expected' => 'ds'], ]; } - /** - * @dataProvider tableNameToClassNameProvider - */ + #[DataProvider('tableNameToClassNameProvider')] public function testGetClassNameByTableName(string $tableName, string $expected): void { $this->assertEquals($expected, EmgHelper::getClassNameByTableName($tableName)); } - public function tableNameToClassNameProvider(): array + public static function tableNameToClassNameProvider(): array { return [ - ['className' => 'users', 'expected' => 'User'], - ['className' => 'service_accounts', 'expected' => 'ServiceAccount'], - ['className' => 'mice', 'expected' => 'Mouse'], - ['className' => 'ds', 'expected' => 'D'], + ['tableName' => 'users', 'expected' => 'User'], + ['tableName' => 'service_accounts', 'expected' => 'ServiceAccount'], + ['tableName' => 'mice', 'expected' => 'Mouse'], + ['tableName' => 'ds', 'expected' => 'D'], ]; } - /** - * @dataProvider tableNameToForeignColumnNameProvider - */ + #[DataProvider('tableNameToForeignColumnNameProvider')] public function testGetDefaultForeignColumnName(string $tableName, string $expected): void { $this->assertEquals($expected, EmgHelper::getDefaultForeignColumnName($tableName)); } - public function tableNameToForeignColumnNameProvider(): array + public static function tableNameToForeignColumnNameProvider(): array { return [ - ['tableName' => 'organizations', 'expected' => 'organization_id'], + ['tableName' => 'organizations', 'expected' => 'organization_id'], ['tableName' => 'service_accounts', 'expected' => 'service_account_id'], - ['tableName' => 'mice', 'expected' => 'mouse_id'], + ['tableName' => 'mice', 'expected' => 'mouse_id'], ]; } - /** - * @dataProvider tableNamesProvider - */ + #[DataProvider('tableNamesProvider')] public function testGetDefaultJoinTableName(string $tableNameOne, string $tableNameTwo, string $expected): void { $this->assertEquals($expected, EmgHelper::getDefaultJoinTableName($tableNameOne, $tableNameTwo)); } - public function tableNamesProvider(): array + public static function tableNamesProvider(): array { return [ - ['tableNameOne' => 'users', 'tableNameTwo' => 'roles', 'expected' => 'role_user'], - ['tableNameOne' => 'roles', 'tableNameTwo' => 'users', 'expected' => 'role_user'], + ['tableNameOne' => 'users', 'tableNameTwo' => 'roles', 'expected' => 'role_user'], + ['tableNameOne' => 'roles', 'tableNameTwo' => 'users', 'expected' => 'role_user'], ['tableNameOne' => 'accounts', 'tableNameTwo' => 'profiles', 'expected' => 'account_profile'], ]; } public function testIsColumnUnique(): void { - $indexMock = $this->createMock(Index::class); - $indexMock->expects($this->once()) - ->method('getColumns') - ->willReturn(['column_0']); - - $indexMock->expects($this->once()) - ->method('isUnique') - ->willReturn(true); - - $indexMocks = [$indexMock]; - - $tableMock = $this->createMock(Table::class); - $tableMock->expects($this->once()) - ->method('getIndexes') - ->willReturn($indexMocks); + $indexes = [ + [ + 'name' => null, + 'primary' => false, + 'type' => null, + 'unique' => true, + + 'columns' => [ + 'column_0', + ], + ], + ]; - $this->assertTrue(EmgHelper::isColumnUnique($tableMock, 'column_0')); + $this->assertTrue(EmgHelper::isColumnUniqueIndex($indexes, 'column_0')); } public function testIsColumnUniqueTwoIndexColumns(): void { - $indexMock = $this->createMock(Index::class); - $indexMock->expects($this->once()) - ->method('getColumns') - ->willReturn(['column_0', 'column_1']); - - $indexMock->expects($this->never()) - ->method('isUnique'); - - $indexMocks = [$indexMock]; - - $tableMock = $this->createMock(Table::class); - $tableMock->expects($this->once()) - ->method('getIndexes') - ->willReturn($indexMocks); + $indexes = [ + [ + 'name' => null, + 'primary' => false, + 'type' => null, + 'unique' => true, + + 'columns' => [ + 'column_0', + 'column_1', + ], + ], + ]; - $this->assertFalse(EmgHelper::isColumnUnique($tableMock, 'column_0')); + $this->assertFalse(EmgHelper::isColumnUniqueIndex($indexes, 'column_0')); } public function testIsColumnUniqueIndexNotUnique(): void { - $indexMock = $this->createMock(Index::class); - $indexMock->expects($this->once()) - ->method('getColumns') - ->willReturn(['column_0']); - - $indexMock->expects($this->once()) - ->method('isUnique') - ->willReturn(false); - - $indexMocks = [$indexMock]; - - $tableMock = $this->createMock(Table::class); - $tableMock->expects($this->once()) - ->method('getIndexes') - ->willReturn($indexMocks); + $indexes = [ + [ + 'name' => null, + 'primary' => true, + 'type' => null, + 'unique' => false, + + 'columns' => [ + 'column_0', + ], + ], + ]; - $this->assertFalse(EmgHelper::isColumnUnique($tableMock, 'column_0')); + $this->assertFalse(EmgHelper::isColumnUniqueIndex($indexes, 'column_0')); } }