大きなループでの文字列連結、.=の積み重ねをどう避けるか
大きなループでの文字列連結は、.= を積み重ねるより、断片をいったん配列に貯めて最後に implode でまとめて繋ぐほうが、見通しよく軽く書ける、という書き方です。
.=を大量に回すと何が起きるか
ループのなかで $out .= $line; を何万回も回す書き方、動くには動くのですが、行数が増えてくると引っかかりを感じることがあります。連結のたびに文字列が育っていくので、その分の処理が積み重なるわけですね。数十行くらいなら誤差ですが、CSVを行単位で組み立てるような処理だと差が見えてきます。
$out = '';
foreach ($rows as $row) {
$out .= implode(',', $row) . "\n"; // 毎回 $out が伸びていく
}
いったん配列に貯めてimplodeで一括連結
私がよくやるのは、断片を配列に push しておいて最後に implode で一気につなぐやり方です。連結処理が一回にまとまるので見通しもよく、区切り文字の扱いも implode に任せられて楽です。末尾に余計な区切りが付かないのも地味に嬉しいところですね。
$lines = [];
foreach ($rows as $row) {
$lines[] = implode(',', $row);
}
$out = implode("\n", $lines);
「区切り文字を要素の間だけに入れる」という処理を自分で書くと、先頭や末尾の余りをどう消すかで地味に悩みます。implode はそこをきれいに片付けてくれるので、連結ロジックそのものを持たなくて済むのがいいと思います。
本当に大きいならストリームに書き出す
そもそも全部をメモリ上の一本の文字列にまとめる必要があるのか、という視点も持っておきたいですね。最終的にファイルやレスポンスに流すだけなら、貯めずに逐次書き出したほうがメモリに優しいことが多いです。
$fp = fopen('php://output', 'w');
foreach ($rows as $row) {
fputcsv($fp, $row); // 貯めずにその場で書き出す
}
fclose($fp);
まとめ
小さいループなら .= のままで何も問題ないと思います。ただ件数が読めない、あるいは明らかに大量、という場面では「配列に貯めて implode」か「逐次書き出し」を思い出すと安心です。書き味も implode のほうがすっきりすることが多いので、私は連結が数行を超えたらまず配列にする癖をつけています。
よくある質問
Q. 数行程度の連結でも配列にすべきですか?
A. 小さいループなら .= のままで何も問題ありません。件数が読めない、あるいは明らかに大量、という場面だけ配列や逐次書き出しを思い出せば十分だと思います。
Q. 最終的にファイルへ出すだけでも配列に貯めるべきですか?
A. その場合は貯めずに fputcsv などで逐次書き出すほうがメモリに優しいことが多いです。一本の文字列にまとめる必要が本当にあるか、を先に考えるといいですね。