Skip to content

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