29
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravel Excel使ってみた

Last updated at Posted at 2020-05-15

#概要
今もっともメジャーと思われる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のソースに追加。

[config/app.php]
'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;
  }
29
32
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?