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
可以實現多種關係. 例如 FromQuery
, FromCollection
, …
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. |
巨集
Writer
和 Sheet
都是可以進行巨集操作的,這意味著它可以很容易地擴充套件以滿足你的需要。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;
});
}