[譯] Laravel-Excel 3.0 文件

多釐發表於2019-02-16

Basics

最簡單的匯出方法是建立一個自定義的匯出類, 這裡我們使用發票匯出作為示例.

App/Exports 下建立一個 InvoicesExport

namespace AppExports;

use MaatwebsiteExcelConcernsFromCollection;

class InvoicesExport implements FromCollection
{
    public function collection()
    {
        return Invoice::all();
    }
}

在控制器中你可以使用如下方式來下載

public function export() 
{
    return Excel::download(new InvoicesExport, `invoices.xlsx`);
}

或者儲存在 s3 磁碟中

public function storeExcel() 
{
    return Excel::store(new InvoicesExport, `invoices.xlsx`, `s3`);
}

依賴注入

如果你的匯出需要依賴, 你可以注入匯出類

namespace AppExports;

use MaatwebsiteExcelConcernsFromCollection;

class InvoicesExport implements FromCollection
{
    public function __construct(InvoicesRepository $invoices)
    {
        $this->invoices = $invoices;
    }

    public function collection()
    {
        return $this->invoices->all();
    }
}
public function export(Excel $excel, InvoicesExport $export) 
{
    return $excel->download($export, `invoices.xlsx`);
}

嚴格的 null 對比

如果你希望 0 在 excel 單元格中就是顯示 0, 而不是顯示 null(空單元格), 你可以使用 WithStrictNullComparison

namespace AppExports;

use MaatwebsiteExcelConcernsFromCollection;
use MaatwebsiteExcelConcernsWithStrictNullComparison;

class InvoicesExport implements FromCollection, WithStrictNullComparison
{
    public function __construct(InvoicesRepository $invoices)
    {
        $this->invoices = $invoices;
    }

    public function collection()
    {
        return $this->invoices->all();
    }
}

Collection 全域性定義/巨集

這個包提供了 laravel collection 的一些額外的方法(巨集) 來簡單的下載或者是儲存到 excel

把 collection 作為 Excel 下載

(new Collection([[1, 2, 3], [1, 2, 3]]))->downloadExcel(
    $filePath,
    $writerType = null,
    $headings = false
)

在磁碟上儲存 collection

(new Collection([[1, 2, 3], [1, 2, 3]]))->storeExcel(
    $filePath,
    $disk = null,
    $writerType = null,
    $headings = false
)

在磁碟上儲存匯出

匯出可以儲存到任何 Laravel 支援的 檔案系統 中

public function storeExcel() 
{
    // Store on default disk
    Excel::store(new InvoicesExport(2018), `invoices.xlsx`);

    // Store on a different disk (e.g. s3)
    Excel::store(new InvoicesExport(2018), `invoices.xlsx`, `s3`);

    // Store on a different disk with a defined writer type. 
    Excel::store(new InvoicesExport(2018), `invoices.xlsx`, `s3`, Excel::XLSX);
}

Exportables / 可匯出的

在之前的例子中, 我們使用 Excel::download 這個 facade 來開始一個匯出.

Laravel-Excel 同樣支援  MaatwebsiteExcelConcernsExportable trait, 來讓一個類可以直接匯出, 當然, 這個類裡邊需要有 collection 方法.

namespace AppExports;

use MaatwebsiteExcelConcernsFromCollection;
use MaatwebsiteExcelConcernsExportable;

class InvoicesExport implements FromCollection
{
    use Exportable;

    public function collection()
    {
        return Invoice::all();
    }
}

我們可以不通過 facade 直接進行類的下載

return (new InvoicesExport)->download(`invoices.xlsx`);

或者是儲存到磁碟上.

return (new InvoicesExport)->store(`invoices.xlsx`, `s3`);

Responsable / 可響應的

之前的例子可以做的簡單一點, 例如我們新增 Laravel 的 Responsable 到匯出類中

namespace AppExports;

use IlluminateContractsSupportResponsable;
use MaatwebsiteExcelConcernsFromCollection;
use MaatwebsiteExcelConcernsExportable;

class InvoicesExport implements FromCollection, Responsable
{
    use Exportable;

    /**
    * It`s required to define the fileName within
    * the export class when making use of Responsable.
    */
    private $fileName = `invoices.xlsx`;

    public function collection()
    {
        return Invoice::all();
    }
}

你可以更簡單的返回匯出類,但是不需要呼叫 ->download() 方法.

return new InvoicesExport();

From Query / 從查詢輸出

在之前的例子中, 我們在匯出類中進行查詢, 當然這個解決方案可以用在小的匯出類中. 對於更大一點資料的匯出類可能造成比較大的效能開銷.

通過使用 FromQuery 關係, 我們可以通過預查詢一個匯出, 這個場景實現的原理是查詢可以分塊執行.

InvoicesExport 類中,新增 FromQuery 關係, 並且新增一個查詢, 並且確保不要使用 ->get() 來獲取到資料!.

namespace AppExports;

use MaatwebsiteExcelConcernsFromQuery;
use MaatwebsiteExcelConcernsExportable;

class InvoicesExport implements FromQuery
{
    use Exportable;

    public function query()
    {
        return Invoice::query();
    }
}

我們可以通過同樣的方式來下載

return (new InvoicesExport)->download(`invoices.xlsx`);

自定義查詢

這種方式可以通過自定義的引數來進行查詢. 簡單的作為依賴項傳入匯出類即可.

作為構造器引數

namespace AppExports;

use MaatwebsiteExcelConcernsFromQuery;
use MaatwebsiteExcelConcernsExportable;

class InvoicesExport implements FromQuery
{
    use Exportable;

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

    public function query()
    {
        return Invoice::query()->whereYear(`created_at`, $this->year);
    }
}

$year 引數可以傳遞給匯出類.

return (new InvoicesExport(2018))->download(`invoices.xlsx`);

作為設定項

namespace AppExports;

use MaatwebsiteExcelConcernsFromQuery;
use MaatwebsiteExcelConcernsExportable;

class InvoicesExport implements FromQuery
{
    use Exportable;

    public function forYear(int $year)
    {
        $this->year = $year;

        return $this;
    }

    public function query()
    {
        return Invoice::query()->whereYear(`created_at`, $this->year);
    }
}

我們可以通過 forYear 方法來調整年份.

return (new InvoicesExport)->forYear(2018)->download(`invoices.xlsx`);

通過檢視

我們可以通過 blade 檢視來建立匯出. 通過使用 FromView 關係.

namespace AppExports;

use IlluminateContractsViewView;
use MaatwebsiteExcelConcernsFromView;

class InvoicesExport implements FromView
{
    public function view(): View
    {
        return view(`exports.invoices`, [
            `invoices` => Invoice::all()
        ]);
    }
}

這種方式會匯出一個 Html 表格到 Excel 單元表, 例如 users.blade.php:

<table>
    <thead>
    <tr>
        <th>Name</th>
        <th>Email</th>
    </tr>
    </thead>
    <tbody>
    @foreach($users as $user)
        <tr>
            <td>{{ $user->name }}</td>
            <td>{{ $user->email }}</td>
        </tr>
    @endforeach
    </tbody>
</table>

佇列

如果你處理更大資料量的資料, 很明智的方法就是使用佇列來執行.

例如下邊的匯出類:

namespace AppExports;

use MaatwebsiteExcelConcernsExportable;
use MaatwebsiteExcelConcernsFromQuery;

class InvoicesExport implements FromQuery
{
    use Exportable;

    public function query()
    {
        return Invoice::query();
    }
}

我們只需要呼叫一個 ->queue() 方法即可.

return (new InvoicesExport)->queue(`invoices.xlsx`);

後臺處理這些查詢的方式是通過多工/多切割的方式來進行. 這些任務使用正確的順序來執行. 並且保證之前的查詢都是正確的.

另一種方式的佇列實現

你可以將匯出作為一個可以扔到佇列中的實現(利用 Laravel), 可以使用 ShouldQueue 約束.

namespace AppExports;

use MaatwebsiteExcelConcernsExportable;
use MaatwebsiteExcelConcernsFromQuery;
use IlluminateContractsQueueShouldQueue;

class InvoicesExport implements FromQuery, ShouldQueue
{
    use Exportable;

    public function query()
    {
        return Invoice::query();
    }
}

在控制器中可以呼叫普通的 ->store() 方法. 基於 ShouldQueue, 通過 laravel 的佇列方式來實現佇列處理. [ps:你需要首先自行配置佇列]

return (new InvoicesExport)->store(`invoices.xlsx`);

追加任務 / jobs

 queue() 方法返回一個 Laravel 的  PendingDispatch 例項, 這意味著你可以把其他的任務串聯起來, 僅僅當前一個任務執行成功的時候, 後續的任務才能夠被執行.

return (new InvoicesExport)->queue(`invoices.xlsx`)->chain([
    new NotifyUserOfCompletedExport(request()->user()),
]);
namespace AppJobs;

use IlluminateBusQueueable;
use IlluminateContractsQueueShouldQueue;

class InvoiceExportCompletedJob implements ShouldQueue
{
    use Queueable;

    public function handle()
    {
        // Do something.
    }
}

自定義佇列

PendingDispatch 返回的時候, 我們可以改變我們使用的佇列.

return (new InvoicesExport)->queue(`invoices.xlsx`)->allOnQueue(`exports`);

多單元表

為了能夠讓匯出支援多單元表, 需要使用 WithMultipleSheets 關係來實現. 這個 sheets() 方法需要返回一個單元表陣列.

namespace AppExports;

use MaatwebsiteExcelConcernsExportable;
use MaatwebsiteExcelConcernsWithMultipleSheets;

class InvoicesExport implements WithMultipleSheets
{
    use Exportable;

    protected $year;

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

    /**
     * @return array
     */
    public function sheets(): array
    {
        $sheets = [];

        for ($month = 1; $month <= 12; $month++) {
            $sheets[] = new InvoicesPerMonthSheet($this->year, $month);
        }

        return $sheets;
    }
}

這個 InvoicesPerMonthSheet 可以實現多種關係. 例如 FromQueryFromCollection, …

namespace AppExports;

use MaatwebsiteExcelConcernsFromQuery;
use MaatwebsiteExcelConcernsWithTitle;

class InvoicesPerMonthSheet implements FromQuery, WithTitle
{
    private $month;
    private $year;

    public function __construct(int $year, int $month)
    {
        $this->month = $month;
        $this->year  = $year;
    }

    /**
     * @return Builder
     */
    public function query()
    {
        return Invoice
            ::query()
            ->whereYear(`created_at`, $this->year)
            ->whereMonth(`created_at`, $this->month);
    }

    /**
     * @return string
     */
    public function title(): string
    {
        return `Month ` . $this->month;
    }
}

以下可以下載 2018 年的所有的發票, 它包含 12 單元表來顯示每個月的資料.

public function download() 
{
    return (new InvoicesExport(2018))->download(`invoices.xlsx`);
}

資料遍歷

遍歷行

通過新增  WithMapping, 你可以遍歷新增到單元行中的每一條資料然後並返回.
這種方法你可以控制每一列的資料, 假設你使用 Eloquent 的 query builder.


use MaatwebsiteExcelConcernsFromQuery;
use MaatwebsiteExcelConcernsWithMapping;

class InvoicesExport implements FromQuery, WithMapping
{
    /**
    * @var Invoice $invoice
    */
    public function map($invoice): array
    {
        return [
            $invoice->invoice_number,
            Date::dateTimeToExcel($invoice->created_at),
        ];
    }
}

新增表頭

可以通過新增一個  WithHeadings 約束來實現. 表頭會新增到所有資料的第一行的位置上.


use MaatwebsiteExcelConcernsFromQuery;
use MaatwebsiteExcelConcernsWithHeadings;

class InvoicesExport implements FromQuery, WithHeadings

    public function headings(): array
    {
        return [
            `#`,
            `Date`,
        ];
    }
}

格式化列

你可以格式化整列, 通過新增 WithColumnFormatting, 如果你想更多範圍的自定義. 推薦使用 AfterSheet 事件來直接和地城的 Worksheet 類進行互動.

namespace AppExports;

use PhpOfficePhpSpreadsheetSharedDate;
use PhpOfficePhpSpreadsheetStyleNumberFormat;
use MaatwebsiteExcelConcernsWithColumnFormatting;
use MaatwebsiteExcelConcernsWithMapping;

class InvoicesExport implements WithColumnFormatting, WithMapping
{
    public function map($invoice): array
    {
        return [
            $invoice->invoice_number,
            Date::dateTimeToExcel($invoice->created_at),
            $invoice->total
        ];
    }

    /**
     * @return array
     */
    public function columnFormats(): array
    {
        return [
            `B` => NumberFormat::FORMAT_DATE_DDMMYYYY,
            `C` => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE,
        ];
    }
}

日期

當操作日期的時候. 推薦使用 PhpOfficePhpSpreadsheetSharedDate::dateTimeToExcel() 來正確的解析你的日期資料.

匯出關係

Interface Explanation
MaatwebsiteExcelConcernsFromCollection Use a Laravel Collection to populate the export.
MaatwebsiteExcelConcernsFromQuery Use an Eloquent query to populate the export.
MaatwebsiteExcelConcernsFromView Use a (Blade) view to to populate the export.
MaatwebsiteExcelConcernsWithTitle Set the Workbook or Worksheet title.
MaatwebsiteExcelConcernsWithHeadings Prepend a heading row.
MaatwebsiteExcelConcernsWithMapping Format the row before it`s written to the file.
MaatwebsiteExcelConcernsWithColumnFormatting Format certain columns.
MaatwebsiteExcelConcernsWithMultipleSheets Enable multi-sheet support. Each sheet can have its own concerns (except this one).
MaatwebsiteExcelConcernsShouldAutoSize Auto-size the columns in the worksheet.
MaatwebsiteExcelConcernsWithStrictNullComparison Uses strict comparisions when testing cells for null value.
MaatwebsiteExcelConcernsWithEvents Register events to hook into the PhpSpreadsheet process.

Traits

Trait Explanation
MaatwebsiteExcelConcernsExportable Add download/store abilities right on the export class itself.
MaatwebsiteExcelConcernsRegistersEventListeners Auto-register the available event listeners.

擴充套件

事件

匯出過程有一些事件,你可以利用這些事件與底層類進行互動,以嚮匯出新增自定義行為。

通過使用事件,您可以連線到父包。如果你需要完全控制匯出,則不需要使用諸如 “query” 或者 “view” 之類的便利方法。

事件將通過新增 WithEvents 關注來啟用。在 registerEvents 方法中,你必須返回一系列事件。Key 是事件的完全限定名(FQN),Value 是可呼叫的事件監聽器。這可以是一個閉包、可呼叫的陣列 或 invokable 類。

namespace AppExports;

use MaatwebsiteExcelConcernsWithEvents;
use MaatwebsiteExcelEventsBeforeExport;
use MaatwebsiteExcelEventsBeforeWriting;
use MaatwebsiteExcelEventsBeforeSheet;

class InvoicesExport implements WithEvents
{
    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            // Handle by a closure.
            BeforeExport::class => function(BeforeExport $event) {
                $event->writer->getProperties()->setCreator(`Patrick`);
            },

            // Array callable, refering to a static method.
            BeforeWriting::class => [self::class, `beforeWriting`],

            // Using a class with an __invoke method.
            BeforeSheet::class => new BeforeSheetHandler()
        ];
    }

    public static function beforeWriting(BeforeWriting $event) 
    {
        //
    }
}

請注意,使用 Closure 將不可能與佇列匯出合併,因為PHP不能序列化閉包。在這些情況下,最好使用 RegistersEventListeners 特性。

自動註冊事件監聽器

通過使用 RegistersEventListeners trait ,你可以自動註冊事件監聽器,而不需要使用 registerEvents 。只有在建立方法時,偵聽器才會被註冊。

namespace AppExports;

use MaatwebsiteExcelConcernsWithEvents;
use MaatwebsiteExcelConcernsRegistersEventListeners;
use MaatwebsiteExcelEventsBeforeExport;
use MaatwebsiteExcelEventsBeforeWriting;
use MaatwebsiteExcelEventsBeforeSheet;
use MaatwebsiteExcelEventsAfterSheet;

class InvoicesExport implements WithEvents
{
    use Exportable, RegistersEventListeners;

    public static function beforeExport(BeforeExport $event)
    {
        //
    }

    public static function beforeWriting(BeforeWriting $event)
    {
        //
    }

    public static function beforeSheet(BeforeSheet $event)
    {
        //
    }

    public static function afterSheet(AfterSheet $event)
    {
        //
    }
}

可用的事件

Event name Payload Explanation
MaatwebsiteExcelEventsBeforeExport $event->writer : Writer Event gets raised at the start of the process.
MaatwebsiteExcelEventsBeforeWriting $event->writer : Writer Event gets raised before the download/store starts.
MaatwebsiteExcelEventsBeforeSheet $event->sheet : Sheet Event gets raised just after the sheet is created.
MaatwebsiteExcelEventsAfterSheet $event->sheet : Sheet Event gets raised at the end of the sheet process.

巨集

WriterSheet 都是可以進行巨集操作的,這意味著它可以很容易地擴充套件以滿足你的需要。Writer 和 Sheet都有一個 ->getDelegate() 方法,它返回底層的PhpSpreadsheet 類。這將允許你為 PhpSpreadsheets 方法新增快捷方法,而這個方法在這個包中是不可用的。

Writer / 寫入

use MaatwebsiteExcelWriter;

Writer::macro(`setCreator`, function (Writer $writer, string $creator) {
    $writer->getDelegate()->getProperties()->setCreator($creator);
});

Sheet / 單元表

use MaatwebsiteExcelSheet;

Sheet::macro(`setOrientation`, function (Sheet $sheet, $orientation) {
    $sheet->getDelegate()->getPageSetup()->setOrientation($orientation);
});

你還可以為樣式單元新增一些快捷方法。你可以自由使用這個巨集,或者創造你自己的語法!

use MaatwebsiteExcelSheet;

Sheet::macro(`styleCells`, function (Sheet $sheet, string $cellRange, array style) {
    $sheet->getDelegate()->getStyle($cellRange)->applyFromArray($style);
});

以上例子可作:

namespace AppExports;

use MaatwebsiteExcelConcernsWithEvents;
use MaatwebsiteExcelEventsBeforeExport;
use MaatwebsiteExcelEventsAfterSheet;

class InvoicesExport implements WithEvents
{
    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            BeforeExport::class  => function(BeforeExport $event) {
                $event->writer->setCreator(`Patrick`);
            },
            AfterSheet::class    => function(AfterSheet $event) {
                $event->sheet->setOrientation(PhpOfficePhpSpreadsheetWorksheetPageSetup::ORIENTATION_LANDSCAPE);

                $event->sheet->styleCells(
                    `B2:G8`,
                    [
                        `borders` => [
                            `outline` => [
                                `borderStyle` => PhpOfficePhpSpreadsheetStyleBorder::BORDER_THICK,
                                `color` => [`argb` => `FFFF0000`],
                            ],
                        ]
                    ]
                );
            },
        ];
    }
}

對於 PhpSpreadsheet 方法, 可檢視文件: https://phpspreadsheet.readthedocs.io/

測試 / Testing

The Excel facade can be used to swap the exporter to a fake.

測試下載

/**
* @test
*/
public function user_can_download_invoices_export() 
{
    Excel::fake();

    $this->actingAs($this->givenUser())
         ->get(`/invoices/download/xlsx`);

    Excel::assertDownloaded(`filename.xlsx`, function(InvoicesExport $export) {
        // Assert that the correct export is downloaded.
        return $export->collection()->contains(`#2018-01`);
    });
}

測試儲存匯出

/**
* @test
*/
public function user_can_store_invoices_export() 
{
    Excel::fake();

    $this->actingAs($this->givenUser())
         ->get(`/invoices/store/xlsx`);

    Excel::assertStored(`filename.xlsx`, `diskName`);

    Excel::assertStored(`filename.xlsx`, `diskName`, function(InvoicesExport $export) {
        return true;
    });

    // When passing the callback as 2nd param, the disk will be the default disk.
    Excel::assertStored(`filename.xlsx`, function(InvoicesExport $export) {
        return true;
    });
}

測試佇列匯出

/**
* @test
*/
public function user_can_queue_invoices_export() 
{
    Excel::fake();

    $this->actingAs($this->givenUser())
         ->get(`/invoices/queue/xlsx`);

    Excel::assertQueued(`filename.xlsx`, `diskName`);

    Excel::assertQueued(`filename.xlsx`, `diskName`, function(InvoicesExport $export) {
        return true;
    });

    // When passing the callback as 2nd param, the disk will be the default disk.
    Excel::assertQueued(`filename.xlsx`, function(InvoicesExport $export) {
        return true;
    });
}

相關文章