備忘録

PHPでCSVファイル出力

PHP

CSVファイル出力(open、close、write、download、UTF-16LE、BOM付き、ダブルコーテーション)をclassにまとめてみました

CSVファイル出力機能をいくつか作成したことがありますが、仕様が微妙に違っていたりして統一感がなかったので、1つのファイルにまとめてみました。

使い方(1)

ファイル名:sample1.csv、ファイル文字コード:SJIS-win、区切り文字:,(カンマ)、改行コード:\r\n、ダブルコーテーション:あり

require_once('csv.class.php');
$csv = new csv();

// 初期値設定(ファイル文字コード:SJIS-win、区切り文字:,(カンマ)、改行コード:\r\n、ダブルコーテーション:あり)
$csv->init();

// ファイルオープン(ファイル名:sample1.csv)
$csv->open('sample1.csv');

// ヘッダー項目出力
$csv->write(array('郵便番号','所在地','名称'));

// データ出力
$csv->write(array('zip' => '〒102-8688', 'address' => '東京都千代田区九段南1-2-1', 'name' => '千代田区役所'));
$csv->write(array('zip' => '〒104-8404', 'address' => '東京都中央区築地一丁目1番1号', 'name' => '中央区役所'));

// ファイルクローズ
$csv->close();

// ファイルダウンロード
$csv->download();

// ファイル削除
$csv->unlink();

// 結果 sample1.csv
// "郵便番号","所在地","名称"
// "〒102-8688","東京都千代田区九段南1-2-1","千代田区役所"
// "〒104-8404","東京都中央区築地一丁目1番1号","中央区役所"

使い方(2)

ファイル名:/home/work/sample2.csv、ファイル文字コード:UTF-16LE(BOM付き)、区切り文字:TAB(タブ)、改行コード:\n、ダブルコーテーション:なし

require_once('csv.class.php');
$csv = new csv();
try {
	// ファイル文字コード=UTF-16LE(BOM付き)
	if ($csv->setEncode('UTF-16LE', true) === false) {
		throw new Exception('setEncode');
	}

	// 区切り文字=TAB(タブ)
	if ($csv->setDelimiter("\t") === false) {
		throw new Exception('setDelimiter');
	}

	// 改行コード=\n
	if ($csv->setNewLine("\n") === false) {
		throw new Exception('setNewLine');
	}

	// ダブルコーテーション=なし
	if ($csv->setQuote(false) === false) {
		throw new Exception('setQuote');
	}

	// ファイルオープン ファイル名=/home/work/sample2.csv
	if ($csv->open('/home/work/sample2.csv') === false) {
		throw new Exception('open');
	}

	// ヘッダー項目出力
	if ($csv->write(array('郵便番号','所在地','名称')) === false) {
		throw new Exception('write');
	}

	$arr = array(
				array(
					'zip'     => '〒102-8688',
					'address' => '東京都千代田区九段南1-2-1',
					'name'    => '千代田区役所',
				),
				array(
					'zip'     => '〒104-8404',
					'address' => '東京都中央区築地一丁目1番1号',
					'name'    => '中央区役所',
				),
			);

	// データ出力
	foreach ($arr as $key=>$row) {
		//1行づつ出力
		if ($csv->write($row) === false) {
			throw new Exception('write');
		}
	}

	// ファイルクローズ
	if ($csv->close() === false) {
		throw new Exception('close');
	}

	// ファイルダウンロード
	if ($csv->download() === false) {
		throw new Exception('download');
	}

	// ファイル削除
	if ($csv->unlink() === false) {
		throw new Exception('unlink');
	}
} catch (Exception $e) {
	echo sprintf('ERROR=%s', $e->getMessage());
}

// 結果 sample2.csv
// 郵便番号	所在地	名称
// 〒102-8688	東京都千代田区九段南1-2-1	千代田区役所
// 〒104-8404	東京都中央区築地一丁目1番1号	中央区役所

設定

■初期値設定
init()
・下記内容を一括設定します
	文字コード			=	SJIS-win
	BOM					=	なし
	区切り文字			=	,(カンマ)
	改行コード			=	\r\n
	ダブルコーテーション=	あり


■ファイル文字コード設定
setEncode(文字コード, BOM)
・文字コード = SJIS SJIS-win EUC-JP eucJP-win UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE
・BOM        = false:BOMなし true:BOM付き
	文字コード と BOM の組み合わせ
		SJIS		BOMなし
		SJIS-win	BOMなし
		EUC-JP		BOMなし
		eucJP-win	BOMなし
		UTF-8		BOM付き or BOMなし
		UTF-16BE	BOM付き or BOMなし
		UTF-16LE	BOM付き or BOMなし
		UTF-32BE	BOM付き or BOMなし
		UTF-32LE	BOM付き or BOMなし


■ 区切り文字設定
setDelimiter(区切り文字)
・区切り文字 = ,(カンマ) \t(タブ) :(コロン) ;(セミコロン) |(パイプライン) 半角スペース


■改行コード設定
setNewLine(改行コード)
・改行コード = \r\n(Windows系) \n(UNIX系) \r(MacOS9以前)


■ダブルコーテーション設定
setQuote(ダブルコーテーション)
・ダブルコーテーション = false(設定なし) true(設定あり)

csv出力

csv.class.php

<?php
/**
 * csv出力
 * csv.class.php ver1.00
 **/
class csv {
	/**
	 * 変数
	 */
	protected $m_sCharSet          = 'UTF-8';//文字コード
	protected $m_sEncode           = 'SJIS-win';//ファイル出力の文字エンコーディング
	protected $m_sDelimiter        = ',';//区切り文字
	protected $m_sNewLine          = "\r\n";//改行コード
	protected $m_bQuote            = true;//ダブルクォーテーション
	protected $m_sBom              = null;//BOM
	protected $m_arrFile           = array();//ファイル(path=絶対パス、directory=ディレクトリ、file=ファイル名)
	protected $m_clsHdl            = null;//ファイルポインタ
	
	public function __construct() {
		$this->init();
	}
	public function __destruct() {
	}

	/**
	 * 初期値設定
	 * 引数:なし
	 * 戻値:結果
	 */
	public function init() {
		if ($this->m_clsHdl !== null) {
			return false;
		}
		$this->m_sEncode    = 'SJIS-win';//ファイル出力の文字エンコーディング
		$this->m_sDelimiter = ',';//区切り文字
		$this->m_sNewLine   = "\r\n";//改行コード
		$this->m_bQuote     = true;//ダブルクォーテーション
		$this->m_sBom       = null;//BOM
		$this->m_arrFile    = array();//ファイル(path=絶対パス、directory=ディレクトリ、file=ファイル名)
		$this->m_clsHdl     = null;//ファイルポインタ
		return true;
	}

	/**
	 * オープン
	 * 引数:ファイル名
	 * 戻値:結果
	 */
	public function open($p_sValue='') {
		if ($this->m_clsHdl !== null) {
			return false;
		}
		//ファイル名・ディレクトリ取得
		$this->m_arrFile = $this->_parseFile($p_sValue);
		if ($this->m_arrFile === false) {
			return false;
		}
		//書き込みでファイルをオープン
		$this->m_clsHdl = fopen($this->m_arrFile['path'], 'w');
		if ($this->m_clsHdl === false) {
			$this->m_arrFile = array();
			$this->m_clsHdl  = null;
			return false;
		}
		//BOM付きはファイルの先頭にBOM (Byte Order Mark) を付加
		if ($this->m_sBom !== null) {
			$bResult = fwrite($this->m_clsHdl, $this->m_sBom);
			if ($bResult === false) {
				$this->close();
				$this->m_arrFile = array();
				$this->m_clsHdl  = null;
				return false;
			}
		}
		return true;
	}

	/**
	 * クローズ
	 * 引数:なし
	 * 戻値:結果
	 */
	public function close() {
		if ($this->m_clsHdl === null) {
			return false;
		}
		//ファイルをクローズ
		$bResult = fclose($this->m_clsHdl);
		if ($bResult === false) {
			return false;
		}
		$this->m_clsHdl = null;
		return true;
	}

	/**
	 * 出力
	 * 引数:出力データ(1行単位)
	 * 戻値:結果
	 */
	public function write($p_arrValue) {
		if ($this->m_clsHdl === null) {
			return false;
		}
		//配列を文字列に変換
		$arrWork = array();
		$sCsv    = '';
		if (is_array($p_arrValue) === true) {
			foreach ($p_arrValue as $key=>$row) {
				$arrWork[] = $this->_value($row);
			}
		} else {
			$arrWork[] = $this->_quote($p_arrValue);
		}
		$sCsv = implode($this->m_sDelimiter, $arrWork).$this->m_sNewLine;
		//文字エンコーディングを変換
		if ($this->m_sCharSet !== $this->m_sEncode) {
			$sCsv = mb_convert_encoding($sCsv, $this->m_sEncode, $this->m_sCharSet);
		}
		//ファイルへ出力
		$bResult = fwrite($this->m_clsHdl, $sCsv);
		if ($bResult === false) {
			return false;
		}
		return true;
	}

	/**
	 * ダウンロード
	 * 引数:なし
	 * 戻値:結果
	 */
	public function download() {
		if (is_file($this->m_arrFile['path']) === false) {
			return false;
		}
		header('Content-Type: application/octet-stream');
		header('Content-Disposition: attachment; filename="'.$this->m_arrFile['file'].'"');
		header('Content-Length: '.filesize($this->m_arrFile['path']));
		ob_end_clean();
		readfile($this->m_arrFile['path']);
		return true;
	}

	/**
	 * 削除
	 * 引数:なし
	 * 戻値:結果
	 */
	public function unlink() {
		if ($this->m_clsHdl !== null) {
			return false;
		}
		if (is_file($this->m_arrFile['path']) === false) {
			return false;
		}
		//ファイルの削除
		$bResult = @unlink($this->m_arrFile['path']);
		if ($bResult === false) {
			return false;
		}
		return true;
	}

	/**
	 * ファイル名・ディレクトリパスを取得
	 * 引数:ファイル名
	 * 戻値:結果
	 */
	protected function _parseFile($p_sValue='') {
		if ((strpos($p_sValue, '/') !== false) && (strpos($p_sValue, '\\') !== false)) {
			return false;
		}
		$sDirName  = '';
		$sFileName = '';
		if (strlen($p_sValue) > 0) {
			$sFileName = $p_sValue;
			$nPos = strrpos($p_sValue, '\\');
			if ($nPos !== false) {
				$sDirName  = substr($p_sValue, 0, $nPos + 1);
				$sFileName = substr($p_sValue, $nPos + 1);
			} else {
				$nPos = strrpos($p_sValue, '/');
				if ($nPos !== false) {
					$sDirName  = substr($p_sValue, 0, $nPos + 1);
					$sFileName = substr($p_sValue, $nPos + 1);
				}
			}// if ($nPos !== false)
		}// if (strlen($p_sValue) > 0)
		if (strlen($sDirName) === 0) {
			$sDirName = dirname(__FILE__).DIRECTORY_SEPARATOR;
		}
		if (strlen($sFileName) === 0) {
			$sFileName = sprintf('%s.csv', date('YmdHis'));
		}
		if ((is_dir($sDirName) === false) || ($this->_chkFileName($sFileName) === false)) {
			return false;
		}
		return array(
			'path'      => $sDirName.$sFileName,
			'directory' => $sDirName,
			'file'      => $sFileName
		);
	}

	/**
	 * ファイル名チェック
	 * 引数:ファイル名
	 * 戻値:結果
	 */
	protected function _chkFileName($p_sValue) {
		//2バイト文字はエラーとする
		if (strlen($p_sValue) !== mb_strlen($p_sValue, $this->m_sCharSet)) {
			return false;
		}
		//50文字以内に制限
		if ((strlen($p_sValue) < 1) || (50 < strlen($p_sValue))) {
			return false;
		}
		//先頭・末尾がスペースはエラー
		$sPattern = "#^ | $#";
		if (preg_match($sPattern, $p_sValue) === 1) {
			return false;
		}
		//制御文字は使用不可
		$sPattern = '#[\x00-\x1f\x7f]#';
		if (preg_match($sPattern, $p_sValue) === 1) {
			return false;
		}
		//ハードウェア名は使用不可
		$sPattern = '#^(AUX|COM[0-9]|CON|LPT[0-9]|NUL|PRN)(\.|$)#i';
		if (preg_match($sPattern, $p_sValue) === 1) {
			return false;
		}
		// \ / : * ? " < > | は使用不可
		$sPattern = '#[\\\/:*?"<>|]#';
		if (preg_match($sPattern, $p_sValue) === 1) {
			return false;
		}
		// 「.」「..」 又は、先頭・末尾が「.」 はエラー
		if (($p_sValue === '.') || ($p_sValue === '..') || (substr($p_sValue, 0, 1) === '.') || (substr($p_sValue, -1) === '.')) {
			return false;
		}
		return true;
	}

	/**
	 * ダブルクォーテーション
	 * 引数:文字列
	 * 戻値:変換後文字列
	 */
	protected function _quote($p_sValue) {
		if ($this->m_bQuote === true) {
			$p_sValue = '"'.preg_replace('#(?<!\\\\)\"#', '""', $p_sValue).'"';
		}
		return $p_sValue;
	}

	/**
	 * 多次元配列を文字列に変換
	 * 引数:配列
	 * 戻値:文字列
	 */
	protected function _value($arrValue) {
		$arrWork = array();
		if (is_array($arrValue) === true) {
			foreach ($arrValue as $key=>$row) {
				if (is_array($row) === true) {
					$arrWork[] = $this->_value($row);
				} else {
					$arrWork[] = $this->_quote($row);
				}
			}
		} else {
			$arrWork[] = $this->_quote($arrValue);
		}
		return implode($this->m_sDelimiter, $arrWork);
	}

	/**
	 * 変換後の文字エンコーディング設定
	 * 引数:エンコーディング
	 * 引数:BOMの設定(false:BOMなし true:BOM付き)
	 * 戻値:結果
	 */
	public function setEncode($p_sValue, $p_bBom=false) {
		if ($this->m_clsHdl !== null) {
			return false;
		}
		$arrEncoding = array(
			array('encode' => 'SJIS',      'bom' => null),
			array('encode' => 'SJIS-win',  'bom' => null),
			array('encode' => 'EUC-JP',    'bom' => null),
			array('encode' => 'eucJP-win', 'bom' => null),
			array('encode' => 'UTF-8',     'bom' => pack('C*', 0xEF, 0xBB, 0xBF)),
			array('encode' => 'UTF-16BE',  'bom' => pack('C*', 0xFE, 0xFF)),
			array('encode' => 'UTF-16LE',  'bom' => pack('C*', 0xFF, 0xFE)),
			array('encode' => 'UTF-32BE',  'bom' => pack('C*', 0x00, 0x00, 0xFE, 0xFF)),
			array('encode' => 'UTF-32LE',  'bom' => pack('C*', 0xFF, 0xFE, 0x00, 0x00)),
		);
		$bChk = false;
		foreach ($arrEncoding as $key=>$row) {
			if (strtolower($row['encode']) === strtolower($p_sValue)) {
				$bChk = true;
				$this->m_sEncode = $row['encode'];
				if ($p_bBom === true) {
					if ($row['bom'] !== null) {
						$this->m_sBom = $row['bom'];
					} else {
						$bChk = false;
					}
				} else if ($p_bBom === false) {
					$this->m_sBom = null;
				} else {
					$this->m_sBom = null;
					$bChk = false;
				}
				break;
			}// if (strtolower($row['encode']) === 
		}// foreach ($arrEncoding as $key=>$row)
		return $bChk;
	}

	/**
	 * 区切り文字設定
	 * 引数:区切り文字
	 * 戻値:結果
	 */
	public function setDelimiter($p_sValue) {
		if ($this->m_clsHdl !== null) {
			return false;
		}
		$arrDelimiter = array(',', "\t", ':', ';', '|', ' ');
		$bChk = false;
		foreach ($arrDelimiter as $key=>$row) {
			if ($row === $p_sValue) {
				$bChk = true;
				$this->m_sDelimiter = $row;
				break;
			}
		}
		return $bChk;
	}

	/**
	 * 改行コード設定
	 * 引数:改行コード
	 * 戻値:結果
	 */
	public function setNewLine($p_sValue) {
		if ($this->m_clsHdl !== null) {
			return false;
		}
		$arrNewLine = array("\r\n", "\n", "\r");
		$bChk = false;
		foreach ($arrNewLine as $key=>$row) {
			if ($row === $p_sValue) {
				$bChk = true;
				$this->m_sNewLine = $row;
				break;
			}
		}
		return $bChk;
	}

	/**
	 * ダブルクォーテーション設定
	 * 引数:ダブルクォーテーションの設定(false:なし true:あり)
	 */
	public function setQuote($p_bQuote) {
		$bChk = false;
		if ($p_bQuote === true) {
			$bChk           = true;
			$this->m_bQuote = true;
		} else if ($p_bQuote === false) {
			$bChk           = true;
			$this->m_bQuote = false;
		}
		return $bChk;
	}

}
?>