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

Filament Address Field

ИСХОДНИК PHP
ВЕРСИЯ 1.0
АВТОР Cododel

Превью композитного поля адреса

🔥 FilamentPHP - композитное поле адреса

Композитное поле — это поле, которое состоит из нескольких других полей, при этом хранится в базе данных в одной, единственной, колонке.

Иногда приходится делать композитные поля, чтобы:

  • Добавить валидацию на отдельные части строки
  • Стандартизировать хранимое значение
  • Разбить для удобства пользования

Задача

В моем случае адрес — это обычная строка по типу:

Брянская область, г. Брянск, ул. Романа Брянского, д. 15/1, кв. 276

Она хранится в базе данных именно в таком виде, при этом на сайте для нее 7 полей: Область, Город, Улица, Дом, Подъезд, Этаж, Квартира.

Цель: Упростить работу менеджеров, сняв с них ответственность за стандартизацию адреса.

Решение

И в FilamentPHP сейчас нет такой возможности из коробки…
Я убил примерно два с половиной часа, чтобы придумать рабочее решение, и справился! 🥳

Представляю вам компонент композитного поля адреса для FilamentPHP 😃

Исходный код

<?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 ]