CakePHP CSVファイルをエクスポート→Excelで編集→そのままインポートする方法

CakePhP1.3系の記事です。2.x系には非対応となっております。

目的

・CSVファイルをエクスポート、インポートする。
・エクスポートしたCSVファイルを、Excelなどで編集した後、そのままインポートしてデータを更新できるようにする。

サンプルデータ

Entry というMVCモデルを使うことにします。

entries テーブルの構造

エクスポート

CSVヘルパーの作成

CSV Helper (PHP 5)

app/views/helpers/ フォルダ内に、「csv.php」という名前のファイルを作成。
以下コードをコピペします。

<?php
class CsvHelper extends AppHelper {

    var $delimiter = ',';
    var $enclosure = '"';
    var $filename = 'Export.csv';
    var $line = array();
    var $buffer;

    function CsvHelper() {
        $this->clear();
    }

    function clear() {
        $this->line = array();
        $this->buffer = fopen('php://temp/maxmemory:'. (5*1024*1024), 'r+');
    }

    function addField($value) {
        $this->line[] = $value;
    }

    function endRow() {
        $this->addRow($this->line);
        $this->line = array();
    }

    function addRow($row) {
        fputcsv($this->buffer, $row, $this->delimiter, $this->enclosure);
    }

    function renderHeaders() {
        header("Content-type:application/vnd.ms-excel");
        header("Content-disposition:attachment;filename=".$this->filename);
    }

    function setFilename($filename) {
        $this->filename = $filename;
        if (strtolower(substr($this->filename, -4)) != '.csv') {
            $this->filename .= '.csv';
        }
    }

    function render($outputHeaders = true, $to_encoding = null, $from_encoding = "auto") {
        if ($outputHeaders) {
            if (is_string($outputHeaders)) {
                $this->setFilename($outputHeaders);
            }
            $this->renderHeaders();
        }
        rewind($this->buffer);
        $output = stream_get_contents($this->buffer);
        if ($to_encoding) {
            $output = mb_convert_encoding($output, $to_encoding, $from_encoding);
        }
        return $this->output($output);
    }
}

コントローラーにメソッド追加(entries_controller.php)

・作成したCSVヘルパーの読み込み

class EntriesController extends AppController {

	var $name = 'Entries';
	var $helpers = array('Csv');

	/*(省略)*/

・エクスポート用のメソッドを作成(csv_export)

function csv_export() {
	Configure::write('debug', 0);
	$this->layout = false;
	$filename = date('YmdHis');
	$th = array('id', 'name', 'email');
	$th_name = array('id', '名前', 'メールアドレス');
	$td = $this->Entry->find('all', array('fields' => $th));
	$this->set(compact('filename', 'th_name', 'td'));
}

ビューの作成(csv_export.ctp)

<?php
$csv->addRow($th_name);
foreach ($td as $t) {
	$csv->addRow();
	$csv->addField($t['Entry']['id']);
	$csv->addField($t['Entry']['name']);
	$csv->addField($t['Entry']['email']);
	$csv->endRow();
}
$csv->setFilename($filename);
echo $csv->render(true, 'sjis', 'utf-8');
?>

実行

/entries/csv_export」へアクセスすれば、エクスポートが実行されます。

エクスポートしたCSVファイルを、エクセルで開くとこんな感じ↓

インポート

ビューの作成(csv_import.ctp)

<?php echo $form->create('Entry', array('type' => 'file')); ?>
<?php echo $form->file('file_name'); ?>
<?php echo $form->end('実行'); ?>

コントローラーにメソッド追加(entries_controller.php)

・インポート用のメソッドを作成(csv_import)

function csv_import(){
	if (!empty($this->data)) {
		$up_file = $this->data['Entry']['file_name']['tmp_name'];
		$fileName = TMP.'/csv/'.$this->data['Entry']['file_name']['name'];
		if (is_uploaded_file($up_file)){
			move_uploaded_file($up_file, $fileName);
			$this->Entry->begin();
			try {
				$csvData = file($fileName, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
				foreach($csvData as $key => $line){
					if ($key != 0) { // 1行目は飛ばす
						$record = split(",", $line);
						mb_language("Japanese");
						$data = array(
							'id' => mb_convert_encoding($record[0], "UTF-8", "auto"),
							'name' => mb_convert_encoding($record[1], "UTF-8", "auto"),
							'email' => mb_convert_encoding($record[2], "UTF-8", "auto"),
						);
						$this->Entry->create($data);
						$this->Entry->save();
					}
				}
				$this->Entry->commit();
			} catch(Exception $e) {
				$this->Entry->rollback();
			}
		}
	}
}

※4行目: アップロード先のフォルダは任意で構いませんが、ここでは app/tmp フォルダ内に csv フォルダを作成して、そこを指定しています。パーミッションの設定もお忘れなく。

※11行目: さきほどエクスポートしたCSVファイルを、そのままインポートする為、1行目(ラベル)を飛ばしています。

新規追加と更新

「id」の列に数値が入っていれば”上書き”、空なら”新規追加”となります。

・新規追加の場合

「id」の列を空にしておく。

データベースを空にしてからインポートする場合

インポートする前にテーブルのデータを空にするコードを追加します。
ソースコードで言うと、8行目の次。

//(省略)
			try {
				$this->Entry->deleteAll('1 = 1', false);
				$csvData = file($fileName, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
//(省略)

バリデーション

CSVファイルをインポートする際も、モデルに記述されたバリデーションルールは適用されます。
例えば、nameフィールドに”入力必須”のバリデーションルールを設定していた場合、「name」の列が空だとインポートされません。
一部のデータにミスがあると、他すべてのデータがインポートされません。

インポートが成功したのか失敗したのかを明示したいならば、setFlash等を使うといいと思います。

//(省略)
if ($this->Entry->save()) {
	$this->Session->setFlash('インポートに成功しました');
} else {
	$this->Session->setFlash('インポートに失敗しました');
}
//(省略)

おまけ

データベースに格納する値が、”文字列”ではなく”数値”であることも多いと思います。
例えば、「sex(性別)」フィールドがある場合、文字列ではなく数値で格納しているかもしれません。
(1:男、2:女)

数値を文字列に変換してエクスポートする方法

・コントローラ

function csv_export() {
	Configure::write('debug', 0);
	$this->layout = false;
	$filename = date('YmdHis');
	$th = array('id', 'name', 'sex');
	$th_name = array('id', '名前', '性別');
	$td = $this->Entry->find('all', array('fields' => $th));
	$sexArr = array('1' => '男', '2' => '女');
	$this->set(compact('filename', 'th_name', 'td', 'sexArr'));
}

・ビュー

<?php
$csv->addRow($th_name);
foreach ($td as $t) {
	$csv->addRow();
	$csv->addField($t['Entry']['id']);
	$csv->addField($t['Entry']['name']);
	$csv->addField($sexArr[$t['Entry']['sex']]);
	$csv->endRow();
}
$csv->setFilename($filename);
echo $csv->render(true, 'sjis', 'utf-8');
?>

・エクスポートされるCSVデータ

ただし、この方法でエクスポートしたCSVファイルでは、「エクスポート→Excelで編集→そのままインポート」は不可になります。
エクスポートだけする場合に有効です。

Comments

  • Kana より:

    こんにちは。CSVファイルのエクスポート、インポート機能を探してこちらにたどり着きました。
    cakePHP2.xで実装したいのですが、こちらは2.xは非対応でしょうか?

コメントを残す