PHPでコレクション操作する時に使っておきたい関数

JavaScriptを書いた後にPHPを書こうとするとUnderscore.jsやlodashのような簡潔な配列操作がしたいのにPHPだとよく知らないというケースが個人的によくあります。PHPの勉強不足ですね。

たとえばJavaScriptだと、こんな操作です。

const foundItem = _.find( items, (item) => {
  reuturn item.score === 2
})

闇雲にforeachを使ってしまわないために、個人的な備忘録としてメモしておきたいと思います。

配列から要素を見つける(find)

array_searchという関数を利用します。返り値は要素ではなくキーです。

単純な値の配列の場合

$array = [1, 2, 3, 4];
$idx = array_search( 3, $array );
echo $idx; // 2

連想配列の配列の場合

$array = [['score' => 1], ['score' => 2], ['score' => 3], ['score' => 4]];
$idx = array_search( 3, array_column( $array, 'score' ) );
echo $idx; // 2

さらに連想配列のネストが深くなるとarray_columnを多重で呼ぶ事になるでしょうか。ちょっと嫌ですね。。Underscore.jsと同じようにcallableを使うんじゃだめだったんでしょうか…。

配列の中身を加工する(map)

そのまんまarray_mapという関数があります。

$items = [['score' => 1], ['score' => 2], ['score' => 3], ['score' => 4]];
$result = array_map( function( $item ) {
        return $item['score'];
}, $items );
echo var_export( $result, true ); // [ 1, 2, 3, 4 ]

似たものでarray_walkarray_walk_recursiveという関数もあります。

配列の中身を再帰的に処理する(reduce)

そのまんまarray_reduceという関数があります。

$items = [['score' => 1], ['score' => 2], ['score' => 3], ['score' => 4]];
$result = array_reduce( $items, function( $carry, $item ) {
        return $carry + $item['score'];
});
echo var_export( $result, true ); // 10

reduceが使えるのは便利ですね。ただ、なぜかmapと引数の順番が逆ですね。なぜ…!?

配列を要素の値で絞り込む(filter)

そのまんまarray_filterという関数があります。

$items = [['score' => 1], ['score' => 2], ['score' => 3], ['score' => 4]];
$result = array_filter( $items, function( $item ) {
        return $item['score'] % 2 === 0;
});
echo var_export( $result, true ); // [ [ 'score' => 2 ], [ 'score' => 4 ] ]

これを応用して空要素を除外するのに下記のように記述することもできます。

配列の空要素を削除するときのarray_filterの働き

$result = [ 1, 2, '', 3, '', 4, 5 ];
$result = array_filter($result, 'strlen'); // [ 1, 2, 3, 4, 5 ]

配列から要素が存在するか確認する(contains)

in_arrayという関数があります。

$array = [ 1, 2, 3, 4 ];
if ( in_array( 3, $array ) ) {
        echo 'contains'; // contains
}

配列から重複した値を削除する(uniq)

array_uniqueという関数があります。

$array = [ 1, 2, 3, 2 ];
$array = array_unique( $array );
echo var_export( $array, true ); // [ 1, 2, 3 ]

配列から要素を抽出する(pluck)

array_searchのところでも出てきましたが、array_columnという関数を使います。

$items = [ [ 'score' => 1 ], [ 'score' => 2 ], [ 'score' => 3 ] ];
$scores = array_column( $items, 'score' );
echo var_export( $scores, true ); // [ 1, 2, 3 ]

連想配列からキーを抽出する(keys)

array_keysという関数を利用します。第2引数で該当する値を指定する事ができます。

$array = array(0 => 100, "color" => "red");
print_r(array_keys($array)); // [ 0, 'color' ]

$array = array("blue", "red", "green", "blue", "blue");
print_r(array_keys($array, "blue")); // [ 0, 3, 4 ]

連想配列から値を抽出する(values)

array_valuesという関数を使います。

$array = array("size" => "XL", "color" => "gold");
print_r(array_values($array)); // [ 'XL', 'gold' ]

連想配列からキーの存在を確認する(has)

配列の場合、array_key_existsという関数を利用します。

$search_array = array('first' => 1, 'second' => 4);
if (array_key_exists('first', $search_array)) {
    echo "この配列には 'first' という要素が存在します";
}

ちなみにこの関数はissetと同じように思えますが、実はissetは値がNULLだとfalseを返すようです。このarray_key_existsは値がNULLでもキーが存在していさえいればtrueを返します。

オブジェクトの場合はproperty_existsという関数を利用します。

callableを使う場合の注意

JavaScriptを使うと関数を関数に渡しますが、PHPだとあまり一般的ではありません。特にクロージャに関してはJavaScriptと文法上も違いがあり関数外の変数に関しては明示的にuseを指定する必要があります。

ですので、下記の例で言うと、$avg_scoreはarray_walkに渡した関数の中では参照できないため以下は正しく動作しません。

$avg_score = 2;

$items = [ [ 'score' => 1 ], [ 'score' => 2 ], [ 'score' => 3 ] ];
array_walk( $items, function ( &$item ) {
        print_r( "avg $avg_score" );
        $item['superior'] = $item['score'] > $avg_score;
} );

print_r( $items );

正しくは下記のようになります。

$avg_score = 2;

$items = [ [ 'score' => 1 ], [ 'score' => 2 ], [ 'score' => 3 ] ];
array_walk( $items, function ( &$item ) use ( $avg_score ) {
        print_r( "avg $avg_score" );
        $item['superior'] = $item['score'] > $avg_score;
} );

print_r( $items );

JavaScriptと同じ感覚だと痛い目を見そうですので、この点は特に注意しましょう。


ちなみにPHPでもライブラリでUnderscore.jsを意識したライブラリがいくつかあるようです。

PHPのコレクション処理ライブラリUnderbar.phpの紹介

Underscore.js系
Underscore.php (by brianhaveri)
Underscore.php (by anahkiasen)
Underscore-Walla-Walla.php

LINQ系
PHPLinq
phinq
pinq
Ginq

PHPの組み込み関数でも対応はできますが、やはり使い勝手が悪いと言われてるところもありますので、ガチPHPなプロジェクトではこのようなライブラリを利用しても良いかもしれませんね。逆にWordPressプラグイン等の軽量なプログラムの場合は組み込みの関数を利用しておくと良いと思います。

追記

関連してとても役立つ記事を見つけました。

Porting Clojure functions to PHP for Better Functional Programming