Skip to content

Commit 3f11992

Browse files
committed
feat: add automatic model context detection for form and infolist builders
Add smart auto-detection that allows CustomFields form and infolist builders to work without explicit forModel()/forSchema() calls by detecting context from Filament component hierarchy. Changes: - Add FormContainer component with deferred schema generation - Update InfolistContainer to use inline priority resolution - Add explicitModel tracking in BaseBuilder - Implement priority system: explicit > auto-detect > fallback - Use inline priority pattern (KISS): explicitModel ?? getRecord() ?? getModel() All builders now support three usage patterns: 1. Auto-detection: CustomFields::form()->build() 2. Explicit model: CustomFields::form()->forModel(Post::class)->build() 3. Explicit schema: CustomFields::form()->forSchema($schema)->build() Priority resolution ensures explicit parameters always take precedence over auto-detection. Graceful fallback returns empty array when no context available. Zero breaking changes - all existing usage patterns continue to work.
1 parent 2acf486 commit 3f11992

File tree

5 files changed

+97
-32
lines changed

5 files changed

+97
-32
lines changed

src/Filament/Integration/Builders/BaseBuilder.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ abstract class BaseBuilder
1919
{
2020
protected Model&HasCustomFields $model;
2121

22+
protected Model|string|null $explicitModel = null;
23+
2224
protected Builder $sections;
2325

2426
protected array $except = [];
@@ -52,6 +54,7 @@ public function forModel(Model|string $model): static
5254
}
5355

5456
$this->model = $model;
57+
$this->explicitModel = $model;
5558

5659
$this->sections = CustomFields::newSectionModel()->query()
5760
->forEntityType($model::class)

src/Filament/Integration/Builders/FormBuilder.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ class FormBuilder extends BaseBuilder
1616
{
1717
public function build(): Grid
1818
{
19-
return Grid::make(1)->schema($this->values()->toArray());
19+
return FormContainer::make()
20+
->forModel($this->explicitModel ?? null)
21+
->only($this->only)
22+
->except($this->except);
2023
}
2124

2225
private function getDependentFieldCodes(Collection $fields): array
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Relaticle\CustomFields\Filament\Integration\Builders;
4+
5+
use Filament\Schemas\Components\Grid;
6+
use Illuminate\Database\Eloquent\Model;
7+
8+
final class FormContainer extends Grid
9+
{
10+
private Model|string|null $explicitModel = null;
11+
12+
private array $except = [];
13+
14+
private array $only = [];
15+
16+
public static function make(array|int|null $columns = 1): static
17+
{
18+
$container = new self($columns);
19+
20+
// Defer schema generation until component is in container
21+
$container->schema(fn (): array => $container->generateSchema());
22+
23+
return $container;
24+
}
25+
26+
public function forModel(Model|string|null $model): static
27+
{
28+
$this->explicitModel = $model;
29+
30+
return $this;
31+
}
32+
33+
public function except(array $fieldCodes): static
34+
{
35+
$this->except = $fieldCodes;
36+
37+
return $this;
38+
}
39+
40+
public function only(array $fieldCodes): static
41+
{
42+
$this->only = $fieldCodes;
43+
44+
return $this;
45+
}
46+
47+
private function generateSchema(): array
48+
{
49+
// Inline priority: explicit ?? record ?? model class
50+
$model = $this->explicitModel ?? $this->getRecord() ?? $this->getModel();
51+
52+
if ($model === null) {
53+
return []; // Graceful fallback
54+
}
55+
56+
$builder = app(FormBuilder::class);
57+
58+
return $builder
59+
->forModel($model)
60+
->only($this->only)
61+
->except($this->except)
62+
->values()
63+
->toArray();
64+
}
65+
}

src/Filament/Integration/Builders/InfolistBuilder.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Relaticle\CustomFields\Models\CustomField;
1515
use Relaticle\CustomFields\Models\CustomFieldSection;
1616
use Relaticle\CustomFields\Services\Visibility\BackendVisibilityService;
17-
use Throwable;
1817

1918
final class InfolistBuilder extends BaseBuilder
2019
{
@@ -26,14 +25,8 @@ final class InfolistBuilder extends BaseBuilder
2625

2726
public function build(): Component
2827
{
29-
try {
30-
$model = $this->model;
31-
} catch (Throwable) {
32-
$model = null;
33-
}
34-
3528
return InfolistContainer::make()
36-
->forModel($model)
29+
->forModel($this->explicitModel ?? null)
3730
->hiddenLabels($this->hiddenLabels)
3831
->visibleWhenFilled($this->visibleWhenFilled)
3932
->withoutSections($this->withoutSections)
@@ -44,7 +37,7 @@ public function build(): Component
4437
/**
4538
* @return Collection<int, mixed>
4639
*/
47-
public function values(null | (Model & HasCustomFields) $model = null): Collection
40+
public function values(null|(Model&HasCustomFields) $model = null): Collection
4841
{
4942
if ($model !== null) {
5043
$this->forModel($model);

src/Filament/Integration/Builders/InfolistContainer.php

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,36 @@
33
namespace Relaticle\CustomFields\Filament\Integration\Builders;
44

55
use Filament\Forms\Components\Field;
6-
use Filament\Schemas\Components\Component;
6+
use Filament\Schemas\Components\Grid;
77
use Illuminate\Database\Eloquent\Model;
88

9-
final class InfolistContainer extends Component
9+
final class InfolistContainer extends Grid
1010
{
11-
protected string $view = 'filament-schemas::components.grid';
11+
private Model|string|null $explicitModel = null;
1212

13-
protected Model|string|null $recordModel;
13+
private array $except = [];
1414

15-
protected array $except = [];
16-
17-
protected array $only = [];
15+
private array $only = [];
1816

1917
private bool $hiddenLabels = false;
2018

2119
private bool $visibleWhenFilled = false;
2220

2321
private bool $withoutSections = false;
2422

25-
public function __construct()
23+
public static function make(array|int|null $columns = 1): static
2624
{
27-
// Defer schema generation until we can safely access the record
28-
$this->schema(fn() => $this->generateSchema());
29-
}
25+
$container = new self($columns);
3026

31-
public static function make(): static
32-
{
33-
return app(self::class);
27+
// Defer schema generation until component is in container
28+
$container->schema(fn (): array => $container->generateSchema());
29+
30+
return $container;
3431
}
3532

3633
public function forModel(Model|string|null $model): static
3734
{
38-
$this->recordModel = $model;
35+
$this->explicitModel = $model;
3936

4037
return $this;
4138
}
@@ -78,19 +75,23 @@ public function withoutSections(bool $withoutSections = true): static
7875
/**
7976
* @return array<int, Field>
8077
*/
81-
protected function generateSchema(): array
78+
private function generateSchema(): array
8279
{
83-
$model = $this->recordModel ?? $this->getRecord() ?? $this->getModel();
80+
// Inline priority: explicit ?? record ?? model class
81+
$model = $this->explicitModel ?? $this->getRecord() ?? $this->getModel();
8482

85-
$builder = app(InfolistBuilder::class);
83+
if ($model === null) {
84+
return []; // Graceful fallback
85+
}
8686

87-
return $builder
87+
$builder = app(InfolistBuilder::class)
88+
->forModel($model)
8889
->only($this->only)
8990
->except($this->except)
9091
->hiddenLabels($this->hiddenLabels)
9192
->visibleWhenFilled($this->visibleWhenFilled)
92-
->withoutSections($this->withoutSections)
93-
->values($model)
94-
->toArray();
93+
->withoutSections($this->withoutSections);
94+
95+
return $builder->values()->toArray();
9596
}
9697
}

0 commit comments

Comments
 (0)