Back to Deck
[snippet]
Filament Address Field
ИСХОДНИК PHP
ВЕРСИЯ 1.0
АВТОР Cododel

🔥 FilamentPHP - композитное поле адреса
Композитное поле — это поле, которое состоит из нескольких других полей, при этом хранится в базе данных в одной, единственной, колонке.
Иногда приходится делать композитные поля, чтобы:
- Добавить валидацию на отдельные части строки
- Стандартизировать хранимое значение
- Разбить для удобства пользования
Задача
В моем случае адрес — это обычная строка по типу:
Брянская область, г. Брянск, ул. Романа Брянского, д. 15/1, кв. 276Она хранится в базе данных именно в таком виде, при этом на сайте для нее 7 полей: Область, Город, Улица, Дом, Подъезд, Этаж, Квартира.
Цель: Упростить работу менеджеров, сняв с них ответственность за стандартизацию адреса.
Решение
И в FilamentPHP сейчас нет такой возможности из коробки…
Я убил примерно два с половиной часа, чтобы придумать рабочее решение, и справился! 🥳
Представляю вам компонент композитного поля адреса для FilamentPHP 😃
Исходный код
<?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; }}