配列を特定のフォーマット（構造）に矯正する方法と、大量の引数を管理するための１提案

# なにがしたい？

たとえば下記のような「会社登録」のための関数クラスを作ったとします。
$request はフォームからの入力やAPIでポストされたデータを想定しています。

```php:Usecases\Company\Create.php
class Create 
{
    // コンストラクタで関連したオブジェクトを取得しているけど省略

    public function __invoke(array $request)
    {
        // 会社の作成
        $company = ($this->company_create)($request['company']);

        // 店舗の作成
        $request['store']['company_id'] = $company['id'];
        $store = ($this->store_create)($request['store']);

        // 管理スタッフを作成
        $request['staff']['store_id'] = $store['id'];
        $staff = ($this->staff_create)($request['staff'],'company_admin');

        return $company;
    }
}
```

これを呼び出すコードはこんな感じです。


```php:CompanyController.php
    public function store(Request $request, Create $companyCreate)
    {
        $company = $companyCreate($request->all());

        return view('company.show',compact('company'));
    }
```

ところが、フォームがレガシーコードで、CompanyCreate()が想定している$requestのフォーマットと違うことが判明。コントローラで配列のフォーマットを変換しないといけない。

でもチョット待って！

その「CompanyCreate()が想定している **$requestのフォーマット**」ってどんなの？ 仕様書に書いてある？ うわ、仕様書めっちゃ古いしコレ大丈夫なん？ あかんやん、DBに store_name が無いって怒られる。他にも足りないのめっちゃあるし、なんやねんこれーーー！

とならないように、**必要なデータがなにか？**が、 **Company\Create.php を見ただけでわかる**ようにならないか？という課題です。 

**NOW IN PROGRESS...**

既知の問題があって継続的改善中。

- 要素数が可変の配列に対応できない `'stores[]'=>` か `'stores.*'=>` と書きたい
- Laravelのバリデータ使えば良いんじゃないか
- でもそれだと「余分な要素のカット」ができない
- デフォルト値のセットもできない
- 必要なデータが何かはわかるようになるけど……


# 結論

こんな実装をしました。

```php:Usecases\Company\Create.php
class Create 
{
    use \Usecases\Concerns\ArrayFilterDefault;

    // コンストラクタで関連したオブジェクトを取得しているけど省略

    /**
     * 引数に渡すデータ配列のフォーマット
     * ここに指定されたカラム以外のデータは無視される
     * null 以外を指定すると、省略時のデフォルト値になる
     */
    protected $request_format = [
        'company' => [
            'name'    => null,
            'comment' => null,
            'sample'  => false, // デフォルト値
        ],
        'store'   => [
            'name'          => '本店事務所',
            'pref'          => null,
            'address1'      => null,
            'address2'      => null,
            'tel'           => null,
            'comment'       => null,
        ],
        'staff'   => [
            'name'     => null,
            'email'    => null,
            'password' => null,
        ],
    ];

    public function __invoke(array $request)
    {
        $request = $this->ArrayFilterDefault($this->request_format, $request);
        // 略
    }
}
```

トレイトはこんな感じ。

```php:Usecases\Concerns\ArrayFilterDefault.php
namespace Usecases\Concerns;

trait ArrayFilterDefault {
    
    // http://arstropica.com/uncategorized/google-analytics-http-api/
    private function array_intersect_key_recursive(array $array1, array $array2) {
        $array1 = array_intersect_key($array1, $array2);
        foreach ($array1 as $key => &$value) {
            if (is_array($value) && is_array($array2[$key])) {
                $value = $this->array_intersect_key_recursive($value, $array2[$key]);
            }
        }
        return $array1;
    }

    // https://wpscholar.com/blog/filter-multidimensional-array-php/    
    private function array_filter_recursive( array $array, callable $callback = null ) {
        foreach ( $array as &$value ) {
            if ( is_array( $value ) ) {
                $value = $this->array_filter_recursive( $value, $callback );
            }
        }
        $array = is_callable( $callback ) ? array_filter( $array, $callback ) : array_filter( $array );
        return $array;
    }

    /**
     * 与えられた配列を、指定されたフォーマットの配列に合わせる
     */
    protected function ArrayFilterDefault(array $defaults, array $options)
    {
        return $this->array_filter_recursive( 
            array_replace_recursive( 
                $defaults, 
                $this->array_intersect_key_recursive(
                    $options, 
                    $defaults
                )
            )
            , function($val) { return !is_null($val) && ($val!==[]); }
        );
    }
}
```

呼び出し元はこのようになりました。

```php:CompanyController.php
    public function store(Request $request, Create $companyCreate)
    {
        $data = [
            'company' => [
                'name'      => $request->name,
                'comment'   => $request->comment,
            ],
            'store'   => [
                'pref'          => $request->prefecture,
                'address1'      => $request->address,
                'address2'      => $request->address_rest,
                'tel'           => $request->tel,
                'comment'       => $request->comment,
            ],
            'staff'   => [
                'name'     => $request->manager_name,
                'email'    => $request->manager_email,
                'password' => $request->password,
            ],
        ];
        $company = $companyCreate($data);

        return view('company.show',compact('company'));
    }
```


# 解説

## 配列を指定の書式に矯正する

アイデアとしては、Createのクラス定義に欲しい配列の定義を書いておいて、その配列に指定されているキーしか受け付けない、というものです。まずはそんなフィルタが作れないかなーと。Laravelの$fillableのようなものですが、多次元配列に対応したい。

```php
array_replace_recursive( $defaults, array_intersect_key($options, $defaults));
```

**array_intersect_key**
http://php.net/manual/en/function.array-intersect-key.php#115006
2つの配列の共通のキーのみ残す。

**array_intersect_key_recursive**
http://arstropica.com/uncategorized/google-analytics-http-api/
それを再帰的に適用する。

**array_filter**
http://php.net/manual/ja/function.array-filter.php
特定の値を取り除く

**array_filter_recursive**
https://wpscholar.com/blog/filter-multidimensional-array-php/   
それを再帰的に適用する。

デフォルト配列には必要なカラムを書いておき、基本的にすべての値を null にしておきます。
array_intersect_key_recursive すると両方にあるキーだけ残るので、最後に値がNULLのものを取り除けば完了。その際、**空配列**を除去するかどうかは判断の分かれるところです。今回は除去しましたが、デフォルト値としてから配列を与えたいケースがある場合は実装を変更する必要があるかも。

```php:テンプレート配列
$defaults = [
    'company' => [
        'name'    => null,
        'comment' => null,
        'sample'  => false, // デフォルト値
    ],
    'store'   => [
        'name'          => '本店事務所', // デフォルト値
        'pref'          => null,
        'comment'       => null,
    ],
    'staff'   => [
        'name'     => null,
        'email'    => null,
        'password' => null,
    ],
];
```

```php:実際の配列
$options = [
    'company' => [
        // 'name'    => 1, // 少ない
        // 'comment' => 2, // 少ない
        // 'sample'  => 3, // 少ない
        'DUMMY'    => 'DUMMY', // 多い
    ],
    'store'   => [
        'DUMMY'    => 'DUMMY', // 多い
        'name'          => 4,
        'pref'          => 5,
        'comment'       => 9,
    ],
    // 'staff'   => [  // 少ない
    //     'name'     => 10,
    //     'email'    => 11,
    //     'password' => 12,
    //     'DUMMY'    => 'DUMMY',
    // ],
    'DUMMY'    => 'DUMMY', // 多い
],

```

```php:結果
[
    'company' => [
        'sample'  => false,  // デフォルト値だけ残る
    ],
    'store'   => [
        'name'          => 4,
        'pref'          => 5,
        'comment'       => 9,
    ],
    // 'staff'   => [], // 空配列は除去される
];
```




## 大量の引数問題

……というか最近考えていること

「引数がたくさんある」というのは、良くないコードだと言われています。それだけ多くのデータに依存しているから、テストもパターンが多くなるし、コードもとても読みにくくなります。


```php
class CompanyController extends Controller 
{
    public function store(
                $name,
                $name_kana,
                $zipcode,
                $pref,
                $address1,
                $address2,
                $tel,
                $president,
                $email,
                $comment
    )
    {
      // do something ...
    }
}
```


じゃあ、こうしてみましょうか。

```php
class CompanyController extends Controller 
{
    public function store( $data )
    {
        // do something ...
    }
}
```

うん、スッキリ。
じゃあ、処理を書いていきましょう。

```php
class CompanyController extends Controller 
{
    public function store( $data )
    {
        // do something ...
        if( $data['pref'] == '東京都' ){
            // do something ....
        }
        // do something ...
        foreach( $data['users'] as $user ){
            // do something ....
        }
        // do something ...
        another_function( $data );
        // do something ...
    }
}
```


```php
        $data['..........?']
```

あれ？
$data って中身どうなってるんだっけ？


```php
        // ERROR: undefined index 'users'
        foreach( $data['users'] as $user ){
```

おや？
$data に入っているはずのusersが入ってきてないぞ！

```php
        another_function( $data );
```

うわ！
$data まるごと渡しちゃってる。
この先でなにやってるか全然わかんないぞ。

ほら、言わんこっちゃない。

でも。

これって、**$data の型が保証されていないから**というのが問題の本質ではないかと思ったんですよね。もっというと、大量の引数で問題が顕著に現れるのは、全然関係ないオブジェクトや条件値が複数絡み合ってくるパターン。逆に、この例のように、１つ２つのオブジェクトに関した「データ」が大量にあるときは、それが全部見えたほうがむしろ都合がいいはず。

そこで

```php
class CompanyController extends Controller 
{
    public function store(
                $name,
                $name_kana,
                $zipcode,
                $pref,
                $address1,
                $address2,
                $tel,
                $president,
                $email,
                $comment
    )
    {
      // do something ...
    }
}
```

これに戻ってくるんですが、さすがにガチで「引数」だとメンテナンス性が悪いし可読性が悪い。順番も覚えないといけないし、何より順番を間違えたらどえらいことに。そこで配列で「名前付き引数」にしました。さらに多次元配列を使うことでグルーピングも可能に。

こうして、Createが欲しい配列が常にその場に書いてあるようになれば、このように、大量に代入を並べて型変換していても、むしろスッキリ読みやすく、メンテナンス性も高くなったと思います。

```php:CompanyController.php
    public function store(Request $request, Create $companyCreate)
    {
        $data = [
            'company' => [
                'name'      => $request->name,
                'comment'   => $request->comment,
            ],
            'store'   => [
                'pref'          => $request->prefecture,   // Requestの書式が違うが
                'address1'      => $request->address,      // 1つ1つ手渡し
                'address2'      => $request->address_rest, // こうすると
                'tel'           => $request->tel,          // コアである
                'comment'       => $request->comment,      // Createを修正せずに
            ],                                             // うまく結合できる
            'staff'   => [
                'name'     => $request->manager_name,      // Coreに合わせる
                'email'    => $request->manager_email,     // コントローラは調整役
                'password' => $request->password,          
            ],
        ];
        $company = $companyCreate($data);

        return view('company.show',compact('company'));
    }
```

昔はこんなコードをみたら「なんて冗長なことしてるんだ！」と、コード圧縮にやっ気になっていましたが、こういう、特に「モジュール同士のコミュニケーションポイント」は、一括でぽんとブラックボックス渡しをせず、冗長でも１つ１つ渡していくほうが、結合がゆるく、メンテナンスしやすいコードになる、こともあるのかな、と。

# 補足

もっというと、引数はそんなにガッツリ固まってなくても良くて、リソースごとに分けても良いし、

```php
    public function __invoke(array $company,array $store, array $staff)
    {
        $request = $this->ArrayFilterDefault($this->company_format, $company);
        $request = $this->ArrayFilterDefault($this->store_format, $store);
        $request = $this->ArrayFilterDefault($this->staff_format, $staff);
        // 略
    }
```

もっというと、ここはふつう、クラスだろ！

```php
    public function __invoke(Company $company,Store $store, Staff $staff)
    {
        // 略
    }
```

というのはごもっともなのですが、諸事情あり、今回はよりプリミティブに配列で実装してみました。
諸事情と言うか、個人的な修行不足です。また次回挑戦します！