Shopware 6.7 introduces significant enhancements to the import/export functionality, providing developers with powerful tools to create custom data synchronization solutions. This blog post explores the technical aspects of building custom import/export profiles, focusing on the new capabilities and best practices for implementation.
Understanding Shopware 6.7 Import/Export Architecture
The import/export system in Shopware 6.7 has been completely revamped to provide more flexibility and control over data processing. The core architecture now utilizes a plugin-based approach where custom profiles can be registered and executed programmatically.
Key Components
The import/export functionality is built around several core components:
- ImportExportProfile: The main profile entity that defines how data should be processed
- ImportExportLog: Tracks all import/export operations for debugging and monitoring
- DataConverter: Handles the transformation of data between formats
- EntityReader/Writer: Manages the actual reading and writing of entities
Creating Custom Import/Export Profiles
Step 1: Define Your Profile Structure
To create a custom import/export profile, you need to extend the existing import/export service. The first step involves defining your profile configuration:
<?php declare(strict_types=1);
namespace MyPlugin\Profile;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
use Shopware\Core\Framework\Plugin\Plugin;
use Shopware\Core\Framework\Util\Random;
class CustomImportExportProfile extends Plugin
{
public function install(InstallContext $context): void
{
// Register custom import/export profile
$this->registerCustomProfile($context);
}
private function registerCustomProfile(InstallContext $context): void
{
$profile = [
'name' => 'Custom Product Import',
'sourceEntity' => 'product',
'destinationEntity' => 'product',
'format' => 'csv',
'mapping' => [
['source' => 'id', 'target' => 'id'],
['source' => 'name', 'target' => 'name'],
['source' => 'price', 'target' => 'price'],
['source' => 'categories', 'target' => 'categories'],
],
'settings' => [
'delimiter' => ';',
'enclosure' => '"',
'escape' => '\\',
]
];
// Register profile in database
$this->createProfile($profile);
}
}
Step 2: Implement Data Conversion Logic
The heart of any import/export profile lies in its data conversion capabilities. Shopware 6.7 introduces new interfaces for implementing custom converters:
<?php declare(strict_types=1);
namespace MyPlugin\Converter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\ImportExport\Convert\ConvertContext;
use Shopware\Core\Framework\ImportExport\Convert\ConverterInterface;
use Shopware\Core\Framework\ImportExport\Convert\ConverterRegistry;
use Shopware\Core\Framework\ImportExport\Exception\InvalidDataException;
use Shopware\Core\Framework\ImportExport\ImportExportLog;
use Shopware\Core\Framework\ImportExport\ImportExportProfile;
class CustomProductConverter implements ConverterInterface
{
public function convert(array $data, ConvertContext $context): array
{
// Process and transform data according to your business logic
$convertedData = [];
foreach ($data as $row) {
$convertedRow = [
'id' => $row['id'] ?? null,
'name' => $this->processProductName($row['name'] ?? ''),
'price' => $this->processPrice($row['price'] ?? 0),
'categories' => $this->processCategories($row['categories'] ?? ''),
'custom_field' => $this->generateCustomField($row)
];
$convertedData[] = $convertedRow;
}
return $convertedData;
}
private function processProductName(string $name): string
{
// Custom processing logic for product names
return trim(htmlspecialchars_decode($name));
}
private function processPrice(float $price): array
{
// Process price according to your requirements
return [
'gross' => $price,
'net' => $price / 1.2,
'currencyId' => $this->getCurrencyId()
];
}
private function processCategories(string $categories): array
{
// Parse and convert category string to array
return explode(',', $categories);
}
private function generateCustomField(array $data): string
{
// Generate custom field based on source data
return md5(implode('|', $data));
}
}
Step 3: Register Custom Converters
To make your converters available within the import/export system, you need to register them properly:
<?php declare(strict_types=1);
namespace MyPlugin\Profile;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
use Shopware\Core\Framework\Plugin\Plugin;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class CustomImportExportProfile extends Plugin
{
public function build(ContainerBuilder $container): void
{
parent::build($container);
// Register custom converters
$container->register('my_plugin.custom_product_converter', CustomProductConverter::class)
->addTag('shopware.import_export.converter', ['type' => 'product']);
}
public function install(InstallContext $context): void
{
parent::install($context);
// Register the custom profile with the system
$this->registerProfile($context);
}
}
Advanced Configuration Options
Custom Entity Mapping
Shopware 6.7 allows for complex entity mapping through the use of custom mappers:
<?php declare(strict_types=1);
namespace MyPlugin\Mapper;
use Shopware\Core\Framework\ImportExport\Mapping\MapperInterface;
use Shopware\Core\Framework\ImportExport\Mapping\MappingContext;
class CustomEntityMapper implements MapperInterface
{
public function map(array $data, MappingContext $context): array
{
// Custom mapping logic for complex entity relationships
foreach ($data as &$row) {
if (isset($row['product_number'])) {
$row['custom_product_id'] = $this->generateProductId($row['product_number']);
}
if (isset($row['customer_group'])) {
$row['customer_group_id'] = $this->resolveCustomerGroup($row['customer_group']);
}
}
return $data;
}
private function generateProductId(string $number): string
{
// Generate custom product ID based on your logic
return 'PROD-' . strtoupper($number);
}
private function resolveCustomerGroup(string $group): ?string
{
// Resolve customer group name to ID
return match ($group) {
'vip' => 'vip-group-id',
'regular' => 'regular-group-id',
default => null,
};
}
}
Error Handling and Logging
Robust error handling is crucial for import/export operations. Shopware 6.7 provides enhanced logging capabilities:
<?php declare(strict_types=1);
namespace MyPlugin\ImportExport;
use Psr\Log\LoggerInterface;
use Shopware\Core\Framework\ImportExport\ImportExportLog;
use Shopware\Core\Framework\ImportExport\ImportExportProfile;
use Shopware\Core\Framework\ImportExport\Process\ImportProcess;
class CustomImportProcessor
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function processImport(array $data, ImportExportProfile $profile): array
{
$results = [];
$errors = [];
foreach ($data as $index => $row) {
try {
// Process individual row
$processedRow = $this->processRow($row);
$results[] = $processedRow;
$this->logger->info("Processed row {$index}", [
'profile' => $profile->getName(),
'row_data' => $row
]);
} catch (\Exception $e) {
$errors[] = [
'row_index' => $index,
'error_message' => $e->getMessage(),
'error_code' => $e->getCode()
];
$this->logger->error("Error processing row {$index}", [
'profile' => $profile->getName(),
'error' => $e->getMessage(),
'row_data' => $row
]);
}
}
return [
'results' => $results,
'errors' => $errors,
'total_rows' => count($data),
'successful_rows' => count($results)
];
}
private function processRow(array $row): array
{
// Your row processing logic here
return $row;
}
}
Performance Optimization
Batch Processing
For large datasets, implementing batch processing is essential:
<?php declare(strict_types=1);
namespace MyPlugin\Batch;
class BatchProcessor
{
private const BATCH_SIZE = 1000;
public function processLargeDataSet(array $data): array
{
$results = [];
$batches = array_chunk($data, self::BATCH_SIZE);
foreach ($batches as $batchIndex => $batch) {
$batchResults = $this->processBatch($batch);
$results = array_merge($results, $batchResults);
// Clear memory between batches
gc_collect_cycles();
}
return $results;
}
private function processBatch(array $batch): array
{
// Process batch of data
return array_map([$this, 'processRow'], $batch);
}
}
Memory Management
Proper memory management is crucial when dealing with large imports:
<?php declare(strict_types=1);
class MemoryOptimizedImporter
{
public function importWithMemoryManagement(string $filePath): void
{
// Set memory limit for the process
ini_set('memory_limit', '512M');
$handle = fopen($filePath, 'r');
if (!$handle) {
throw new \RuntimeException('Could not open file: ' . $filePath);
}
// Process file line by line
while (($line = fgets($handle)) !== false) {
$this->processLine($line);
// Clear memory periodically
if (ftell($handle) % 10000 === 0) {
gc_collect_cycles();
}
}
fclose($handle);
}
}
Integration with Existing Systems
API Integration
Custom import/export profiles can be integrated with external APIs:
<?php declare(strict_types=1);
namespace MyPlugin\ApiIntegration;
use GuzzleHttp\Client;
use Shopware\Core\Framework\ImportExport\ImportExportProfile;
class ApiBasedImporter
{
private Client $httpClient;
public function __construct(Client $httpClient)
{
$this->httpClient = $httpClient;
}
public function importFromApi(ImportExportProfile $profile, string $apiUrl): array
{
$response = $this->httpClient->get($apiUrl);
$data = json_decode($response->getBody()->getContents(), true);
// Process and transform API data
return $this->transformApiData($data);
}
private function transformApiData(array $data): array
{
// Transform API response to Shopware compatible format
$transformed = [];
foreach ($data as $item) {
$transformed[] = [
'id' => $item['id'],
'name' => $item['name'],
'price' => $item['price'],
'external_id' => $item['external_id']
];
}
return $transformed;
}
}
Best Practices and Recommendations
- Validate Data Early: Always validate data before processing to prevent errors downstream
- Use Transactions: Wrap import operations in database transactions for atomicity
- Implement Proper Error Handling: Log errors comprehensively for debugging purposes
- Monitor Performance: Track import/export performance and optimize accordingly
- Handle Large Datasets: Implement batch processing for large data sets
Conclusion
Shopware 6.7's enhanced import/export capabilities provide developers with unprecedented flexibility to create custom data synchronization solutions. By leveraging the new architecture, implementing custom converters, and following best practices, you can build robust import/export profiles that seamlessly integrate with your existing systems and business requirements.
The key to successful implementation lies in understanding the underlying architecture, properly handling data transformations, and ensuring optimal performance for large datasets. With these tools and techniques at your disposal, you can create sophisticated import/export solutions that meet even the most complex business requirements.