使用 Laravel 导出和导入平面文件,如同魔法般简单

v5.0.3 2023-02-15 09:35 UTC

This package is auto-updated.

Last update: 2024-09-15 13:12:21 UTC


README

TravisCI

安装

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