real-media-technic-staudacher / laravel-flatfiles
使用 Laravel 导出和导入平面文件,如同魔法般简单
v5.0.3
2023-02-15 09:35 UTC
Requires
- php: >=8.0
- laravel/framework: ^9.0|^10.0
- league/csv: ^9.8
Requires (Dev)
- orchestra/testbench: ^7.0
- phpunit/phpunit: ^9.0
README
安装
composer require real-media-technic-staudacher/laravel-flatfiles:^5.0
要覆盖默认配置
php artisan vendor:publish
然后选择选项 Provider: RealMediaTechnicStaudacher\LaravelFlatfiles\FlatfileExportServiceProvider
。默认配置如下
return [ 'default' => env('FLATFILE_DRIVER', 'csv'), 'drivers' => [ 'csv' => [ 'charset' => 'UTF-8', 'delimiter' => ';', 'enclosure' => '"', 'bom' => true, 'force_enclosure' => false, 'ignore_sylk_exception' => false, ], ], ];
如您所见,目前只支持 CSV 导出 ;-)
使用示例 / 基本工作流程
// Implement FlatfileFields to define your export fields (See later sections for details) class ExportJob implements ShouldQueue, FlatfileFields { // FlatfileExporter magically find out, whether your auto-injecting method's class implement the FlatfileFields-interface! // If so, it use this field definition by default public function handle(FlatfileExport $flatfile, $exportFilepath = '/subfolderOnDisk/export.csv') { // Expose where to export the file. Based on file extension (ie. .csv) we select the proper exporter for you) $flatfile->to($exportFilepath, 'diskNameOrInstance'); $flatfile->addHeader(); // You may want to load any data globally to prevent database queries for each row or even cell $imagePaths = $this->imagepaths(); // Only needed for very custom contents in your flatfile! $flatfile->beforeEachRow(function (Model $model) use ($imagePaths) { // Do some very special magic to make custom image paths available for your "cells" for // each row. // Typically here you merge the globally loaded objects with the data you need for you cell // $model here is an eloquent model selected by queryToSelectEachRow() }); // Here we use a query builder (if you want to) and ensure to restrict memory usage by chunking $this->queryToSelectEachRow()->chunk(500, function ($chunk) use ($flatfile) { $flatfile->addRows($chunk); }); // Dont forget to properly "close" the operation by this command $flatfile->moveToTarget(); } // In your field definition to are supposed to "only" pick out loaded or prepared data instead of // doing complex calculations (See beforeEachRow()) public function fields(): array { return []; // Your field definition } // Return an elequent query builder and carefully eager load relations you will gonna use in your cells! protected function queryToSelectEachRow() { return Products::whereCategory(15)->with('images'); } }
加载导出
最简单的方法是在实现 FlatfileFields
接口时自动注入 FlatfileExport
// This will lookup for your field definition in the current class class ExportJob implements ShouldQueue, FlatfileFields { public function handle(FlatfileExport $flatfile) {} }
如果您想使用专门用于字段定义的类,或者从其他地方获取数组,请使用 withFields()
public function handle(FlatfileExport $flatfile) { $flatfile->withFields($objImplementingFlatfileFields); // Alternatively you can resolve and assign fields in one step // app(FlatfileExport::class, [$objImplementingFlatfileFields]); // app(FlatfileExport::class, ['fields' => $objImplementingFlatfileFields]); }
指定目标文件 / 位置
使用文件系统磁盘
- 这使您能够导出到所有可用的文件系统驱动程序
- 导出首先在本地/临时位置生成,然后流式传输到磁盘
$flatfile->to('relative/path/to/file-with-extension.csv', Storage::disk('name')); // Do export ... $flatfile->moveToTarget();
准备全局导出资源
...
遍历数据并将数据写入导出文件
- 预选将代表平面文件中单行的模型
- 通过此结果集分块处理以限制资源
public function handle() { $flatfile = app(FlatfileExport::class, [$this]); $flatfile->to($this->csvFilepath, $this->disk); // Optionally add a Header $flatfile->addHeader(); // Proposed way to step through a large result set $this->queryToSelectEachRow()->chunk(500, function ($chunk) use ($flatfile) { $flatfile->addRows($chunk); }); } protected function queryToSelectEachRow(): Builder { return CampaignModels::whereCampaignId($this->campaignId)->with(['model.product', 'campaign']); }
完成导出
$flatfile->moveToTarget();
定义字段
字段在实现 FlatfileFields
接口的对象/类中定义,因此有一个 public function field()
。直接在您的导出处理类中实现此函数,或在专用类中,如 DTO 类。
为什么?您可以在字段定义中添加回调,因此可以轻松地定义动态单元格
在您的 field()
方法中,您需要定义一个字段数组。这将生成一个平面文件,其中每个字段数组元素都有一个列。
假设我们加载了一个具有属性名为 product_name
的产品集合。定义如下
$fields = [ 'product_name' => 'Product Name' ];
字段定义非常灵活。最好通过自己学习示例来学习
$fields = [ 'relation.columnOfRelation' => 'Column Header Label', // Relations should be eager loaded [ 'label' => 'Label with special characters', 'column' => 'relation.columnOfRelation', // Value of param $value in callback (optional) 'callback' => function ($model, $value) { // Format cell values return $model->currencySign.' '.number_format($value); } ], 'attribute' => [ // Column name can also still be the key of the array 'label' => 'Label with special characters', 'callback' => function ($model, $value) {} ], 'Column header' => function() { // For callbacks the header label can also be specified in the key! Crazy... return 'static cell content'; }, 'Empty cell' => '', ];
每行一个关系
如果您有一个要放入一行并保留其父关系作为一行(如果它没有关系)的关系,可以使用以下方法
public function handle() { // ... // relation has to be loaded in items of course $items->each(function (Item $item) use ($export) { $export->addRowForEachRelation($item, ['relation', 'more.*.relations'], 'fieldAccessorAlias', true); }); // ... } public function fields(): array { return [ 'fieldAccessorAlias.fieldName' => 'Output of relation fieldName if it is existing', 'fieldAccessorAlias.fieldNameMoreRelations' => 'Output of more.*.relations fieldName if it is existing', ]; }
SYLK 文件格式错误
默认情况下,如果标题行中的第一个列名为 ID
,则会抛出异常。这背后的原因是 SYLK 格式化,在某些版本中不允许与 Microsoft Excel 完美打开。您可以通过配置 drivers.csv.ignore_sylk_exception
再次禁用此异常。
升级指南
从 v2 升级到 v3
- 添加了返回类型。也在接口中。所以主要需要检查的是接口:例如,
public function fields(): array;
。提示:在整个项目中搜索public function fields(
。 - 新命名空间。将导入从
LaravelFlatfiles\*
更改为RealMediaTechnicStaudacher\LaravelFlatfiles\*
- 更改了字段回调方法的回调参数的顺序,以防止在大多数调用中
$null
。现在:function (Asset $asset)
。提示:在您的编辑器中搜索function ($null
和所有function fields
。