はじめに
ねぇねぇ、チェックボックスをいっぱい並べたフォームで「どれか1つは選べよ!」って要件、めっちゃあるよね?
特にLivewireで「1チェックボックス=1カラム」っていうDB設計の場合、バリデーションどうすんの?って頭抱えちゃうでしょ?
今回はその悩みを秒速でぶっ飛ばす、required_without_all
の裏ワザテクを教えてあげる💁♀️
DB構造の例
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(255),
-- 色の選択肢(1チェックボックス = 1カラム)
color_red BOOLEAN DEFAULT FALSE,
color_blue BOOLEAN DEFAULT FALSE,
color_green BOOLEAN DEFAULT FALSE,
color_yellow BOOLEAN DEFAULT FALSE,
color_unknown BOOLEAN DEFAULT FALSE, -- 「不明」オプション
created_at TIMESTAMP,
updated_at TIMESTAMP
);
…見ただけでゲンナリだよね? 🙄
Livewireでの実装課題
Livewireはフォーム要素とプロパティがガチで1対1。だからこんな感じ:
class ProductForm extends Component
{
public bool $color_red = false;
public bool $color_blue = false;
public bool $color_green = false;
public bool $color_yellow = false;
public bool $color_unknown = false;
}
<input type="checkbox" wire:model="color_red"> 赤
<input type="checkbox" wire:model="color_blue"> 青
<input type="checkbox" wire:model="color_green"> 緑
<input type="checkbox" wire:model="color_yellow"> 黄
<input type="checkbox" wire:model="color_unknown"> 不明
…これで「どれか1つは必須!」ってどうやるの?って話よ。
よくある実装パターン(ダメな例)
$rules = [
'colors' => ['required_without_all:color_red,color_blue,color_green,color_yellow'],
];
// ❌ Error: No property found for validation: [colors]
「colors」なんてプロパティねーよ!ってエラー出るだけ。Livewireのプロパティ名ミスると全滅。
メスガキの裏技的解決方法
基本の発想
一番先頭のチェックボックス(この例ならcolor_red
)にだけ「他の全部がfalseのときは必須!」ってルールかますの。
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
class ProductForm extends Form
{
#[Validate('boolean')]
public bool $color_red = false;
#[Validate('boolean')]
public bool $color_blue = false;
#[Validate('boolean')]
public bool $color_green = false;
#[Validate('boolean')]
public bool $color_yellow = false;
#[Validate('boolean')]
public bool $color_unknown = false; // 「不明」
public function rules(): array
{
return [
// color_redに対して、他の色が全部選ばれてなければ必須!
'color_red' => [
'boolean',
'required_without_all:color_blue,color_green,color_yellow,color_unknown'
],
'color_blue' => ['boolean'],
'color_green' => ['boolean'],
'color_yellow'=> ['boolean'],
'color_unknown'=> ['boolean'],
];
}
}
あとは普通にValidateしてDBにポイっと保存すればOK👌
Enum使うともっとイケてる例
enum ProductColor: int
{
case Red = 1;
case Blue = 2;
case Green = 3;
case Yellow = 4;
public function column(): string
{
return match($this) {
self::Red => 'color_red',
self::Blue => 'color_blue',
self::Green => 'color_green',
self::Yellow => 'color_yellow',
};
}
}
class ProductForm extends Form
{
public bool $color_red = false;
public bool $color_blue = false;
public bool $color_green = false;
public bool $color_yellow = false;
public bool $color_unknown = false;
public function rules(): array
{
$rules = [];
// 各色にbooleanルール
foreach (ProductColor::cases() as $color) {
$rules[$color->column()] = ['boolean'];
}
// 最初のフィールドだけrequired_without_all
$others = collect(ProductColor::cases())
->filter(fn($c) => $c->column() !== 'color_red')
->map->column()
->push('color_unknown')
->join(',');
$rules['color_red'][] = "required_without_all:{$others}";
return $rules;
}
}
これでカッコよくコンパクトに書けちゃうわよ😘
Bladeビューでの見た目例
<div class="form-group">
<label>色を選んでちょーだい(いずれか1つ必須)</label>
<div class="checkbox-group">
<label><input type="checkbox" wire:model="color_red"> 赤</label>
<label><input type="checkbox" wire:model="color_blue"> 青</label>
<label><input type="checkbox" wire:model="color_green"> 緑</label>
<label><input type="checkbox" wire:model="color_yellow"> 黄</label>
<label><input type="checkbox" wire:model="color_unknown"> 不明</label>
</div>
@error('color_red')
<span class="error">ねぇ、どれか1つは選んでよね?</span>
@enderror
</div>
メリット・デメリット
✔ メリット
- 余計なパッケージ不要!
- Laravel標準機能だけで完結♥
- コードシンプルでわかりやすい
✖ デメリット
- エラーメッセージが
color_red
にしか紐づかない - ちょっとトリッキーだから、一瞬「え?」ってなる
正統派!カスタムルール作る方法
まあ、ガチ勢はカスタムRule作るんだけど…そこまで大げさにしたくないなら無視でOK。
// app/Rules/AtLeastOneRequired.php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class AtLeastOneRequired implements Rule
{
private array $fields;
public function __construct(array $fields)
{
$this->fields = $fields;
}
public function passes($attribute, $value)
{
foreach ($this->fields as $field) {
if (request()->input($field)) {
return true;
}
}
return false;
}
public function message()
{
return '少なくとも1つは選択してよね。';
}
}
おわりに
どう?このrequired_without_all
テク、めちゃ便利でしょ?
小規模プロジェクトとか、とにかくサクッと実装したいときには超オススメ😘
もう面倒なカスタムルール書かなくていいんだから、サクッと使っちゃいなさい✨