DataMapper
DataMapper provides a modern, fluent API for transforming data between different structures. It supports template-based mapping, queries with SQL-like operators, property-specific filters, and much more.
Quick Example
Section titled “Quick Example”use Event4u\DataHelpers\DataMapper;
$source = [ 'user' => [ 'name' => 'John Doe', 'email' => 'john@example.com', ], 'orders' => [ ['id' => 1, 'total' => 100, 'status' => 'shipped'], ['id' => 2, 'total' => 200, 'status' => 'pending'], ['id' => 3, 'total' => 150, 'status' => 'shipped'], ],];
// Approach 1: Fluent API with query builder$result = DataMapper::from($source) ->query('orders.*') ->where('status', '=', 'shipped') ->orderBy('total', 'DESC') ->end() ->template([ 'customer_name' => '{{ user.name }}', 'customer_email' => '{{ user.email }}', 'shipped_orders' => [ '*' => [ 'id' => '{{ orders.*.id }}', 'total' => '{{ orders.*.total }}', ], ], ]) ->map() ->getTarget();
// Approach 2: Template-based with WHERE/ORDER BY operators (recommended)$template = [ 'customer_name' => '{{ user.name }}', 'customer_email' => '{{ user.email }}', 'shipped_orders' => [ 'WHERE' => [ '{{ orders.*.status }}' => 'shipped', ], 'ORDER BY' => [ '{{ orders.*.total }}' => 'DESC', ], '*' => [ 'id' => '{{ orders.*.id }}', 'total' => '{{ orders.*.total }}', ], ],];
$result = DataMapper::from($source) ->template($template) ->map() ->getTarget();
// Both approaches produce the same result:// [// 'customer_name' => 'John Doe',// 'customer_email' => 'john@example.com',// 'shipped_orders' => [// ['id' => 3, 'total' => 150],// ['id' => 1, 'total' => 100],// ],// ]Why Use Template-Based Approach?
Section titled “Why Use Template-Based Approach?”The template-based approach (Approach 2) has a significant advantage: templates can be stored in a database and created with a drag-and-drop editor, enabling no-code data mapping:
// Store templates in database$source = [ 'user' => [ 'name' => 'John Doe', 'email' => 'john@example.com', ], 'orders' => [ ['id' => 1, 'total' => 100, 'status' => 'shipped'], ['id' => 2, 'total' => 200, 'status' => 'pending'], ['id' => 3, 'total' => 150, 'status' => 'shipped'], ],];
// Load template from database (created with drag-and-drop editor)$template = Mappings::find(3)->template;
$result = DataMapper::from($source) ->template($template) ->map() ->getTarget();This makes it possible to map import files, API responses, etc. without any programming.
Use cases:
- Import Wizards - Let users map CSV/Excel columns to your data structure
- API Integration - Store API response mappings in database
- Multi-Tenant Systems - Each tenant can have custom mappings
- Dynamic ETL - Build data transformation pipelines without code
- Form Builders - Map form submissions to different data structures
Fluent API Overview
Section titled “Fluent API Overview”The DataMapper uses a fluent, chainable API:
DataMapper::from($source) // Start with source data ->target($target) // Optional: Set target object/array ->template($template) // Define mapping template ->query($path) // Start query builder ->where($field, $op, $val) // Add WHERE condition ->orderBy($field, $dir) // Add ORDER BY ->limit($n) // Add LIMIT ->end() // End query builder ->property($name) // Access property API ->setFilter($filter) // Set property filter ->end() // End property API ->pipeline($filters) // Set global filters ->skipNull() // Skip null values ->map() // Execute mapping ->getTarget(); // Get resultBasic Usage
Section titled “Basic Usage”Simple Template Mapping
Section titled “Simple Template Mapping”$result = DataMapper::from($source) ->template([ 'name' => '{{ user.name }}', 'email' => '{{ user.email }}', 'age' => '{{ user.profile.age }}', ]) ->map() ->getTarget();Template Syntax
Section titled “Template Syntax”Templates use {{ }} for dynamic values:
- Dynamic values:
'{{ user.name }}'- Fetches value from source - Static values:
'admin'- Used as literal string (no{{ }}) - Dot-notation:
'{{ user.profile.address.street }}'- Nested access - Wildcards:
'{{ users.*.email }}'- Array operations
Mapping to Objects
Section titled “Mapping to Objects”class UserDTO{ public string $name; public string $email;}
$result = DataMapper::from($source) ->target(UserDTO::class) ->template([ 'name' => '{{ user.name }}', 'email' => '{{ user.email }}', ]) ->map() ->getTarget(); // Returns UserDTO instanceNested Structures
Section titled “Nested Structures”$result = DataMapper::from($source) ->template([ 'customer' => [ 'name' => '{{ user.name }}', 'contact' => [ 'email' => '{{ user.email }}', 'phone' => '{{ user.phone }}', ], ], 'orders' => [ '*' => [ 'id' => '{{ orders.*.id }}', 'total' => '{{ orders.*.total }}', ], ], ]) ->map() ->getTarget();Query Builder
Section titled “Query Builder”The query builder provides SQL-like operators for filtering and transforming data during mapping.
Basic Queries
Section titled “Basic Queries”// Fluent API approach$result = DataMapper::from($source) ->query('orders.*') ->where('total', '>', 100) ->orderBy('total', 'DESC') ->limit(5) ->end() ->template([ 'items' => [ '*' => [ 'id' => '{{ orders.*.id }}', 'total' => '{{ orders.*.total }}', ], ], ]) ->map() ->getTarget();
// Template-based approach (same result)$result = DataMapper::from($source) ->template([ 'items' => [ 'WHERE' => [ '{{ orders.*.total }}' => ['>', 100], ], 'ORDER BY' => [ '{{ orders.*.total }}' => 'DESC', ], 'LIMIT' => 5, '*' => [ 'id' => '{{ orders.*.id }}', 'total' => '{{ orders.*.total }}', ], ], ]) ->map() ->getTarget();WHERE Conditions
Section titled “WHERE Conditions”// Simple comparison->where('status', '=', 'active')->where('price', '>', 100)->where('stock', '<=', 10)
// Multiple conditions (AND logic)->where('status', '=', 'active')->where('price', '>', 100)
// BETWEEN->where('price', 'BETWEEN', [50, 150])
// IN->where('status', 'IN', ['active', 'pending'])
// LIKE (pattern matching)->where('name', 'LIKE', 'John%')
// NULL checks->where('deleted_at', 'IS NULL')->where('email', 'IS NOT NULL')ORDER BY
Section titled “ORDER BY”// Single field->orderBy('price', 'DESC')
// Multiple fields->orderBy('category', 'ASC')->orderBy('price', 'DESC')LIMIT and OFFSET
Section titled “LIMIT and OFFSET”// Limit results->limit(10)
// Skip items->offset(20)
// Pagination->offset(20)->limit(10)DISTINCT
Section titled “DISTINCT”// Remove duplicates->distinct('email')GROUP BY
Section titled “GROUP BY”// Group and aggregate->groupBy('category', [ 'total' => 'SUM(price)', 'count' => 'COUNT(*)', 'avg_price' => 'AVG(price)',])Pipeline Filters
Section titled “Pipeline Filters”Apply filters to all mapped values globally.
Global Filters
Section titled “Global Filters”use Event4u\DataHelpers\DataMapper\Pipeline\Filters\TrimStrings;use Event4u\DataHelpers\DataMapper\Pipeline\Filters\UppercaseStrings;
$result = DataMapper::from($source) ->pipeline([ new TrimStrings(), new UppercaseStrings(), ]) ->template([ 'name' => '{{ user.name }}', 'email' => '{{ user.email }}', ]) ->map() ->getTarget();
// All string values are trimmed and uppercasedAdding Filters
Section titled “Adding Filters”$mapper = DataMapper::from($source) ->template($template);
// Add single filter$mapper->addPipelineFilter(new TrimStrings());
// Add multiple filters$mapper->pipeline([ new TrimStrings(), new UppercaseStrings(),]);Built-in Filters
Section titled “Built-in Filters”Data Helpers includes 40+ built-in filters:
- String Filters: TrimStrings, UppercaseStrings, LowercaseStrings, etc.
- Number Filters: RoundNumbers, FormatCurrency, etc.
- Date Filters: FormatDate, ParseDate, etc.
- Array Filters: FlattenArray, UniqueValues, etc.
- Validation Filters: ValidateEmail, ValidateUrl, etc.
See Filters Documentation for complete list.
Property-Specific Filters
Section titled “Property-Specific Filters”Apply filters to specific properties only.
Using setFilter()
Section titled “Using setFilter()”$result = DataMapper::from($source) ->setFilter('name', new TrimStrings(), new UppercaseStrings()) ->setFilter('email', new TrimStrings(), new LowercaseStrings()) ->template([ 'name' => '{{ user.name }}', 'email' => '{{ user.email }}', 'bio' => '{{ user.bio }}', ]) ->map() ->getTarget();
// Only 'name' and 'email' are filtered, 'bio' is notUsing Property API
Section titled “Using Property API”$result = DataMapper::from($source) ->property('name') ->setFilter(new TrimStrings(), new UppercaseStrings()) ->end() ->property('email') ->setFilter(new TrimStrings(), new LowercaseStrings()) ->end() ->template($template) ->map() ->getTarget();Nested Properties
Section titled “Nested Properties”// Works with dot-notation->setFilter('user.profile.bio', new TrimStrings())
// Works with wildcards->setFilter('items.*.name', new TrimStrings())Property API
Section titled “Property API”The Property API provides focused access to individual properties.
Get Property Target
Section titled “Get Property Target”$mapper = DataMapper::from($source) ->template([ 'name' => '{{ user.name }}', 'email' => '{{ user.email }}', ]);
// Get mapping target for property$target = $mapper->property('name')->getTarget();// Returns: '{{ user.name }}'Get Property Filters
Section titled “Get Property Filters”$mapper->setFilter('name', new TrimStrings());
$filters = $mapper->property('name')->getFilter();// Returns: [TrimStrings]Get Mapped Value
Section titled “Get Mapped Value”// Execute mapping and get value for specific property$value = $mapper->property('name')->getMappedValue();// Returns: 'John Doe' (after applying filters)Reset Property Filters
Section titled “Reset Property Filters”$mapper->property('name') ->setFilter(new TrimStrings()) ->resetFilter() // Remove all filters ->setFilter(new UppercaseStrings()) // Set new filter ->end();Discriminator (Polymorphic Mapping)
Section titled “Discriminator (Polymorphic Mapping)”Automatically select target class based on a discriminator field (Liskov Substitution Principle).
Basic Usage
Section titled “Basic Usage”abstract class Animal{ public string $name; public int $age;}
class Dog extends Animal{ public string $breed;}
class Cat extends Animal{ public int $lives;}
$source = [ 'type' => 'dog', 'name' => 'Rex', 'age' => 5, 'breed' => 'Golden Retriever',];
$result = DataMapper::from($source) ->target(Animal::class) ->discriminator('type', [ 'dog' => Dog::class, 'cat' => Cat::class, ]) ->template([ 'name' => '{{ name }}', 'age' => '{{ age }}', 'breed' => '{{ breed }}', ]) ->map() ->getTarget();
// Returns Dog instance (because type='dog')Nested Discriminator
Section titled “Nested Discriminator”// Discriminator field can be nested->discriminator('meta.classification.type', [ 'premium' => PremiumUser::class, 'basic' => BasicUser::class,])Fallback Behavior
Section titled “Fallback Behavior”// If discriminator value not found, falls back to original target$result = DataMapper::from(['type' => 'unknown']) ->target(Animal::class) ->discriminator('type', [ 'dog' => Dog::class, 'cat' => Cat::class, ]) ->template($template) ->map() ->getTarget();
// Returns Animal instance (fallback)Copy and Extend
Section titled “Copy and Extend”Create independent copies of mapper configurations.
Copy Configuration
Section titled “Copy Configuration”$baseMapper = DataMapper::from($source) ->target(User::class) ->template([ 'name' => '{{ name }}', ]);
// Create independent copy$extendedMapper = $baseMapper->copy() ->extendTemplate([ 'email' => '{{ email }}', ]) ->addPipelineFilter(new TrimStrings());
// $baseMapper is unchanged// $extendedMapper has extended configExtend Template
Section titled “Extend Template”$mapper = DataMapper::from($source) ->template([ 'name' => '{{ user.name }}', ]);
// Extend with additional fields$mapper->extendTemplate([ 'email' => '{{ user.email }}', 'phone' => '{{ user.phone }}',]);
// Template now has all three fieldsReset and Delete
Section titled “Reset and Delete”Manage template operators dynamically.
Reset to Original
Section titled “Reset to Original”$mapper = DataMapper::from($source) ->template([ 'items' => [ 'WHERE' => ['{{ products.*.status }}' => 'active'], 'ORDER BY' => ['{{ products.*.price }}' => 'DESC'], '*' => ['id' => '{{ products.*.id }}'], ], ]);
// Modify with query$mapper->query('products.*') ->where('price', '>', 75) ->orderBy('price', 'ASC') ->end();
// Reset WHERE to original template value$mapper->reset()->where();
// Reset entire template$mapper->reset()->all();Delete Operators
Section titled “Delete Operators”// Delete specific operator$mapper->delete()->where();
// Delete all operators$mapper->delete()->all();Chainable
Section titled “Chainable”// Chain multiple operations$mapper->reset()->where()->orderBy();$mapper->delete()->limit()->offset();Performance
Section titled “Performance”DataMapper is optimized for performance:
- 3.7x faster than Symfony Serializer for DTO mapping
- Zero reflection overhead for template-based mapping
- Efficient caching for path resolution and reflection
- Minimal overhead (7.1%) for Fluent API wrapper
See Performance Benchmarks for detailed comparison.
Code Examples
Section titled “Code Examples”The following working examples demonstrate DataMapper in action:
- Simple Mapping - Basic template-based mapping
- Template-Based Queries - WHERE/ORDER BY in templates (recommended for database-stored templates)
- With Hooks - Using hooks for custom logic
- Pipeline - Filter pipelines and transformations
- Mapped Data Model - Using MappedDataModel class
- Template Expressions - Advanced template syntax
- Reverse Mapping - Bidirectional mapping
- DTO Integration - Integration with SimpleDTO
All examples are fully tested and can be run directly:
php examples/main-classes/data-mapper/simple-mapping.phpphp examples/main-classes/data-mapper/template-based-queries.phpphp examples/main-classes/data-mapper/with-hooks.phpRelated Tests
Section titled “Related Tests”The functionality is thoroughly tested. Key test files:
- DataMapperTest.php - Core functionality tests
- DataMapperHooksTest.php - Hook system tests
- DataMapperPipelineTest.php - Pipeline tests
- MapperQueryTest.php - Query integration tests
- MultiSourceFluentTest.php - Multi-source mapping tests
- MultiTargetMappingTest.php - Multi-target mapping tests
- DataMapperIntegrationTest.php - End-to-end scenarios
Run the tests:
# Run all DataMapper teststask test:unit -- --filter=DataMapper
# Run specific test filevendor/bin/pest tests/Unit/DataMapper/DataMapperTest.phpSee Also
Section titled “See Also”- DataAccessor - Read nested data
- DataMutator - Modify nested data
- DataFilter - Query and filter data
- Core Concepts: Wildcards - Wildcard operators
- Examples - 90+ code examples