Laravel API 多次元連想配列のリクエストパラメータをスネークケースに変換する関数【改良版】

# はじめに
Laravelを用いたAPI開発で、フロントエンドからのリクエストパラメータを「キャメルケース」と「スネークケース」両方とも許容できるようにするタスクのメモ。

以前、同タスクの内容を以下でまとめたのだが、今回はロジックを一新したのでまとめる。

https://qiita.com/takeuchiYuya/items/13ea090576464b21add0

上記事では階層の制限があるロジックだったが、今回は制限や条件なしで動く関数を作成できた。
また、本記事では関数の部分のみ記事にするので、関数以外の部分は上の記事を参考に。

# 目次
1. [前提の仕様](#前提の仕様)
2. [関数](#関数)
3. [参考文献](#参考文献)

# 前提の仕様
・フロントからのリクエストパラメータを「キャメルケース」と「スネークケース」両方許容できるように
・バックエンド側では「スネークケース」に統一する
・アプリ内のすべてのPOSTリクエストに対応（リクエストオブジェクトの構造やパラメータは各処理によって異なり、多次元配列もある）

# 関数
再起処理による関数
```php
public function handle(Request $request, Closure $next)
{
    $array_snake_recursive = function($array, $new_array=[], $keys=[]) use(&$array_snake_recursive) {
        foreach ($array as $key => $value) {
            if (preg_match('/[A-Z]/', $key)) {
                $key = Str::snake($key);
            }

            $index = count($keys);
            if (is_array($value) && count($value) > 0) {
                $new_array[$index][$key] = [];
                $keys[] = $key;
                $new_array = $array_snake_recursive($value, $new_array, $keys);
                array_splice($keys, -1);
                array_splice($new_array, -1);
            } else {
                $new_array[$index][$key] = $value;
            }
        }

        // 一個上の階層に代入
        if ($index > 0) {
            $new_array[$index-1][$keys[$index-1]] = $new_array[$index];
        }

        // 再起処理の最後のみ
        if (count($new_array) === 1) {
            $new_array = $new_array[0];
        }

        return $new_array;
    };

    $array = $request->all();
    $new_array = $array_snake_recursive($array);
    $request->replace($new_array);
    
    return $next($request);
}
```
#### 【ロジック】
・```Str::snake()```でスネークケースへ変換
・```$index```は```$keys```の要素数

・```$new_array[]```は2階層構造で、1階層目はインデックス番号、2階層目はオブジェクト（キー：バリュー）の形で入れていく
・```$keys[]```は インデックス：値（キー名）の構造

・条件式によって処理を分ける
　foreach内```$value```の値がarray型かつ要素数が1以上だった場合（if）
　　再起処理の前
　　　・```$keys```に```$key```を代入
　　　・```$new_array[$index]```に```[]```を代入
　　再起処理を走らせる
　　再起処理の後
　　　・```$keys```の末尾を削除
　　　・```$new_array```の末尾を削除
　foreach内```$value```の値がstring型 or NULL or array型かつ要素数が0の場合（else）
　・```$index```と```$keys```の値を使い、変数で```$new_array```のキー(名)を指定し、```$value```の値を代入


例を交えて説明する
```php:例）リクエストされた元配列
$original_array = {
    "A": "値",
    "B": {
      "C": {
         "D": "値" 
      },
      "E": "値"
    },
    "F": {
      "G": "値"
    }
}
```
以下、再起処理、```$original_array["b"]["c"]["d"]```を処理中の状態
```php:
$new_array = [
    0: { "a": "値" }, { "b": [] },
    1: { "c": [] },
    // 2: { "d": "値" } // 後述のelseのところでこうなる
]
```
```php:
$keys = [
    0: "b",
    1: "c",
]
```
処理してる箇所
```php:
else {
    $new_array[$index][$key] = $value;
    // 例) -> $new_array[2]["d"] = "値";
}
```

・下の階層の値を一個上の階層の配列に代入する（dをcに代入、cをaに代入していく）
```php
if ($index > 0) {
    $new_array[$index-1][$keys[$index-1]] = $new_array[$index];
    // 例) -> $new_array[1]["c"] = $new_array[2];
}
```
処理終わり次第
・```$new_array[]```のインデックス0以降の末尾を削除する
・```$keys[]```の末尾を削除する
（dの処理が終わった後、cのforeach終了）
```php
array_splice($keys, -1);
array_splice($new_array, -1);
```

以上を再起的に繰り返していく

ちなみに以下再起処理、```$original_array["f"]["g"]```を処理中の状態
```php
$new_array = [
    0: { "a": "値" }, { "b": { "c": { "d": "値" }, "e": "値" } }, { "f": [] },
    // 1: { "g": "値"} // elseの処理でこうなる
]
```
```php:
$keys = [
    0: "f",
]
```
処理してる箇所
```php:
else {
    $new_array[$index][$key] = $value;
    // 例) -> $new_array[1]["g"] = "値";
}
```
↓
```php
if ($index > 0) {
    $new_array[$index-1][$keys[$index-1]] = $new_array[$index];
    // 例) -> $new_array[0]["f"] = $new_array[1];
}
```

再起処理の最後の```$new_array```の状態
```php
$new_array = [
    0: { "a": "値" }, 
       { "b": {
           "c": { 
             "d": "値" 
           }, 
           "e": "値" 
         } 
       }, 
       { "f": {
             "g": "値"
          }
       }
]
```
最後に構造を整えてreturnする
```php
// 再起処理の最後のみ
if (count($new_array) === 1) {
    $new_array = $new_array[0];
}
```

上記ロジックでは構造や階層の深さがどんな配列でも対応できる（はず）

### 終わりに
いいのができた気がする。
バグやもっといいアイデアあったらご教授ください。


# 参考文献
- [Laravel API 多次元連想配列のリクエストパラメータをスネークケースに変換する関数を作成した時のメモ](https://qiita.com/takeuchiYuya/items/13ea090576464b21add0)