PHPの仕事で一番厄介だった所をメモっとく。

社内システムをWeb化するときに今まで使ってた Excel の文書をそのまま使いたいんだよねー、 とかお客さん言われる事は多いと思うんだけど今回もそうだった。

で、PHPでExcelってどうすんだろと思ってググったら PHPExcel ってのが定番らしいのでこれを使うことにした。

こいつが重くってどうにもならないのだが今回は お客さんが許してくれたので結果おーらい。 次は使わないけど。

本題。
Excelのテンプレートに項目を埋めて出力するってパターンが基本だと思うのだが 今回はテンプレートの行をコピーしながら表を作る仕様になっていた。

PHPExcelの機能に行コピーぐらい用意されてると思ったら無い。
ググったら自前でセル単位に書式と値をコピーするサンプルが有った。
これを使って見たのだがダメダメ。
セルの結合がコピーできて無い。

PHPExcelでセルの結合をコピーする方法を調べたのだがまともに出来ない事が分かった。

  • セルの結合状態はシート全体に対してしか取れない。
    しかも文字列で "AB12:AC15" みたいな形式の配列。
  • 設定は同じ形式を関数に渡せば結合される。

つまり、対象の結合を捜し出して、文字列ばらして、 行を差し替えて文字列再構築とかを全部のセルにやらなきゃいけない orz

こんな有りがちな機能がなんでこんなに大変なんだ?

しょうがねーから以下の4つを完全にコピーする関数を作ったよ。

  • セルの書式
  • セルの値
  • セルの結合
  • 行の高さ

こんな感じ。

require_once 'PHPExcel/Classes/PHPExcel.php'; class Excel { public $sheet; public function __construct($templ) { $reader = PHPExcel_IOFactory::createReader('Excel5'); $this->excel = $reader->load($templ); $this->sheet = $this->excel->setActiveSheetIndex(0); $this->templ = $templ; } public function apply($map) { foreach ($map as $key => $value) { $this->sheet->setCellValue($key, $value); // Note: $valueはUTF-8必須 } } public function copyRows( $srcRow, // 複製元行番号 $dstRow, // 複製先行番号 $height, // 複製行数 $width // 複製カラム数 ) { $sheet = $this->sheet; for($row=0; $row<$height; $row++) { // セルの書式と値の複製 for ($col=0; $col<$width; $col++) { $cell = $sheet->getCellByColumnAndRow($col, $srcRow+$row); $style = $sheet->getStyleByColumnAndRow($col, $srcRow+$row); $dstCell = PHPExcel_Cell::stringFromColumnIndex($col).(string)($dstRow+$row); $sheet->setCellValue($dstCell, $cell->getValue()); $sheet->duplicateStyle($style, $dstCell); } // 行の高さ複製。 $h = $sheet->getRowDimension($srcRow+$row)->getRowHeight(); $sheet->getRowDimension($dstRow+$row)->setRowHeight($h); } // セル結合の複製 // - $mergeCell="AB12:AC15" 複製範囲の物だけ行を加算して復元。 // - $merge="AB16:AC19" foreach ($sheet->getMergeCells() as $mergeCell) { $mc = explode(":", $mergeCell); $col_s = preg_replace("/[0-9]*/" , "",$mc[0]); $col_e = preg_replace("/[0-9]*/" , "",$mc[1]); $row_s = ((int)preg_replace("/[A-Z]*/" , "",$mc[0])) - $srcRow; $row_e = ((int)preg_replace("/[A-Z]*/" , "",$mc[1])) - $srcRow; // 複製先の行範囲なら。 if (0 <= $row_s && $row_s < $height) { $merge = $col_s.(string)($dstRow+$row_s).":".$col_e.(string)($dstRow+$row_e); $sheet->mergeCells($merge); } } } public function output() { header('Content-Type: application/vnd.ms-excel'); header('Content-Disposition: attachment;filename="'.$this->templ); header("Expires: Thu, 01 Dec 1994 16:00:00 GMT"); header("Last-Modified: ". gmdate("D, d M Y H:i:s"). " GMT"); header("Cache-Control: no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Cache-Control: private",false); header("Pragma: no-cache"); $writer = PHPExcel_IOFactory::createWriter($this->excel, 'Excel5'); $writer->save('php://output'); } }

実行サンプル。 require_once 'Excel.php'; setlocale(LC_ALL,'ja_JP.UTF-8'); function doMap($excel, $data, $count) { $L = ($count+1) * 12; $excel->sheet->insertNewRowBefore($L+1, 12); $excel->copyRows(1, $L+1, 12, 53); $L2 = $L + 2; $L4 = $L + 4; $L5 = $L + 5; $L6 = $L + 6; $L8 = $L + 8; $L9 = $L + 9; $L10 = $L + 10; $L11 = $L + 11; $L12 = $L + 12; $ptime = strptime($data[0],"%Y/%m"); $map = array( "K$L2" => $ptime["tm_year"]+1900, // 年 "O$L2" => $ptime["tm_mon"]+1, // 月 "Y$L2" => date("Y",time()), // 支給年 "AC$L2"=> date("m",time()), // 月 "AF$L2"=> date("d",time()), // 日 "AM$L2"=> $data[1], // 所属 "AV$L2"=> $data[2], // 氏名 //------------------------------ "J$L4" => $data[3], // 出勤日数 "R$L4" => $data[4], // 欠勤日数 "Z$L4" => $data[5], // 有給 "AH$L4"=> $data[6], // 代休 "J$L5" => $data[7], // 終業時間 "R$L5" => $data[8], // 残業時間 "Z$L5" => $data[9], // 休出時間 //------------------------------ "J$L6" => $data[10], // 基本給 "R$L6" => $data[11], // 残業手当 "Z$L6" => $data[12], // 通勤手当 "AH$L6"=> $data[13], // 住宅手当 "AP$L6"=> $data[14], // 家族手当 "J$L8" => $data[15], // 支給額 //------------------------------ "J$L9" => $data[16], // 健康保険 "R$L9" => $data[17], // 介護保険 "Z$L9" => $data[18], // 構成年金 "AP$L9"=> $data[19], // 雇用保険 "J$L10"=> $data[20], // 所得税 "R$L10"=> $data[21], // 住民税 "J$L11"=> $data[22], // 控除額 //------------------------------ "J$L12"=> $data[23], // 差引支給額 ); $excel->apply($map); } $excel = new Excel("kyuuyo-templ.xls"); $fp = fopen("sample.csv","r"); $header = fgetcsv($fp); $count = 0; while ($data=fgetcsv($fp)) { doMap($excel, $data, $count++); } fclose($fp); //$excel->sheet->removeRow(1, 12); $excel->output(); exit;

実行結果:一番上のテンプレート12行を複製しながら中身をCSVから埋め込んでいます。

(出典:http://template.k-solution.info/2006/12/01_excel_9.html )



これ、使いたければ使っても良いけどすっごい遅い事は覚悟して下さい。
このサンプルでも数十人分なら応答時間は分単位になるでしょう。
できれば PHPExcel 自体の使用を止めたほうが良いです (^^;

参考:http://atamoco.boy.jp/php5/phpexcel/20110406_1.php