1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel + Livewireで実現する「1カラム=1チェックボックス」דメスガキ式”選択強制UX

Posted at

はじめに

ねぇねぇ、チェックボックスをいっぱい並べたフォームで「どれか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テク、めちゃ便利でしょ?
小規模プロジェクトとか、とにかくサクッと実装したいときには超オススメ😘
もう面倒なカスタムルール書かなくていいんだから、サクッと使っちゃいなさい✨

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?