Filament Address Field | Cododel
CODODELDEV
EN / RU
Back to Deck
[snippet]

Filament Address Field

SOURCE PHP
VERSION 1.0
AUTHOR Cododel

Composite address field preview

🔥 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. 276

It’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

<?php
namespace 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;
}
}
[ ▲ 0 ]