在 Silverstripe 4 中扩展 CsvBulkloader 和 ModelAdmin Class

Extending CsvBulkloader and ModelAdmin Class in Silverstripe 4

我希望扩展 Silverstripe 的 CSVBulkLoader class 来执行一些业务逻辑 before/upon 导入。


WineAdmin Class(扩展 ModelAdmin)中,我有一个用 $model_importers[=59= 定义的自定义加载程序] 属性:

//WineAdmin.php
private static $model_importers = [
  'Wine' => 'WineCsvBulkLoader'
];

WineCsvBulkLoader Class 中,$columnMap 属性 将 CSV 列映射到 SS DataObject 列:

//WineCsvBulkLoader.php
use SilverStripe\Dev\CsvBulkLoader;

class WineCsvBulkLoader extends CsvBulkLoader 
{
   public $columnMap = [
        // csv columns              // SS DO columns
        'Item Number'               => 'ItemNumber',
        'COUNTRY'                   => 'Country',
        'Producer'                  => 'Producer',
        'BrandName'                 => 'BrandName',
         // etc
   ];
  • 当导入为 运行 时,正在调用 WineCsvBulkLoader class,但是,列映射似乎无法正常工作,仅在 [key ] 和数组中的 [value] 是相同的。否则,导入的列为空。可能是什么原因造成的?

此外,$duplicateChecks 属性 设置为查找重复项。

   public $duplicateChecks = [
     'ItemNumber' => 'ItemNumber'
   ];

}
  • $duplicateChecks 属性 在存在重复项时实际做什么?是否跳过记录?
  • 我可以在这里使用回调吗?

docs 中,我找到了一些示例方法的代码,该方法将一列中的数据分成两部分并将这些部分映射到 class 上的单独列:

public static function importFirstAndLastName(&$obj, $val, $record) 
{
   $parts = explode(' ', $val);
   if(count($parts) != 2) return false;
   $obj->FirstName = $parts[0];
   $obj->LastName = $parts[1];
}
  • $obj是最终的导入对象吗?它是如何处理的?
  • $val 似乎是正在导入的 csv 中列的值。对吗?
  • $record 中包含什么?

以下是我希望进行的一些额外改进:

  • 在导入时读取字节顺序标记(如果存在),并使用它做一些有用的事情
  • 导入后,检查是否有重复记录,如果有重复,我只想更新记录中发生变化的列。
  • 删除数据库中已有的记录,不在正在导入的 CSV 中的记录
  • 添加任何必要的安全措施以安全地使用此自定义 class。
  • 导出带 BOM 的 CSV(字节顺序标记为 UTF8)

我不是在寻找完整的答案,但很感激任何见解。

我将尝试根据 SilverStripe 4.2.0 回答您的一些问题:

根据 CsvBulkLoader::findExistingObject 中的逻辑判断,duplicateChecks 属性 用于帮助查找现有记录以更新它(而不是创建它)。它将使用数组中定义的值来查找 first 匹配给定值的记录和 return 它。

What does the $duplicateChecks property actually do when there is a duplicate? Does it skip the record?

没有,它只会 return 它找到的第一条记录。

Can I use callbacks here?

有点。您可以在 CsvBulkLoader 的实例上使用方法,但不能直接将回调传递给它(例如来自 _config.php 等)。示例:

public $duplicateChecks = [
    'YourFieldName' => [
        'callback' => 'loadRecordByMyFieldName'
    ]
];

/**
 * Take responsibility for loading a record based on "MyFieldName" property
 * given the CSV value for "MyFieldName" and the original array record for the row
 *
 * @return DataObject|false
 */
public function loadRecordByMyFieldName($inputFieldName, array $record)
{
    // ....

注意: duplicateChecks 回调当前未包含在单元测试中。 CsvBulkLoaderTest 中有一个待办事项来添加它们。

Is $obj the final import object? How does it get processed?

您可以在 CsvBulkLoader::processRecord:

中查看调用这些神奇方法的位置
if ($mapped && strpos($this->columnMap[$fieldName], '->') === 0) {
    $funcName = substr($this->columnMap[$fieldName], 2);
    $this->$funcName($obj, $val, $record);    // <-------- here: option 1
} elseif ($obj->hasMethod("import{$fieldName}")) {
    $obj->{"import{$fieldName}"}($val, $record); // <----- here: option 2
} else {
    $obj->update(array($fieldName => $val));
}

这实际上有点误导,特别是因为该方法的 PHPDoc 说 "Note that columnMap isn't used." 然而,优先级将给予 columnMap 属性 中的值 ->myMethodName.在您链接到的文档和框架单元测试中的 CustomLoader 测试实现中,它们都使用此语法专门针对该列的处理程序:

$loader->columnMap = array(
    'FirstName' => '->importFirstName',

在这种情况下,$obj 是您要更新的数据对象(例如 Member)。

如果不这样做,您可以在导入的 DataObject 上定义 importFirstName,然后上面代码中的 elseif 将调用该函数。在这种情况下,不会提供 $obj,因为您可以改用 $this

"Is it the final import object" - 是的。它在代码所在的循环之后写入:

// write record
if (!$preview) {
    $obj->write();
}

您的自定义函数需要将数据设置为 $obj(如果使用 importFieldName 样式则为 $this),但不写入数据。

$val seems to be the value of the column in the csv being imported. Is that correct?

是,在应用任何格式之后。

What is contained in $record?

它是 CSV 中记录的源行,在为上下文提供格式化回调后 运行。


我希望这对您有所帮助,并且您可以实现自己想要实现的目标!框架的这一部分可能最近没有太多人喜欢,所以请随时提出拉取请求以任何方式改进它,即使它只是文档更新!祝你好运。