Arr::add() | Laravelのヘルパ関数を勉強する

「Laravelの `vendor/` ディレクトリのファイルを読んでPHPを学ぼう」という動きが社内であったので、その備忘録です。

環境
Laravel : 5.8

# Arr::add()


`Arr::add` メソッドは指定されたキー／値のペアをそのキーが存在していない場合と `null` がセットされている場合に、配列に追加します。


```php

use Illuminate\Support\Arr;

$array = Arr::add(['name' => 'Desk'], 'price', 100);

// ['name' => 'Desk', 'price' => 100]

$array = Arr::add(['name' => 'Desk', 'price' => null], 'price', 100);

// ['name' => 'Desk', 'price' => 100]

```

[引用:公式Doc](https://readouble.com/laravel/5.8/ja/helpers.html#method-array-add)

# 関数の実態

Laravelの `Arr` ヘルパ関数は以下のディレクトリに実態が記載されています。

```php:vendor/laravel/framework/src/Illuminate/Support/Arr.php

    /**
     * Add an element to an array using "dot" notation if it doesn't exist.
     *
     * @param  array   $array
     * @param  string  $key
     * @param  mixed   $value
     * @return array
     */
    public static function add($array, $key, $value)
    {
        if (is_null(static::get($array, $key))) {
            static::set($array, $key, $value);
        }

        return $array;
    }

```

以下ではさらにこの実態を読み解きます。

## まず `static::get()` を理解する

`is_null(static::get($array, $key))` の内部で何が起きているのかを理解します。

### `static::get`

**役割**：配列 `$array` に キー `$key` があればその値を返す。 そうでない場合は `value($default)` （ここでは `null`）を返す。

```php:vendor/laravel/framework/src/Illuminate/Support/Arr.php

    /**
     * Get an item from an array using "dot" notation.
     *
     * @param  \ArrayAccess|array  $array
     * @param  string|int  $key
     * @param  mixed   $default
     * @return mixed
     */
    public static function get($array, $key, $default = null)
    {
        // $array は配列か？
        if (! static::accessible($array)) {
　　　　　　　　　　　　　　　　　　　　　　　　　// 配列でない場合は null を返す
            return value($default);
        }

        // $key は null か？
        if (is_null($key)) {
　　　　　　　　　　　　　　　　　　　　　　　　　// null である場合は配列($array)をそのまま返す
            return $array;
        }

        // $key は配列($array)のキーとして存在するか？
        if (static::exists($array, $key)) {
　　　　　　　　　　　　　　　　　　　　　　　　　// 存在する場合は配列($array)のキー($key)の値を返す
            return $array[$key];
        }

        // $key に文字列「.」(dot)が含まれるか？
        if (strpos($key, '.') === false) {
            // $key に文字列「.」が含まれていない場合
            // 配列($array)のキー($key)の値が null でない場合はその値を返す
            // そうでない場合は null を返す
            return $array[$key] ?? value($default);
        }

        // $key を「.」で分割した配列にする
        // ex) $key が 'products.desk.price' のとき ['products', 'desk', 'price'] になる
        foreach (explode('.', $key) as $segment) {
            if (static::accessible($array) && static::exists($array, $segment)) {
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　// 配列($array)にキー($segment)が存在する場合はその値を $array として次の $segment の処理へ移る
                $array = $array[$segment];
            } else {
                // もし $segment のうち一つでも配列($array)にキーとして存在しない場合は null を返す
                return value($default);
            }
        }

        return $array;
    }

```

### `static::accessible()`

`static::get()` の先頭で呼ばれている `static::accessible()` も同じファイル内で定義されています。
ファイル内を行ったり来たりしますが、我慢して追い続けます。

**役割**：引数が配列である、あるいは、`ArrayAccess`のインスタンスである場合は `true` を、そうでない場合は `false` を返す。
**返り値**：`boolean`

```php:vendor/laravel/framework/src/Illuminate/Support/Arr.php

    /**
     * Determine whether the given value is array accessible.
     *
     * @param  mixed  $value
     * @return bool
     */
    public static function accessible($value)
    {
        return is_array($value) || $value instanceof ArrayAccess;
    }

```

つまり、「変数は配列として扱えるか？」という意味だと捉えれば良いです。

PHPのArrayAccessインタフェースについて詳しく知りたい方は[こちら](https://qiita.com/kei_Acarroll/items/0cc978dffd40dc2ec939)が参考になります。


### `static::exists()`

`static::exists()` も同じファイル内で定義されています。

**役割**：引数の配列 `$array` にキー `key` があるか調べる。
**返り値**：`boolean`

```php:vendor/laravel/framework/src/Illuminate/Support/Arr.php

    /**
     * Determine if the given key exists in the provided array.
     *
     * @param  \ArrayAccess|array  $array
     * @param  string|int  $key
     * @return bool
     */
    public static function exists($array, $key)
    {
        if ($array instanceof ArrayAccess) {
            return $array->offsetExists($key);
        }

        return array_key_exists($key, $array);
    }

```

### `value()`

これも `Laravel` にビルトインされたヘルパ関数です。
`self::value()` などとなっていないので、一瞬 `PHP` に組み込まれているのか？？と思ってしまいました💦

`value` 関数は指定値を返します。「クロージャ」を関数に渡した場合は実行し、結果を返します。

```

$result = value(true);

// true

$result = value(function () {
    return false;
});

// false

```

[引用:公式Doc](https://readouble.com/laravel/5.8/ja/helpers.html#method-value)

したがって　`Arr::add()` で呼ばれている `value($default);` はそのまま `null` を返すことになります。

なお、 `value()` の実態は以下の部分に定義されています。

```php:vendor/laravel/framework/src/Illuminate/Support/helpers.php

    /**
     * Return the default value of the given value.
     *
     * @param  mixed  $value
     * @return mixed
     */
    function value($value)
    {
        return $value instanceof Closure ? $value() : $value;
    }

```

そのまんまですね。引数がクロージャーでない場合はそのまま値が返ります。

## 次に `static::set` を理解する

```php:vendor/laravel/framework/src/Illuminate/Support/Arr.php

    /**
     * Set an array item to a given value using "dot" notation.
     *
     * If no key is given to the method, the entire array will be replaced.
     *
     * @param  array   $array
     * @param  string  $key
     * @param  mixed   $value
     * @return array
     */
    public static function set(&$array, $key, $value)
    {
        // 第一引数 $array は参照渡しであることに注目！

        // $key は null か？
        if (is_null($key)) {
　　　　　　　　　　　　　　　　　　　　　　　　　// null である場合は値($value)を返す
            return $array = $value;
        }

        // $key を「.」で分割した配列にする
        $keys = explode('.', $key);

        while (count($keys) > 1) {
            // $keys の先頭の要素を取り出す（このとき $keys は要素一つ分だけ短くなる）
            $key = array_shift($keys);

            // If the key doesn't exist at this depth, we will just create an empty array
            // to hold the next value, allowing us to create the arrays to hold final
            // values at the correct depth. Then we'll keep digging into the array.
            // もしこの深さにキーが存在しない場合、次の値を保管するために空の配列を作成します。
            // そうすることで再度の値を正しい深さに保存するための配列を作成することができます。
            // よし、どんどん掘ってくで。
            if (! isset($array[$key]) || ! is_array($array[$key])) {
                $array[$key] = [];
            }

            // 右辺はキーが存在する場合はその値、そうでない場合は上記で作成した空の配列
            // したがって左辺は一つ次の深さの値になる
            $array = &$array[$key];
        }

　　　　　　　　　　　　　　　　　// $keys の最後の一つの要素に対して、$value を代入する
        $array[array_shift($keys)] = $value;

        return $array;
    }

```

思ったより複雑でした。。。


# まとめ


```php:vendor/laravel/framework/src/Illuminate/Support/Arr.php

    /**
     * Add an element to an array using "dot" notation if it doesn't exist.
     *
     * @param  array   $array
     * @param  string  $key
     * @param  mixed   $value
     * @return array
     */
    public static function add($array, $key, $value)
    {
        if (is_null(static::get($array, $key))) {
            static::set($array, $key, $value);
        }

        return $array;
    }

```

1. `static::get($array, $key)` を実行する

・どんな場合に返り値は `null` になる？（ダジャレ？）
- `$array` が配列ではないとき
- `$array` のキーに `$key` が存在し、その値が `null` だったとき
- `$array` にキー `$key` が存在せず、かつ `$key` に「.」（dot）が含まれないとき
- `$key` に「.」（dot）が含まれるが、その `$key$` に対して適切な場所に配列が存在していない場合

これらの場合に `static::set($array, $key, $value);` が呼び出され、 `$array` に対して `$key => $value` がセットされます。

逆に、それら以外の場合には配列には変更がなく、そのまま `$array` が返ります。

# 感想

結構、内部的な処理が多いですね。ヘルパー関数とだけあって、いろいろなケースを想定されていることがわかりました。

特に 「.」(dot)の処理が気になります。

これは例えば以下のようなケースに対応するための処理です。

```
// [
//   'products' => 
//      [
//        'table' => ['price' => 500], 
//      ]
// ]

$array = Arr::add(['products' => ['table' => ['price' => 500]]], 'products.desk.price', 100);

// [
//   'products' => 
//      [
//        'table' => ['price' => 500], 
//        'desk'  => ['price' => 100],
//      ]
// ]

```

こういったケースに対応できるのは強みですね！便利だと感じます。

しかし、もし

```
$array = Arr::add(['name' => 'Desk'], 'price', 100);

// ['name' => 'Desk', 'price' => 100]
```

という状況を実現したいだけであれば、シンプルに以下のように書いたほうがよさそうです。

```
$array = ['name' => 'Desk'];
$array['price'] = 100;

// ['name' => 'Desk', 'price' => 100]
```

Laravelは便利ですが、何でもかんでも使えばいいというわけではないですね！