Ask for record title attribute when generating a resource

This commit is contained in:
Dan Harrin
2025-07-28 22:37:34 +01:00
parent 8a1adb5a72
commit 2b787e6475
4 changed files with 117 additions and 0 deletions
@@ -52,6 +52,7 @@ class ResourceClassGenerator extends ClassGenerator
protected ?string $tableFqn,
protected ?string $clusterFqn,
protected ?string $parentResourceFqn,
protected ?string $recordTitleAttribute,
protected bool $hasViewOperation,
protected bool $isGenerated,
protected bool $isSoftDeletable,
@@ -105,6 +106,7 @@ class ResourceClassGenerator extends ClassGenerator
$this->addNavigationIconPropertyToClass($class);
$this->addClusterPropertyToClass($class);
$this->addParentResourcePropertyToClass($class);
$this->addRecordTitleAttributePropertyToClass($class);
}
protected function addMethodsToClass(ClassType $class): void
@@ -172,6 +174,23 @@ class ResourceClassGenerator extends ClassGenerator
protected function configureParentResourceProperty(Property $property): void {}
protected function addRecordTitleAttributePropertyToClass(ClassType $class): void
{
$recordTitleAttribute = $this->getRecordTitleAttribute();
if (blank($recordTitleAttribute)) {
return;
}
$property = $class->addProperty('recordTitleAttribute', $recordTitleAttribute)
->setProtected()
->setStatic()
->setType('?string');
$this->configureRecordTitleAttributeProperty($property);
}
protected function configureRecordTitleAttributeProperty(Property $property): void {}
protected function addFormMethodToClass(ClassType $class): void
{
$formSchemaFqn = $this->getFormSchemaFqn();
@@ -472,4 +491,9 @@ class ResourceClassGenerator extends ClassGenerator
{
return $this->isSimple;
}
public function getRecordTitleAttribute(): ?string
{
return $this->recordTitleAttribute;
}
}
@@ -33,6 +33,7 @@ use Symfony\Component\Console\Input\InputOption;
use function Filament\Support\discover_app_classes;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\suggest;
use function Laravel\Prompts\text;
#[AsCommand(name: 'make:filament-resource', aliases: [
'filament:make-resource',
@@ -96,6 +97,8 @@ class MakeResourceCommand extends Command
protected ?string $tableFqn = null;
protected ?string $recordTitleAttribute = null;
protected bool $hasViewOperation;
protected bool $isGenerated;
@@ -197,6 +200,12 @@ class MakeResourceCommand extends Command
mode: InputOption::VALUE_REQUIRED,
description: 'The panel to create the resource in',
),
new InputOption(
name: 'record-title-attribute',
shortcut: null,
mode: InputOption::VALUE_REQUIRED,
description: 'The title attribute, used to label each record in the UI',
),
new InputOption(
name: 'resource-namespace',
shortcut: null,
@@ -234,6 +243,7 @@ class MakeResourceCommand extends Command
{
try {
$this->configureModel();
$this->configureRecordTitleAttribute();
$this->configurePanel(question: 'Which panel would you like to create this resource in?');
$this->configureIsSimple();
$this->configureIsNested();
@@ -342,6 +352,24 @@ class MakeResourceCommand extends Command
}
}
protected function configureRecordTitleAttribute(): void
{
$this->recordTitleAttribute = $this->option('record-title-attribute');
if (filled($this->recordTitleAttribute)) {
return;
}
info('The "title attribute" is used to label each record in the UI.');
info('You can leave this blank if records do not have a title.');
$this->recordTitleAttribute = text(
label: 'What is the title attribute for this model?',
placeholder: 'name',
);
}
protected function configureIsSimple(): void
{
$this->isSimple = $this->option('simple');
@@ -612,6 +640,7 @@ class MakeResourceCommand extends Command
'formSchemaFqn' => $this->formSchemaFqn,
'infolistSchemaFqn' => $this->infolistSchemaFqn,
'tableFqn' => $this->tableFqn,
'recordTitleAttribute' => $this->recordTitleAttribute,
'hasViewOperation' => $this->hasViewOperation,
'isGenerated' => $this->isGenerated,
'isSoftDeletable' => $this->isSoftDeletable,
@@ -0,0 +1,50 @@
<?php
namespace App\Filament\Resources\Posts;
use App\Filament\Resources\Posts\Pages\CreatePost;
use App\Filament\Resources\Posts\Pages\EditPost;
use App\Filament\Resources\Posts\Pages\ListPosts;
use App\Filament\Resources\Posts\Schemas\PostForm;
use App\Filament\Resources\Posts\Tables\PostsTable;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Filament\Tests\Fixtures\Models\Post;
class PostResource extends Resource
{
protected static ?string $model = Post::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
protected static ?string $recordTitleAttribute = 'title';
public static function form(Schema $schema): Schema
{
return PostForm::configure($schema);
}
public static function table(Table $table): Table
{
return PostsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListPosts::route('/'),
'create' => CreatePost::route('/create'),
'edit' => EditPost::route('/{record}/edit'),
];
}
}
@@ -30,6 +30,20 @@ it('can generate a resource class', function (): void {
->toMatchSnapshot();
});
it('can generate a resource class with a record title attribute', function (): void {
$this->artisan('make:filament-resource', [
'model' => 'Post',
'--model-namespace' => 'Filament\Tests\Fixtures\Models',
'--panel' => 'admin',
'--record-title-attribute' => 'title',
'--no-interaction' => true,
]);
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
expect(file_get_contents($path))
->toMatchSnapshot();
});
it('can generate a resource form', function (): void {
$this->artisan('make:filament-resource', [
'model' => 'Post',