Skip to content

Reverse Mapping

Reverse mapping enables bidirectional data transformation using a single template definition with the Fluent API.

Reverse mapping provides bidirectional data transformation:

  • Forward mapping: Transform data from source to target using ->map()
  • Reverse mapping: Transform data from target back to source using ->reverseMap()

This is particularly useful when:

  • Converting between Dtos and domain models
  • Synchronizing data between different formats
  • Implementing undo/redo functionality
  • Building bidirectional API transformations

The Fluent API provides ->reverseMap() method that reverses the template direction:

use event4u\DataHelpers\DataMapper;
$template = [
'profile' => [
'name' => '{{ user.name }}',
'email' => '{{ user.email }}',
],
];
// Forward: user -> profile
$userData = ['user' => ['name' => 'John', 'email' => 'john@example.com']];
$result = DataMapper::source($userData)
->template($template)
->map()
->getTarget();
// Result: ['profile' => ['name' => 'John', 'email' => 'john@example.com']]
// Reverse: profile -> user (using the SAME template!)
$profileData = ['profile' => ['name' => 'Jane', 'email' => 'jane@example.com']];
$result = DataMapper::source($profileData)
->template($template)
->reverseMap()
->getTarget();
// Result: ['user' => ['name' => 'Jane', 'email' => 'jane@example.com']]

Templates are reversed automatically when using ->reverseMap():

$template = [
'profile' => [
'name' => '{{ user.name }}',
'email' => '{{ user.email }}',
],
];
// Forward: user -> profile
$userData = ['user' => ['name' => 'John', 'email' => 'john@example.com']];
$profile = DataMapper::source($userData)
->template($template)
->map()
->getTarget();
// Result: ['profile' => ['name' => 'John', 'email' => 'john@example.com']]
// Reverse: profile -> user
$profileData = ['profile' => ['name' => 'Jane', 'email' => 'jane@example.com']];
$user = DataMapper::source($profileData)
->template($template)
->reverseMap()
->getTarget();
// Result: ['user' => ['name' => 'Jane', 'email' => 'jane@example.com']]

Execute reverse mapping using the configured template.

$source = ['profile' => ['name' => 'John']];
$template = ['name' => '{{ profile.name }}'];
$result = DataMapper::source($source)
->template($template)
->reverseMap();

Returns: DataMapperResult with reversed mapping

Methods on Result:

  • getTarget() - Get the reversed target data
  • getSource() - Get the original source data
  • getTemplate() - Get the template used

All standard DataMapper configuration options work with reverse mapping:

$source = ['profile' => ['name' => 'John']];
$template = ['name' => '{{ profile.name }}'];
$result = DataMapper::source($source)
->template($template)
->skipNull(true) // Skip null values
->reindexWildcard(false) // Don't reindex arrays
->reverseMap();
$template = [
'id' => '{{ user.id }}',
'name' => '{{ user.name }}',
'email' => '{{ user.email }}',
];
// Forward: Model -> Dto
$userModel = ['id' => 1, 'name' => 'John', 'email' => 'john@example.com'];
$userDto = DataMapper::source(['user' => $userModel])
->target(UserDto::class)
->template($template)
->map()
->getTarget();
// Reverse: Dto -> Model
$userModel = DataMapper::source(['user' => $userDto])
->target(User::class)
->template($template)
->reverseMap()
->getTarget();
$template = [
'user' => [
'firstName' => '{{ request.first_name }}',
'lastName' => '{{ request.last_name }}',
'email' => '{{ request.email }}',
],
];
// Forward: API request -> Internal format
$apiRequest = ['first_name' => 'John', 'last_name' => 'Doe', 'email' => 'john@example.com'];
$internal = DataMapper::source(['request' => $apiRequest])
->template($template)
->map()
->getTarget();
// Reverse: Internal format -> API response
$apiResponse = DataMapper::source($internal)
->template($template)
->reverseMap()
->getTarget();
$template = [
'external' => [
'user_id' => '{{ internal.userId }}',
'user_name' => '{{ internal.userName }}',
],
];
// Sync to external system
$internalData = ['userId' => 123, 'userName' => 'john_doe'];
$externalData = DataMapper::source(['internal' => $internalData])
->template($template)
->map()
->getTarget();
// Sync back from external system
$internalData = DataMapper::source($externalData)
->template($template)
->reverseMap()
->getTarget();
$template = [
'profile' => [
'personal' => [
'name' => '{{ user.name }}',
'age' => '{{ user.age }}',
],
'contact' => [
'email' => '{{ user.email }}',
'phone' => '{{ user.phone }}',
],
],
];
// Forward
$userData = ['name' => 'John', 'age' => 30, 'email' => 'john@example.com', 'phone' => '123-456'];
$profile = DataMapper::source(['user' => $userData])
->template($template)
->map()
->getTarget();
// Reverse
$userData = DataMapper::source($profile)
->template($template)
->reverseMap()
->getTarget();
$template = [
'products' => [
'*' => [
'name' => '{{ items.*.title }}',
'price' => '{{ items.*.cost }}',
],
],
];
// Forward
$itemsData = [
['title' => 'Product A', 'cost' => 10.99],
['title' => 'Product B', 'cost' => 20.99],
];
$products = DataMapper::source(['items' => $itemsData])
->template($template)
->map()
->getTarget();
// Reverse
$items = DataMapper::source($products)
->template($template)
->reverseMap()
->getTarget();
use event4u\DataHelpers\DataMapper\Pipeline\Filters\TrimStrings;
use event4u\DataHelpers\DataMapper\Pipeline\Filters\UppercaseStrings;
$template = [
'user' => [
'name' => '{{ person.name }}',
'email' => '{{ person.email }}',
],
];
// Forward: Applies filters
$data = ['name' => ' john ', 'email' => 'john@example.com'];
$user = DataMapper::source(['person' => $data])
->template($template)
->pipeline([new TrimStrings(), new UppercaseStrings()])
->map()
->getTarget();
// Reverse: Filters are NOT reversed (data flows as-is)
$person = DataMapper::source($user)
->template($template)
->reverseMap()
->getTarget();

Some transformations cannot be reversed due to information loss:

// ❌ Cannot reverse - Information loss
$template = [
'initials' => '{{ user.firstName }}{{ user.lastName }}',
];
// Forward: 'John' + 'Doe' -> 'JohnDoe'
// Reverse: 'JohnDoe' -> Cannot split back to 'John' and 'Doe'

Pipeline filters are NOT automatically reversed:

use event4u\DataHelpers\DataMapper\Pipeline\Filters\UppercaseStrings;
$template = [
'name' => '{{ user.name }}',
];
// Forward: 'john' -> 'JOHN' (with UppercaseStrings filter)
$result = DataMapper::source(['user' => ['name' => 'john']])
->template($template)
->pipeline([new UppercaseStrings()])
->map()
->getTarget();
// Reverse: 'JOHN' -> 'JOHN' (filter NOT reversed)
$reverse = DataMapper::source($result)
->template($template)
->reverseMap()
->getTarget();
// ✅ Good - Reversible
$template = [
'target' => [
'name' => '{{ source.name }}',
'email' => '{{ source.email }}',
],
];
// ❌ Bad - Not reversible (concatenation)
$template = [
'target' => [
'fullName' => '{{ source.firstName }} {{ source.lastName }}',
],
];
$template = [
'profile' => [
'name' => '{{ user.name }}',
'email' => '{{ user.email }}',
],
];
$originalData = ['user' => ['name' => 'John', 'email' => 'john@example.com']];
// Test forward
$forward = DataMapper::source($originalData)
->template($template)
->map()
->getTarget();
// Test reverse
$reverse = DataMapper::source($forward)
->template($template)
->reverseMap()
->getTarget();
// Verify
assert($originalData === $reverse);
// ⚠️ WARNING: This mapping is not reversible
// Forward only - use for read operations
$template = [
'display' => [
'fullName' => '{{ user.firstName }} {{ user.lastName }}',
],
];