Back to Deck
[snippet]
Filament Address Field
SOURCE PHP
VERSION 1.0
AUTHOR Cododel

🔥 FilamentPHP - Composite Address Field
A composite field is a field that consists of multiple sub-fields while being stored in the database as a single column.
Sometimes you need composite fields to:
- Add validation to individual parts of a string
- Standardize the stored value
- Split for better user experience
The Challenge
In my case, an address is a simple string like:
Bryansk Oblast, Bryansk, ul. Romana Bryanskogo, d. 15/1, apt. 276It’s stored in the database exactly in this format, while the form has 7 separate fields: State, City, Street, Building, Entrance, Floor, Apartment.
Goal: Simplify managers’ work by removing their responsibility for address standardization.
The Solution
FilamentPHP doesn’t have this capability out of the box…
I spent about two and a half hours figuring out a working solution, and I succeeded! 🥳
Presenting the composite address field component for FilamentPHP 😃
Source Code
<?phpnamespace App\Filament\Resources\Components;
use Filament\Forms\Components;use Filament\Forms\Components\Grid;use Filament\Forms\Components\Group;use Filament\Forms\Components\TextInput;use Filament\Forms\Set;use Illuminate\Support\Str;use LiveWire\Component as Livewire;
class AddressComponent extends Group{ static $postfixes = [ 'state' => ' область', ]; static $prefixes = [ 'state' => '', 'city' => 'г.', 'street' => 'ул.', 'building' => 'д.', 'entrance' => 'п.', 'floor' => 'э.', 'apartment' => 'кв.', ]; public static function make(array|\Closure $schema = [], string $fieldName = 'address'): static { return parent::make($schema) ->schema([ Components\Hidden::make($fieldName)->live(),
Grid::make()->columns(3)->schema([ TextInput::make('state')->label('Область')->autofocus()->live(onBlur: true), TextInput::make('city')->label('Город')->live(onBlur: true), TextInput::make('street')->label('Улица')->live(onBlur: true), ]), Grid::make()->columns(4)->schema([ TextInput::make('building')->label('Дом')->live(onBlur: true), TextInput::make('entrance')->label('Подьезд')->live(onBlur: true), TextInput::make('floor')->label('Этаж')->live(onBlur: true), TextInput::make('apartment')->label('Квартира')->live(onBlur: true), ]), ]) ->afterStateHydrated(function (Set $set, ?array $state, Livewire $livewire) use ($fieldName) { foreach (AddressComponent::parseString($state[$fieldName]) as $key => $value) { $set($key, $value); } }) ->afterStateUpdated(function (Set $set, ?array $state) use ($fieldName) { return $set('address', AddressComponent::toString($state, $fieldName)); }); }
static function toString(array $state, string $fieldName = 'address') { $parts = [ 'state' => $state['state'], 'city' => $state['city'], 'street' => $state['street'], 'building' => $state['building'], 'entrance' => $state['entrance'], 'floor' => $state['floor'], 'apartment' => $state['apartment'], ];
$values = array_filter($parts, fn($value) => !empty($value)); if (isset($values['state']) && !str_contains(Str::of($values['state'])->trim(' ')->lower()->split('/[\s,]+/')->last(), 'обл')) { $values['state'] = $values['state'] . AddressComponent::$postfixes['state']; } $address = collect($values)->map(function ($value, $key) { return (string) Str::of($value) ->trim(' ') ->prepend(AddressComponent::$prefixes[$key], ' '); })->implode(', ');
return $address; }
static function parseString(?string $address) { if (empty($address)) return []; $parts = explode(', ', $address);
// parse state by prefixes $address = []; foreach (AddressComponent::$prefixes as $key => $prefix) { if ($key == 'state') continue; $address[$key] = ''; foreach ($parts as $part) { if (str_contains($part, $prefix)) { $address[$key] = (string) Str::of($part)->replace($prefix, '')->trim(); // remove parsed part $parts = array_diff($parts, [$part]); break; } } } if (empty($address['state'])) { $address['state'] = array_pop($parts); }
return $address; }}