PHP CSV 処理のスニペット集

このページは、PHP の CSV 処理のスニペットなどをまとめる予定のページです。

目次

注意

  • コードのライセンスは CC0 (クレジット表示不要、改変可、商用可) です。

スニペット

CSV 読み込み (Shift_JIS)

$f = fopen('test.csv', 'r'); // データ取得
stream_filter_prepend($f, 'convert.iconv.cp932/utf-8'); // Shift_JIS のファイル読み込み用に変換フィルターをセット
$locale = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'C'); // fgetcsv() がロケールの影響を受けないよう C ロケールに変更
while (($row = fgetcsv($f)) !== false) { // CSV読み込み
    print_r($row); // TODO ここで必要な処理を行います。
}
setlocale(LC_CTYPE, $locale); // ロケールを戻す
fclose($f);

CSV 書き込み (Shift_JIS)

$data = array(
    array( 'id' => 1, 'name' => '山田', 'title' => '山田記事1'),
    array( 'id' => 2, 'name' => '鈴木', 'title' => '鈴木記事2'),
    array( 'id' => 3, 'name' => '東京', 'title' => '東京記事3'),
);

$f = fopen('test.csv', 'w');
stream_filter_prepend($f, 'convert.iconv.utf-8/cp932');
foreach ($data as $row) {
    fputcsv($f, $row);
}
fclose($f);

CSV ダウンロード (Shift_JIS)

$data = array(
    array( 'id' => 1, 'name' => '山田', 'title' => '山田記事1'),
    array( 'id' => 2, 'name' => '鈴木', 'title' => '鈴木記事2'),
    array( 'id' => 3, 'name' => '東京', 'title' => '東京記事3'),
);
$name = 'テスト.csv';

header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($name));
header('Cache-Control: no-store');

$f = fopen('php://output', 'w');
stream_filter_prepend($f, 'convert.iconv.utf-8/cp932');
foreach ($data as $row) {
    fputcsv($f, $row);
}
fclose($f);

ユーティリティ関数

(主にフレームワークなどを使う必要が無い小さなプログラム用の関数です)

CSV 読み込み

Shift_JIS

/**
 * Shift_JIS の CSV を読み込みます。(読み込んだデータは UTF-8 として扱います)
 * @param string $filename ファイルパス (fopen() で処理可能なパス)
 * @return array|false 成功した場合 CSV の行配列、失敗した場合 false を返します。(E_WARNING なども発生します)
 */
function csv_read_sjis($filename) {
    $data = array();
    $f = fopen($filename, 'rb');
    if ($f === false) return false;

    stream_filter_prepend($f, 'convert.iconv.cp932/utf-8');
    $locale = setlocale(LC_CTYPE, 0);
    setlocale(LC_CTYPE, 'C'); // fgetcsv() がロケールの影響を受けないよう C ロケールに変更
    while (($row = fgetcsv($f)) !== false) $data[] = $row;
    setlocale(LC_CTYPE, $locale); // ロケールを戻す
    fclose($f);
    return $data;
}

UTF-8

/**
 * UTF-8 の CSV を読み込みます。
 * @param string $filename ファイルパス (fopen() で処理可能なパス)
 * @return array|false 成功した場合 CSV の行配列、失敗した場合 false を返します。(E_WARNING なども発生します)
 */
function csv_read($filename) {
    $data = array();
    $f = fopen($filename, 'rb');
    if ($f === false) return false;

    $locale = setlocale(LC_CTYPE, 0);
    setlocale(LC_CTYPE, 'C'); // fgetcsv() がロケールの影響を受けないよう C ロケールに変更
    while (($row = fgetcsv($f)) !== false) $data[] = $row;
    setlocale(LC_CTYPE, $locale); // ロケールを戻す
    fclose($f);
    return $data;
}

CSV 書き込み (一括)

/**
 * 連想配列の配列を CSV 出力します。
 *
 * @param resource $filename ファイルパス (fopen() で処理可能なパス)
 * @param array $data 連想配列の配列
 * @param array $options 出力設定
 * <pre>
 *     array columnDefs:     (必須) 列情報
 *         string name:            (任意) データ行の連想配列のキー
 *         string headerText:      (任意) ヘッダ行に出力する見出しの名前
 *         string groupHeaderText: (任意) headerTextの上に出力する見出しの名前 (もし1つでもこの項目の設定があれば、見出し行を計2行にする)
 *         bool main:              (任意) 主要列か (true/false。未指定時は false) 。主要列の場合、前の行の値が同じ値だったときに値を空欄にします。
 *     string delimiter:  (任意) 値を区切る文字 (',' など。未指定時はfputcsv()の既定値)
 *     string enclosure:  (任意) 値を囲む文字 ('"' など。未指定時はfputcsv()の既定値)
 *     string escapeChar: (任意) 特殊文字をエスケープする文字 ("\\" など。未指定時はfputcsv()の既定値)
 *     string newLine:    (任意) 改行コード (\n または \r\n)
 *     string encoding:   (任意) 出力する文字エンコーディング (cp932 など iconv の文字エンコーディング名)
 * </pre>
 * @return void
 */
function csv_write($filename, $data, $options = array()) {
    $f = fopen($filename, 'wb');
    if ($f === false) return false;

    $columnDefs = isset($options['columnDefs']) ? $options['columnDefs'] : array();
    $delimiter = isset($options['delimiter']) ? $options['delimiter'] : ',';
    $enclosure = isset($options['enclosure']) ? $options['enclosure'] :'"';
    $escapeChar = isset($options['escapeChar']) ? $options['escapeChar'] :"\\";

    $newLine = isset($options['newLine']) ? $options['newLine'] : null;
    if ($newLine == "\r\n") {
        stream_filter_append($f, 'csv_write_crlf');
    }

    $encoding = isset($options['encoding']) ? $options['encoding'] : null;
    if ($encoding) {
        stream_filter_append($f, 'convert.iconv.utf-8/' . $encoding);
    }

    // グループヘッダ行
    $groupHeaderRow = array();
    $groupHeaderRowRequired = false; // グループヘッダ出力必要か
    foreach ($columnDefs as $col) {
        $text = isset($col['groupHeaderText']) ? $col['groupHeaderText'] : '';
        if (!$groupHeaderRowRequired && strlen($text) > 0) $groupHeaderRowRequired = true;
        $groupHeaderRow[] = $text;
    }
    if ($groupHeaderRowRequired) {
        fputcsv($f, $groupHeaderRow, $delimiter, $enclosure, $escapeChar);
    }

    // ヘッダ行
    $headerRow = array();
    $headerRowRequired = false; // ヘッダ出力必要か
    foreach ($columnDefs as $col) {
        $text = isset($col['headerText']) ? $col['headerText'] : '';
        if (!$headerRowRequired && strlen($text) > 0) $headerRowRequired = true;
        $headerRow[] = $text;
    }
    if ($headerRowRequired) {
        fputcsv($f, $headerRow, $delimiter, $enclosure, $escapeChar);
    }

    if ($columnDefs) {
        $lastMain = array();
        foreach ($data as $row) {
            $outputRow = array();
            foreach ($columnDefs as $index => $col) {
                $name = $col['name'];
                $v = isset($row[$name]) ? $row[$name] : '';
                $main = isset($col['main']) && $col['main'];
    
                // 主要行の場合、前の値と同じであれば空で出力
                if ($main) {
                    $lastMainValue = isset($lastMain[$name]) ? $lastMain[$name] : '';
                    if ($v === $lastMainValue) {
                        $v = '';
                    }
                    else {
                        $lastMain[$name] = $v;
                    }
                }
    
                // 取得できた値を出力行配列に追加
                if (count($outputRow) - 1 != $index) {
                    for ($i = count($outputRow); $i < $index; $i++) {
                        $outputRow[] = '';
                    }
                }
                $outputRow[] = $v;
            }
            fputcsv($f, $outputRow, $delimiter, $enclosure, $escapeChar);
        }
    }
    else {
        foreach ($data as $row) {
            fputcsv($f, $row, $delimiter, $enclosure, $escapeChar);
        }
    }
    fclose($f);
}
// https://stackoverflow.com/questions/12722894/how-can-i-change-the-line-endings-used-by-fputcsv
class crlf_filter extends php_user_filter
{
    function filter($in, $out, &$consumed, $closing)
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            // make sure the line endings aren't already CRLF
            $bucket->data = preg_replace("/(?<!\r)\n/", "\r\n", $bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('csv_write_crlf', 'crlf_filter');
使用例
// UTF-8 + LF で下記を出力
//
// 1,山田,山田記事1
// 1,山田,山田記事2
// 1,山田,山田記事3
// 1,山田,山田記事4
// 1,山田,山田記事5
// 2,鈴木,鈴木記事1
// 2,鈴木,鈴木記事2
// 3,東京,東京記事1
// 3,東京,東京記事2
// 3,東京,東京記事3
csv_write('test1.csv', $data);

// UTF-8 + LF で下記を出力
//
// ID,名前,タイトル
// 1,山田,山田記事1
// 1,山田,山田記事2
// 1,山田,山田記事3
// 1,山田,山田記事4
// 1,山田,山田記事5
// 2,鈴木,鈴木記事1
// 2,鈴木,鈴木記事2
// 3,東京,東京記事1
// 3,東京,東京記事2
// 3,東京,東京記事3
csv_write('test2.csv', $data, array(
    'columnDefs' => array(
        array( 'name' => 'id', 'headerText' => 'ID' ),
        array( 'name' => 'name', 'headerText' => '名前' ),
        array( 'name' => 'title', 'headerText' => 'タイトル' ),
    ),
));

// Shift_JIS + CRLF で下記を出力
//
// ID,名前,タイトル
// 1,山田,山田記事1
// ,,山田記事2
// ,,山田記事3
// ,,山田記事4
// ,,山田記事5
// 2,鈴木,鈴木記事1
// ,,鈴木記事2
// 3,東京,東京記事1
// ,,東京記事2
// ,,東京記事3
csv_write('test3.csv', $data, array(
    'columnDefs' => array(
        array( 'name' => 'id', 'headerText' => 'ID', 'main' => true ),
        array( 'name' => 'name', 'headerText' => '名前', 'main' => true ),
        array( 'name' => 'title', 'headerText' => 'タイトル', ),
    ),
    'encoding' => 'cp932',
    'newLine' => "\r\n"
));

// UTF-8 + LF で下記を出力
//
// ユーザー,,投稿
// ID,名前,タイトル
// 1,山田,山田記事1
// 1,山田,山田記事2
// 1,山田,山田記事3
// 1,山田,山田記事4
// 1,山田,山田記事5
// 2,鈴木,鈴木記事1
// 2,鈴木,鈴木記事2
// 3,東京,東京記事1
// 3,東京,東京記事2
// 3,東京,東京記事3
csv_write('test4.csv', $data, array(
    'columnDefs' => array(
        array( 'name' => 'id', 'headerText' => 'ID', 'groupHeaderText' => 'ユーザー' ),
        array( 'name' => 'name', 'headerText' => '名前' ),
        array( 'name' => 'title', 'headerText' => 'タイトル', 'groupHeaderText' => '投稿' ),
    ),
));