在 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 中记录的源行,在为上下文提供格式化回调后 运行。
我希望这对您有所帮助,并且您可以实现自己想要实现的目标!框架的这一部分可能最近没有太多人喜欢,所以请随时提出拉取请求以任何方式改进它,即使它只是文档更新!祝你好运。
我希望扩展 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 中记录的源行,在为上下文提供格式化回调后 运行。
我希望这对您有所帮助,并且您可以实现自己想要实现的目标!框架的这一部分可能最近没有太多人喜欢,所以请随时提出拉取请求以任何方式改进它,即使它只是文档更新!祝你好运。