Implement a System Transformer¶
Use this guide when adding a transformer for a new e-commerce system. A transformer takes the normalised DTOs produced by an integration and writes them into Conflux's unified Eloquent models.
Prerequisites¶
- The target system driver has a case in
App\Enums\SystemDriver - An integration for the system exists and implements
IntegrationContract(see Implement a System Integration)
1. Create the transformer class¶
Extend AbstractTransformer, in the same namespace and directory as the integration it belongs to (app/Integrations/<PlatformName>/ — see Implement a System Integration). The default store() implementation handles customers, orders, and products out of the box — only override it if the platform needs different mapping logic.
<?php
declare(strict_types=1);
namespace App\Integrations\Acme;
use App\Services\AbstractTransformer;
class AcmeTransformer extends AbstractTransformer {}
That is sufficient for a platform whose data maps cleanly to the unified models — the platform-specific field mapping (raw API response → DTO) lives on public static methods on this same class instead, called directly by the integration; see Implement a System Integration, step 3.
2. Override store() for platform-specific logic¶
If a platform requires custom field mapping, a different dedup key, or pre-processing, override store():
use App\Data\CustomerData;
use App\Data\OrderData;
use App\Data\ProductData;
use App\Models\SystemConnection;
use App\Models\Customer;
class AcmeTransformer extends AbstractTransformer
{
protected function store(CustomerData|OrderData|ProductData $item, SystemConnection $connection): void
{
if ($item instanceof CustomerData) {
// platform-specific mapping
Customer::updateOrCreate(
['system_connection_id' => $connection->id, 'source_id' => $item->sourceId],
[
'source_system' => $item->sourceSystem,
'email' => strtolower($item->email), // normalise for Acme
'external_data' => $item->customAttributes->isEmpty() ? null : $item->customAttributes,
],
);
return;
}
parent::store($item, $connection); // fall back to default for orders and products
}
}
3. Use the transformer¶
Call transform() with the collection returned by the integration and the active connection. The call never throws — failures are collected per item, not propagated.
use App\Integrations\Acme\AcmeTransformer;
$items = $integration->requestBulkExport($connection, $request);
$result = (new AcmeTransformer)->transform($items, $connection);
if ($result->failed > 0) {
foreach ($result->failures as $failure) {
Log::error('Transformation failed', [
'source_id' => $failure->item->sourceId,
'reason' => $failure->reason->getMessage(),
]);
}
}
4. Write integration tests¶
Test each entity type — that records are created on first run and updated on subsequent runs.
// tests/Integration/Integrations/Acme/AcmeTransformerTest.php
describe('transform customers', function (): void {
test('stores a customer in the database', function (): void {
$connection = SystemConnection::factory()->create();
/** @var Collection<int, CustomerData|OrderData|ProductData> $data */
$data = collect([CustomerData::from([
'source_id' => 'acme-cust-1',
'source_system' => 'acme',
'email' => 'jane@example.com',
'created_at' => '2024-01-01',
'updated_at' => '2024-01-01',
])]);
$result = (new AcmeTransformer)->transform($data, $connection);
expect($result->succeeded)->toBe(1);
$this->assertDatabaseHas('customers', [
'system_connection_id' => $connection->id,
'source_id' => 'acme-cust-1',
'email' => 'jane@example.com',
]);
});
test('upserts customer on repeated transform', function (): void {
$connection = SystemConnection::factory()->create();
$transformer = new AcmeTransformer;
$makeData = fn (string $email): Collection => collect([CustomerData::from([
'source_id' => 'acme-cust-1',
'source_system' => 'acme',
'email' => $email,
'created_at' => '2024-01-01',
'updated_at' => '2024-01-01',
])]);
$transformer->transform($makeData('old@example.com'), $connection); // @phpstan-ignore argument.type
$transformer->transform($makeData('new@example.com'), $connection); // @phpstan-ignore argument.type
expect(Customer::count())->toBe(1);
$this->assertDatabaseHas('customers', ['email' => 'new@example.com']);
});
});
See Also¶
- Transformer Contract reference — full specification for models and value objects
- Transformer Pipeline design — reasoning behind the design decisions
- Implement a System Integration — the upstream step that produces DTOs for the transformer