Make table class command

This commit is contained in:
Dan Harrin
2024-11-24 18:30:47 +00:00
parent fbc6ce7141
commit f4b76ed5ac
7 changed files with 306 additions and 13 deletions
@@ -23,14 +23,14 @@ trait CanAskForComponentLocation
if (blank($locations)) {
return [
"App\\{$pathNamespace}",
"App\\Filament\\{$pathNamespace}",
app_path($path),
'',
];
}
$options = [
null => "App\\{$pathNamespace}",
null => "App\\Filament\\{$pathNamespace}",
...array_map(
fn (string $namespace): string => "{$namespace}\\{$pathNamespace}",
array_combine(
@@ -47,7 +47,7 @@ trait CanAskForComponentLocation
if (blank($namespace)) {
return [
"App\\{$pathNamespace}",
"App\\Filament\\{$pathNamespace}",
app_path($path),
'',
];
@@ -21,7 +21,7 @@ use Nette\PhpGenerator\Literal;
use Nette\PhpGenerator\Method;
use Nette\PhpGenerator\TraitUse;
class LivewireTableClassGenerator extends ClassGenerator
class LivewireTableComponentClassGenerator extends ClassGenerator
{
use CanGenerateTables;
use CanReadModelSchemas;
@@ -0,0 +1,88 @@
<?php
namespace Filament\Tables\Commands\FileGenerators;
use Filament\Support\Commands\Concerns\CanReadModelSchemas;
use Filament\Support\Commands\FileGenerators\ClassGenerator;
use Filament\Tables\Commands\FileGenerators\Concerns\CanGenerateTables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\Method;
class TableClassGenerator extends ClassGenerator
{
use CanGenerateTables;
use CanReadModelSchemas;
/**
* @param class-string<Model> $modelFqn
*/
final public function __construct(
protected string $fqn,
protected string $modelFqn,
protected bool $isGenerated,
) {}
public function getNamespace(): string
{
return $this->extractNamespace($this->getFqn());
}
/**
* @return array<string>
*/
public function getImports(): array
{
return [
Table::class,
...($this->hasPartialImports() ? ['Filament\Tables'] : []),
Builder::class,
$this->getModelFqn(),
];
}
public function getBasename(): string
{
return class_basename($this->getFqn());
}
protected function addMethodsToClass(ClassType $class): void
{
$this->addConfigureMethodToClass($class);
}
protected function addConfigureMethodToClass(ClassType $class): void
{
$method = $class->addMethod('configure')
->setPublic()
->setStatic()
->setReturnType(Table::class)
->setBody($this->generateTableMethodBody($this->getModelFqn()));
$method->addParameter('table')
->setType(Table::class);
$this->configureConfigureMethod($method);
}
protected function configureConfigureMethod(Method $method): void {}
public function getFqn(): string
{
return $this->fqn;
}
/**
* @return ?class-string<Model>
*/
public function getModelFqn(): ?string
{
return $this->modelFqn;
}
public function isGenerated(): bool
{
return $this->isGenerated;
}
}
@@ -136,7 +136,7 @@ class MakeColumnCommand extends Command
$path,
$viewNamespace,
] = $this->askForComponentLocation(
path: 'Filament/Tables/Columns',
path: 'Tables/Columns',
question: 'Where would you like to create the column?',
);
@@ -154,8 +154,8 @@ class MakeColumnCommand extends Command
$this->viewPath,
] = $this->askForViewLocation(
str($this->fqn)
->afterLast('\\Filament\\Tables\\Columns\\')
->prepend('Filament\\Tables\\Columns\\')
->afterLast('\\Tables\\Columns\\')
->prepend('Tables\\Columns\\')
->replace('\\', '/')
->explode('/')
->map(Str::kebab(...))
@@ -6,7 +6,7 @@ use Filament\Support\Commands\Concerns\CanAskForLivewireComponentLocation;
use Filament\Support\Commands\Concerns\CanAskForViewLocation;
use Filament\Support\Commands\Concerns\CanManipulateFiles;
use Filament\Support\Commands\Exceptions\InvalidCommandOutput;
use Filament\Tables\Commands\FileGenerators\LivewireTableClassGenerator;
use Filament\Tables\Commands\FileGenerators\LivewireTableComponentClassGenerator;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
@@ -19,6 +19,7 @@ use function Laravel\Prompts\suggest;
use function Laravel\Prompts\text;
#[AsCommand(name: 'make:filament-livewire-table', aliases: [
'filament:livewire-table',
'make:livewire-table',
])]
class MakeLivewireTableCommand extends Command
@@ -35,6 +36,7 @@ class MakeLivewireTableCommand extends Command
* @var array<string>
*/
protected $aliases = [
'filament:livewire-table',
'make:livewire-table',
];
@@ -172,10 +174,6 @@ class MakeLivewireTableCommand extends Command
->studly()
->replace('/', '\\');
if (blank($this->modelFqnEnd)) {
$this->modelFqnEnd = 'Resource';
}
$this->modelFqn = "{$modelNamespace}\\{$this->modelFqnEnd}";
}
@@ -223,7 +221,7 @@ class MakeLivewireTableCommand extends Command
throw new InvalidCommandOutput;
}
$this->writeFile($this->path, app(LivewireTableClassGenerator::class, [
$this->writeFile($this->path, app(LivewireTableComponentClassGenerator::class, [
'fqn' => $this->fqn,
'modelFqn' => $this->modelFqn,
'isGenerated' => $this->isGenerated,
@@ -0,0 +1,206 @@
<?php
namespace Filament\Tables\Commands;
use Filament\Support\Commands\Concerns\CanAskForComponentLocation;
use Filament\Support\Commands\Concerns\CanManipulateFiles;
use Filament\Support\Commands\Exceptions\InvalidCommandOutput;
use Filament\Tables\Commands\FileGenerators\TableClassGenerator;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\suggest;
use function Laravel\Prompts\text;
#[AsCommand(name: 'make:filament-table', aliases: [
'filament:table',
])]
class MakeTableCommand extends Command
{
use CanAskForComponentLocation;
use CanManipulateFiles;
protected $description = 'Create a new Filament table class';
protected $name = 'make:filament-table';
/**
* @var class-string
*/
protected string $fqn;
protected string $fqnEnd;
protected string $path;
/**
* @var class-string<Model>
*/
protected string $modelFqn;
protected string $modelFqnEnd;
protected bool $isGenerated;
/**
* @var array<string>
*/
protected $aliases = [
'filament:table',
];
/**
* @return array<InputArgument>
*/
protected function getArguments(): array
{
return [
new InputArgument(
name: 'name',
mode: InputArgument::OPTIONAL,
description: 'The name of the table class to generate, optionally prefixed with directories',
),
new InputArgument(
name: 'model',
mode: InputArgument::OPTIONAL,
description: 'The name of the model to generate the table for, optionally prefixed with directories',
),
];
}
/**
* @return array<InputOption>
*/
protected function getOptions(): array
{
return [
new InputOption(
name: 'generate',
shortcut: 'G',
mode: InputOption::VALUE_NONE,
description: 'Generate the table columns based on the attributes of a model',
),
new InputOption(
name: 'model-namespace',
shortcut: null,
mode: InputOption::VALUE_REQUIRED,
description: 'The namespace of the model class, [App\\Models] by default',
),
new InputOption(
name: 'force',
shortcut: 'F',
mode: InputOption::VALUE_NONE,
description: 'Overwrite the contents of the files if they already exist',
),
];
}
public function handle(): int
{
try {
$this->configureFqnEnd();
$this->configureModel();
$this->configureIsGenerated();
$this->configureLocation();
$this->createTable();
} catch (InvalidCommandOutput) {
return static::INVALID;
}
$this->components->info("Table [{$this->fqn}] created successfully.");
return static::SUCCESS;
}
protected function configureFqnEnd(): void
{
$this->fqnEnd = (string) str($this->argument('name') ?? text(
label: 'What is the Table name?',
placeholder: 'BlogPostsTable',
required: true,
))
->trim('/')
->trim('\\')
->trim(' ')
->studly()
->replace('/', '\\');
}
protected function configureModel(): void
{
$modelNamespace = $this->option('model-namespace') ?? 'App\\Models';
$modelFqns = collect(get_declared_classes())
->filter(fn (string $class): bool => is_subclass_of($class, Model::class) &&
str($class)->startsWith("{$modelNamespace}\\"))
->map(fn (string $class): string => str($class)->after("{$modelNamespace}\\"))
->all();
$this->modelFqnEnd = (string) str($this->argument('model') ?? suggest(
label: 'What is the model name?',
options: function (string $search) use ($modelFqns): array {
$search = str($search)->trim()->replace(['\\', '/'], '');
if (blank($search)) {
return $modelFqns;
}
return array_filter(
$modelFqns,
fn (string $class): bool => str($class)->replace(['\\', '/'], '')->contains($search, ignoreCase: true),
);
},
placeholder: 'BlogPost',
))
->trim('/')
->trim('\\')
->trim(' ')
->studly()
->replace('/', '\\');
$this->modelFqn = "{$modelNamespace}\\{$this->modelFqnEnd}";
}
protected function configureIsGenerated(): void
{
$this->isGenerated = $this->option('generate') || confirm(
label: 'Would you like to generate the table columns based on the attributes of the model?',
default: false,
);
}
protected function configureLocation(): void
{
[
$namespace,
$path,
] = $this->askForComponentLocation(
path: 'Tables',
question: 'Where would you like to create the table?',
);
$this->fqn = "{$namespace}\\{$this->fqnEnd}";
$this->path = (string) str("{$path}\\{$this->fqnEnd}.php")
->replace('\\', '/')
->replace('//', '/');
}
protected function createTable(): void
{
if (! $this->option('force') && $this->checkForCollision($this->path)) {
throw new InvalidCommandOutput;
}
$this->writeFile($this->path, app(TableClassGenerator::class, [
'fqn' => $this->fqn,
'modelFqn' => $this->modelFqn,
'isGenerated' => $this->isGenerated,
]));
}
}
@@ -24,6 +24,7 @@ class TablesServiceProvider extends PackageServiceProvider
->hasCommands([
Commands\MakeColumnCommand::class,
Commands\MakeLivewireTableCommand::class,
Commands\MakeTableCommand::class,
])
->hasTranslations()
->hasViews();