Nhảy tới nội dung

Excel

Module Excel tích hợp PhpSpreadsheet vào framework, hỗ trợ export và import file Excel/CSV với API theo kiểu concern-based.

Cài đặt

composer require phpoffice/phpspreadsheet

Đăng ký service provider trong bootstrap/app.php:

use Vietiso\Core\Excel\ExcelServiceProvider;

$app->register(ExcelServiceProvider::class);

Export

Tạo Export class

Tạo một class implement interface nguồn dữ liệu và dùng trait Exportable:

use Vietiso\Core\Excel\Concerns\FromCollection;
use Vietiso\Core\Excel\Concerns\WithHeadings;
use Vietiso\Core\Excel\Concerns\Exportable;

class UsersExport implements FromCollection, WithHeadings
{
use Exportable;

public function collection(): iterable
{
return User::all();
}

public function headings(): array
{
return ['ID', 'Họ tên', 'Email', 'Ngày tạo'];
}
}

Download file

use Vietiso\Core\Excel\Facade\Excel;

// Qua Facade
return Excel::download(new UsersExport, 'users.xlsx');

// Qua trait Exportable
return (new UsersExport)->download('users.xlsx');

Lưu file lên disk

// Lưu vào local filesystem
Excel::store(new UsersExport, 'exports/users.xlsx');

// Lưu lên disk cụ thể (s3, ftp, ...)
Excel::store(new UsersExport, 'exports/users.xlsx', disk: 's3');

// Qua trait
(new UsersExport)->store('exports/users.xlsx', disk: 's3');

Lấy raw content

$content = Excel::raw(new UsersExport, Excel::XLSX);

Định dạng file hỗ trợ

Hằng sốĐịnh dạng
Excel::XLSXExcel 2007+ (.xlsx) — mặc định
Excel::XLSExcel 97-2003 (.xls)
Excel::CSVCSV (.csv)
Excel::ODSOpenDocument (.ods)
Excel::HTMLHTML (.html)

Nguồn dữ liệu Export

FromCollection

Trả về bất kỳ iterable nào (array, Collection,...):

class UsersExport implements FromCollection
{
public function collection(): iterable
{
return User::where('active', 1)->get();
}
}

FromArray

Trả về plain PHP array:

class ReportExport implements FromArray
{
public function array(): array
{
return [
['Alice', 'alice@example.com'],
['Bob', 'bob@example.com'],
];
}
}

FromQuery

Trả về query builder — writer tự gọi ->get():

class UsersExport implements FromQuery
{
public function query(): mixed
{
return User::query()->orderBy('name');
}
}

FromGenerator

Tối ưu bộ nhớ cho dataset rất lớn — yield từng dòng:

class BigExport implements FromGenerator
{
public function generator(): \Generator
{
foreach (User::cursor() as $user) {
yield [$user->id, $user->name, $user->email];
}
}
}

Tùy chỉnh Export

WithMapping — biến đổi từng dòng

class UsersExport implements FromCollection, WithMapping
{
public function collection(): iterable
{
return User::all();
}

public function map(mixed $row): array
{
return [
$row->id,
$row->name,
$row->email,
$row->created_at->format('d/m/Y'),
];
}
}

WithStyles — định dạng ô

use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class UsersExport implements FromCollection, WithStyles
{
public function styles(Worksheet $sheet): array
{
return [
1 => ['font' => ['bold' => true, 'size' => 12]], // dòng tiêu đề
'A' => ['font' => ['italic' => true]], // cột A
];
}
}

WithColumnWidths — độ rộng cột

class UsersExport implements FromCollection, WithColumnWidths
{
public function columnWidths(): array
{
return ['A' => 10, 'B' => 30, 'C' => 40];
}
}

ShouldAutoSize — tự động co dãn cột

use Vietiso\Core\Excel\Concerns\ShouldAutoSize;

class UsersExport implements FromCollection, ShouldAutoSize
{
// ...
}

WithTitle — đặt tên sheet

use Vietiso\Core\Excel\Concerns\WithTitle;

class UsersExport implements FromCollection, WithTitle
{
public function title(): string
{
return 'Danh sách người dùng';
}
}

WithChunkExport — export theo chunk (tiết kiệm RAM)

Kết hợp với FromQuery để phân trang query tự động:

class UsersExport implements FromQuery, WithChunkExport
{
public function query(): mixed
{
return User::query();
}

public function chunkSize(): int
{
return 1000;
}
}

Export nhiều sheet

use Vietiso\Core\Excel\Concerns\WithMultipleSheets;

class UsersWorkbook implements WithMultipleSheets
{
public function sheets(): array
{
return [
new ActiveUsersSheet,
new InactiveUsersSheet,
];
}
}

Mỗi sheet là một export class thông thường:

class ActiveUsersSheet implements FromQuery, WithTitle, WithHeadings
{
public function title(): string { return 'Active'; }
public function headings(): array { return ['ID', 'Tên', 'Email']; }
public function query(): mixed { return User::where('active', 1); }
}

Import

Tạo Import class

use Vietiso\Core\Excel\Concerns\ToModel;
use Vietiso\Core\Excel\Concerns\WithHeadingRow;
use Vietiso\Core\Excel\Concerns\Importable;

class UsersImport implements ToModel, WithHeadingRow
{
use Importable;

public function model(array $row): mixed
{
return new User([
'name' => $row['ho_ten'],
'email' => $row['email'],
]);
}
}

Chạy import

use Vietiso\Core\Excel\Facade\Excel;

// Qua Facade
Excel::import(new UsersImport, 'uploads/users.xlsx');

// Qua trait Importable
(new UsersImport)->import('uploads/users.xlsx');

// Từ disk
Excel::import(new UsersImport, 'imports/users.xlsx', disk: 's3');

Đọc dữ liệu về array

$rows = Excel::toArray(new UsersImport, 'uploads/users.xlsx');

foreach ($rows as $row) {
// $row là array
}

Đích nhận dữ liệu Import

ToModel — lưu từng dòng thành model

class UsersImport implements ToModel
{
public function model(array $row): mixed
{
if (empty($row[1])) {
return null; // bỏ qua dòng này
}

return new User([
'name' => $row[0],
'email' => $row[1],
]);
}
}

ToCollection — nhận toàn bộ dữ liệu một lần

class UsersImport implements ToCollection
{
public array $users = [];

public function collection(iterable $rows): void
{
foreach ($rows as $row) {
$this->users[] = $row;
}
}
}

ToArray — nhận raw array

class UsersImport implements ToArray
{
public function array(array $array): void
{
// $array là toàn bộ dữ liệu của sheet
}
}

Tùy chỉnh Import

WithHeadingRow — dùng dòng đầu làm key

class UsersImport implements ToModel, WithHeadingRow
{
public function model(array $row): mixed
{
// key là heading đã được normalize (snake_case)
return new User([
'name' => $row['ho_ten'],
'email' => $row['email'],
]);
}
}

WithStartRow — bắt đầu đọc từ dòng chỉ định

use Vietiso\Core\Excel\Concerns\WithStartRow;

class UsersImport implements ToModel, WithStartRow
{
public function startRow(): int
{
return 3; // bỏ qua 2 dòng đầu
}
}

WithChunkReading — đọc theo chunk (tiết kiệm RAM)

class UsersImport implements ToModel, WithChunkReading
{
public function chunkSize(): int
{
return 500;
}
}

WithBatchInserts — lưu DB theo batch

class UsersImport implements ToModel, WithBatchInserts
{
public function batchSize(): int
{
return 200; // insert 200 records/lần
}
}

WithValidation — validate từng dòng

use Vietiso\Core\Excel\Concerns\WithValidation;

class UsersImport implements ToModel, WithHeadingRow, WithValidation
{
public function rules(array $data): array
{
return [
'ho_ten' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
];
}

public function customValidationMessages(): array
{
return [
'email.unique' => 'Email :input đã tồn tại trong hệ thống.',
];
}
}

SkipsOnFailure — bỏ qua dòng lỗi validation

use Vietiso\Core\Excel\Concerns\SkipsOnFailure;
use Vietiso\Core\Excel\Validators\Failure;

class UsersImport implements ToModel, WithValidation, SkipsOnFailure
{
public array $failures = [];

public function onFailure(Failure ...$failures): void
{
$this->failures = array_merge($this->failures, $failures);
}
}

Sau khi import, kiểm tra các dòng bị bỏ qua:

$import = new UsersImport;
$import->import('users.xlsx');

foreach ($import->failures as $failure) {
echo "Dòng {$failure->row()}: " . implode(', ', $failure->errors());
}

SkipsOnError — bỏ qua dòng ném exception

use Vietiso\Core\Excel\Concerns\SkipsOnError;

class UsersImport implements ToModel, SkipsOnError
{
public function onError(\Throwable $e): void
{
logger()->error('Import error: ' . $e->getMessage());
}
}

Events

Đăng ký event trên Export/Import

use Vietiso\Core\Excel\Concerns\WithEvents;
use Vietiso\Core\Excel\Events\AfterSheet;
use Vietiso\Core\Excel\Events\BeforeExport;

class UsersExport implements FromCollection, WithEvents
{
public function registerEvents(): array
{
return [
BeforeExport::class => function (BeforeExport $event) {
// chạy trước khi bắt đầu export
},
AfterSheet::class => function (AfterSheet $event) {
$event->sheet->getDelegate()->setTitle('Users');
},
];
}
}

Danh sách Events

EventThời điểm
BeforeExportTrước khi bắt đầu export
AfterExportSau khi export hoàn thành
BeforeWritingTrước khi ghi file
AfterWritingSau khi ghi file xong
BeforeSheetTrước khi bắt đầu xử lý một sheet
AfterSheetSau khi xử lý xong một sheet
BeforeImportTrước khi bắt đầu import
AfterImportSau khi import hoàn thành

Bảng tổng hợp Concerns

Export

Interface / TraitMô tả
FromCollectionDữ liệu từ iterable (array, Collection,...)
FromArrayDữ liệu từ plain PHP array
FromQueryDữ liệu từ query builder
FromGeneratorDữ liệu từ PHP Generator (ít tốn RAM nhất)
WithHeadingsThêm dòng tiêu đề
WithMappingBiến đổi từng dòng trước khi ghi
WithStylesĐịnh dạng ô/cột/dòng
WithColumnWidthsĐộ rộng cột
ShouldAutoSizeTự động co dãn cột
WithTitleĐặt tên sheet
WithMultipleSheetsExport nhiều sheet
WithChunkExportExport theo chunk, tiết kiệm RAM
WithEventsĐăng ký event listener
Exportable (trait)Thêm download(), store(), raw() vào class

Import

Interface / TraitMô tả
ToModelMap từng dòng thành model, tự động save
ToCollectionNhận toàn bộ dữ liệu một lần
ToArrayNhận raw array
WithHeadingRowDùng dòng đầu làm key
WithStartRowBắt đầu đọc từ dòng chỉ định
WithMappingBiến đổi từng dòng sau khi đọc
WithValidationValidate từng dòng
SkipsOnFailureBỏ qua dòng lỗi validation, nhận danh sách failures
SkipsOnErrorBỏ qua dòng ném exception
WithChunkReadingĐọc file theo chunk, tiết kiệm RAM
WithBatchInsertsLưu DB theo batch
WithMultipleSheetsImport nhiều sheet
WithEventsĐăng ký event listener
Importable (trait)Thêm import(), toArray(), toCollection()