Skip to content

Commit 70b3823

Browse files
committed
Generate infolists
1 parent 5b95963 commit 70b3823

File tree

9 files changed

+380
-15
lines changed

9 files changed

+380
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<?php
2+
3+
namespace Filament\Infolists\Commands\FileGenerators\Concerns;
4+
5+
use Filament\Infolists\Components\IconEntry;
6+
use Filament\Infolists\Components\ImageEntry;
7+
use Filament\Infolists\Components\TextEntry;
8+
use Illuminate\Database\Eloquent\Model;
9+
use Illuminate\Support\Str;
10+
use Nette\PhpGenerator\Literal;
11+
12+
trait CanGenerateModelInfolists
13+
{
14+
/**
15+
* @param ?class-string<Model> $model
16+
*/
17+
public function generateInfolistMethodBody(?string $model = null): string
18+
{
19+
return <<<PHP
20+
return \$schema
21+
->columns([
22+
{$this->outputInfolistComponents($model)}
23+
]);
24+
PHP;
25+
}
26+
27+
/**
28+
* @param ?class-string<Model> $model
29+
* @param array<string> $exceptColumns
30+
* @return array<string>
31+
*/
32+
public function getInfolistComponents(?string $model = null, array $exceptColumns = []): array
33+
{
34+
if (! $this->isGenerated()) {
35+
return [];
36+
}
37+
38+
if (blank($model)) {
39+
return [];
40+
}
41+
42+
if (! class_exists($model)) {
43+
return [];
44+
}
45+
46+
$schema = $this->getModelSchema($model);
47+
$table = $this->getModelTable($model);
48+
49+
$components = [];
50+
51+
foreach ($schema->getColumns($table) as $column) {
52+
if ($column['auto_increment']) {
53+
continue;
54+
}
55+
56+
$type = $this->parseColumnType($column);
57+
58+
if (in_array($type['name'], [
59+
'json',
60+
'text',
61+
])) {
62+
continue;
63+
}
64+
65+
$componentName = $column['name'];
66+
67+
if (in_array($componentName, $exceptColumns)) {
68+
continue;
69+
}
70+
71+
if (str($componentName)->endsWith([
72+
'_token',
73+
])) {
74+
continue;
75+
}
76+
77+
if (str($componentName)->contains([
78+
'password',
79+
])) {
80+
continue;
81+
}
82+
83+
if (str($componentName)->endsWith('_id')) {
84+
$guessedRelationshipName = $this->guessBelongsToRelationshipName($componentName, $model);
85+
86+
if (filled($guessedRelationshipName)) {
87+
$guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($componentName, app($model)->{$guessedRelationshipName}()->getModel()::class);
88+
89+
$componentName = "{$guessedRelationshipName}.{$guessedRelationshipTitleColumnName}";
90+
}
91+
}
92+
93+
$componentData = [];
94+
95+
if (in_array($componentName, [
96+
'id',
97+
'sku',
98+
'uuid',
99+
])) {
100+
$componentData['label'] = [Str::upper($componentName)];
101+
}
102+
103+
if ($type['name'] === 'boolean') {
104+
$componentData['type'] = IconEntry::class;
105+
$componentData['boolean'] = [];
106+
} else {
107+
$componentData['type'] = match (true) {
108+
$componentName === 'image', str($componentName)->startsWith('image_'), str($componentName)->contains('_image_'), str($componentName)->endsWith('_image') => ImageEntry::class,
109+
default => TextEntry::class,
110+
};
111+
112+
if ($type['name'] === 'date') {
113+
$componentData['date'] = [];
114+
}
115+
116+
if ($type['name'] === 'time') {
117+
$componentData['time'] = [];
118+
}
119+
120+
if (in_array($type['name'], [
121+
'datetime',
122+
'timestamp',
123+
])) {
124+
$componentData['dateTime'] = [];
125+
}
126+
127+
if (in_array($type['name'], [
128+
'integer',
129+
'decimal',
130+
'float',
131+
'double',
132+
'money',
133+
])) {
134+
$componentData[in_array($componentName, [
135+
'cost',
136+
'money',
137+
'price',
138+
]) || $type['name'] === 'money' ? 'money' : 'numeric'] = [];
139+
}
140+
}
141+
142+
$this->importUnlessPartial($componentData['type']);
143+
144+
$components[$componentName] = $componentData;
145+
}
146+
147+
return array_map(
148+
function (array $componentData, string $componentName): string {
149+
$component = (string) new Literal("{$this->simplifyFqn($componentData['type'])}::make(?)", [$componentName]);
150+
151+
unset($componentData['type']);
152+
153+
foreach ($componentData as $methodName => $parameters) {
154+
$component .= new Literal(PHP_EOL . " ->{$methodName}(...?:)", [$parameters]);
155+
}
156+
157+
return "{$component},";
158+
},
159+
$components,
160+
array_keys($components),
161+
);
162+
}
163+
164+
/**
165+
* @param ?class-string<Model> $model
166+
* @param array<string> $exceptColumns
167+
*/
168+
public function outputInfolistComponents(?string $model = null, array $exceptColumns = []): string
169+
{
170+
$columns = $this->getInfolistComponents($model, $exceptColumns);
171+
172+
if (empty($columns)) {
173+
return '//';
174+
}
175+
176+
return implode(PHP_EOL . ' ', $columns);
177+
}
178+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Filament\Commands\FileGenerators\Resources\Concerns;
4+
5+
use Filament\Infolists\Commands\FileGenerators\Concerns\CanGenerateModelInfolists;
6+
use Filament\Infolists\Components\TextEntry;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Nette\PhpGenerator\Literal;
9+
10+
trait CanGenerateResourceInfolists
11+
{
12+
use CanGenerateModelInfolists;
13+
14+
/**
15+
* @param ?class-string<Model> $model
16+
* @param array<string> $exceptColumns
17+
*/
18+
public function generateInfolistMethodBody(?string $model = null, array $exceptColumns = []): string
19+
{
20+
return <<<PHP
21+
return \$schema
22+
->components([
23+
{$this->outputInfolistComponents($model, $exceptColumns)}
24+
]);
25+
PHP;
26+
}
27+
28+
/**
29+
* @param ?class-string<Model> $model
30+
* @param array<string> $exceptColumns
31+
*/
32+
public function outputInfolistComponents(?string $model = null, array $exceptColumns = []): string
33+
{
34+
$components = $this->getInfolistComponents($model, $exceptColumns);
35+
36+
if (empty($components)) {
37+
$recordTitleAttribute = $this->getRecordTitleAttribute();
38+
39+
if (blank($recordTitleAttribute)) {
40+
return '//';
41+
}
42+
43+
$this->importUnlessPartial(TextEntry::class);
44+
45+
return new Literal(<<<PHP
46+
{$this->simplifyFqn(TextEntry::class)}::make(?),
47+
PHP, [$recordTitleAttribute]);
48+
}
49+
50+
return implode(PHP_EOL . ' ', $components);
51+
}
52+
}

packages/panels/src/Commands/FileGenerators/Resources/ResourceClassGenerator.php

+3-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BackedEnum;
66
use Filament\Clusters\Cluster;
77
use Filament\Commands\FileGenerators\Resources\Concerns\CanGenerateResourceForms;
8+
use Filament\Commands\FileGenerators\Resources\Concerns\CanGenerateResourceInfolists;
89
use Filament\Commands\FileGenerators\Resources\Concerns\CanGenerateResourceTables;
910
use Filament\Resources\Pages\Page;
1011
use Filament\Resources\Resource;
@@ -26,6 +27,7 @@
2627
class ResourceClassGenerator extends ClassGenerator
2728
{
2829
use CanGenerateResourceForms;
30+
use CanGenerateResourceInfolists;
2931
use CanGenerateResourceTables;
3032
use CanReadModelSchemas;
3133

@@ -205,12 +207,7 @@ protected function addInfolistMethodToClass(ClassType $class): void
205207
? <<<PHP
206208
return {$this->simplifyFqn($infolistSchemaFqn)}::configure(\$schema);
207209
PHP
208-
: <<<'PHP'
209-
return $schema
210-
->components([
211-
//
212-
]);
213-
PHP;
210+
: $this->generateInfolistMethodBody($this->getModelFqn(), exceptColumns: Arr::wrap($this->getForeignKeyColumnToNotGenerate()));
214211

215212
$method = $class->addMethod('infolist')
216213
->setPublic()

packages/panels/src/Commands/FileGenerators/Resources/Schemas/ResourceInfolistSchemaClassGenerator.php

+67-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,26 @@
22

33
namespace Filament\Commands\FileGenerators\Resources\Schemas;
44

5+
use Filament\Commands\FileGenerators\Resources\Concerns\CanGenerateResourceInfolists;
56
use Filament\Schemas\Schema;
7+
use Filament\Support\Commands\Concerns\CanReadModelSchemas;
68
use Filament\Support\Commands\FileGenerators\ClassGenerator;
9+
use Illuminate\Database\Eloquent\Model;
10+
use Illuminate\Database\Eloquent\Relations\HasMany;
11+
use Illuminate\Support\Arr;
712
use Nette\PhpGenerator\ClassType;
813
use Nette\PhpGenerator\Method;
914

1015
class ResourceInfolistSchemaClassGenerator extends ClassGenerator
1116
{
17+
use CanGenerateResourceInfolists;
18+
use CanReadModelSchemas;
19+
1220
final public function __construct(
1321
protected string $fqn,
22+
protected string $modelFqn,
23+
protected ?string $parentResourceFqn,
24+
protected bool $isGenerated,
1425
) {}
1526

1627
public function getNamespace(): string
@@ -25,6 +36,7 @@ public function getImports(): array
2536
{
2637
return [
2738
Schema::class,
39+
...($this->hasPartialImports() ? ['Filament\Infolists'] : []),
2840
];
2941
}
3042

@@ -44,12 +56,7 @@ protected function addConfigureMethodToClass(ClassType $class): void
4456
->setPublic()
4557
->setStatic()
4658
->setReturnType(Schema::class)
47-
->setBody(<<<'PHP'
48-
return $schema
49-
->components([
50-
//
51-
]);
52-
PHP);
59+
->setBody($this->generateInfolistMethodBody($this->getModelFqn(), exceptColumns: Arr::wrap($this->getForeignKeyColumnToNotGenerate())));
5360
$method->addParameter('schema')
5461
->setType(Schema::class);
5562

@@ -58,8 +65,62 @@ protected function addConfigureMethodToClass(ClassType $class): void
5865

5966
protected function configureConfigureMethod(Method $method): void {}
6067

68+
public function getForeignKeyColumnToNotGenerate(): ?string
69+
{
70+
if (! class_exists($this->getParentResourceFqn())) {
71+
return null;
72+
}
73+
74+
$model = $this->getParentResourceFqn()::getModel();
75+
76+
if (! class_exists($model)) {
77+
return null;
78+
}
79+
80+
$modelInstance = app($model);
81+
$relationshipName = (string) str($this->getModelBasename())->plural()->camel();
82+
83+
if (! method_exists($modelInstance, $relationshipName)) {
84+
return null;
85+
}
86+
87+
$relationship = $modelInstance->{$relationshipName}();
88+
89+
if (! ($relationship instanceof HasMany)) {
90+
return null;
91+
}
92+
93+
return $relationship->getForeignKeyName();
94+
}
95+
96+
public function getModelBasename(): string
97+
{
98+
return class_basename($this->getModelFqn());
99+
}
100+
101+
/**
102+
* @return ?class-string
103+
*/
104+
public function getParentResourceFqn(): ?string
105+
{
106+
return $this->parentResourceFqn;
107+
}
108+
61109
public function getFqn(): string
62110
{
63111
return $this->fqn;
64112
}
113+
114+
/**
115+
* @return class-string<Model>
116+
*/
117+
public function getModelFqn(): string
118+
{
119+
return $this->modelFqn;
120+
}
121+
122+
public function isGenerated(): bool
123+
{
124+
return $this->isGenerated;
125+
}
65126
}

packages/panels/src/Commands/FileGenerators/Resources/Schemas/ResourceTableClassGenerator.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ protected function addConfigureMethodToClass(ClassType $class): void
6969
$this->configureConfigureMethod($method);
7070
}
7171

72+
protected function configureConfigureMethod(Method $method): void {}
73+
7274
public function getForeignKeyColumnToNotGenerate(): ?string
7375
{
7476
if (! class_exists($this->getParentResourceFqn())) {
@@ -110,8 +112,6 @@ public function getParentResourceFqn(): ?string
110112
return $this->parentResourceFqn;
111113
}
112114

113-
protected function configureConfigureMethod(Method $method): void {}
114-
115115
public function getFqn(): string
116116
{
117117
return $this->fqn;

0 commit comments

Comments
 (0)