Skip to content

Commit 009bdf5

Browse files
committed
refactor: enhance CRUD command to use model variable and update Bootstrap 5 conventions in generated views
1 parent 158da40 commit 009bdf5

3 files changed

Lines changed: 100 additions & 20 deletions

File tree

app/Console/Commands/MakeCrudStislaCommand.php

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ protected function generateIndexView(string $modelName, array $fields, string $v
240240

241241
// Build table headers and columns
242242
$tableHeaders = $this->buildTableHeaders($fields);
243-
$tableColumns = $this->buildTableColumns($fields);
243+
$tableColumns = $this->buildTableColumns($fields, $modelName);
244244

245245
$content = $this->replacePlaceholders($stub, [
246246
'modelTitle' => Str::plural($modelName),
@@ -272,9 +272,9 @@ protected function buildTableHeaders(array $fields): string
272272
/**
273273
* Build table columns.
274274
*/
275-
protected function buildTableColumns(array $fields): string
275+
protected function buildTableColumns(array $fields, ?string $modelName = null): string
276276
{
277-
$modelVariable = Str::camel(array_key_first($fields) ?? 'item');
277+
$modelVariable = Str::camel($modelName ?? 'item');
278278
$columns = [];
279279

280280
foreach (array_keys($fields) as $field) {
@@ -298,7 +298,7 @@ protected function generateCreateView(string $modelName, array $fields, string $
298298
$stub = $this->getStub('view-create-stisla');
299299

300300
// Build form fields
301-
$formFields = $this->buildFormFields($fields, false);
301+
$formFields = $this->buildFormFields($fields, false, $modelName);
302302

303303
$content = $this->replacePlaceholders($stub, [
304304
'modelTitle' => $modelName,
@@ -325,7 +325,7 @@ protected function generateEditView(string $modelName, array $fields, string $vi
325325
$stub = $this->getStub('view-edit-stisla');
326326

327327
// Build form fields with values
328-
$formFields = $this->buildFormFields($fields, true);
328+
$formFields = $this->buildFormFields($fields, true, $modelName);
329329

330330
$content = $this->replacePlaceholders($stub, [
331331
'modelTitle' => $modelName,
@@ -353,7 +353,7 @@ protected function generateShowView(string $modelName, array $fields, string $vi
353353
$stub = $this->getStub('view-show-stisla');
354354

355355
// Build display fields
356-
$displayFields = $this->buildDisplayFields($fields);
356+
$displayFields = $this->buildDisplayFields($fields, $modelName);
357357

358358
$content = $this->replacePlaceholders($stub, [
359359
'modelTitle' => $modelName,
@@ -370,10 +370,10 @@ protected function generateShowView(string $modelName, array $fields, string $vi
370370
/**
371371
* Build form fields for create/edit views.
372372
*/
373-
protected function buildFormFields(array $fields, bool $withValue = false): string
373+
protected function buildFormFields(array $fields, bool $withValue = false, ?string $modelName = null): string
374374
{
375375
$formFields = [];
376-
$modelVariable = Str::camel(array_key_first($fields) ?? 'item');
376+
$modelVariable = Str::camel($modelName ?? 'item');
377377

378378
foreach ($fields as $name => $type) {
379379
$label = Str::title(str_replace('_', ' ', $name));
@@ -392,9 +392,13 @@ protected function buildFormFields(array $fields, bool $withValue = false): stri
392392
} elseif ($inputType === 'checkbox') {
393393
$checked = $withValue ? " {{ \${$modelVariable}->{$name} ? 'checked' : '' }}" : '';
394394
$formFields[] = " <div class=\"form-group\">\n".
395-
" <div class=\"custom-control custom-checkbox\">\n".
396-
" <input type=\"checkbox\" name=\"{$name}\" value=\"1\" class=\"custom-control-input\" id=\"{$name}\"{$checked}>\n".
397-
" <label class=\"custom-control-label\" for=\"{$name}\">{$label}</label>\n".
395+
" <input type=\"hidden\" name=\"{$name}\" value=\"0\">\n".
396+
" <div class=\"form-check\">\n".
397+
" <input type=\"checkbox\" name=\"{$name}\" value=\"1\" class=\"form-check-input @error('{$name}') is-invalid @enderror\" id=\"{$name}\"{$checked}>\n".
398+
" <label class=\"form-check-label\" for=\"{$name}\">{$label}</label>\n".
399+
" @error('{$name}')\n".
400+
" <div class=\"invalid-feedback\">{{ \$message }}</div>\n".
401+
" @enderror\n".
398402
" </div>\n".
399403
" </div>\n";
400404
} else {
@@ -415,18 +419,18 @@ protected function buildFormFields(array $fields, bool $withValue = false): stri
415419
/**
416420
* Build display fields for show view.
417421
*/
418-
protected function buildDisplayFields(array $fields): string
422+
protected function buildDisplayFields(array $fields, ?string $modelName = null): string
419423
{
420424
$displayFields = [];
421-
$modelVariable = Str::camel(array_key_first($fields) ?? 'item');
425+
$modelVariable = Str::camel($modelName ?? 'item');
422426

423427
foreach ($fields as $name => $type) {
424428
$label = Str::title(str_replace('_', ' ', $name));
425429

426430
if (in_array($type, ['date', 'datetime', 'timestamp'])) {
427431
$value = "{{ \${$modelVariable}->{$name} ? \${$modelVariable}->{$name}->format('Y-m-d H:i') : '-' }}";
428432
} elseif (in_array($type, ['boolean', 'tinyInteger'])) {
429-
$value = "@if(\${$modelVariable}->{$name})<span class=\"badge badge-success\">Yes</span>@else<span class=\"badge badge-secondary\">No</span>@endif";
433+
$value = "@if(\${$modelVariable}->{$name})<span class=\"badge bg-success\">Yes</span>@else<span class=\"badge bg-secondary\">No</span>@endif";
430434
} else {
431435
$value = "{{ \${$modelVariable}->{$name} ?? '-' }}";
432436
}
@@ -459,15 +463,26 @@ protected function appendRoutes(string $modelName): bool
459463
return false;
460464
}
461465

462-
// Append route
463-
$route = "\n// Auto-generated routes for {$modelName}\n".
464-
"Route::resource('{$routeName}', App\\Http\\Controllers\\{$modelName}Controller::class);\n";
466+
$route = $this->buildRouteDefinition($modelName);
465467

466468
File::append($routePath, $route);
467469

468470
return true;
469471
}
470472

473+
/**
474+
* Build an auth-protected resource route for the generated CRUD.
475+
*/
476+
protected function buildRouteDefinition(string $modelName): string
477+
{
478+
$routeName = Str::kebab(Str::plural($modelName));
479+
480+
return "\n// Auto-generated routes for {$modelName}\n".
481+
"Route::middleware('auth')->group(function () {\n".
482+
" Route::resource('{$routeName}', App\\Http\\Controllers\\{$modelName}Controller::class);\n".
483+
"});\n";
484+
}
485+
471486
/**
472487
* Display generation results.
473488
*/

stubs/view-index-stisla.stub

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
@if(session('success'))
2323
<div class="alert alert-success alert-dismissible show fade">
2424
<div class="alert-body">
25-
<button class="close" data-dismiss="alert">
26-
<span>&times;</span>
27-
</button>
25+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
2826
{{ session('success') }}
2927
</div>
3028
</div>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
use App\Console\Commands\MakeCrudStislaCommand;
6+
use PHPUnit\Framework\TestCase;
7+
use ReflectionMethod;
8+
9+
class CrudStislaGeneratorTest extends TestCase
10+
{
11+
public function test_generated_fields_use_model_variable_instead_of_first_field_name(): void
12+
{
13+
$command = new MakeCrudStislaCommand;
14+
$fields = ['title' => 'string', 'is_active' => 'boolean'];
15+
16+
$tableColumns = $this->invoke($command, 'buildTableColumns', [$fields, 'Product']);
17+
$formFields = $this->invoke($command, 'buildFormFields', [$fields, true, 'Product']);
18+
$displayFields = $this->invoke($command, 'buildDisplayFields', [$fields, 'Product']);
19+
20+
$this->assertStringContainsString('$product->title', $tableColumns);
21+
$this->assertStringNotContainsString('$title->title', $tableColumns);
22+
$this->assertStringContainsString('$product->title', $formFields);
23+
$this->assertStringNotContainsString('$title->title', $formFields);
24+
$this->assertStringContainsString('$product->is_active', $displayFields);
25+
$this->assertStringNotContainsString('$title->is_active', $displayFields);
26+
}
27+
28+
public function test_generated_markup_uses_bootstrap_five_conventions(): void
29+
{
30+
$command = new MakeCrudStislaCommand;
31+
32+
$checkbox = $this->invoke($command, 'buildFormFields', [['is_active' => 'boolean'], true, 'Product']);
33+
$displayFields = $this->invoke($command, 'buildDisplayFields', [['is_active' => 'boolean'], 'Product']);
34+
$indexStub = file_get_contents(__DIR__.'/../../stubs/view-index-stisla.stub');
35+
36+
$this->assertStringContainsString('form-check', $checkbox);
37+
$this->assertStringContainsString('form-check-input', $checkbox);
38+
$this->assertStringNotContainsString('custom-control', $checkbox);
39+
$this->assertStringContainsString('badge bg-success', $displayFields);
40+
$this->assertStringContainsString('badge bg-secondary', $displayFields);
41+
$this->assertStringNotContainsString('badge-success', $displayFields);
42+
$this->assertStringContainsString('data-bs-dismiss="alert"', $indexStub);
43+
$this->assertStringNotContainsString('data-dismiss="alert"', $indexStub);
44+
}
45+
46+
public function test_generated_resource_route_is_auth_protected(): void
47+
{
48+
$command = new MakeCrudStislaCommand;
49+
50+
$route = $this->invoke($command, 'buildRouteDefinition', ['Product']);
51+
52+
$this->assertStringContainsString("Route::middleware('auth')->group(function () {", $route);
53+
$this->assertStringContainsString("Route::resource('products', App\\Http\\Controllers\\ProductController::class);", $route);
54+
$this->assertStringContainsString('});', $route);
55+
}
56+
57+
/**
58+
* @param array<int, mixed> $arguments
59+
*/
60+
private function invoke(MakeCrudStislaCommand $command, string $method, array $arguments): mixed
61+
{
62+
$reflection = new ReflectionMethod($command, $method);
63+
$reflection->setAccessible(true);
64+
65+
return $reflection->invokeArgs($command, $arguments);
66+
}
67+
}

0 commit comments

Comments
 (0)