`$get()` returns raw (uncast) state when called from the field's parent container, but cast state from a sibling
Package
filament/schemas
Package Version
v5.8.2
Laravel Version
v13.32.0
Livewire Version
v4.4.5
PHP Version
PHP 8.5.10
Problem description
$get('field') returns a different type depending on which component calls it, when the target field has a state cast (e.g. a Select with ->options(SomeEnum::class), which registers EnumStateCast).
- Called from a closure on a sibling container (e.g. another
Tab'sbadge()/visible()),$get('status')finds theSelectand returns$component->getState(), i.e. the enum instance. - Called from a closure on the parent container of that field (e.g. the
Tabthat holds theSelect),$get('status')returns the raw string value.
Cause: Get::__invoke() pushes the calling component onto skipComponentsChildContainersWhileSearching before searching, so the search never descends into the caller's own children. When the field isn't found it falls back to data_get($livewire, $path), which reads the raw Livewire state and bypasses the field's state casts.
Skipping the caller's children is presumably intentional (to avoid recursing into children that aren't built yet), but the fallback silently changing the returned type is surprising. The same $get('status') === Status::Published comparison is true in one closure and false in another, and (string) $get('status') throws in one place and works in another.
Where the behaviour comes from (packages/schemas/src/Components/Utilities/Get.php):
static::$skipComponentsChildContainersWhileSearching[] = $this->component;
$component = ($this->component->getStatePath() === $path)
? $this->component
: $this->component->getRootContainer()->getComponentByStatePath(
$path,
withHidden: true,
withAbsoluteStatePath: true,
skipComponentsChildContainersWhileSearching: /* includes the caller */,
);
if (! $component) {
return data_get($livewire, $path); // raw, uncast
}
return $component->getState(); // castExpected behavior
$get() should return the same type for the same path regardless of which component the closure is attached to. Either the fallback should still apply the target field's state casts, or (if that's not feasible) the docs should state that $get() from a parent container returns raw, uncast state.
Steps to reproduce
Minimal page (full code in the reproduction repo):
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
}
// In a page/form schema, with state filled as ['status' => 'published']:
Tabs::make()->tabs([
Tab::make('Parent tab')
->badge(fn (Get $get): string => get_debug_type($get('status'))) // "string"
->schema([
Select::make('status')->options(Status::class),
]),
Tab::make('Sibling tab')
->badge(fn (Get $get): string => get_debug_type($get('status'))) // "App\Enums\Status"
->schema([]),
]),git clone https://github.com/sawirricardo/filament-get-cast-repro && cd filament-get-cast-repro && composer setupphp artisan test tests/Feature/GetCastTest.php
The test asserts both badges report the same type and fails with:
- 'Parent tab' => 'App\Enums\Status',
+ 'Parent tab' => 'string',
'Sibling tab' => 'App\Enums\Status',Alternatively log in at /admin and open Get Cast Page: the two tab badges show string and App\Enums\Status for the same call.
Reproduction repository (issue will be closed if this is not valid)
https://github.com/sawirricardo/filament-get-cast-repro
Relevant log output
$ php artisan test tests/Feature/GetCastTest.php
FAILED Tests\Feature\GetCastTest > get returns the same type regardless of which component calls it
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
Array &0 [
- 'Parent tab' => 'App\Enums\Status',
+ 'Parent tab' => 'string',
'Sibling tab' => 'App\Enums\Status',
]
at tests/Feature/GetCastTest.php:24
Tests: 1 failed (1 assertions)Source: filamentphp/filament