ttree / contentrepositoryimporter
帮助导入 Neos 内容存储数据的辅助包
Requires
- cocur/slugify: ^2.5
- ezyang/htmlpurifier: ^4.10.0
- neos/neos: ^7.0 || ^8.0
README
本包包含通用的工具,用于帮助导入 Neos 内容存储中的数据。
包含哪些内容?
- 命令控制器(CLI),用于启动导入预设
- 基于简单的约定
- DataProvider:用于从外部源准备和清理数据
- 导入器:从 DataProvider 获取数据并将所有内容推送到 CR
- DataType:用于清理值并在 DataProvider 之间共享代码的简单对象
- 将导入分割成多个子命令,以避免高内存使用
- 没有太大的魔法,您总是可以通过覆盖默认配置和方法来控制
基本 DataProvider
每个数据提供者都必须扩展 DataProvider
抽象类或实现 DataProviderInterface
接口。检查抽象数据提供者的源代码,那里有一些有用的东西可以探索。
当您从外部源处理数据时,更新 count
属性很重要。在处理过程中,您可以决定跳过一些数据(无效数据、缺失值等),因此我们不能使用 SQL count 功能。
尽量在数据提供者中做大部分的数据清理,以便数据到达导入器时已经准备好插入。基本上,提供者构建的数组应该包含与您的节点类型属性名匹配的属性名。如果您需要传输不会匹配节点属性的值,请用 '_' 前缀。
有一些魔法值,这些值必须位于数组的顶层
- __identifier(可选)此 UUID 将用于导入的节点,您应该使用
AbstractImporter::applyProperties
来启用此功能,默认使用 - __externalIdentifier(必需)数据的对外标识符,这一点非常重要。该包跟踪导入的数据
- __label(必需)此记录的标签,由导入器主要用于记录(此值不导入,但有助于跟踪过程)。如果您运行两次相同的导入,导入的节点将被更新而不是创建。
提示:如果您的节点属性不在数组的顶层,您可以覆盖 AbstractImporter::getPropertiesFromDataProviderPayload
方法
提供者的输出
您的提供者应该输出类似以下内容
[
'__label' => 'The external content lable, for internal use'
'__externalIdentifier' => 'The external external identifier, for internal use'
'title' => 'My title'
'year' => 1999
'text' => '...'
]
提示:如果您的提供者不返回数组,您必须注册一个 TypeConverter 来将其转换为数组。属性映射器由导入器自动使用。
内容维度支持
如果您的数据提供者遵循此约定,导入器可以自动创建您的节点变体
[
'__label' => 'The external content lable, for internal use'
'__externalIdentifier' => 'The external external identifier, for internal use'
'title' => 'My title'
'year' => 1999
'text' => '...',
'@dimensions' => [
'@en' => [
'@strategy' => 'merge',
'title' => '...',
],
'@fr' => [
'@strategy' => 'merge',
'title' => '...',
],
]
]
@en
是预设名称,您必须在 Settings.yaml
中配置预设
Ttree:
ContentRepositoryImporter:
dimensionsImporter:
presets:
fr:
language: ['fr', 'en', 'de']
en:
language: ['en', 'de']
de:
language: ['de']
在预设部分之间共享数据
您可以将导入操作分成多个部分。每个部分会在单独的请求中执行。有时,在不同部分之间共享数据是有用的(例如,在第一部分中导入分类,在第二部分中映射文档与分类)。这些解决方案可以解决此用例,我们集成了一个名为保险库的功能。保险库简单来说是一个缓存,可以在导入器和数据提供者中通过调用$this->vault->set($key, $name)
和$this->vault->get($key)
来访问。当前的预设是命名空间,因此您可以使用简单的键,如名称、ID等。
如果您调用flow import:init --preset your-preset
,缓存将被刷新。
基本提供者
class BasicDataProvider extends DataProvider { /** * @return array */ public function fetch() { $result = []; $query = $this->createQuery() ->select('*') ->from('demo_table', 'd') ->orderBy('d.name'); $statement = $query->execute(); while ($demoRecord = $statement->fetch()) { $result[] = [ '__externalIdentifier' => (integer)$demoRecord['id'], 'name' => String::create($demoRecord['name'])->getValue() ]; } $this->count = count($result); return $result; } }
一个基本的导入器
每个数据导入器都必须扩展抽象类AbstractImporter
或实现接口ImporterInterface
。
在processRecord
方法中,您将处理每条记录的处理,例如为每个传入的数据记录创建内容库节点。
不要忘记使用registerNodeProcessing
注册已处理的节点。该方法将处理诸如日志记录和跟踪导入节点等功能,以决定是否需要创建或更新本地节点。
class ProductImporter extends AbstractImporter { /** * @var string */ protected $externalIdentifierDataKey = 'productNumber'; /** * @var string */ protected $labelDataKey = 'properties.name'; /** * @var string */ protected $nodeNamePrefix = 'product-'; /** * @var string */ protected $nodeTypeName = 'Acme.Demo:Product'; /** * Starts batch processing all commands * * @return void * @api */ public function process() { $this->initializeStorageNode('shop/products', 'products', 'Products', 'products'); $this->initializeNodeTemplates(); $nodeTemplate = new NodeTemplate(); $this->processBatch($nodeTemplate); } }
一个基本的预设
您可以在Settings.yaml
中配置导入预设。预设分为多个部分。如果您使用batchSize
,当前部分将通过子CLI请求批量执行。这可以解决大型导入的内存或性能问题。
Ttree: ContentRepositoryImporter: sources: default: host: localhost driver: pdo_mysql dbname: database user: user password: password extraSourceDatabase: host: localhost driver: pdo_mysql dbname: database user: user password: password presets: 'base': parts: 'news': label: 'News Import' dataProviderClassName: 'Your\Package\Importer\DataProvider\NewsDataProvider' importerClassName: 'Your\Package\Importer\Importer\NewsImporter' 'page': label: 'Page Import' dataProviderClassName: 'Your\Package\Importer\DataProvider\PageDataProvider' dataProviderOptions: source: 'extraSourceDatabase' someOption: 'Some option that will be available in the options property of the data provider' importerClassName: 'Your\Package\Importer\Importer\PageImporter' importerOptions: siteNodePath: '/sites/my-site' someOption: 'Some option that will be available in the options property of the importer' batchSize': 120 'pageContent': label: 'Page Content Import' dataProviderClassName: 'Your\Package\Importer\DataProvider\PageContentDataProvider' importerClassName: 'Your\Package\Importer\Importer\PageContentImporter' batchSize: 120
开始您的导入过程
提示:不要忘记从您进行导入的包中引入此包,以确保正确的加载顺序,这样设置就会被正确覆盖。
从CLI
flow import:batch --preset base
您还可以筛选预设步骤
flow import:batch --preset base --parts page,pageContent
为了测试目的或如果您想覆盖预设中定义的值,您还可以指定在隔离的子进程中一次应导入的记录数
flow import:batch --preset base --batch-size 50
传递超过参数给数据提供者
导入过程支持向DataProvider
传递未命名的超过参数。如果您想允许只导入一条记录,这可能很有用。
flow import:batch --preset base recordIdentifier:1234
超过参数将通过$this->getExceedingArguments()
在DataProvider
中可用。您需要自行处理这些数据并将其应用到您的抓取逻辑中。
基于命令的导入器
某些数据源可能由命令而不是数据记录组成。例如,一个JSON文件可能包含create
、update
和delete
指令,这可以减少导入器端的猜测工作,例如哪些记录可能是新的,哪些应该更新,以及记录不存在是否意味着应该从内容库中删除相应的节点。
对于这些情况,您可以扩展AbstractCommandBasedImporter
。如果您的数据记录包含一个mode
字段,导入器将尝试调用同一类中的相应命令方法。
以下是一个数据源文件示例
[ { "mode": "create", "mpn": "1081251137", "languageIdentifier": "de", "properties": { "label": "Coffee Machine", "price": "220000", "externalKey": "1081251137" } }, { "mode": "delete", "mpn": "591500202" } ]
一个对应的ProductImporter
可能看起来像这样
/** * Class ProductImporter */ class ProductImporter extends AbstractCommandBasedImporter { /** * @var string */ protected $storageNodeNodePath = 'products'; /** * @var string */ protected $storageNodeTitle = 'Products'; /** * @var string */ protected $externalIdentifierDataKey = 'mpn'; /** * @var string */ protected $labelDataKey = 'properties.Label'; /** * @var string */ protected $nodeNamePrefix = 'product-'; /** * @var string */ protected $nodeTypeName = 'Acme.MyShop:Product'; /** * Creates a new product * * @param string $externalIdentifier * @param array $data * @return void */ protected function createCommand($externalIdentifier, array $data) { $this->applyProperties($data['properties'], $this->nodeTemplate); $node = $this->storageNode->createNodeFromTemplate($this->nodeTemplate); $this->registerNodeProcessing($node, $externalIdentifier); } /** * Updates a product * * @param string $externalIdentifier * @param array $data * @return void */ protected function updateCommand($externalIdentifier, array $data) { $this->applyProperties($data['properties'], $this->nodeTemplate); $node = $this->storageNode->createNodeFromTemplate($this->nodeTemplate); $this->registerNodeProcessing($node, $externalIdentifier); } /** * Deletes a product * * @param string $externalIdentifier * @param array $data */ protected function deleteCommand($externalIdentifier, array $data) { // delete the product node } }
CSV数据提供者
此包附带了一个用于CSV文件的基本数据提供者,这对于许多场景来说已经足够了。此数据提供者的类名为Ttree\ContentRepositoryImporter\DataProvider\CsvDataProvider
。
以下选项可以传递给数据提供者
csvFilePath
:导入文件的完整路径和文件名csvDelimiter
:CSV文件中使用的分隔符(默认:,
)csvEnclosure
:用于封装值的字符(默认:"
)skipHeader
:如果CSV文件的第一行应该被忽略(默认:false)
以下是一个使用CSV数据提供者的预设示例
Ttree: ContentRepositoryImporter: presets: 'products': parts: 'products': label: 'Product Import' batchSize: 100 dataProviderClassName: 'Ttree\ContentRepositoryImporter\DataProvider\CsvDataProvider' dataProviderOptions: csvFilePath: '/tmp/Products.csv' csvDelimiter: ';' csvEnclosure: '"' skipHeader: true importerClassName: 'Acme\MyProductImporter\Service\Import\ProductImporter' importerOptions: siteNodePath: '/sites/wwwacmecom'
致谢
本开发由ttree ltd - neos 解决方案提供商赞助。
我们竭尽全力以满腔热情制作这个包,我们欢迎赞助、支持请求等,请联系我们。
许可证
根据GPLv3+许可,请参阅LICENSE