「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]
関数の実態
Laravelの Arr
ヘルパ関数は以下のディレクトリに実態が記載されています。
/**
* 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
)を返す。
/**
* 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
/**
* 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
/**
* 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
したがって Arr::add()
で呼ばれている value($default);
はそのまま null
を返すことになります。
なお、 value()
の実態は以下の部分に定義されています。
/**
* Return the default value of the given value.
*
* @param mixed $value
* @return mixed
*/
function value($value)
{
return $value instanceof Closure ? $value() : $value;
}
そのまんまですね。引数がクロージャーでない場合はそのまま値が返ります。
次に static::set
を理解する
/**
* 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;
}
思ったより複雑でした。。。
まとめ
/**
* 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($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は便利ですが、何でもかんでも使えばいいというわけではないですね!