PHP Laravelを用いてExcel出力を行う

## 概要
PHP（Laravel)を用いてExcel出力を行う方法を共有します。
以前はPHPExcelが主流みたいでしたが、現在はその後継となるPhpspreadsheetというライブラリが一般的です。
（githubの[README](https://github.com/PHPOffice/PhpSpreadsheet#phpexcel-vs-phpspreadsheet-)にも記載されている通りPHPExcelよりもPhpspreadsheetを使うことが望ましいでしょう。）

今回は社員名簿リストをExcelにて出力します。
ゴールイメージは下記のような感じです。
![スクリーンショット 2022-09-19 1.22.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823188/67c030f6-a545-c7ed-72c0-3992fec2d755.png)


## 今回やること
- Excel出力/ブラウザからダウンロード
- 列幅の調整
- 行の固定

## 環境
```
PHP 8.1.10
Laravel 9.30.1
```

## 1.Phpspreadsheetをインストールする
まずはPhpspreadsheetをインストールします。

```
composer require phpoffice/phpspreadsheet
```

## 2.PhpSpreadsheetクラスの作成
Serviceクラスとして切り出して、新たにExcel出力するファイルを作成します。
（今回はブラウザからダウンロードすることを想定して作成しています。）

```php:app/Services/PhpSpreadsheetService.php
<?php

namespace App\Services;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\IOFactory;

class PhpSpreadsheetService
{
    private const HEADER_ROW = 1;
    private const BODY_ROW = 2;
    private const HEADER_NAMES = [
        '社員ID',
        '氏名',
        '生年月日',
        '部署',
        '職種',
    ];

    /**
     * Excelファイルを出力.
     *
     * @return void
     */
    public function export(): void
    {
        $this->spreadsheet = new Spreadsheet();

        // ヘッダー部分出力
        $column_index = range('A', 'Z');
        foreach (self::HEADER_NAMES as $key => $header_name) {
            $this->spreadsheet->getActiveSheet()->setCellValue($column_index[$key].self::HEADER_ROW, $header_name);
        }
        
        // 社員名簿部分出力
        $employees = $this->getEmployees();
        foreach ($employees as $key => $employee) {
            $body_index = 0;    
            foreach ($employee as $employ_key => $employee_value) {
                $this->spreadsheet->getActiveSheet()->setCellValue($column_index[$body_index].(self::BODY_ROW + $key), $employee_value);
                $body_index++;
            }
        }

        // 1行目(ヘッダー)を固定
        $this->spreadsheet->getActiveSheet()->freezePane('A2');

        // 列幅を調整
        $this->spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(12);
        $this->spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(12);
        $this->spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(14);
        $this->spreadsheet->getActiveSheet()->getColumnDimension('E')->setWidth(14);

        // Excelファイルをダウンロード
        $file_name = '社員名簿.xlsx';
        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;');
        header("Content-Disposition: attachment; filename=\"{$file_name}\"");
        header('Cache-Control: max-age=0');

        $writer = IOFactory::createWriter($this->spreadsheet, 'Xlsx');
        $writer->save('php://output');
        exit;
    }
    
    /**
     * 社員名簿を取得.
     *
     * @return array
     */
    private function getEmployees(): array
    {
        return [
            [
                'id' => '1',
                'name' => '山田花子',
                'birthday' => '1990-01-23',
                'department' => '営業部',
                'occupation' => '営業',
            ],
            [
                'id' => '2',
                'name' => '田中太郎',
                'birthday' => '1993-08-01',
                'department' => '人事部',
                'occupation' => '企画',
            ],
            [
                'id' => '3',
                'name' => '高橋二郎',
                'birthday' => '1984-11-01',
                'department' => 'システム部',
                'occupation' => 'エンジニア',
            ],
        ];
    }
}

```

## 3. 簡易的なダウンロードページ作成
ブラウザからダウンロードできることを想定して、簡易的なダウンロードページを作成します。今回の本目的とは異なるのでCSSは何も充てておりません。ご了承ください。

```resources/views/index.blade.php
<html>
    <body>
        <div>
            <form action="{{ url('/download') }}" method="POST">
                @csrf
                <h2>下記のボタンを押下してExcelファイルをダウンロードしてください。</h2>
                <button>download</button>
            </form>
        </div>
    </body>
</html>
```


ルーティング設定
```routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\SpreadsheetController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/index', [SpreadsheetController::class, 'index']);
Route::post('/download', [SpreadsheetController::class, 'download']);

Route::get('/', function () {
    return view('welcome');
});

```

コントローラー側の表示/Excelダウンロード処理
```app/Http/Controllers/SpreadsheetController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Services\PhpSpreadsheetService;
use Illuminate\View\View;

class SpreadsheetController extends Controller
{
    protected $spreadsheet;

    /**
     * 
     * @param PhpSpreadsheetService $spreadsheet
     * 
     * @return void
     */
    public function __construct(PhpSpreadsheetService $spreadsheet)
    {
        $this->spreadsheet = $spreadsheet;
    }
    
    /**
     * Excelダウンロードページを表示.
     *
     * @return View
     */
    public function index(): View
    {
        return view('index');
    }

    /**
     * Excelファイルをダウンロード.
     *
     * @return View
     */
    public function download(): View
    {
        $this->spreadsheet->export();
        return view('index');
    }
}

```

## 4. 動作確認
CSSを充てていないので質素な感じですが、ダウンロードボタンを押します。
![スクリーンショット 2022-09-19 1.25.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823188/d9f326fa-dea4-796a-ae69-a3b4b04829c0.png)


Excelを開くとゴールイメージと同じような内容が出力されました。
これにて終了です。
![スクリーンショット 2022-09-19 1.27.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823188/645015c3-87c6-2fff-dd70-dea1adc61aa4.png)


## 参考
- [PhpSpreadsheet 公式ドキュメント](https://phpspreadsheet.readthedocs.io/en/latest/topics/accessing-cells/)