Upgrade make:module command with full ServiceProvider structure
Enhanced make:module command: - Added --model flag for creating model + migration + Filament resource - Added --api flag for including API routes - Added --no-filament flag to skip Filament resource - Creates full ServiceProvider structure with auto-registration - Creates Config, Permissions, Routes, Views, Tests - Module migrations stored in module folder - Filament resources stored in module folder - Auto-registers ServiceProvider in bootstrap/providers.php Also added: - ModuleAuditable trait for audit trail support - Updated Modules README to document new command options
This commit is contained in:
@@ -8,82 +8,322 @@
|
||||
|
||||
class MakeModuleCommand extends Command
|
||||
{
|
||||
protected $signature = 'make:module {name : The name of the module}';
|
||||
protected $signature = 'make:module
|
||||
{name : The name of the module (PascalCase)}
|
||||
{--model= : Create a model with the given name}
|
||||
{--api : Include API routes}
|
||||
{--no-filament : Skip Filament resource generation}';
|
||||
|
||||
protected $description = 'Create a new module with standard structure';
|
||||
protected $description = 'Create a new module with full structure (ServiceProvider, Config, Routes, Views, Permissions)';
|
||||
|
||||
protected string $studlyName;
|
||||
protected string $kebabName;
|
||||
protected string $snakeName;
|
||||
protected string $modulePath;
|
||||
protected ?string $modelName;
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$name = $this->argument('name');
|
||||
$studlyName = Str::studly($name);
|
||||
$kebabName = Str::kebab($name);
|
||||
$this->studlyName = Str::studly($name);
|
||||
$this->kebabName = Str::kebab($name);
|
||||
$this->snakeName = Str::snake($name);
|
||||
$this->modelName = $this->option('model') ? Str::studly($this->option('model')) : null;
|
||||
|
||||
$modulePath = app_path("Modules/{$studlyName}");
|
||||
$this->modulePath = app_path("Modules/{$this->studlyName}");
|
||||
|
||||
if (File::exists($modulePath)) {
|
||||
$this->error("Module {$studlyName} already exists!");
|
||||
if (File::exists($this->modulePath)) {
|
||||
$this->error("Module {$this->studlyName} already exists!");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info("Creating module: {$studlyName}");
|
||||
$this->info("Creating module: {$this->studlyName}");
|
||||
$this->newLine();
|
||||
|
||||
$this->createDirectories($modulePath);
|
||||
$this->createModel($modulePath, $studlyName);
|
||||
$this->createMigration($studlyName);
|
||||
$this->createController($modulePath, $studlyName);
|
||||
$this->createRoutes($modulePath, $studlyName, $kebabName);
|
||||
$this->createViews($modulePath, $studlyName, $kebabName);
|
||||
$this->createFilamentResource($studlyName);
|
||||
$this->createTests($studlyName);
|
||||
$this->createDirectoryStructure();
|
||||
$this->createServiceProvider();
|
||||
$this->createConfig();
|
||||
$this->createPermissions();
|
||||
$this->createController();
|
||||
$this->createRoutes();
|
||||
$this->createViews();
|
||||
|
||||
if ($this->modelName) {
|
||||
$this->createModel();
|
||||
$this->createMigration();
|
||||
|
||||
if (!$this->option('no-filament')) {
|
||||
$this->createFilamentResource();
|
||||
}
|
||||
}
|
||||
|
||||
$this->createTests();
|
||||
$this->registerServiceProvider();
|
||||
|
||||
$this->info("\n✅ Module {$studlyName} created successfully!");
|
||||
$this->info("\nNext steps:");
|
||||
$this->info("1. Run migrations: php artisan migrate");
|
||||
$this->info("2. Register routes in routes/web.php:");
|
||||
$this->info(" require __DIR__.'/../app/Modules/{$studlyName}/routes.php';");
|
||||
$this->info("3. Access at: /{$kebabName}");
|
||||
$this->newLine();
|
||||
$this->info("✅ Module {$this->studlyName} created successfully!");
|
||||
$this->newLine();
|
||||
$this->warn("Next steps:");
|
||||
$this->line(" 1. Run migrations: <info>php artisan migrate</info>");
|
||||
$this->line(" 2. Seed permissions: <info>php artisan db:seed --class=RolePermissionSeeder</info>");
|
||||
$this->line(" 3. Clear caches: <info>php artisan optimize:clear</info>");
|
||||
$this->newLine();
|
||||
$this->line(" Access at:");
|
||||
$this->line(" - Frontend: <info>/{$this->kebabName}</info>");
|
||||
if ($this->modelName && !$this->option('no-filament')) {
|
||||
$this->line(" - Admin: <info>/admin → {$this->studlyName}</info>");
|
||||
}
|
||||
if ($this->option('api')) {
|
||||
$this->line(" - API: <info>/api/{$this->kebabName}</info>");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function createDirectories(string $path): void
|
||||
protected function createDirectoryStructure(): void
|
||||
{
|
||||
File::makeDirectory($path, 0755, true);
|
||||
File::makeDirectory("{$path}/Models", 0755, true);
|
||||
File::makeDirectory("{$path}/Controllers", 0755, true);
|
||||
File::makeDirectory("{$path}/Views", 0755, true);
|
||||
$directories = [
|
||||
'',
|
||||
'/Config',
|
||||
'/Database/Migrations',
|
||||
'/Database/Seeders',
|
||||
'/Filament/Resources',
|
||||
'/Http/Controllers',
|
||||
'/Http/Middleware',
|
||||
'/Http/Requests',
|
||||
'/Models',
|
||||
'/Policies',
|
||||
'/Services',
|
||||
'/Routes',
|
||||
'/Resources/views',
|
||||
];
|
||||
|
||||
foreach ($directories as $dir) {
|
||||
File::makeDirectory("{$this->modulePath}{$dir}", 0755, true);
|
||||
}
|
||||
|
||||
$this->info("✓ Created directory structure");
|
||||
}
|
||||
|
||||
protected function createModel(string $path, string $name): void
|
||||
protected function createServiceProvider(): void
|
||||
{
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$name}\Models;
|
||||
namespace App\Modules\\{$this->studlyName};
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class {$name} extends Model
|
||||
class {$this->studlyName}ServiceProvider extends ServiceProvider
|
||||
{
|
||||
use HasFactory;
|
||||
public function register(): void
|
||||
{
|
||||
\$this->mergeConfigFrom(
|
||||
__DIR__ . '/Config/{$this->snakeName}.php',
|
||||
'{$this->snakeName}'
|
||||
);
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
\$this->loadMigrationsFrom(__DIR__ . '/Database/Migrations');
|
||||
\$this->loadViewsFrom(__DIR__ . '/Resources/views', '{$this->kebabName}');
|
||||
|
||||
\$this->registerRoutes();
|
||||
\$this->registerPermissions();
|
||||
}
|
||||
|
||||
protected function registerRoutes(): void
|
||||
{
|
||||
Route::middleware(['web', 'auth'])
|
||||
->prefix('{$this->kebabName}')
|
||||
->name('{$this->kebabName}.')
|
||||
->group(__DIR__ . '/Routes/web.php');
|
||||
|
||||
if (file_exists(__DIR__ . '/Routes/api.php')) {
|
||||
Route::middleware(['api', 'auth:sanctum'])
|
||||
->prefix('api/{$this->kebabName}')
|
||||
->name('api.{$this->kebabName}.')
|
||||
->group(__DIR__ . '/Routes/api.php');
|
||||
}
|
||||
}
|
||||
|
||||
protected function registerPermissions(): void
|
||||
{
|
||||
// Permissions are registered via RolePermissionSeeder
|
||||
// See: Permissions.php in this module
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$this->modulePath}/{$this->studlyName}ServiceProvider.php", $stub);
|
||||
$this->info("✓ Created ServiceProvider");
|
||||
}
|
||||
|
||||
protected function createConfig(): void
|
||||
{
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
return [
|
||||
'name' => '{$this->studlyName}',
|
||||
'slug' => '{$this->kebabName}',
|
||||
'version' => '1.0.0',
|
||||
|
||||
'audit' => [
|
||||
'enabled' => true,
|
||||
'strategy' => 'all', // 'all', 'include', 'exclude', 'none'
|
||||
'include' => [],
|
||||
'exclude' => [],
|
||||
],
|
||||
];
|
||||
PHP;
|
||||
|
||||
File::put("{$this->modulePath}/Config/{$this->snakeName}.php", $stub);
|
||||
$this->info("✓ Created Config");
|
||||
}
|
||||
|
||||
protected function createPermissions(): void
|
||||
{
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
return [
|
||||
'{$this->snakeName}.view' => 'View {$this->studlyName}',
|
||||
'{$this->snakeName}.create' => 'Create {$this->studlyName} records',
|
||||
'{$this->snakeName}.edit' => 'Edit {$this->studlyName} records',
|
||||
'{$this->snakeName}.delete' => 'Delete {$this->studlyName} records',
|
||||
];
|
||||
PHP;
|
||||
|
||||
File::put("{$this->modulePath}/Permissions.php", $stub);
|
||||
$this->info("✓ Created Permissions");
|
||||
}
|
||||
|
||||
protected function createController(): void
|
||||
{
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$this->studlyName}\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class {$this->studlyName}Controller extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
\$this->authorize('{$this->snakeName}.view');
|
||||
|
||||
return view('{$this->kebabName}::index');
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$this->modulePath}/Http/Controllers/{$this->studlyName}Controller.php", $stub);
|
||||
$this->info("✓ Created Controller");
|
||||
}
|
||||
|
||||
protected function createRoutes(): void
|
||||
{
|
||||
// Web routes
|
||||
$webStub = <<<PHP
|
||||
<?php
|
||||
|
||||
use App\Modules\\{$this->studlyName}\Http\Controllers\\{$this->studlyName}Controller;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', [{$this->studlyName}Controller::class, 'index'])->name('index');
|
||||
PHP;
|
||||
|
||||
File::put("{$this->modulePath}/Routes/web.php", $webStub);
|
||||
|
||||
// API routes (if requested)
|
||||
if ($this->option('api')) {
|
||||
$apiStub = <<<PHP
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
// API routes here
|
||||
});
|
||||
PHP;
|
||||
|
||||
File::put("{$this->modulePath}/Routes/api.php", $apiStub);
|
||||
}
|
||||
|
||||
$this->info("✓ Created Routes" . ($this->option('api') ? ' (web + api)' : ''));
|
||||
}
|
||||
|
||||
protected function createViews(): void
|
||||
{
|
||||
$stub = <<<BLADE
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('{$this->studlyName}') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
{{ __('{$this->studlyName} module is working!') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
BLADE;
|
||||
|
||||
File::put("{$this->modulePath}/Resources/views/index.blade.php", $stub);
|
||||
$this->info("✓ Created Views");
|
||||
}
|
||||
|
||||
protected function createModel(): void
|
||||
{
|
||||
$tableName = Str::snake(Str::plural($this->modelName));
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$this->studlyName}\Models;
|
||||
|
||||
use App\Traits\ModuleAuditable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
|
||||
class {$this->modelName} extends Model implements Auditable
|
||||
{
|
||||
use HasFactory, ModuleAuditable;
|
||||
|
||||
protected \$table = '{$tableName}';
|
||||
|
||||
protected \$fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected \$casts = [
|
||||
//
|
||||
];
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/Models/{$name}.php", $stub);
|
||||
$this->info("✓ Created Model: {$name}");
|
||||
File::put("{$this->modulePath}/Models/{$this->modelName}.php", $stub);
|
||||
$this->info("✓ Created Model: {$this->modelName}");
|
||||
}
|
||||
|
||||
protected function createMigration(string $name): void
|
||||
protected function createMigration(): void
|
||||
{
|
||||
$table = Str::snake(Str::plural($name));
|
||||
$tableName = Str::snake(Str::plural($this->modelName));
|
||||
$timestamp = date('Y_m_d_His');
|
||||
$migrationName = "create_{$table}_table";
|
||||
$migrationName = "create_{$tableName}_table";
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
@@ -96,7 +336,7 @@ protected function createMigration(string $name): void
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('{$table}', function (Blueprint \$table) {
|
||||
Schema::create('{$tableName}', function (Blueprint \$table) {
|
||||
\$table->id();
|
||||
\$table->string('name');
|
||||
\$table->text('description')->nullable();
|
||||
@@ -106,231 +346,252 @@ public function up(): void
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('{$table}');
|
||||
Schema::dropIfExists('{$tableName}');
|
||||
}
|
||||
};
|
||||
PHP;
|
||||
|
||||
$migrationPath = database_path("migrations/{$timestamp}_{$migrationName}.php");
|
||||
$migrationPath = "{$this->modulePath}/Database/Migrations/{$timestamp}_{$migrationName}.php";
|
||||
File::put($migrationPath, $stub);
|
||||
$this->info("✓ Created Migration: {$migrationName}");
|
||||
}
|
||||
|
||||
protected function createController(string $path, string $name): void
|
||||
protected function createFilamentResource(): void
|
||||
{
|
||||
$plural = Str::plural($name);
|
||||
$variable = Str::camel($name);
|
||||
$pluralVariable = Str::camel($plural);
|
||||
$modelClass = "App\\Modules\\{$this->studlyName}\\Models\\{$this->modelName}";
|
||||
$tableName = Str::snake(Str::plural($this->modelName));
|
||||
|
||||
$stub = <<<PHP
|
||||
// Create the resource
|
||||
$resourceStub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$name}\Controllers;
|
||||
namespace App\Modules\\{$this->studlyName}\Filament\Resources;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\\{$name}\Models\\{$name};
|
||||
use Illuminate\Http\Request;
|
||||
use App\Modules\\{$this->studlyName}\Filament\Resources\\{$this->modelName}Resource\Pages;
|
||||
use App\Modules\\{$this->studlyName}\Models\\{$this->modelName};
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class {$name}Controller extends Controller
|
||||
class {$this->modelName}Resource extends Resource
|
||||
{
|
||||
public function index()
|
||||
protected static ?string \$model = {$this->modelName}::class;
|
||||
|
||||
protected static ?string \$navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?string \$navigationGroup = '{$this->studlyName}';
|
||||
|
||||
protected static ?int \$navigationSort = 1;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
\${$pluralVariable} = {$name}::latest()->paginate(15);
|
||||
return view('{$name}::index', compact('{$pluralVariable}'));
|
||||
return auth()->user()?->can('{$this->snakeName}.view') ?? false;
|
||||
}
|
||||
|
||||
public function create()
|
||||
public static function form(Form \$form): Form
|
||||
{
|
||||
return view('{$name}::create');
|
||||
return \$form
|
||||
->schema([
|
||||
Forms\Components\Section::make('{$this->modelName} Details')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Textarea::make('description')
|
||||
->rows(3),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request \$request)
|
||||
public static function table(Table \$table): Table
|
||||
{
|
||||
\$validated = \$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
{$name}::create(\$validated);
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} created successfully.');
|
||||
return \$table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('description')
|
||||
->limit(50),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show({$name} \${$variable})
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return view('{$name}::show', compact('{$variable}'));
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function edit({$name} \${$variable})
|
||||
public static function getPages(): array
|
||||
{
|
||||
return view('{$name}::edit', compact('{$variable}'));
|
||||
}
|
||||
|
||||
public function update(Request \$request, {$name} \${$variable})
|
||||
{
|
||||
\$validated = \$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
\${$variable}->update(\$validated);
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} updated successfully.');
|
||||
}
|
||||
|
||||
public function destroy({$name} \${$variable})
|
||||
{
|
||||
\${$variable}->delete();
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} deleted successfully.');
|
||||
return [
|
||||
'index' => Pages\List{$this->modelName}s::route('/'),
|
||||
'create' => Pages\Create{$this->modelName}::route('/create'),
|
||||
'edit' => Pages\Edit{$this->modelName}::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/Controllers/{$name}Controller.php", $stub);
|
||||
$this->info("✓ Created Controller: {$name}Controller");
|
||||
}
|
||||
File::put("{$this->modulePath}/Filament/Resources/{$this->modelName}Resource.php", $resourceStub);
|
||||
|
||||
protected function createRoutes(string $path, string $name, string $kebabName): void
|
||||
{
|
||||
$variable = Str::camel($name);
|
||||
|
||||
$stub = <<<PHP
|
||||
// Create Pages directory
|
||||
File::makeDirectory("{$this->modulePath}/Filament/Resources/{$this->modelName}Resource/Pages", 0755, true);
|
||||
|
||||
// Create List page
|
||||
$listStub = <<<PHP
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\\{$name}\Controllers\\{$name}Controller;
|
||||
namespace App\Modules\\{$this->studlyName}\Filament\Resources\\{$this->modelName}Resource\Pages;
|
||||
|
||||
Route::middleware(['web', 'auth'])->group(function () {
|
||||
Route::resource('{$kebabName}', {$name}Controller::class)->names('{$variable}');
|
||||
});
|
||||
use App\Modules\\{$this->studlyName}\Filament\Resources\\{$this->modelName}Resource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class List{$this->modelName}s extends ListRecords
|
||||
{
|
||||
protected static string \$resource = {$this->modelName}Resource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/routes.php", $stub);
|
||||
$this->info("✓ Created Routes");
|
||||
}
|
||||
File::put("{$this->modulePath}/Filament/Resources/{$this->modelName}Resource/Pages/List{$this->modelName}s.php", $listStub);
|
||||
|
||||
protected function createViews(string $path, string $name, string $kebabName): void
|
||||
{
|
||||
$plural = Str::plural($name);
|
||||
$variable = Str::camel($name);
|
||||
$pluralVariable = Str::camel($plural);
|
||||
|
||||
$indexView = <<<BLADE
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('{$plural}') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<div class="flex justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold">{$plural} List</h3>
|
||||
<a href="{{ route('{$variable}.create') }}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||
Create New
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse(\${$pluralVariable} as \${$variable})
|
||||
<tr>
|
||||
<td class="px-6 py-4">{{ \${$variable}->name }}</td>
|
||||
<td class="px-6 py-4">{{ \${$variable}->description }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="{{ route('{$variable}.edit', \${$variable}) }}" class="text-blue-600 hover:text-blue-900">Edit</a>
|
||||
<form action="{{ route('{$variable}.destroy', \${$variable}) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 ml-2">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="px-6 py-4 text-center">No {$pluralVariable} found.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ \${$pluralVariable}->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
BLADE;
|
||||
|
||||
File::put("{$path}/Views/index.blade.php", $indexView);
|
||||
$this->info("✓ Created Views");
|
||||
}
|
||||
|
||||
protected function createFilamentResource(string $name): void
|
||||
{
|
||||
$this->call('make:filament-resource', [
|
||||
'name' => $name,
|
||||
'--generate' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createTests(string $name): void
|
||||
{
|
||||
$variable = Str::camel($name);
|
||||
|
||||
$stub = <<<PHP
|
||||
// Create Create page
|
||||
$createStub = <<<PHP
|
||||
<?php
|
||||
|
||||
use App\Modules\\{$name}\Models\\{$name};
|
||||
use App\Models\User;
|
||||
namespace App\Modules\\{$this->studlyName}\Filament\Resources\\{$this->modelName}Resource\Pages;
|
||||
|
||||
it('can list {$variable}s', function () {
|
||||
\$user = User::factory()->create();
|
||||
{$name}::factory()->count(3)->create();
|
||||
use App\Modules\\{$this->studlyName}\Filament\Resources\\{$this->modelName}Resource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
\$response = \$this->actingAs(\$user)->get(route('{$variable}.index'));
|
||||
|
||||
\$response->assertStatus(200);
|
||||
});
|
||||
|
||||
it('can create {$variable}', function () {
|
||||
\$user = User::factory()->create();
|
||||
|
||||
\$response = \$this->actingAs(\$user)->post(route('{$variable}.store'), [
|
||||
'name' => 'Test {$name}',
|
||||
'description' => 'Test description',
|
||||
]);
|
||||
|
||||
\$response->assertRedirect(route('{$variable}.index'));
|
||||
\$this->assertDatabaseHas('{$variable}s', ['name' => 'Test {$name}']);
|
||||
});
|
||||
class Create{$this->modelName} extends CreateRecord
|
||||
{
|
||||
protected static string \$resource = {$this->modelName}Resource::class;
|
||||
}
|
||||
PHP;
|
||||
|
||||
$testPath = base_path("tests/Modules/{$name}");
|
||||
File::put("{$this->modulePath}/Filament/Resources/{$this->modelName}Resource/Pages/Create{$this->modelName}.php", $createStub);
|
||||
|
||||
// Create Edit page
|
||||
$editStub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$this->studlyName}\Filament\Resources\\{$this->modelName}Resource\Pages;
|
||||
|
||||
use App\Modules\\{$this->studlyName}\Filament\Resources\\{$this->modelName}Resource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class Edit{$this->modelName} extends EditRecord
|
||||
{
|
||||
protected static string \$resource = {$this->modelName}Resource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$this->modulePath}/Filament/Resources/{$this->modelName}Resource/Pages/Edit{$this->modelName}.php", $editStub);
|
||||
|
||||
$this->info("✓ Created Filament Resource: {$this->modelName}Resource");
|
||||
}
|
||||
|
||||
protected function createTests(): void
|
||||
{
|
||||
$testPath = base_path("tests/Feature/Modules/{$this->studlyName}");
|
||||
File::makeDirectory($testPath, 0755, true);
|
||||
File::put("{$testPath}/{$name}Test.php", $stub);
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Modules\\{$this->studlyName};
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class {$this->studlyName}Test extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_user_can_view_{$this->snakeName}_index(): void
|
||||
{
|
||||
\$user = User::factory()->create();
|
||||
\$user->givePermissionTo('{$this->snakeName}.view');
|
||||
|
||||
\$response = \$this->actingAs(\$user)
|
||||
->get('/{$this->kebabName}');
|
||||
|
||||
\$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_unauthorized_user_cannot_view_{$this->snakeName}(): void
|
||||
{
|
||||
\$user = User::factory()->create();
|
||||
|
||||
\$response = \$this->actingAs(\$user)
|
||||
->get('/{$this->kebabName}');
|
||||
|
||||
\$response->assertStatus(403);
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$testPath}/{$this->studlyName}Test.php", $stub);
|
||||
$this->info("✓ Created Tests");
|
||||
}
|
||||
|
||||
protected function registerServiceProvider(): void
|
||||
{
|
||||
$providersPath = base_path('bootstrap/providers.php');
|
||||
|
||||
if (File::exists($providersPath)) {
|
||||
$content = File::get($providersPath);
|
||||
$providerClass = "App\\Modules\\{$this->studlyName}\\{$this->studlyName}ServiceProvider::class";
|
||||
|
||||
if (!str_contains($content, $providerClass)) {
|
||||
// Add the provider before the closing bracket
|
||||
$content = preg_replace(
|
||||
'/(\];)/',
|
||||
" {$providerClass},\n$1",
|
||||
$content
|
||||
);
|
||||
File::put($providersPath, $content);
|
||||
$this->info("✓ Registered ServiceProvider in bootstrap/providers.php");
|
||||
}
|
||||
} else {
|
||||
$this->warn("⚠ Could not auto-register ServiceProvider. Add manually to config/app.php:");
|
||||
$this->line(" App\\Modules\\{$this->studlyName}\\{$this->studlyName}ServiceProvider::class");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user