diff --git a/composer.json b/composer.json index 5894682..fb65b76 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,8 @@ "illuminate/database": "^10.0 || ^11.0", "illuminate/support": "^10.0 || ^11.0", "laravel-lang/config": "dev-main as 1.6.0", - "laravel-lang/locales": "^2.8" + "laravel-lang/locales": "^2.8", + "laravel/prompts": "^0.1.24" }, "require-dev": { "orchestra/testbench": "^8.23 || ^9.1", @@ -58,6 +59,7 @@ }, "autoload-dev": { "psr-4": { + "App\\": "vendor/orchestra/testbench-core/laravel/app/", "Tests\\": "tests/" } }, diff --git a/src/Console/ModelMakeCommand.php b/src/Console/ModelMakeCommand.php index 4ec4fd6..a8a5a3b 100644 --- a/src/Console/ModelMakeCommand.php +++ b/src/Console/ModelMakeCommand.php @@ -4,11 +4,19 @@ namespace LaravelLang\Models\Console; -use App\Models\Test; -use DragonCode\Support\Facades\Instances\Instance; +use DragonCode\Support\Facades\Filesystem\File; use Illuminate\Console\Command; +use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Console\ModelMakeCommand as BaseMakeCommand; use Illuminate\Support\Str; +use LaravelLang\Config\Facades\Config; +use LaravelLang\Models\Eloquent\Translation; +use LaravelLang\Models\Services\ClassMap; + +use function Laravel\Prompts\confirm; +use function Laravel\Prompts\info; +use function Laravel\Prompts\search; +use function Laravel\Prompts\text; class ModelMakeCommand extends Command { @@ -16,22 +24,98 @@ class ModelMakeCommand extends Command protected $description = 'Creates a model for storing translations'; + protected array $columns = ['locale', 'title', 'description']; + public function handle(): void { - if (! $model = $this->model()) { - dd('nope', $model); + if (!$model = $this->model()) { + info('You haven\'t selected a model.'); + return; } - - dd('aaa'); + + $columns = $this->columns(); + + $this->generateModel($model, $columns, Config::shared()->models->suffix); + $this->generateMigration($model, $columns, Config::shared()->models->suffix); + $this->generateHelper($model); + } + + protected function generateModel(string $model, array $columns, string $suffix): void + { + $fillable = array_map( + fn (string $column) => sprintf(" '$column',"), + $columns + ); + + $casts = array_map( + fn (string $column) => sprintf(" '%s' => ColumnCast::class,", $column), + array_filter($columns, fn (string $column) => $column !== 'locale') + ); + + $content = \DragonCode\Support\Facades\Helpers\Str::of( + file_get_contents(__DIR__ . '/../../stubs/model.stub') + )->replaceFormat([ + 'namespace' => Str::of($model)->ltrim('\\')->beforeLast('\\'), + 'model' => Str::afterLast($model, '\\'), + 'suffix' => $suffix, + 'fillable' => implode(PHP_EOL, $fillable), + 'casts' => implode(PHP_EOL, $casts), + ], '{{%s}}') + ->toString(); + + $path = ClassMap::path($model); + $extension = pathinfo($path, PATHINFO_EXTENSION); + + File::store( + Str::beforeLast($path, '.' . $extension) . $suffix . '.' . $extension, + $content + ); + } + + protected function generateMigration(string $model, array $columns, string $suffix): void + { + /** @var Model $base */ + $base = new $model; + + /** @var Translation $translated */ + $translated = new ($model . $suffix); + + $columns = array_map( + fn (string $column) => sprintf(" \$table->string('$column')->nullable();"), + array_filter($columns, fn (string $column) => $column !== 'locale') + ); + + $columnType = $base->getKeyType() === 'uuid' ? 'uuid' : 'bigInteger'; + + $content = \DragonCode\Support\Facades\Helpers\Str::of( + file_get_contents(__DIR__ . '/../../stubs/migration_create.stub') + )->replaceFormat([ + 'table' => $translated->getTable(), + 'primaryType' => $columnType, + 'columns' => implode(PHP_EOL, $columns), + ], '{{%s}}') + ->toString(); + + File::store( + database_path(sprintf("migrations/%s_create_%s_table.php", date('Y_m_d_His'), $translated->getTable())), + $content + ); + } + + protected function generateHelper(string $model): void + { + $this->call(ModelsHelperCommand::class, compact('model')); } protected function model(): ?string { - $model = $this->askTranslationModel(); + $model = $this->resolveModelClass( + $this->askTranslationModel() + ); - if (! $this->modelExists($model)) { - if (! $this->askModel()) { + if (!$model) { + if (!$this->ascToCreate()) { return null; } @@ -41,40 +125,77 @@ protected function model(): ?string return $model; } + protected function columns(): array + { + if ($columns = $this->option('columns')) { + return collect($columns)->prepend('locale')->all(); + } + + if ($columns = $this->askColumns()) { + return collect($columns)->prepend('locale')->all(); + } + + return $this->columns; + } + protected function askTranslationModel(): string { if ($model = $this->argument('model')) { return $model; } - return $this->ask('Specify the namespace of the model for which you want to create a storage'); + return search( + 'Specify the model name for which you want to create a translation repository:', + fn (string $value) => $this->findModel($value), + 'E.g. Post' + ); } - protected function askModel(): bool + protected function findModel(string $value): array { - return $this->confirm('No model with this namespace was found. Do you want to create it?', true); + return ClassMap::find($value); } - protected function modelExists(string $class): bool + protected function askColumns(array $columns = []): ?array { - dd( - $class , - Str::start($class, '\\'), - class_exists(Str::start($class, '\\')), - Test::class, - class_exists(Test::class), - Instance::exists(Test::class) - ); - return class_exists(Str::start($class, '\\')); + if ($column = text('Enter a column name', hint: 'Or press Enter for continue')) { + return array_filter(array_merge([$column], $this->askColumns($columns))); + } + + return null; + } + + protected function ascToCreate(): bool + { + return confirm('No model with this namespace was found. Do you want to create it?', true); + } + + protected function resolveModelClass(string $model): ?string + { + $model = Str::of($model)->replace('/', '\\')->start('\\')->toString(); + + $values = [ + $model, + '\App' . $model, + '\App\Models' . $model, + ]; + + foreach ($values as $value) { + if (class_exists($value)) { + return $value; + } + } + + return null; } protected function createBaseModel(string $model): void { $this->call(BaseMakeCommand::class, [ - 'name' => $model, + 'name' => Str::after($model, 'App\\Models\\'), '--migration' => true, - '--factory' => true, - '--seed' => true, + '--factory' => true, + '--seed' => true, ]); } } diff --git a/src/Console/ModelsHelperCommand.php b/src/Console/ModelsHelperCommand.php index bc0491b..a1b9581 100644 --- a/src/Console/ModelsHelperCommand.php +++ b/src/Console/ModelsHelperCommand.php @@ -37,7 +37,7 @@ protected function generate(string $model): void protected function models(): array { if ($model = $this->argument('model')) { - return [$model]; + return [ltrim($model, '\\')]; } return ClassMap::get(); diff --git a/src/Services/ClassMap.php b/src/Services/ClassMap.php index 459e67d..6cd33cf 100644 --- a/src/Services/ClassMap.php +++ b/src/Services/ClassMap.php @@ -6,6 +6,7 @@ use Composer\ClassMapGenerator\ClassMapGenerator; use DragonCode\Support\Facades\Instances\Instance; +use Illuminate\Support\Str; use LaravelLang\Config\Facades\Config; use LaravelLang\Models\HasTranslations; @@ -19,13 +20,29 @@ public static function get(): array ->all(); } + public static function find(string $value): array + { + return collect(static::map()) + ->keys() + ->filter(static fn (string $class) => static::contains($class, $value)) + ->all(); + } + + public static function path(string $class): ?string + { + return collect(static::map()) + ->filter(static fn (string $path, string $name) => $name === ltrim($class, '\\')) + ->first(); + } + protected static function map(): array { - return ClassMapGenerator::createMap(static::path()); + return ClassMapGenerator::createMap(static::modelsPath()); } - protected static function path(): string + protected static function modelsPath(): string { + return base_path('app'); return Config::hidden()->models->directory; } @@ -33,4 +50,9 @@ protected static function isTranslatable(string $class): bool { return Instance::of($class, HasTranslations::class); } + + protected static function contains(string $class, string $needle): bool + { + return Str::of($class)->lower()->contains(strtolower($needle)); + } } diff --git a/stubs/migration_create.stub b/stubs/migration_create.stub index e507caf..cadcd7e 100644 --- a/stubs/migration_create.stub +++ b/stubs/migration_create.stub @@ -12,10 +12,10 @@ return new class extends Migration { Schema::create('{{table}}', function (Blueprint $table) { $table->id(); - $table->string('locale'); - $table->{{primaryType}}('item_id')->index(); + $table->string('locale'); + {{columns}} $table->unique(['item_id', 'locale']); diff --git a/stubs/model.stub b/stubs/model.stub index c3b367c..f0ea157 100644 --- a/stubs/model.stub +++ b/stubs/model.stub @@ -4,11 +4,16 @@ declare(strict_types=1); namespace {{namespace}}; -use LaravelLang\Models\Translation; +use LaravelLang\Models\Casts\ColumnCast; +use LaravelLang\Models\Eloquent\Translation; class {{model}}{{suffix}} extends Translation { protected $fillable = [ -{{columns}} +{{fillable}} + ]; + + protected $casts = [ +{{casts}} ]; } diff --git a/tests/Unit/Console/ModelTest.php b/tests/Unit/Console/ModelTest.php index e0be95e..734f2ae 100644 --- a/tests/Unit/Console/ModelTest.php +++ b/tests/Unit/Console/ModelTest.php @@ -57,5 +57,6 @@ ->toContain('$table->string(\'title\')->nullable()') ->toContain('$table->string(\'description\')->nullable()'); + expect($migration)->toBeReadableFile(); expect($helper)->toBeReadableFile(); });