【Laravel】Requestクラスのmergeメソッドがネストした配列を意図せず壊しやすい問題と mergeRecursive の追加による解決方法

 Laravel では FormRequest を使ってリクエストデータを処理する際、Laravel に組み込まれている merge メソッドを使って入力データをマージすることができます。これは例えば次のように使えます。
11.x HTTPリクエスト Laravel#merging-additional-input

// https://readouble.com/laravel/11.x/ja/requests.html#merging-additional-input から引用
$request->merge(['votes' => 0]);

 しかし merge メソッドは深いネストを持つ配列データをマージする際に、意図せぬ上書きを起こしてしまう場合があるという問題があります。内部実装は別物なのですが array_merge のような挙動です。これを array_merge_recursive の様に重複キーの配列の場合にいい感じにまとめるようにしたいです。

<?php
// mergeメソッドで test.b が壊れるのを確認します。
$r = new class extends FormRequest {
    public function init(): void
    {
        $this->getInputSource()->add(['test' => ['a' => 1, 'b' => [2],]]);
    }
};
// ['test' => ['a' => 1, 'b' => [2],]] となるように初期化し
$r->init();
$this->info(json_encode($r->all())); // {"test":{"a":1,"b":[2]}}
// merge メソッドを実行させます。
$r->merge(['test' => ['b' => [3],'c' => 4, 'd' => ['e' =>5],]]);
// test.b の中にあった 2 が消えました。
$this->info(json_encode($r->all())); // {"test":{"b":[3],"c":4,"d":{"e":5}}}

// こんな感じのネストした配列をいい感じに再帰的に混ぜてくれる再帰的マージメソッドが欲しくなります。
echo json_encode(array_merge_recursive(
    ['test' => ['a' => 1, 'b' => [2],]],
    ['test' => [          'b' => [3],'c' => 4, 'd' => ['e' =>5],]]
)); // {"test":{"a":1,"b":[2,3],"c":4,"d":{"e":5}}}
echo "\n";

 もし混ぜる必要もなくキーが存在する場合に上書きしないのであれば mergeIfMissing を使えばいいのですが、mergeIfMissing では配列のネストした部分に値を差し込むことができません。先ほどの例でいえば test の中身がまったく変わらないわけです。そういうわけでこの記事ではこの問題を解決するための「再帰的マージ」を実現するmergeRecursiveメソッドの実装方法を紹介します。

 実装例が次です:

public function mergeRecursive(array $input)
{
    // merge メソッドで操作しているデータソースを取得
    $inputSrc = $this->getInputSource();
    // merge メソッドではシンプルに追加していたところを
    // 全取得した後に array_merge_recursive で再帰的に結合して全体を置き換えるようにする
    $inputSrc->replace(array_merge_recursive($inputSrc->all(), $input));

    return $this;
}

 これを実際に動かすと次のようになります。

<?php
// 実際はフォームリクエストクラスのベースクラスなりトレイトなりに追加した方が取り回しやすくてよさげ
$r = new class extends FormRequest {
    public function init(): void
    {
        $this->getInputSource()->add(['test' => ['a' => 1, 'b' => [2],]]);
    }
    
    public function mergeRecursive(array $input)
    {
        // merge メソッドで操作しているデータソースを取得
        $inputSrc = $this->getInputSource();
        // merge メソッドではシンプルに追加していたところを
        // 全取得した後に array_merge_recursive で再帰的に結合して全体を置き換えるようにする
        $inputSrc->replace(array_merge_recursive($inputSrc->all(), $input));
    
        return $this;
    }
};

$r->init();
$this->info(json_encode($r->all())); // {"test":{"a":1,"b":[2]}}
// mergeRecursive メソッドを実行させます。
$r->mergeRecursive(['test' => ['b' => [3],'c' => 4, 'd' => ['e' =>5],]]);
$this->info(json_encode($r->all())); // {"test":{"a":1,"b":[2,3],"c":4,"d":{"e":5}}}

 こんな感じでいい感じにネストした配列も混ざるようにできます。使いどころはあまりありませんが知っておくとちょっと便利です。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG