#06 FilamentPHP基礎
バリデーションとフォームの整理——Sectionと条件付き表示
フォームのレイアウト課題
フィールドが増えてくると、フォームが縦に長くなり使いにくくなります。FilamentにはSection・Grid・Tabsなどのレイアウトコンポーネントが用意されており、フォームを構造化できます。
また ->hidden() / ->visible() / ->live() を使うと、他のフィールドの値に応じて動的にUIを変化させられます。
Section でフィールドをグループ化
Section::make() は関連するフィールドをまとめてカードUI(折りたたみ可能)で表示します。
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\FileUpload;
public static function form(Form $form): Form
{
return $form
->schema([
// 基本情報セクション
Section::make('基本情報')
->description('記事の基本的な情報を入力してください')
->schema([
TextInput::make('title')
->label('タイトル')
->required()
->maxLength(255),
TextInput::make('slug')
->label('スラッグ')
->required()
->unique(ignoreRecord: true),
])
->columns(2),
// メディアセクション(折りたたみ可能)
Section::make('メディア')
->schema([
FileUpload::make('thumbnail')
->label('サムネイル画像')
->image()
->disk('public'),
])
->collapsed() // 最初から折りたたんで表示
->collapsible(), // 折りたたみを許可
// 公開設定セクション(右サイドバー風)
Section::make('公開設定')
->schema([
Toggle::make('is_published')
->label('公開する'),
DateTimePicker::make('published_at')
->label('公開日時'),
])
->aside(), // サイドバー表示(左に説明、右にフィールド)
]);
}
Section のオプション
| メソッド | 説明 |
|---|---|
->columns(2) | セクション内を2カラムレイアウトに |
->collapsed() | 初期状態で折りたたむ |
->collapsible() | ユーザーが折りたためるようにする |
->aside() | 左にタイトル・説明、右にフィールドのレイアウト |
->compact() | パディングを小さくしてコンパクト表示 |
->icon('heroicon-o-cog') | セクションタイトル横にアイコン |
Grid で横並びレイアウト
Grid::make() はSectionを使わず、フィールドをグリッドで並べます。
use Filament\Forms\Components\Grid;
Grid::make(3) // 3カラムグリッド
->schema([
TextInput::make('first_name')
->label('名'),
TextInput::make('last_name')
->label('姓'),
TextInput::make('phone')
->label('電話番号'),
]),
// 個別フィールドでカラム数を指定
TextInput::make('address')
->label('住所')
->columnSpan(2), // 3カラム中2カラム分の幅
TextInput::make('zip')
->label('郵便番号')
->columnSpan(1), // 残り1カラム
Tabs でタブ切り替えレイアウト
use Filament\Forms\Components\Tabs;
Tabs::make('設定')
->tabs([
Tabs\Tab::make('基本情報')
->icon('heroicon-o-document')
->schema([
TextInput::make('title')->required(),
Textarea::make('content'),
]),
Tabs\Tab::make('SEO設定')
->icon('heroicon-o-magnifying-glass')
->schema([
TextInput::make('meta_title')
->label('メタタイトル'),
Textarea::make('meta_description')
->label('メタディスクリプション'),
]),
Tabs\Tab::make('公開設定')
->icon('heroicon-o-globe-alt')
->schema([
Toggle::make('is_published'),
DateTimePicker::make('published_at'),
]),
])
->columnSpanFull(),
条件付き表示:hidden / visible / disabled
フィールドの表示・非表示・無効化を動的に制御するには Forms\Get クロージャを使います。
use Filament\Forms\Get;
use Filament\Forms\Set;
// is_published が true のときだけ published_at を表示
DateTimePicker::make('published_at')
->label('公開日時')
->visible(fn (Get $get): bool => $get('is_published')),
// type が 'external' のとき URL フィールドを表示
TextInput::make('url')
->label('外部URL')
->url()
->hidden(fn (Get $get): bool => $get('type') !== 'external'),
// 新規作成時のみ入力可(編集時は無効化)
TextInput::make('username')
->label('ユーザー名')
->disabled(fn (string $operation): bool => $operation === 'edit'),
// 特定の条件でフィールドを必須にする
TextInput::make('company_name')
->label('会社名')
->required(fn (Get $get): bool => $get('is_corporate')),
live() でリアルタイム更新
->live() を付けたフィールドは値が変わるたびにLivewireのリクエストが発生し、他のフィールドの状態をリアルタイムに更新します。
Toggle::make('is_published')
->label('公開する')
->live(), // これを付けると値変更時に他フィールドが再評価される
DateTimePicker::make('published_at')
->label('公開日時')
->visible(fn (Get $get): bool => $get('is_published')),
->live(onBlur: true) にするとフォーカスが外れたタイミングに変更します(リクエスト数を減らしたい場合)。
afterStateUpdated でフィールド間連動
->afterStateUpdated() を使うと、あるフィールドの変更に反応して別のフィールドの値をセットできます。
// タイトルの入力からスラッグを自動生成
TextInput::make('title')
->label('タイトル')
->live(onBlur: true)
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
// タイトルが変わったらスラッグを更新(スラッグが未変更の場合のみ)
if (($get('slug') ?? '') === \Str::slug($old)) {
$set('slug', \Str::slug($state));
}
}),
TextInput::make('slug')
->label('スラッグ')
->required(),
// カテゴリ変更でサブカテゴリをリセット
Select::make('category_id')
->label('カテゴリ')
->options(Category::pluck('name', 'id'))
->live()
->afterStateUpdated(fn (Set $set) => $set('subcategory_id', null)),
Select::make('subcategory_id')
->label('サブカテゴリ')
->options(fn (Get $get) =>
Subcategory::where('category_id', $get('category_id'))
->pluck('name', 'id')
->toArray()
),
カスタムバリデーション rules()
Laravelのバリデーションルールをそのまま使えます。
// 文字列ルール
TextInput::make('username')
->rules(['required', 'alpha_dash', 'min:3', 'max:20']),
// Ruleクラスを使ったユニークチェック(編集時は自分自身を除外)
TextInput::make('email')
->rules([
'required',
'email',
fn (string $operation, $record) => $operation === 'create'
? \Illuminate\Validation\Rule::unique('users', 'email')
: \Illuminate\Validation\Rule::unique('users', 'email')->ignore($record?->id),
]),
// クロージャでカスタムルール
TextInput::make('password_confirm')
->label('パスワード(確認)')
->password()
->rules([
fn (Get $get): \Closure => function (string $attribute, $value, \Closure $fail) use ($get) {
if ($value !== $get('password')) {
$fail('パスワードが一致していません。');
}
},
]),
ヘルパーテキストとヒント
フィールドにサポートテキストを追加できます。
TextInput::make('slug')
->label('スラッグ')
->helperText('URLに使用されます。英数字とハイフンのみ使用できます。')
->hint('例: my-first-post')
->hintIcon('heroicon-o-information-circle'),
TextInput::make('price')
->label('価格')
->prefix('¥') // 入力欄の前に表示
->suffix('円'), // 入力欄の後に表示
TextInput::make('api_key')
->label('APIキー')
->suffixAction(
Forms\Components\Actions\Action::make('generate')
->label('生成')
->icon('heroicon-o-arrow-path')
->action(fn (Set $set) => $set('api_key', \Str::random(32)))
),
完成形:整理されたフォーム例
public static function form(Form $form): Form
{
return $form
->schema([
Section::make('基本情報')
->schema([
TextInput::make('title')
->label('タイトル')
->required()
->live(onBlur: true)
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
if (($get('slug') ?? '') === \Str::slug($old)) {
$set('slug', \Str::slug($state));
}
}),
TextInput::make('slug')
->label('スラッグ')
->required()
->unique(ignoreRecord: true)
->helperText('URLに使用されます'),
])
->columns(2),
Section::make('本文')
->schema([
RichEditor::make('content')
->label('本文')
->required()
->columnSpanFull(),
]),
Section::make('公開設定')
->schema([
Toggle::make('is_published')
->label('公開する')
->live(),
DateTimePicker::make('published_at')
->label('公開日時')
->visible(fn (Get $get): bool => (bool) $get('is_published'))
->seconds(false),
])
->columns(2)
->aside(),
]);
}
まとめ
Section::make()でフィールドをグループ化、折りたたみ・サイドバーレイアウトに対応Grid::make()で横並びレイアウト、->columnSpan()で幅を制御Tabs::make()でタブ切り替えレイアウト->hidden()/->visible()/->disabled()にGetクロージャを渡して動的制御->live()で値変更時にLivewireを再レンダリング->afterStateUpdated()でフィールド間の値連動を実装->rules([])でLaravelバリデーションルールを直接指定->helperText()/->hint()/->prefix()/->suffix()でUI補助テキストを追加
次回はカスタムページとウィジェットでダッシュボードを作成します。