kczer/excel-importer

用于可配置EXCEL数据导入的库

3.1 2021-08-25 06:34 UTC

This package is auto-updated.

Last update: 2024-09-25 13:58:52 UTC


README

目录

Excel-importer

Excel-importer 是一个PHP库,它允许轻松导入EXCEL格式的数据并将其解析为对象。

安装

您可以使用composer安装它,如下所示

composer require kcze/excel-importer

文档

创建简单的EXCEL数据导入

首先,您需要创建一个扩展 Kczer\ExcelImporter\AbstractExcelImporter 的 Import 类。

对于版本 >= 3.0

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\AbstractExcelImporter;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\Configuration\ExcelCellConfiguration;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\IntegerExcelCell;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\StringExcelCell;
use Kczer\ExcelImporter\Exception\ExcelCellConfiguration\UnexpectedExcelCellClassException;

class MySimpleExcelImporter extends AbstractExcelImporter
{
    protected function configureExcelCells(): void
    {
        $this
            ->addExcelCell(StringExcelCell::class, 'Cell header name', 'A', false)
            ->addExcelCell(IntegerExcelCell::class, 'Another header name', 'B');
    }
        
    public function processParsedData(): void
    {
        // Since 3.1, you can pass callback as first argument to this function to modify the message
        // Separator is now the second parameter
        $excelRow->getMergedErrorMessage(static function(string $message): string {
            //Do something with the message
        }); 
        // The rest is same as before 3.0
    }
}

对于版本 < 3.0

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\AbstractExcelImporter;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\Configuration\ExcelCellConfiguration;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\IntegerExcelCell;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\StringExcelCell;
use Kczer\ExcelImporter\Exception\ExcelCellConfiguration\UnexpectedExcelCellClassException;

class MySimpleExcelImporter extends AbstractExcelImporter
{

    /**
     * @return ExcelCellConfiguration[]
     */
    protected function getExcelCellConfigurations(): array
    {
        // This method returns row configuration for Your import.
        // Keys are column keys (can be int indexed but it's not reccommended).
        /*
            Values are configurations that needs to be pased AbstractExcelClass
            extending class names, column names, and (optinoally) 
            tell if cell is required (with default to true).
        */
        return [
            'A' => new ExcelCellConfiguration(StringExcelCell::class, 'Cell header name', false),
            'B' => new ExcelCellConfiguration(IntegerExcelCell::class, 'Another header name'),
        ];
    }

    public function processParsedData(): void
    {
        $this->hasErrors(); // Check if any validation error occurred
        
        // We can do something with Data. 
        
        // Gets an array of ExcelRow objects
        $excelRows = $this->getExcelRows(); 
        foreach ($excelRows as $excelRow) {
            // The same as above for one row
            $excelRow->hasErrors(); 
            // Gets merged messages from all cells for current row by separator given in argument (default ' | ')
            $excelRow->getMergedErrorMessage(); 
            // Gets an array of ExcelCells (the same as configured in getExcelCellConfigurations())
            $excelCells = $excelRow->getExcelCells(); 
            // Gets value of current row 'A' cell
            $excelCells['A']->getValue(); 
            // Gets int-parsed value of current row 'B' cell
            $excelCells['B']->getValue(); 
            // Gets string representation of value ready to dispaly (the same value as given in EXCEL file for predefined ExcelCells)
            $excelCells['B']->getDisplayValue();
        }
    }
}

⚠️ 自版本 3.0 起不再支持 int 列键。

然后使用服务

<?php

use My\SampleNamespace\MySimpleExcelImporter;

$importer = new MySimpleExcelImporter();

// Second parameter tells whether to omit first row, or not (with default to true)
$importer->parseExcelData('some/file/path/excelFile.xlsx', true);
$importer->processParsedData();

⚠️ 自版本 3.0 起导入始终忽略第一行。

processParsedData() 中显示的所有方法都是公共的,因此可以从服务外部访问。如您在配置中看到的那样,使用了 StringExcelCell 和 IntegerExcelCell 类。这些类用于对EXCEL单元格值执行适当的解析和验证。您可以创建自己的 ExcelCell 类 - 更多信息见下文。

预定义的 ExcelCell 类

  • StringExcelCell - 简单的字符串值,不验证数据。getValue() 返回字符串。
  • IntegerExcelCell - 仅接受有效的整数。getValue() 返回整数。
  • FloatExcelCell - 仅接受有效的数字。getValue() 返回浮点数。
  • BoolExcelCell - 接受 'y', 'yes', 't', 'tak', 't', 'true'(不区分大小写)作为真。其他值视为假。getValue() 返回布尔值。
  • DateTimeExcelCell - 接受 DateTime 类构造函数接受的任何字符串。getValue() 返回 DateTime 对象。
  • AbstractDictionaryExcelCell - 适用于键值类型示例的抽象类。
  • AbstractMultipleDictionaryExcelCell - 可以将多个 AbstractDictionaryCell 字典合并为一个的抽象类。

恭喜!您已创建了自己的第一个简单的EXCEL导入类。虽然如此,使用 Excel-importer 进行导入还有更好的更快的方法。

将EXCEL数据映射到对象

在上一个例子中,导入的结果是 ExcelRow 对象数组,但如果我们想将EXCEL数据映射到某个模型对象呢?假设我们有一个名为 SomeModel 的PHP对象。我们可以通过扩展 Kczer\ExcelImporter\AbstractModelExcelImporter 来导入它。

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\AbstractModelExcelImporter;

class SomeModelImporter extends AbstractModelExcelImporter
{

    /**
     * @inheritDoc
     */
    public function processParsedData(): void
    {
        // Gets array of SomeModel objects
        // Note that models are created ONLY if $this->hasErrors() return false
        $this->getModels(); 
    }

    /**
     * @inheritDoc
     */
    protected function getImportModelClass(): string
    {
        return SomeModel::class;// Our model class 
    }
}

它几乎准备好了。要使其工作,我们需要进行一个额外的设置步骤。在我们的 SomeModel 类中

<?php

namespace My\SampleNamespace;

use DateTime;
use Kczer\ExcelImporter\Annotation\ExcelColumn;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\StringExcelCell;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\IntegerExcelCell;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\DateTimeExcelCell;

class SomeModel
{
    
    /** 
     * @ExcelColumn(cellName="Name", targetExcelCellClass=StringExcelCell::class, columnKey="A")
     * 
     * @var string 
     */
    private $name;

    /** 
     * @ExcelColumn(cellName="Code", targetExcelCellClass=IntegerExcelCell::class, columnKey="B")
     * 
     * @var int 
     */
    private $code;
    
    /**
     * @ExcelColumn(cellName="Some date", targetExcelCellClass=DateTimeExcelCell::class, columnKey="C", required=false)
     * 
     * @var  ?DateTime
     */
    private $someDate;

    
    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getCode(): int
    {
        return $this->code;
    }

    public function setCode(int $code): void
    {
        $this->code = $code;
    }

    public function getSomeDate(): ?DateTime
    {
        return $this->someDate;
    }

    public function setSomeDate(?DateTime $someDate): void
    {
        $this->someDate = $someDate;
    }
}

以下代码中最重要的部分是 ExcelColumn 注解类,它告诉导入器有关我们的模型数据类型、名称、列键(再次,键列可以省略,然后采用属性顺序,但不会被考虑)和单元格必填的信息。这两个类就是您需要做的全部。Excel导入器会做其余的工作,并为您创建模型实例。

DictionaryExcelCell

字典EXCEL单元格用于定义单元格可以有的“范围”值。当单元格值可以是例如数据库中某个资源的ID时,它非常完美。示例 DictionaryExcelCell 类

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\ExcelElement\ExcelCell\AbstractDictionaryExcelCell;

class SampleDictionaryExcelCell extends AbstractDictionaryExcelCell
{

    /**
     * @inheritDoc
     */
    protected function getDictionary(): array
    {
       return [
           1 => new User('user 1'),
           2 => new User('user 2'),
           3 => new User('user 3'),
           4 => new User('user 4'),
       ];
    }
}

现在,我们只需将此类添加到导入配置中,或将它添加到 ExcelColumn 注解中,Excel-importer 将只接受范围 1-4 的值,并且 getValue 将返回 User 对象。

MultipleDictionaryExcelCell

假设某一列可以包含来自一个字典或另一个字典的值。MultipleDictionaryExcelCell 是这种情况下的完美工具。

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\ExcelElement\ExcelCell\AbstractMultipleDictionaryExcelCell;

class SampleMultidictionaryExcelCell extends AbstractMultipleDictionaryExcelCell
{

    /**
     * @inheritDoc
     */
    public function getSubDictionaryExcelCellClasses(): array
    {
        return [
            SomeDictionaryClass::class,
            SomeOtherDictionaryClasss::class
        ];
    }
}

现在,Excel-import 接受来自 SomeDictionaryClass 和 SomeOtherDictionaryClasss 字典的值(如果键相交,则具有较低数组键的类具有优先权)。

自定义ExcelCellClasses

如果您希望ExcelCell类更加灵活或需要进行更多验证,您只需扩展AbstractExcelCellClass并创建自定义验证和返回数据类型。在示例中,我们将创建需要是有效电子邮件的单元格。

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\ExcelElement\ExcelCell\AbstractExcelCell;

class RegexValidatableExcelCell extends AbstractExcelCell
{

    /**
     * returned value will be returned by the getValue() method
     * Note, that getValue() will return this value only if cell doesn't contain any error
     */
    protected function getParsedValue(): ?string
    {
        // In this case, we don't want to do any parsing as string is proper data type for email address
        return $this->rawValue;
    }

    /**
     * Method should return null if value is valid,
     * or string with error message if not
     */
    protected function validateValueRequirements(): ?string
    {
        // We can access the raw string value with $this->rawValue
        // Note that the raw value will be null in case of empty cell
        if (filter_var($this->rawValue, FILTER_VALIDATE_EMAIL) === false) {
            
            // Below method creates error message in format [cellName] - [given_message]
            return $this->createErrorMessageWithNamePrefix('Value is not a valid email address');
        }
        
        return null;
    }
}

更复杂的导入

有时,我们需要验证行内单元格之间的依赖关系,甚至行之间的依赖关系。我们也可以这样做。AbstractExcelImporter实现了可被覆盖的checkRow checkRowRequirements()方法,可以用来检查所需的依赖关系并在需要时添加一些错误。该方法在AbstractModelExcelImporter创建模型之前被调用,因此我们仍然能够从EXCEL数据中创建对象。

依赖关系验证示例

假设我们有一个模型

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\Annotation\ExcelColumn;
use Kczer\ExcelImporter\ExcelElement\ExcelCell\IntegerExcelCell;

class SampleModelClass
{
    /**
     * @ExcelColumn(cellName="Number 1", targetExcelCellClass=IntegerExcelCell::class, columnKey="A")
     *
     * @var int
     */
    private $num1;

    /**
     * @ExcelColumn(cellName="Number 1", targetExcelCellClass=IntegerExcelCell::class, columnKey="B")
     *
     * @var int
     */
    private $num2;


    public function getNum1(): int
    {
        return $this->num1;
    }
    public function setNum1(int $num1): void
    {
        $this->num1 = $num1;
    }
    public function getNum2(): int
    {
        return $this->num2;
    }
    public function setNum2(int $num2): void
    {
        $this->num2 = $num2;
    }
}

假设num1应该大于num2。我们可以这样验证这个依赖关系

<?php

namespace My\SampleNamespace;

use Kczer\ExcelImporter\AbstractModelExcelImporter;

class DependencyValidationExcelImport extends AbstractModelExcelImporter
{

    protected function checkRowRequirements(): void
    {
        foreach ($this->getExcelRows() as $excelRow) {
            $exclCells = $excelRow->getExcelCells();
            if ($exclCells['A']->getValue() <= $exclCells['B']->getValue()) {
                
                $excelRow->addErrorMessage('Number 1 should be bigger than Number 2');
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function processParsedData(): void
    {
        // TODO: Implement processParsedData() method.
    }

    /**
     * @inheritDoc
     */
    protected function getImportModelClass(): string
    {
        return SampleModelClass::class;
    }
}

如果验证添加了任何错误,那么EXCEL将被视为无效,因此模型将不会被创建。

数据编码

如果您想对导入器中的数据进行编码(例如,与请求一起发送),可以这样做

<?php
$serializedInstance = $importer->serializeInstance();

... 然后重新创建导入器实例

<?php
MyImporterClass::createFromSerialized($serializedInstance);

从版本3.0开始,也可以将行作为JSON获取

<?php
$rowsJson = $importer->getExcelRowsAsJson();

... 然后从该JSON重新创建行

<?php
$importer->parseJson($rowsJson);