Template Expressions
Powerful template expression engine for declarative data transformations - inspired by Twig, but designed specifically for data mapping.
Introduction
Section titled “Introduction”The Template Expression Engine provides a powerful expression syntax that works across all mapping methods:
- Transform values using filter syntax (e.g.,
| lower,| trim) - Provide defaults for null/missing values (e.g.,
?? 'Unknown') - Chain multiple filters (e.g.,
| trim | lower | ucfirst) - Reference source fields (e.g.,
{{ user.name }}) - Reference target fields using aliases (e.g.,
{{ @fieldName }}) - Use static values (e.g.,
'admin'without{{ }}) - Wildcard support - Apply filters to array elements (e.g.,
{{ users.*.name | upper }})
Key Features:
- 🎯 Declarative syntax - Define transformations in the template
- 🔄 Unified across all methods - Same syntax in
map(),mapFromFile()andmapFromTemplate() - 🔄 Composable filters - Chain multiple transformations
- 📦 35+ built-in filters - Common transformations out of the box
- 🔧 Extensible - Register custom filters
- ⚡ Fast - Optimized expression parsing and evaluation
Quick Start
Section titled “Quick Start”use event4u\DataHelpers\DataMapper;
$sources = [ 'user' => [ 'firstName' => 'alice', 'email' => ' ALICE@EXAMPLE.COM ', 'age' => null, ],];
$template = [ 'profile' => [ // Simple expression 'name' => '{{ user.firstName | ucfirst }}',
// Expression with default value 'age' => '{{ user.age ?? 18 }}',
// Multiple filters 'email' => '{{ user.email | trim | lower }}', ],];
$result = DataMapper::source($sources) ->template($template) ->map() ->getTarget();
// Result:// [// 'profile' => [// 'name' => 'Alice',// 'age' => 18,// 'email' => 'alice@example.com',// ]// ]Expression Syntax
Section titled “Expression Syntax”Simple Variables
Section titled “Simple Variables”Access source data using dot-notation paths wrapped in {{ }}:
$template = [ 'name' => '{{ user.name }}', 'email' => '{{ user.contact.email }}',];Default Values
Section titled “Default Values”Provide fallback values for null/missing data using ??:
$template = [ 'name' => '{{ user.name ?? "Unknown" }}', 'age' => '{{ user.age ?? 18 }}', 'role' => '{{ user.role ?? "guest" }}',];Filters
Section titled “Filters”Transform values using the pipe | operator:
$template = [ 'name' => '{{ user.name | upper }}', 'email' => '{{ user.email | lower }}', 'title' => '{{ post.title | trim }}',];Chaining Filters
Section titled “Chaining Filters”Chain multiple filters together:
$template = [ 'name' => '{{ user.name | trim | lower | ucfirst }}', 'slug' => '{{ post.title | trim | lower | replace:" ":"-" }}',];Alias References
Section titled “Alias References”Reference target fields using @ prefix:
$template = [ 'firstName' => '{{ user.firstName }}', 'lastName' => '{{ user.lastName }}', 'fullName' => '{{ @firstName }} {{ @lastName }}',];Built-in Filters
Section titled “Built-in Filters”String Filters
Section titled “String Filters”// upper - Convert to uppercase'{{ name | upper }}' // 'john' -> 'JOHN'
// lower - Convert to lowercase'{{ name | lower }}' // 'JOHN' -> 'john'
// ucfirst - Uppercase first character'{{ name | ucfirst }}' // 'john' -> 'John'
// trim - Remove whitespace'{{ name | trim }}' // ' john ' -> 'john'
// replace - Replace text'{{ name | replace:"a":"b" }}' // 'apple' -> 'bpple'Array Filters
Section titled “Array Filters”// first - Get first element'{{ items | first }}' // [1, 2, 3] -> 1
// last - Get last element'{{ items | last }}' // [1, 2, 3] -> 3
// count - Count elements'{{ items | count }}' // [1, 2, 3] -> 3
// join - Join array elements'{{ items | join:", " }}' // [1, 2, 3] -> '1, 2, 3'Type Casting Filters
Section titled “Type Casting Filters”Cast values to specific types:
// int / integer - Convert to integer'{{ value | int }}' // '42' -> 42'{{ value | integer }}' // '2075436601850' -> 2075436601850
// float - Convert to float'{{ value | float }}' // '3.14' -> 3.14'{{ price | float }}' // '19.99' -> 19.99
// bool / boolean - Convert to boolean'{{ value | bool }}' // '1' -> true'{{ active | boolean }}' // 'yes' -> true
// string - Convert to string'{{ value | string }}' // 42 -> '42''{{ id | string }}' // 123 -> '123'
// array - Convert to array'{{ value | array }}' // 'test' -> ['test']'{{ items | array }}' // Wraps scalars, converts objects
// decimal - Format as decimal with precision'{{ price | decimal }}' // 123.456 -> '123.46' (default: 2 decimals)'{{ amount | decimal:4 }}' // 123.456 -> '123.4560' (4 decimals)
// json - Convert to JSON string'{{ data | json }}' // ['a' => 1] -> '{"a":1}''{{ items | json }}' // Encodes arrays/objects to JSONType Casting Details:
- int/integer: Casts numeric values to integers, skips null and non-numeric values
- bool/boolean: Converts
'1','true','yes','on',1,true→true;'0','false','no','off','',0,false→false - float: Casts numeric values to floats, skips null and non-numeric values
- string: Casts scalar values to strings, skips null and non-scalar values
- array: Wraps scalars in array, converts objects to arrays, keeps arrays unchanged
- decimal: Formats numbers with specified precision (default: 2), useful for prices and amounts
- json: Encodes arrays/objects to JSON strings, skips existing strings to avoid double-encoding
Date Filters
Section titled “Date Filters”// date - Format date'{{ created | date:"Y-m-d" }}' // DateTime -> '2024-01-15'
// timestamp - Convert to timestamp'{{ created | timestamp }}' // DateTime -> 1705276800Data Cleaning Filters
Section titled “Data Cleaning Filters”// empty_to_null - Convert empty values to null'{{ bio | empty_to_null }}' // '' -> null, [] -> null
// empty_to_null with zero conversion'{{ count | empty_to_null:"zero" }}' // 0 -> null
// empty_to_null with string zero conversion'{{ value | empty_to_null:"string_zero" }}' // '0' -> null
// empty_to_null with false conversion'{{ active | empty_to_null:"false" }}' // false -> null
// empty_to_null with multiple conversions'{{ amount | empty_to_null:"zero,string_zero" }}' // 0 -> null, '0' -> null
// empty_to_null with all conversions'{{ flexible | empty_to_null:"zero,string_zero,false" }}' // 0, '0', false -> null
// default - Provide default value'{{ name | default:"Unknown" }}' // null -> 'Unknown'ConvertEmptyToNull Options:
- No options: Converts
"",[]andnulltonull "zero": Also converts integer0tonull"string_zero": Also converts string"0"tonull"false": Also converts booleanfalsetonull"zero,string_zero": Converts both zero types tonull"zero,string_zero,false": Converts all three types tonull
Note: By default, boolean false is not converted to null unless you use the "false" option.
See also: ConvertEmptyToNull Attribute for SimpleDto usage.
Custom Filters
Section titled “Custom Filters”Register Custom Filter
Section titled “Register Custom Filter”use event4u\DataHelpers\DataMapper\Pipeline\FilterRegistry;
// Custom filters must implement FilterInterface// See documentation for creating custom filtersFilterRegistry::register(SlugifyFilter::class);
$template = [ 'slug' => '{{ title | slugify }}',];Filter with Parameters
Section titled “Filter with Parameters”// Custom filters must implement FilterInterfaceFilterRegistry::register(TruncateFilter::class);
$template = [ 'excerpt' => '{{ content | truncate:100 }}',];Wildcard Support
Section titled “Wildcard Support”Apply filters to array elements:
$sources = [ 'users' => [ ['name' => 'john'], ['name' => 'jane'], ],];
$template = [ 'names' => '{{ users.*.name | upper }}',];
// Result: ['names' => ['JOHN', 'JANE']]WHERE and ORDER BY Clauses
Section titled “WHERE and ORDER BY Clauses”WHERE Clauses
Section titled “WHERE Clauses”Filter array elements:
$template = [ 'result' => [ 'WHERE' => [ '{{ items.*.price }}' => ['>', 100], ], '*' => [ 'name' => '{{ items.*.name }}', 'price' => '{{ items.*.price }}', ], ],];ORDER BY Clauses
Section titled “ORDER BY Clauses”Sort array elements:
$template = [ 'result' => [ 'ORDER BY' => [ '{{ items.*.price }}' => 'DESC', ], '*' => [ 'name' => '{{ items.*.name }}', 'price' => '{{ items.*.price }}', ], ],];Advanced Examples
Section titled “Advanced Examples”Complex Transformation
Section titled “Complex Transformation”$template = [ 'user' => [ 'name' => '{{ person.firstName | trim | ucfirst }} {{ person.lastName | trim | ucfirst }}', 'email' => '{{ person.email | trim | lower }}', 'role' => '{{ person.role ?? "guest" | upper }}', 'created' => '{{ person.createdAt | date:"Y-m-d H:i:s" }}', ],];Type Casting in Templates
Section titled “Type Casting in Templates”$sources = [ 'product' => [ 'id' => '2075436601850', // String from API 'price' => '19.99', // String from API 'stock' => '42', // String from API 'active' => '1', // String from API 'tags' => 'electronics', // Single value 'metadata' => ['color' => 'red'], // Array ],];
$template = [ 'product' => [ 'id' => '{{ product.id | int }}', // Cast to integer 'price' => '{{ product.price | decimal:2 }}', // Format as decimal 'stock' => '{{ product.stock | integer }}', // Cast to integer 'active' => '{{ product.active | bool }}', // Cast to boolean 'tags' => '{{ product.tags | array }}', // Wrap in array 'metadata' => '{{ product.metadata | json }}', // Encode to JSON ],];
$result = DataMapper::source($sources) ->template($template) ->map() ->getTarget();
// Result:// [// 'product' => [// 'id' => 2075436601850, // int// 'price' => '19.99', // string (formatted)// 'stock' => 42, // int// 'active' => true, // bool// 'tags' => ['electronics'], // array// 'metadata' => '{"color":"red"}', // JSON string// ]// ]Nested Data
Section titled “Nested Data”$template = [ 'order' => [ 'customer' => [ 'name' => '{{ order.customer.name | ucfirst }}', 'email' => '{{ order.customer.email | lower }}', ], 'items' => [ '*' => [ 'name' => '{{ order.items.*.name | trim }}', 'price' => '{{ order.items.*.price | float }}', ], ], ],];With Aliases
Section titled “With Aliases”$template = [ 'firstName' => '{{ user.firstName | ucfirst }}', 'lastName' => '{{ user.lastName | ucfirst }}', 'fullName' => '{{ @firstName }} {{ @lastName }}', 'greeting' => 'Hello, {{ @fullName }}!',];Best Practices
Section titled “Best Practices”1. Use Filters for Transformations
Section titled “1. Use Filters for Transformations”// ✅ Good'{{ name | trim | ucfirst }}'
// ❌ Bad - Use callback instead'{{ name }}' // Then transform in PHP2. Provide Defaults
Section titled “2. Provide Defaults”// ✅ Good'{{ user.role ?? "guest" }}'
// ❌ Bad'{{ user.role }}' // May be null3. Chain Filters Logically
Section titled “3. Chain Filters Logically”// ✅ Good - Logical order'{{ name | trim | lower | ucfirst }}'
// ❌ Bad - Illogical order'{{ name | ucfirst | lower | trim }}'See Also
Section titled “See Also”- DataMapper - DataMapper guide
- Callback Filters - Custom callbacks
- Query Builder - Query builder