code-rhapsodie/ibexa-dataflow-bundle
Integrates Code Rhapsodie Dataflow Bundle into Ibexa 4.0+ to manage content imports from external sources. Provides a backoffice UI to create and schedule dataflow processes (one-off or recurring) with per-type options.
## Getting Started
### Minimal Steps to Begin
1. **Installation**:
```bash
composer require code-rhapsodie/ibexa-dataflow-bundle
Add bundles to config/bundles.php in the correct order:
CodeRhapsodie\DataflowBundle\CodeRhapsodieDataflowBundle::class => ['all' => true],
CodeRhapsodie\IbexaDataflowBundle\CodeRhapsodieIbexaDataflowBundle::class => ['all' => true],
Import routing:
# config/routing/ibexa_dataflow.yaml
_cr.ibexa_dataflow:
resource: '@CodeRhapsodieIbexaDataflowBundle/Resources/config/routing.yaml'
Database Setup: Follow Dataflow Bundle's schema update guide.
Queue Configuration: Configure the job runner as per Dataflow Bundle's Queue section.
First Dataflow:
Define a DataflowType (see Dataflow Bundle docs).
Example minimal DataflowType:
use CodeRhapsodie\IbexaDataflowBundle\Writer\ContentWriter;
use CodeRhapsodie\DataflowBundle\DataflowType\AbstractDataflowType;
class MyDataflowType extends AbstractDataflowType
{
public function __construct(private ContentWriter $contentWriter) {}
protected function buildDataflow(DataflowBuilder $builder, array $options): void
{
$builder->addWriter($this->contentWriter);
}
}
Tag it as a service:
services:
App\Dataflow\MyDataflowType:
tags:
- { name: coderhapsodie.dataflow.type }
Access UI: Navigate to Admin > Ibexa Dataflow in the Ibexa backoffice to create schedules.
Define DataflowType:
Extend AbstractDataflowType and implement buildDataflow() to chain readers, steps, and writers.
Example:
protected function buildDataflow(DataflowBuilder $builder, array $options): void
{
$builder
->addReader(new CsvReader($options['file_path']))
->addStep($this->contentStructureFactory->createStep($options['content_type']))
->addWriter($this->contentWriter);
}
Content Processing:
Use ContentWriter to handle Ibexa content creation/updates. Transform data into ContentCreateStructure or ContentUpdateStructure:
$builder->addStep(function ($data) {
return $this->contentStructureFactory->transform(
$data,
'remote-id-' . $data['id'],
'eng-GB',
'article',
42 // Parent location ID
);
});
Scheduled Jobs:
+1 day) and first run time in the UI.Filtering:
Use NotModifiedContentFilter to skip unchanged updates:
$builder->addStep($this->notModifiedContentFilter);
Error Handling:
Log exceptions via setLogger() on writers/filters:
$this->contentWriter->setLogger($this->logger);
Environment-Specific Config:
Store parent location IDs or sensitive options in .env.local:
DATAFLOW_PARENT_LOCATION_ID=42
Custom Field Types:
Extend AbstractFieldComparator for unsupported field types (e.g., ibexa_matrix):
class MatrixFieldComparator extends AbstractFieldComparator
{
protected function compareValues(Value $currentValue, Value $newValue): bool
{
return $currentValue->value === $newValue->value;
}
}
Register as a service:
services:
App\FieldComparator\MatrixFieldComparator:
tags:
- { name: coderhapsodie.ibexa_dataflow.field_comparator, fieldType: 'ibexa_matrix' }
UI Customization:
Override templates in templates/bundles/coderhapsodieibexadataflow/ to modify the admin UI.
Testing:
Mock ContentWriter and ContentStructureFactory in unit tests:
$this->contentWriter->expects($this->once())
->method('write')
->with($this->isInstanceOf(ContentCreateStructure::class));
Performance:
MODE_UPDATE_ONLY to avoid redundant checks for existing content.Bundle Loading Order:
Critical: CodeRhapsodieDataflowBundle must load before CodeRhapsodieIbexaDataflowBundle. Misordering causes service registration failures.
Siteaccess Flag:
Use --siteaccess (not --connection) for Dataflow commands:
php bin/console code-rhapsodie:dataflow:run my_dataflow --siteaccess=admin
Exception: dump-schema uses --connection.
Remote ID Conflicts:
Ensure remoteId in ContentStructureFactory::transform() is unique. Duplicate IDs cause overwrites or errors.
Field Type Mismatches:
NotModifiedContentFilter skips unsupported field types. Log warnings if relying on this filter:
if (!$this->notModifiedContentFilter->supports($fieldType)) {
$this->logger->warning('Unsupported field type: ', [$fieldType]);
}
Queue Stuck Jobs: Failed jobs may linger in the queue. Clear them manually:
php bin/console doctrine:query:sql "DELETE FROM dataflow_job WHERE status = 'failed' AND created_at < NOW() - INTERVAL '1 day'"
Parent Location ID:
Hardcoding IDs breaks across environments. Use parameters.yaml:
parameters:
dataflow.parent_location.article: '%env(ARTICLE_PARENT_LOCATION_ID)%'
Enable Debug Mode:
Add to config/packages/dev/monolog.yaml:
handlers:
dataflow:
type: stream
path: "%kernel.logs_dir%/dataflow.log"
level: debug
Log Dataflow Steps:
Inject LoggerInterface and log data at each step:
$builder->addStep(function ($data) use ($logger) {
$logger->debug('Processing data', ['data' => $data]);
// ...
});
Dry Runs:
Test dataflows without writing to Ibexa by mocking ContentWriter:
$writer = $this->createMock(ContentWriter::class);
$writer->method('write')->willReturnCallback(function ($structure) {
$this->logger->info('Would write:', [$structure->getRemoteId()]);
});
Schema Validation: Validate YAML options in the UI with a custom validator:
use Symfony\Component\Validator\Constraints as Assert;
$constraints = new Assert\Collection([
'options' => new Assert\All([
new Assert\Type('array'),
new Assert\NotBlank(),
]),
]);
UI Refresh:
Clear cache after adding new DataflowType services:
php bin/console cache:clear
Custom Writers:
Extend AbstractWriter to support non-content operations (e.g., taxonomy imports):
class TaxonomyWriter extends AbstractWriter
{
protected function process($data): void
{
// Custom logic
}
}
Dynamic Options:
Fetch options from external sources (e.g., API) in buildDataflow():
$options = $this->optionFetcher->fetch($options['source']);
Pre/Post Hooks:
Add hooks via Symfony events (e.g., dataflow.job.start):
services:
App\EventListener\DataflowHookListener:
tags:
- { name: kernel.event_listener, event: dataflow.job.start, method: onJobStart }
UI Extensions: Override Twig templates or add JavaScript via asset bundles:
// assets/dataflow.js
document.addEventListener('DOMContentLoaded',
How can I help you explore Laravel packages today?