#概要
今もっともメジャーと思われるLaravelのExcelプラグイン。
早さだけなら「fast-excel」もあるが情報がないので。
#インストール
phpで以下の拡張モジュールが必要なのであらかじめ使えるようにしておく。
php_zip php_xml php_gd2
composerでLaravelプロジェクトにプラグイン追加
$ composer require maatwebsite/excel
laravel 6.9の環境ではlaravel-excel 3.1.19がインストールされた。
次に設定を施していく。まずはlaravelのソースに追加。
'providers' => [
/*
* Package Service Providers...
*/
+ Maatwebsite\Excel\ExcelServiceProvider::class,
]
(略)
'aliases' => [
(略)
'Event' => Illuminate\Support\Facades\Event::class,
+ 'Excel' => Maatwebsite\Excel\Facades\Excel::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
(略)
次に以下のコマンドを叩いて専用の設定ファイルを生成する。
$ php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"
設定ファイル laravel/config/excel.php が作られます。
デフォルトでPDFでのエクスポート機能が有効になってました。
##サンプル
###その1 xlsxテンプレートをもとにxlsx出力
データを埋め込み、セルの書式設定、罫線設定を行って出力する場合
<?php
namespace App\Sample;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\BeforeExport;
use Maatwebsite\Excel\Events\BeforeWriting;
use Maatwebsite\Excel\Excel;
use Maatwebsite\Excel\Files\LocalTemporaryFile;
// xlsxテンプレートのセル書式編集を行って出力するサンプルクラス
class ReportInputOutputSample implements WithEvents
{
use Exportable;
private $template_file = null;
/**
* @param string $template_file
* @return $this
*/
public function setTemplate(string $template_file)
{
if (file_exists($template_file)) {
$this->template_file = $template_file;
}
return $this;
}
/**
* @return array
*/
public function registerEvents(): array
{
return [
// ファイル生成直前イベントハンドラ
BeforeExport::class => function (BeforeExport $event) {
// テンプレート読み込み
if (is_null($this->template_file)) {
return;
}
$event->writer->reopen(new LocalTemporaryFile($this->template_file), Excel::XLSX);
return;
},
// 書き込み直前イベントハンドラ
BeforeWriting::class => function (BeforeWriting $event) {
// テンプレート読み込みでついてくる余計な空シートを削除する
$event->writer->removeSheetByIndex(1);
// セルを赤色で塗りつぶし
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B5')->getStyle('B5')->getFill()->setFillType('solid')->getStartColor()->setARGB('FFFFFF00');
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B6')->getStyle('B6')->getFill()->setFillType('solid')->getStartColor()->setARGB('FFFF0000');
// セル結合
$event->writer->getSheetByIndex(0)->getDelegate()->mergeCells('B10:C11');
// セル結合解除
$event->writer->getSheetByIndex(0)->getDelegate()->unmergeCells('B13:C13');
// 上罫線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B16')->getStyle('B16')->getBorders()->getTop()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
// 下罫線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B18')->getStyle('B18')->getBorders()->getBottom()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
// 左罫線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B20')->getStyle('B20')->getBorders()->getLeft()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
// 右罫線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B22')->getStyle('B22')->getBorders()->getRight()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
// 外枠
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B24')->getStyle('B24')->getBorders()->getOutline()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
// 太線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B26')->getStyle('B26')->getBorders()->getOutline()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK);
// 二重線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B28')->getStyle('B28')->getBorders()->getOutline()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DOUBLE);
// 点線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B30')->getStyle('B30')->getBorders()->getOutline()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DOTTED);
// 斜め右上がり罫線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B32')->getStyle('B32')->getBorders()->setDiagonalDirection(1)->getDiagonal()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
// 斜め右下がり罫線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B34')->getStyle('B34')->getBorders()->setDiagonalDirection(2)->getDiagonal()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
// 文字サイズ
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B5')->getStyle('B5')->getFont()->setSize(16);
// 太字
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B6')->getStyle('B6')->getFont()->setBold(true);
// 斜体
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B7')->getStyle('B7')->getFont()->setItalic(true);
// 下線
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B8')->getStyle('B8')->getFont()->setUnderline(true);
// 書体
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B9')->getStyle('B9')->getFont()->setName('HGSゴシックE');
// 色
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B10')->getStyle('B10')->getFont()->getColor()->setARGB('FFFF0000');
// 一括設定
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B11')->getStyle('B11')->applyFromArray([
'font' => [
'size' => '16',
'bold' => true,
'italic' => true,
'underline' => true,
'name' => 'ヒラギノ丸ゴ Pro',
'color' => ['argb' => 'FFFF0000']
]
]);
// 右寄せ
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B12')->getStyle('B12')->getAlignment()->setHorizontal('right');
// 中央寄せ
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B13')->getStyle('B13')->getAlignment()->setHorizontal('center');
// ライブラリクラスでセルに値設定
$event->writer->getSheetByIndex(0)->getDelegate()->getCell('B6')->setValue('これはプログラムで設定した値です。');
// 独自ワークシートクラス
$esh = new ExtendWorksheets($event->writer->getSheetByIndex(0)->getDelegate());
// 独自ワークシートクラスでセルに値設定
$esh->setValue('B9', 'これはプログラムで設定した値です。');
// 独自ワークシートクラスでセルに値設定
$esh->replaceValue('B12', [['target' => '{置換対象1}', 'value' => '!!プログラムで置換1!!'], ['target' => '{置換対象2}', 'value' => '!!プログラムで置換2!!']]);
// セルの値を取得して別セルに設定することでコピー
$cellValue = $esh->getValue('B15');
$esh->setValue('C15', $cellValue);
//csv用データを設範囲設定
$data = [
['名前', '生年月日'],
['氏名1', '1999年1月1日'],
['氏名2', '1999年2月1日'],
['氏名3', '1999年3月1日'],
['氏名4', '1999年4月1日'],
['氏名5', '1999年5月1日'],
];
$esh->fromArray($data, null, 'B18');
return;
},
];
}
}
<?php
namespace App\Sample;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\RichText\RichText as RichText;
class ExtendWorksheets
{
private $worksheets = null;
// コンストラクタを追加
public function __construct(Worksheet $worksheets)
{
$this->worksheets = $worksheets;
return $this;
}
/**
* セルの値を取得
*
* @return cell value
*/
public function getValue($cell_label)
{
return $this->worksheets->getCell($cell_label)->getValue();
}
/**
* セルへ値を上書き設定
* (書式はテンプレートを継承する)
*
* @return $this
*/
public function setValue($cell_label, $value)
{
$this->worksheets->getCell($cell_label)->setValue($value);
return $this;
}
/**
* セルへ書式指定付きで値を上書き設定
*
* @return $this
*/
public function setValueWithFormat($cell_label, $values_ary)
{
$RichText = new RichText();
foreach ($values_ary as $key => $value) {
$specialFont = $RichText->createTextRun($value['value'])->getFont();
if (array_key_exists('size', $value)) {
$specialFont->setSize($value['size']);
}
if (array_key_exists('style', $value)) {
switch ($value['size']) {
case 'bold':
$specialFont->setBold(true);
break;
case 'italic':
$specialFont->setItalic(true);
break;
}
}
if (array_key_exists('family', $value)) {
$specialFont->setName($value['family']);
}
}
$this->worksheets->getCell($cell_label)->setValue($RichText);
return $this;
}
/**
* セルへ値を指定キーワードを置換して設定
* (書式はテンプレートを継承する)
*
* @return $this
*/
public function replaceValue($cell_label, $values_ary)
{
$setValue = self::getValue($cell_label);
foreach ($values_ary as $key => $value) {
$setValue = str_replace($value['target'], $value['value'], $setValue);
}
self::setValue($cell_label, $setValue);
return $this;
}
/**
* セル結合
* @return $this
*/
public function mergeCells($range_label)
{
$this->worksheets->mergeCells($range_label);
return $this;
}
/**
* セル結合解除
* @return $this
*/
public function unmergeCells($range_label)
{
$this->worksheets->unmergeCells($range_label);
return $this;
}
/**
* 複数セルへ値を範囲上書き設定
* (書式はテンプレートを継承する)
*
* @return $this
*/
public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
{
$this->worksheets->fromArray($source, $nullValue, $startCell, $strictNullComparison);
return $this;
}
}
return (new ReportInputOutputSample)->setTemplate(resource_path('sample/template4.xlsx'))->download('laravel-excelサンプル4.xlsx');
###その2 csv出力サンプル
データを設定してcsvで出力。
<?php
namespace App\Sample;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\BeforeExport;
use Maatwebsite\Excel\Events\BeforeWriting;
use Maatwebsite\Excel\Excel;
use Maatwebsite\Excel\Files\LocalTemporaryFile;
// CSV出力サンプルクラス
class CsvOutputSmaple implements WithEvents
{
use Exportable;
/**
* @return array
*/
public function registerEvents(): array
{
return [
// 書き込み直前イベントハンドラ
BeforeWriting::class => function (BeforeWriting $event) {
//csv用データを設範囲設定
$data = [
['名前', '生年月日'],
['氏名1', '1999年1月1日'],
['氏名2', '1999年2月1日'],
['氏名3', '1999年3月1日'],
['氏名4', '1999年4月1日'],
['氏名5', '1999年5月1日'],
];
$event->writer->getSheetByIndex(0)->getDelegate()->fromArray($data, null, 'A1');
return;
},
];
}
}
return (new CsvOutputSmaple)->download('laravel-excelサンプル5.csv');
###その3 csvインポートサンプル
文字コードの認識がおかしい?ワイド文字が入ると区切りがおかしくなるので、今のところCSVインポートはLaravel-Excel以外の方法で検討したほうがよさそう。
<?php
namespace App\Sample;
use Maatwebsite\Excel\Concerns\ToModel;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\Importable;
use App\Sample\ReportInputOutputSample006ImportModel;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
// CSVインポートのサンプルクラス
class ImportSample implements ToCollection
{
use Importable;
// public function model(array $row)
// {
// return new ReportInputOutputSample006ImportModel([
// 'name' => $row[0],
// 'kana' => $row[1],
// 'sex' => $row[2],
// ]);
// }
public function collection(Collection $rows)
{
foreach ($rows as $row) {
foreach ($row as $c) {
Log::debug(mb_detect_encoding($c, 'Shift-JIS,UTF-8,EUC-JP'));
Log::debug(mb_convert_encoding($c, 'Shift-JIS'));
}
}
}
}
(new ImportSample)->import(resource_path('sample/target6.csv'), null, \Maatwebsite\Excel\Excel::CSV);
###その4 xlsxインポートサンプル
その3のcsvインポートと同じ。呼出しで入力ファイルパスと種別を変えればいい。
(new ImportSample)->import(resource_path('sample/target7.xlsx'), null, \Maatwebsite\Excel\Excel::XLSX);
###その5 pdfエクスポートサンプル
呼出しで出力ファイルの拡張子をpdfに変えればいい。ただしワイド文字が文字化けする。この対応方法が不明。
return (new ReportInputOutputSample)->setTemplate(resource_path('sample/template4.xlsx'))->download('laravel-excelサンプル4.pdf');
##フロント側のダウンロード処理
帳票を作成してダウンロードさせるコード
「/api/sample/report-io」のAPIで上記のLaravel-Excelを使ったコントローラー処理を呼び出す想定。
/**
* サンプル処理
*/
doSample(mode: number) {
const _this = this;
// ローディング状態に変更
const loading = this.$loading({
lock: true,
text: "実行中...",
background: SystemSetting.waitingBackgroundColor
});
axios({
url: "/api/sample/report-io",
params: { mode: mode },
method: "GET",
responseType: "blob" // これがないと文字化けする
})
.then(res => {
this.download(res);
})
.catch(error => {
var errorMsg = "不明";
// 通知
_this.$message({
message: "実行に失敗しました。[" + errorMsg + "]",
type: "error",
duration: 0,
showClose: true
});
})
.finally(() => {
// ローディング状態終了
loading.close();
});
}
/**
* ファイルダウンロード
*/
download(res) {
const blob = new Blob([res.data], {
type: res.data.type
});
//レスポンスヘッダからファイル名を取得します
const contentDisposition = res.headers["content-disposition"];
const fileName = this.getFileName(contentDisposition);
//ダウンロードします
saveAs(blob, fileName);
}
/**
* responseヘッダーからファイル名取得
*/
getFileName(contentDisposition) {
let fileName = contentDisposition.substring(
contentDisposition.indexOf("''") + 2,
contentDisposition.length
);
//デコードするとスペースが"+"になるのでスペースへ置換します
fileName = decodeURI(fileName).replace(/\+/g, " ");
return fileName;
}