PHP – 配列の最後の要素の判定にend()は使わない方がいい話

配列をforeachで回して、CSVを作成したり、SQL文を作成したいときに、最後の要素だけ、改行をつけたくなかったりとか、カンマをつけたくなかったりとかありますよね。
そんな時にググったりするとend()関数が見つかって、こいつで出来るかも、と思うかもしれませんが、罠があります。
というのもend()は正確には「配列の最後の要素の値を返す」という機能なので・・・

具体的に説明していきます。

最後の要素の判定にend()を使ってうまくいくパターン

例えば、以下のような場合。

$array = [0 => "aaa", 
    1 => "bbb", 
    2 => "ccc", 
    3 => "ddd", 
    4 => "eee", 
    5 => "fff"];

foreach($array as $k=>$v){
    if($v != end($array)){
        print "${k}=>${v} まだ最後じゃないよ\n";
    }else{
        print "${k}=>${v} これが最後だよ\n";
    }
}

実行結果は以下のようになります。

0=>aaa まだ最後じゃないよ
1=>bbb まだ最後じゃないよ
2=>ccc まだ最後じゃないよ
3=>ddd まだ最後じゃないよ
4=>eee まだ最後じゃないよ
5=>fff これが最後だよ

一見うまくいっているように見えます。

最後の要素の判定にend()を使ってうまくいかないパターン

もうお気づきかもしれませんが、end()は「配列の最後の要素の値を返す」ので、最初のパターンでは最後の要素の値、”fff”が返ってきていました。
が、配列要素内に重複する値が入っていると想定しない動きになってしまいます。
例えば下のようなパターン

$array = [0 => "aaa", 
    1 => "bbb", 
    2 => "ccc", 
    3 => "bbb", 
    4 => "ddd", 
    5 => "ccc"];

foreach($array as $k=>$v){
    if($v != end($array)){
        print "${k}=>${v} まだ最後じゃないよ\n";
    }else{
        print "${k}=>${v} これが最後だよ\n";
    }
}

この場合、下のようになってしまいます。

0=>aaa まだ最後じゃないよ
1=>bbb まだ最後じゃないよ
2=>ccc これが最後だよ
3=>bbb まだ最後じゃないよ
4=>ddd まだ最後じゃないよ
5=>ccc これが最後だよ

今度は最後の要素が”ccc”だったので、途中のkeyが2の要素の箇所でも引っ掛かってしまいますね・・・

素直にcount()を使う

回避するには素直にカウント用の変数を用意して、arrayをcount()するのがいいのかなーと思います。

さっきの例だとこんな感じ

$array = [0 => "aaa", 
    1 => "bbb", 
    2 => "ccc", 
    3 => "bbb", 
    4 => "ddd", 
    5 => "ccc"];

$cnt = 1;
$length = count($array);

foreach($array as $k=>$v){
    if($cnt < $length){
        print "${k}=>${v} まだ最後じゃないよ\n";
    }else{
        print "${k}=>${v} これが最後だよ\n";
    }
    $cnt++;
}

実行結果は↓になります

0=>aaa まだ最後じゃないよ
1=>bbb まだ最後じゃないよ
2=>ccc まだ最後じゃないよ
3=>bbb まだ最後じゃないよ
4=>ddd まだ最後じゃないよ
5=>ccc これが最後だよ

4 件のコメント

  1. ピンバック: いろいろな言語で配列の最後の要素 | IT技術情報局

  2. ピンバック: PHPで配列の最後の値を取得する | IT技術情報局

  3. 通りすがりのエンジニア 返信

    少し古い記事にコメントで失礼します。
    PHPを勉強中のエンジニアで
    配列の最後の要素を取得する方法を調べてる際にこちらの記事に流れ着きました。

    この記事を参考にさせて頂いていたのですが、end の「最後の要素の判定」自体には、「使ってうまくいかないパターン」でも成功しているように思えてきました。
    しっかり、$array[5] == end($array)が成り立っていると思いますが…。

    if($v != end($array)) という、if文の書き方に問題があるだけでは?

    たとえば、もっとストレートに
    if($v != $array[5])
    と書いても同様の問題は発生しますよね?
    そうすると、これはendの問題では無いはずです。

    endを使うことに罠がある、という事ではないように感じてしまったのですが、如何でしょうか?

  4. mobkin 投稿者返信

    コメントありがとうございます!
    お恥ずかしい記事で大変恐縮です・・

    ご指摘の通り、配列の最後の要素を取得するのにend()使うのは全く問題ないと考えます。

    今回の記事で自身がやりたかったことは、

    ・配列には何個、どんなデータが入っているか分からない前提($array[5]のように直接指定は出来ない)
    ・配列の最後だけ処理を分岐したい

    というロジックを実装したかった、という意図がありました。

    例えば、CSVのデータを作成したいとします。
    例の配列aaa~fffを1行に出力する時に最後のfffだけカンマではなく改行にしたい、
    といったパターンです。

    aaa,bbb,ccc,ddd,eee,fff\n

    といったデータですね。これを作るのに

    $csv_data = ”;
    foreach($array as $k=>$v){
    if($v != end($array)){
    $csv_data .= $v.”,”;
    }else{
    $csv_data .= $v.”\n”;
    }
    }

    みたいなことがやりたかったわけです。

    ただ、上記のif分岐、最後の要素判定にend($array)を使ってしまうと、配列の最後のデータと全く同じ要素が途中で出てきてしまった場合、最後の要素と同じくカンマじゃなくて改行にっちゃうよ、というだけの話でした・・・

    「使ってうまくいかないパターン」でやってみると

    aaa,bbb,ccc\nbbb,ddd,ccc\n

    となってしまいます、という感じです。

    今ではCSVのようなデータを作りたい時は、上述のまどろっこしいやり方はそもそもせずに、

    $csv_data = implode(“,”, $array).”\n”;

    と書くと思います・・・( ノД`)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です