在Silverstripe 4中扩展CsvBulkloader和ModelAdmin类

时间:2018-08-19 13:14:03

标签: php silverstripe silverstripe-4

我希望扩展Silverstripe的CSVBulkLoader类,以便在导入之前/导入时进行一些业务逻辑。


WineAdmin类(扩展了ModelAdmin)中,我定义了一个自定义加载器,该加载器定义了 $ model_importers 属性:

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

WineCsvBulkLoader 类中, $ 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类,但是,列映射似乎无法正常工作,仅在数组中的[key]和[value]相同的情况下返回值。否则,导入的列为空。是什么原因造成的?

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

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

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

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

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中的记录
  • 添加必要的任何安全措施,以安全地使用此自定义类。
  • 导出带有BOM的CSV(字节顺序标记为UTF8)

我不是在寻找完整的答案,而是对任何见解表示赞赏。

1 个答案:

答案 0 :(得分:2)

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

根据CsvBulkLoader::findExistingObject中的逻辑,重复检查属性用于帮助查找现有记录以对其进行更新(而不是创建)。它将使用数组中定义的值,以便找到与给定值匹配的 first 记录并返回它。

  

当重复项时,$duplicateChecks属性实际上是做什么的?它会跳过记录吗?

没什么,它只会返回找到的第一条记录。

  

我可以在这里使用回调吗?

种类。您可以在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)
{
    // ....

注意:单元测试当前不覆盖重复检查回调。 CsvBulkLoaderTest中有一个待办事项可以添加它们。

  

$obj是最终的导入对象吗?如何处理?

您可以看到在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说“请注意,未使用columnMap”。但是,将优先考虑columnMap属性中的值为->myMethodName的值。在您链接到的文档以及框架的单元测试中的CustomLoader测试实现中,他们都使用以下语法专门针对该列的处理程序:

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

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

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

“是否是最终的导入对象”-是的。它在代码所在的循环之后被写入:

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

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

  

$val似乎是要导入的csv中的列的值。正确吗?

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

  

$record中包含什么?

这是在其上执行格式化回调后提供给CSV的记录的源行(为上下文提供)。


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