LoginSignup
7
6

More than 3 years have passed since last update.

Laravel-Excelでセル結合した複数シートを出力する

Posted at

この記事は、Fusic その2 Advent Calendar 2019 22日目の記事です。

はじめに

LaravelでExcel出力するなら Laravel-Excel を使っている方が多いかと思います。

ただこのライブラリ、PHPSpreadSheet の完全なラッパーではありません。
それ故に少々詰まったので、書き残しておきます。

環境

PHP 7.4.1
Laravel 6.9.0
Laravel-Excel 3.1

クラス構成

Userモデルを介して取得した全データを、
UsersExportクラスにDIし、各ユーザごとにUsersSheetクラスを作成します。

├── app
│   ├── Exports
│   │   ├── UsersExport.php
│   │   └── UsersSheet.php
│   └── User.php

最終的には各ユーザごとにレイアウトが変動する複雑なExcelファイルの出力が必要だったのですが、
今回はその前段階として、結合セルのある複数シートの出力を目指します。
image.png

セル結合

Laravel-Excel では、PHPSpreadsheet の mergeCells() がラッパーされていません。
即ちセル結合をする際は、ライフサイクルの中で PHPOffice\PHPSpreadSheet\WorkSheet\WorkSheet インスタンスを取得する必要があります。

Laravel-Excelのライフサイクル

以下4つのイベントが順に発火します。

  • BeforeExport
  • BeforeSheet
  • AfterSheet
  • BeforeWriting

公式ドキュメントの記述によると、 PHPOffice\PHPSpreadSheet\WorkSheet\WorkSheetが生成されるのはBeforeSheetイベントの直前です。
しかし、肝心の取得方法は記載されていません。

PHPOffice\PHPSpreadSheet\WorkSheet\WorkSheetの取得

以下のようにして、BeforeSheetイベント内で取得できました。
あとはPHPSpreadSheetのドキュメントに従うだけです。
$event->getSheet()で取得されるのが Maatwebsite\Excel\Sheet なのはややこしいですね。


namespace App\Exports;

use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\BeforeSheet;

class UsersExport implements WithEvents
{
    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            BeforeSheet::class => function (BeforeSheet $event) {
                $sheet = $event->getSheet()->getDelegate();
                // セル結合
                $sheet->mergeCells('A2:D2');
            },
        ];
    }
}

複数シートの出力

続いて、複数シートの出力を行います。

失敗例

Laravel-Excel # Multiple Sheets を参考に、
先ほどのコードに WithMultipleSheetssheets() メソッドを追加してみました。


namespace App\Exports;

use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\BeforeSheet;

class UsersExport implements WithEvents, WithMultipleSheets
{
    protected $users;

    public function __construct($users)
    {
        $this->users = $users;
    }

    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            BeforeSheet::class => function (BeforeSheet $event) {
                $user = $event->getSheet()->user;
                $sheet = $event->getSheet()->getDelegate();
                // セル結合
                $sheet->mergeCells('A2:D2');
                $sheet->setCellValue('A2', $user->name);
            },
        ];
    }

    /**
     * @return array
     */
    public function sheets(): array
    {
        $sheets = [];
        foreach ($this->users as $user) {
            $sheets[] = new UsersSheet($user);
        }
        return $sheets;
    }
}
<?php

namespace App\Exports;

use App\User;

class UsersSheet
{
    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

これを実行すると…

image.png

まっさら!!!

ライフサイクルの確認

各イベントが発火しているのか確認します。

<?php

namespace App\Exports;

use App\User;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Events\BeforeExport;
use Maatwebsite\Excel\Events\BeforeWriting;
use Maatwebsite\Excel\Events\BeforeSheet;
use Maatwebsite\Excel\Events\AfterSheet;

class UsersExport implements WithEvents, WithMultipleSheets
{

    protected $users;

    public function __construct($users)
    {
        $this->users = $users;
    }

    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            BeforeExport::class => function (BeforeExport $event) {
                \Log::debug('===========================================');
                \Log::debug('BeforeExport');
                \Log::debug('===========================================');
            },
            BeforeSheet::class => function (BeforeSheet $event) {
                \Log::debug('===========================================');
                \Log::debug('BeforeSheet');
                \Log::debug('===========================================');
            },
            AfterSheet::class => function (AfterSheet $event) {
                \Log::debug('===========================================');
                \Log::debug('AfterSheet');
                \Log::debug('===========================================');
            },
            BeforeWriting::class => function (BeforeWriting $event) {
                \Log::debug('===========================================');
                \Log::debug('BeforeWriting');
                \Log::debug('===========================================');
            },
        ];
    }


    /**
     * @return array
     */
    public function sheets(): array
    {
        $sheets = [];
        foreach ($this->users as $user) {
            $sheets[] = new UsersSheet($user);
        }
        return $sheets;
    }
}
[2019-12-22 03:42:15] local.DEBUG: ===========================================  
[2019-12-22 03:42:15] local.DEBUG: BeforeExport  
[2019-12-22 03:42:15] local.DEBUG: ===========================================  
[2019-12-22 03:42:15] local.DEBUG: ===========================================  
[2019-12-22 03:42:15] local.DEBUG: BeforeWriting  
[2019-12-22 03:42:15] local.DEBUG: ===========================================  

BeforeSheet, AfterSheet イベントが発火していませんね。

UsersSheet のほうにもデバッグコードを追加してみます。


class UsersSheet implements WithEvents
{
    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            BeforeExport::class => function (BeforeExport $event) {
                \Log::debug('===========================================');
                \Log::debug('BeforeExport -- Sheet');
                \Log::debug('===========================================');
            },
            BeforeSheet::class => function (BeforeSheet $event) {
                \Log::debug('===========================================');
                \Log::debug('BeforeSheet -- Sheet');
                \Log::debug('===========================================');
            },
            AfterSheet::class => function (AfterSheet $event) {
                \Log::debug('===========================================');
                \Log::debug('AfterSheet -- Sheet');
                \Log::debug('===========================================');
            },
            BeforeWriting::class => function (BeforeWriting $event) {
                \Log::debug('===========================================');
                \Log::debug('BeforeWriting -- Sheet');
                \Log::debug('===========================================');
            },
        ];
    }
}
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: BeforeExport  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  
[2019-12-22 04:03:45] local.DEBUG: BeforeWriting  
[2019-12-22 04:03:45] local.DEBUG: ===========================================  

BeforeSheet, AfterSheet はどちらも UsersSheet 内での発火になっています。
Sheetイベントなので、確かに各Sheetクラスに委任するのが自然ですね。

最終コード

以下のコードで、望んでいたExcelが出力できました。

<?php

namespace App\Exports;

use App\User;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;

class UsersExport implements WithMultipleSheets
{
    protected $users;

    public function __construct($users)
    {
        $this->users = $users;
    }

    /**
     * @return array
     */
    public function sheets(): array
    {
        $sheets = [];
        foreach ($this->users as $user) {
            $sheets[] = new UsersSheet($user);
        }
        return $sheets;
    }
}
<?php

namespace App\Exports;

use App\User;
use Maatwebsite\Excel\Concerns\WithTitle;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Events\BeforeSheet;

class UsersSheet implements WithEvents, WithTitle
{
    private $user;

    /**
     * @param User $user
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            BeforeSheet::class => function (BeforeSheet $event) {
                $sheet = $event->getSheet()->getDelegate();
                $sheet->mergeCells('A2:D2');
                $sheet->setCellValue('A2', $this->user->name);
            },
        ];
    }

    public function title(): string
    {
        return $this->user->name;
    }
}

まとめ

今回はセル結合のみを行いましたが、
PHPOffice\PHPSpreadSheet\WorkSheet\WorkSheetインスタンスの取得方法がわかったので、ほかにもいろいろできそうです。
BeforeSheet以外のイベントも上手く使っていきたいと思います。

7
6
0

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
7
6