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?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-07-02

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

環境
Laravel : 5.8

Arr::add()

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


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

関数の実態

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

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)を返す。

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

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インタフェースについて詳しく知りたい方はこちらが参考になります。

static::exists()

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

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

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

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

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

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 を理解する

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;
    }

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

まとめ

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は便利ですが、何でもかんでも使えばいいというわけではないですね!

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?