files = $files; } /** * Execute the console command. */ public function handle(): int { $name = $this->argument('name'); $this->info("🚀 Generating ThrillWiki CRUD for: {$name}"); // Prepare naming conventions $names = $this->prepareNames($name); try { // Generate files $this->generateModel($names); $this->generateController($names); $this->generateFormRequest($names); $this->generateViews($names); $this->generateRoutes($names); if ($this->option('migration')) { $this->generateMigration($names); } if ($this->option('api')) { $this->generateApiResources($names); } if ($this->option('with-tests')) { $this->generateTests($names); } $this->displaySuccessMessage($names); } catch (\Exception $e) { $this->error("❌ Error generating CRUD: " . $e->getMessage()); return self::FAILURE; } return self::SUCCESS; } /** * Prepare all naming variations */ protected function prepareNames(string $name): array { $studly = Str::studly($name); $snake = Str::snake($name); $kebab = Str::kebab($name); $plural = Str::plural($snake); $pluralStudly = Str::studly($plural); return [ 'name' => $name, 'studly' => $studly, 'snake' => $snake, 'kebab' => $kebab, 'plural' => $plural, 'pluralStudly' => $pluralStudly, 'camel' => Str::camel($name), 'pluralCamel' => Str::camel($plural), 'title' => Str::title(str_replace('_', ' ', $snake)), 'pluralTitle' => Str::title(str_replace('_', ' ', $plural)), 'model' => $this->option('model') ?: $studly, 'controller' => $this->option('controller') ?: $studly . 'Controller', 'table' => $plural, ]; } /** * Generate the Eloquent model */ protected function generateModel(array $names): void { $path = app_path("Models/{$names['model']}.php"); if ($this->files->exists($path) && !$this->option('force')) { $this->warn("⚠️ Model already exists: {$path}"); return; } $stub = $this->getModelStub(); $content = $this->replaceStubVariables($stub, $names); $this->files->put($path, $content); $this->info("✅ Model created: app/Models/{$names['model']}.php"); } /** * Generate the controller */ protected function generateController(array $names): void { $path = app_path("Http/Controllers/{$names['controller']}.php"); if ($this->files->exists($path) && !$this->option('force')) { $this->warn("⚠️ Controller already exists: {$path}"); return; } $stub = $this->getControllerStub(); $content = $this->replaceStubVariables($stub, $names); $this->files->put($path, $content); $this->info("✅ Controller created: app/Http/Controllers/{$names['controller']}.php"); } /** * Generate form request validation */ protected function generateFormRequest(array $names): void { $requestName = $names['studly'] . 'Request'; $path = app_path("Http/Requests/{$requestName}.php"); if ($this->files->exists($path) && !$this->option('force')) { $this->warn("⚠️ Form Request already exists: {$path}"); return; } if (!$this->files->isDirectory(dirname($path))) { $this->files->makeDirectory(dirname($path), 0755, true); } $stub = $this->getFormRequestStub(); $content = $this->replaceStubVariables($stub, array_merge($names, ['requestName' => $requestName])); $this->files->put($path, $content); $this->info("✅ Form Request created: app/Http/Requests/{$requestName}.php"); } /** * Generate Blade views */ protected function generateViews(array $names): void { $viewPath = resource_path("views/{$names['plural']}"); if (!$this->files->isDirectory($viewPath)) { $this->files->makeDirectory($viewPath, 0755, true); } $views = ['index', 'show', 'create', 'edit']; foreach ($views as $view) { $path = "{$viewPath}/{$view}.blade.php"; if ($this->files->exists($path) && !$this->option('force')) { $this->warn("⚠️ View already exists: {$path}"); continue; } $stub = $this->getViewStub($view); $content = $this->replaceStubVariables($stub, $names); $this->files->put($path, $content); $this->info("✅ View created: resources/views/{$names['plural']}/{$view}.blade.php"); } } /** * Generate routes */ protected function generateRoutes(array $names): void { $webRoutesPath = base_path('routes/web.php'); $routeDefinition = $this->getRouteDefinition($names); // Check if route already exists $webRoutes = $this->files->get($webRoutesPath); if (strpos($webRoutes, "Route::resource('{$names['plural']}'") !== false) { $this->warn("⚠️ Routes may already exist for {$names['plural']}"); return; } // Append route to web.php $this->files->append($webRoutesPath, "\n" . $routeDefinition); $this->info("✅ Routes added to routes/web.php"); } /** * Generate migration */ protected function generateMigration(array $names): void { $migrationName = "create_{$names['table']}_table"; $timestamp = date('Y_m_d_His'); $path = database_path("migrations/{$timestamp}_{$migrationName}.php"); $stub = $this->getMigrationStub(); $content = $this->replaceStubVariables($stub, $names); $this->files->put($path, $content); $this->info("✅ Migration created: database/migrations/{$timestamp}_{$migrationName}.php"); } /** * Generate API resources */ protected function generateApiResources(array $names): void { // API Controller $apiPath = app_path("Http/Controllers/Api/{$names['controller']}.php"); if (!$this->files->isDirectory(dirname($apiPath))) { $this->files->makeDirectory(dirname($apiPath), 0755, true); } $stub = $this->getApiControllerStub(); $content = $this->replaceStubVariables($stub, $names); $this->files->put($apiPath, $content); $this->info("✅ API Controller created: app/Http/Controllers/Api/{$names['controller']}.php"); // API Resource $resourceName = $names['studly'] . 'Resource'; $resourcePath = app_path("Http/Resources/{$resourceName}.php"); if (!$this->files->isDirectory(dirname($resourcePath))) { $this->files->makeDirectory(dirname($resourcePath), 0755, true); } $stub = $this->getApiResourceStub(); $content = $this->replaceStubVariables($stub, array_merge($names, ['resourceName' => $resourceName])); $this->files->put($resourcePath, $content); $this->info("✅ API Resource created: app/Http/Resources/{$resourceName}.php"); // API Routes $apiRoutesPath = base_path('routes/api.php'); $apiRouteDefinition = $this->getApiRouteDefinition($names); $this->files->append($apiRoutesPath, "\n" . $apiRouteDefinition); $this->info("✅ API Routes added to routes/api.php"); } /** * Generate test files */ protected function generateTests(array $names): void { $testName = $names['controller'] . 'Test'; $testPath = base_path("tests/Feature/{$testName}.php"); if ($this->files->exists($testPath) && !$this->option('force')) { $this->warn("⚠️ Test already exists: {$testPath}"); return; } $stub = $this->getTestStub(); $content = $this->replaceStubVariables($stub, array_merge($names, ['testName' => $testName])); $this->files->put($testPath, $content); $this->info("✅ Test created: tests/Feature/{$testName}.php"); } /** * Get model stub content */ protected function getModelStub(): string { return ' \'boolean\', \'created_at\' => \'datetime\', \'updated_at\' => \'datetime\', \'deleted_at\' => \'datetime\', ]; /** * Scope for active records */ public function scopeActive($query) { return $query->where(\'is_active\', true); } /** * Get cache key for this model */ public function getCacheKey(string $suffix = \'\'): string { return \'thrillwiki.{{ snake }}.\'.$this->id.($suffix ? \'.\'.$suffix : \'\'); } }'; } /** * Get controller stub content */ protected function getControllerStub(): string { return 'filled(\'search\')) { $search = $request->get(\'search\'); $query->where(function ($q) use ($search) { $q->where(\'name\', \'ILIKE\', "%{$search}%") ->orWhere(\'description\', \'ILIKE\', "%{$search}%"); }); } // Filter by status if ($request->filled(\'status\')) { $query->where(\'is_active\', $request->get(\'status\') === \'active\'); } ${{ pluralCamel }} = $query->latest()->paginate(15)->withQueryString(); return view(\'{{ plural }}.index\', compact(\'{{ pluralCamel }}\')); } /** * Show the form for creating a new resource. */ public function create(): View { return view(\'{{ plural }}.create\'); } /** * Store a newly created resource in storage. */ public function store({{ studly }}Request $request): RedirectResponse { ${{ camel }} = {{ studly }}::create($request->validated()); return redirect() ->route(\'{{ plural }}.show\', ${{ camel }}) ->with(\'success\', \'{{ title }} created successfully!\'); } /** * Display the specified resource. */ public function show({{ studly }} ${{ camel }}): View { return view(\'{{ plural }}.show\', compact(\'{{ camel }}\')); } /** * Show the form for editing the specified resource. */ public function edit({{ studly }} ${{ camel }}): View { return view(\'{{ plural }}.edit\', compact(\'{{ camel }}\')); } /** * Update the specified resource in storage. */ public function update({{ studly }}Request $request, {{ studly }} ${{ camel }}): RedirectResponse { ${{ camel }}->update($request->validated()); return redirect() ->route(\'{{ plural }}.show\', ${{ camel }}) ->with(\'success\', \'{{ title }} updated successfully!\'); } /** * Remove the specified resource from storage. */ public function destroy({{ studly }} ${{ camel }}): RedirectResponse { ${{ camel }}->delete(); return redirect() ->route(\'{{ plural }}.index\') ->with(\'success\', \'{{ title }} deleted successfully!\'); } }'; } /** * Get form request stub content */ protected function getFormRequestStub(): string { return ' [\'required\', \'string\', \'max:255\'], \'description\' => [\'nullable\', \'string\'], \'is_active\' => [\'boolean\'], ]; // For updates, make name unique except for current record if ($this->route(\'{{ camel }}\')) { $rules[\'name\'][] = \'unique:{{ table }},name,\' . $this->route(\'{{ camel }}\')->id; } else { $rules[\'name\'][] = \'unique:{{ table }},name\'; } return $rules; } /** * Get custom messages for validator errors. */ public function messages(): array { return [ \'name.required\' => \'The {{ snake }} name is required.\', \'name.unique\' => \'A {{ snake }} with this name already exists.\', ]; } }'; } /** * Get view stub content */ protected function getViewStub(string $view): string { $stubs = [ 'index' => '@extends(\'layouts.app\') @section(\'content\')

{{ pluralTitle }}

Add New {{ title }}
@forelse(${{ pluralCamel }} as ${{ camel }})

{{ ${{ camel }}->name }}

@if(${{ camel }}->description)

{{ ${{ camel }}->description }}

@endif
{{ ${{ camel }}->created_at->format(\'M j, Y\') }} {{ ${{ camel }}->is_active ? \'Active\' : \'Inactive\' }}
Edit
@csrf @method(\'DELETE\')
@empty
No {{ plural }} found.
@endforelse
@if(${{ pluralCamel }}->hasPages())
{{ ${{ pluralCamel }}->links() }}
@endif
@endsection', 'show' => '@extends(\'layouts.app\') @section(\'content\')

{{ ${{ camel }}->name }}

Details

{{ ${{ camel }}->name }}

@if(${{ camel }}->description)

{{ ${{ camel }}->description }}

@endif
{{ ${{ camel }}->is_active ? \'Active\' : \'Inactive\' }}

Metadata

{{ ${{ camel }}->created_at->format(\'F j, Y \at g:i A\') }}

{{ ${{ camel }}->updated_at->format(\'F j, Y \at g:i A\') }}

@csrf @method(\'DELETE\')
@endsection', 'create' => '@extends(\'layouts.app\') @section(\'content\')

Create New {{ title }}

Back to List
@csrf
@error(\'name\')

{{ $message }}

@enderror
@error(\'description\')

{{ $message }}

@enderror
@error(\'is_active\')

{{ $message }}

@enderror
Cancel
@endsection', 'edit' => '@extends(\'layouts.app\') @section(\'content\')

Edit {{ title }}

@csrf @method(\'PUT\')
@error(\'name\')

{{ $message }}

@enderror
@error(\'description\')

{{ $message }}

@enderror
@error(\'is_active\')

{{ $message }}

@enderror
Cancel
@endsection', ]; return $stubs[$view] ?? ''; } /** * Get migration stub content */ protected function getMigrationStub(): string { return 'id(); $table->string(\'name\'); $table->text(\'description\')->nullable(); $table->boolean(\'is_active\')->default(true); $table->timestamps(); $table->softDeletes(); $table->index([\'name\']); $table->index([\'is_active\']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists(\'{{ table }}\'); } };'; } /** * Get API controller stub content */ protected function getApiControllerStub(): string { return 'filled(\'search\')) { $search = $request->get(\'search\'); $query->where(function ($q) use ($search) { $q->where(\'name\', \'ILIKE\', "%{$search}%") ->orWhere(\'description\', \'ILIKE\', "%{$search}%"); }); } // Filter by status if ($request->filled(\'status\')) { $query->where(\'is_active\', $request->get(\'status\') === \'active\'); } ${{ pluralCamel }} = $query->latest()->paginate(15); return response()->json([ \'data\' => {{ studly }}Resource::collection(${{ pluralCamel }}), \'meta\' => [ \'current_page\' => ${{ pluralCamel }}->currentPage(), \'last_page\' => ${{ pluralCamel }}->lastPage(), \'per_page\' => ${{ pluralCamel }}->perPage(), \'total\' => ${{ pluralCamel }}->total(), ] ]); } /** * Store a newly created resource in storage. */ public function store({{ studly }}Request $request): JsonResponse { ${{ camel }} = {{ studly }}::create($request->validated()); return response()->json([ \'message\' => \'{{ title }} created successfully\', \'data\' => new {{ studly }}Resource(${{ camel }}) ], 201); } /** * Display the specified resource. */ public function show({{ studly }} ${{ camel }}): JsonResponse { return response()->json([ \'data\' => new {{ studly }}Resource(${{ camel }}) ]); } /** * Update the specified resource in storage. */ public function update({{ studly }}Request $request, {{ studly }} ${{ camel }}): JsonResponse { ${{ camel }}->update($request->validated()); return response()->json([ \'message\' => \'{{ title }} updated successfully\', \'data\' => new {{ studly }}Resource(${{ camel }}) ]); } /** * Remove the specified resource from storage. */ public function destroy({{ studly }} ${{ camel }}): JsonResponse { ${{ camel }}->delete(); return response()->json([ \'message\' => \'{{ title }} deleted successfully\' ]); } }'; } /** * Get API resource stub content */ protected function getApiResourceStub(): string { return ' $this->id, \'name\' => $this->name, \'description\' => $this->description, \'is_active\' => $this->is_active, \'created_at\' => $this->created_at, \'updated_at\' => $this->updated_at, ]; } }'; } /** * Get test stub content */ protected function getTestStub(): string { return 'user = User::factory()->create(); } /** @test */ public function it_can_display_{{ plural }}_index(): void { {{ studly }}::factory()->count(3)->create(); $response = $this->actingAs($this->user)->get(route(\'{{ plural }}.index\')); $response->assertStatus(200) ->assertSee(\'{{ pluralTitle }}\'); } /** @test */ public function it_can_create_a_{{ snake }}(): void { ${{ camel }}Data = [ \'name\' => \'Test {{ title }}\', \'description\' => \'Test description\', \'is_active\' => true, ]; $response = $this->actingAs($this->user)->post(route(\'{{ plural }}.store\'), ${{ camel }}Data); $response->assertRedirect(); $this->assertDatabaseHas(\'{{ table }}\', ${{ camel }}Data); } /** @test */ public function it_can_show_a_{{ snake }}(): void { ${{ camel }} = {{ studly }}::factory()->create(); $response = $this->actingAs($this->user)->get(route(\'{{ plural }}.show\', ${{ camel }})); $response->assertStatus(200) ->assertSee(${{ camel }}->name); } /** @test */ public function it_can_update_a_{{ snake }}(): void { ${{ camel }} = {{ studly }}::factory()->create(); $updateData = [ \'name\' => \'Updated {{ title }}\', \'description\' => \'Updated description\', \'is_active\' => false, ]; $response = $this->actingAs($this->user)->put(route(\'{{ plural }}.update\', ${{ camel }}), $updateData); $response->assertRedirect(); $this->assertDatabaseHas(\'{{ table }}\', $updateData); } /** @test */ public function it_can_delete_a_{{ snake }}(): void { ${{ camel }} = {{ studly }}::factory()->create(); $response = $this->actingAs($this->user)->delete(route(\'{{ plural }}.destroy\', ${{ camel }})); $response->assertRedirect(); $this->assertSoftDeleted(\'{{ table }}\', [\'id\' => ${{ camel }}->id]); } /** @test */ public function it_validates_required_fields(): void { $response = $this->actingAs($this->user)->post(route(\'{{ plural }}.store\'), []); $response->assertSessionHasErrors([\'name\']); } /** @test */ public function it_can_search_{{ plural }}(): void { ${{ camel }}1 = {{ studly }}::factory()->create([\'name\' => \'Searchable {{ title }}\']); ${{ camel }}2 = {{ studly }}::factory()->create([\'name\' => \'Other {{ title }}\']); $response = $this->actingAs($this->user)->get(route(\'{{ plural }}.index\', [\'search\' => \'Searchable\'])); $response->assertStatus(200) ->assertSee(${{ camel }}1->name) ->assertDontSee(${{ camel }}2->name); } }'; } /** * Replace stub variables with actual values */ protected function replaceStubVariables(string $stub, array $names): string { foreach ($names as $key => $value) { $stub = str_replace("{{ {$key} }}", $value, $stub); } return $stub; } /** * Get route definition */ protected function getRouteDefinition(array $names): string { return "// {$names['pluralTitle']} CRUD routes Route::resource('{$names['plural']}', App\Http\Controllers\\{$names['controller']}::class);"; } /** * Get API route definition */ protected function getApiRouteDefinition(array $names): string { return "// {$names['pluralTitle']} API routes Route::apiResource('{$names['plural']}', App\Http\Controllers\Api\\{$names['controller']}::class);"; } /** * Display success message */ protected function displaySuccessMessage(array $names): void { $this->info(''); $this->info('🎉 ThrillWiki CRUD \'' . $names['studly'] . '\' created successfully!'); $this->info(''); $this->info('📁 Files Generated:'); $this->info(' • app/Models/' . $names['model'] . '.php'); $this->info(' • app/Http/Controllers/' . $names['controller'] . '.php'); $this->info(' • app/Http/Requests/' . $names['studly'] . 'Request.php'); $this->info(' • resources/views/' . $names['plural'] . '/ (index, show, create, edit)'); $this->info(' • routes/web.php (resource routes added)'); if ($this->option('migration')) { $this->info(' • database/migrations/[timestamp]_create_' . $names['table'] . '_table.php'); } if ($this->option('api')) { $this->info(' • app/Http/Controllers/Api/' . $names['controller'] . '.php'); $this->info(' • app/Http/Resources/' . $names['studly'] . 'Resource.php'); $this->info(' • routes/api.php (API routes added)'); } if ($this->option('with-tests')) { $this->info(' • tests/Feature/' . $names['controller'] . 'Test.php'); } $this->info(''); $this->info('🚀 Next Steps:'); $this->info(' 1. Run migration: php artisan migrate'); $this->info(' 2. Create factory: php artisan make:factory ' . $names['studly'] . 'Factory'); $this->info(' 3. Update navigation to include new routes'); $this->info(' 4. Customize fields and validation as needed'); } }