michaelhoughton / silverstripe-importexport
针对PHP 8.1的SilverStripe批量导入导出升级
Requires
- php: >=8.1
- goodby/csv: ^1.3
- silverstripe/admin: ^1.2
- silverstripe/assets: ^1.2
- silverstripe/framework: ^4.2
README
以各种形式从SilverStripe导入和导出数据,包括CSV。该模块作为框架中找到的BulkLoader功能的替代品/彻底改造。
此模块是原始模块的分支
加载过程
- 从源(
BulkLoaderSource
)获取原始数据。 - 数据作为可迭代的行提供(每行是标题->值的映射数组)。
- 行映射到基于用户/开发者提供的映射的标准格式。
- 数据设置/链接/转换为占位符DataObject。
- 现有记录替换占位符,或者占位符成为全新的DataObject。
- DataObject经过验证并保存。
- 所有结果存储在
BulkLoader_Result
中。
用户定义的列映射
用户可以选择哪些列映射到DataObject字段。这消除了定义标题或根据给定模式定义标题的需要。用户可以声明数据的第一行实际上是标题行。映射将在下次在同一个GridField上执行导入时保存。
网格字段导入器
这是一个网格字段组件,用户可以选择CSV文件并将其列映射到数据字段。
$importer = new GridFieldImporter('before'); $gridConfig->addComponent($importer);
导入器使用了CSVFieldMapper
,它显示了CSV的开始内容。
BulkLoaderSource
BulkLoaderSource
提供了一个迭代器来获取记录数据。数据可以来自任何地方,如CSV文件、Web API等。
它可以独立于BulkLoader使用来获取数据。
$source = new CsvBulkLoaderSource(); $source->setFilePath("files/myfile.csv") ->setHasHeader(true) ->setFieldDelimiter(",") ->setFieldEnclosure("'"); foreach($source->getIterator() as $record){ //do stuff }
(更好的)BulkLoader
- 从特定源保存数据并通过ORM持久化到数据库。
- 确定哪些字段可以映射,可以是根据模型制定的、由配置提供的,或两者兼而有之。
- 检测现有记录,并根据标准跳过或更新它们。
- 根据给定的映射将源数据映射到新/现有DataObject。
- 查找、创建并连接关系对象到对象。
- 可以在处理之前清除所有记录。
$source = new CsvBulkLoaderSource(); $source->setFilePath("files/myfile.csv"); $loader = new BetterBulkLoader("Product"); $loader->setSource($source); $loader->addNewRecords = false; // an option to skip new records $result = $loader->load();
ListBulkLoader
通常您会希望将批量加载限制在特定的DataList中。ListBulkLoader是BulkLoader的一个变体,它可以从给定的DataList中添加和删除记录。当然,DataList本身没有实现添加方法,所以您可能会发现它对HasManyList
更有用。
$category = ProductCategory::get()->first(); $source = new CsvBulkLoaderSource(); $source->setFilePath("productlist.csv"); $loader = new ListBulkLoader($category->Products()); $loader->setSource($source); $result = $loader->load();
将记录数据映射到标准格式
您可以通过提供columnMap
来将传入的记录映射到标准格式。
$loader->columnMap = array( 'first name' => 'FirstName', 'Name' => 'FirstName', 'bio' => 'Biography', 'bday' => 'Birthday', 'teamtitle' => 'Team.Title', 'teamsize' => 'Team.TeamSize', 'salary' => 'Contract.Amount' );
此列映射由GridFieldImporter
组件内的CSVFieldMapper
控件生成。
支架与定义可映射字段
如果没有自己定义,可映射字段将被支架。这包括关系上的字段,以便可以建立关系。
可能存在不希望映射的字段,在这种情况下,您应该在您的加载器上指定一个mappableFields
数组。
$loader->mappableFields = array( 'FirstName' => 'First Name', 'Surname' => 'Last Name', 'Biography' => 'Biography', 'Birthday' => 'Birthday', 'Team.Title' => 'Team' );
转换传入的记录数据
您可能想要对传入的记录数据进行一些转换。这可以通过指定针对记录字段名称的回调来完成。
$loader->transforms = array( 'Code' => array( 'callback' => function($value, $placeholder) { //capitalize course codes return strtoupper($value); } ) );
要求特定数据存在
缺少必要数据的记录将被跳过。
$loader->transforms = array( 'Title' => array( 'required' => true ) );
请注意,默认情况下会跳过空记录。
创建和链接相关数据对象
批量加载器可以通过提供回调或使用Relation.FieldName
风格的“点表示法”来处理链接和创建has_one
关系对象。关系处理也在transformations
数组中执行。
您可以在批量加载器级别指定是否创建和链接记录,然后您可以指定每个字段的操作行为。默认行为是同时链接和创建关系对象。
以下是一些配置示例
$loader->transforms = array( //link and create courses 'Course.Title' = array( 'link' => true, 'create' => true ), //only link to existing tutors 'Tutor.Name' => array( 'link' => true, 'create' => false ), //custom way to find parent courses 'Parent' => array( 'callback' => function($value, $placeholder) use ($self){ return Course::get() ->filter("Title", $value) ->first(); } ) );
请注意,上述示例中的$placeholder
指的是一个用于填充以便保存或检查重复项的虚拟数据对象。您不应该在回调中调用$placeholder->write()
。
指定关系列表
与您可能使用ListBulkLoader
将记录约束到给定的数据列表一样,您也可能希望将关系记录约束到列表中。
$loader->transforms = array( //link and create courses 'Course.Title' = array( 'list' => $self->Courses() ) );
确定何时覆盖现有的(重复的)数据对象
对记录数据进行重复检查,映射到标准化的形式。
您可以对数据字段执行重复检查
//course is a duplicate when title is the same $loader->duplicateChecks = array( "Title" );
或对关系进行重复检查
//course selection is a duplicate when course is the same $loader->duplicateChecks = array( "Course.Title" );
也可以使用回调函数找到重复项
$loader->duplicateChecks = array( "FooBar" => array( "callback" => function($fieldName, $record) { if(!isset($record["FirstName"]) || !isset($record["LastName"])){ return null; } return Person::get() ->filter("FirstName", $record['FirstName']) ->filter("LastName", $record['LastName']) ->first(); } ) );
导入期间发布页面
如果您正在导入SiteTree实例,可以使用此配置自动发布这些页面
$loader->setPublishPages(true);
替换所有“遗留”的ModelAdmin导入器
一些简单的yaml配置选项,有助于替换所有导入功能。
ModelAdmin: removelegacyimporters: true addbetterimporters: true
仅移除(非自定义)的scaffolded导入器
ModelAdmin: removelegacyimporters: scaffolded
故障排除
缺少关系对象
如果在加载过程中写入关系对象并失败验证,加载器将简单地忽略该关系对象。
多个关系数据字段未映射到同一关系
如果您已将多个字段映射到同一关系,您可能会遇到将不正确的关系对象连接起来的情况。第一个映射的字段与用于查找关系的字段相同。例如,您可能希望使用标题来查找/创建关系,然后添加一个金额到该同一关系,而不是通过金额查找/创建关系并设置标题。
使用mappableFields数组定义正确的顺序以修复此问题。
贡献
请尽可能为此模块做出贡献。查看问题和里程碑以了解需要做什么。
许可证
MIT
作者
Jeremy Shipman (http://jeremyshipman.com)