Skip to content

Template Expressions

Powerful template expression engine for declarative data transformations - inspired by Twig, but designed specifically for data mapping.

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() and mapFromTemplate()
  • 🔄 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
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',
// ]
// ]

Access source data using dot-notation paths wrapped in {{ }}:

$template = [
'name' => '{{ user.name }}',
'email' => '{{ user.contact.email }}',
];

Provide fallback values for null/missing data using ??:

$template = [
'name' => '{{ user.name ?? "Unknown" }}',
'age' => '{{ user.age ?? 18 }}',
'role' => '{{ user.role ?? "guest" }}',
];

Transform values using the pipe | operator:

$template = [
'name' => '{{ user.name | upper }}',
'email' => '{{ user.email | lower }}',
'title' => '{{ post.title | trim }}',
];

Chain multiple filters together:

$template = [
'name' => '{{ user.name | trim | lower | ucfirst }}',
'slug' => '{{ post.title | trim | lower | replace:" ":"-" }}',
];

Reference target fields using @ prefix:

$template = [
'firstName' => '{{ user.firstName }}',
'lastName' => '{{ user.lastName }}',
'fullName' => '{{ @firstName }} {{ @lastName }}',
];
// 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'
// 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'

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 JSON

Type Casting Details:

  • int/integer: Casts numeric values to integers, skips null and non-numeric values
  • bool/boolean: Converts '1', 'true', 'yes', 'on', 1, truetrue; '0', 'false', 'no', 'off', '', 0, falsefalse
  • 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 - Format date
'{{ created | date:"Y-m-d" }}' // DateTime -> '2024-01-15'
// timestamp - Convert to timestamp
'{{ created | timestamp }}' // DateTime -> 1705276800
// 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 "", [] and null to null
  • "zero": Also converts integer 0 to null
  • "string_zero": Also converts string "0" to null
  • "false": Also converts boolean false to null
  • "zero,string_zero": Converts both zero types to null
  • "zero,string_zero,false": Converts all three types to null

Note: By default, boolean false is not converted to null unless you use the "false" option.

See also: ConvertEmptyToNull Attribute for SimpleDto usage.

use event4u\DataHelpers\DataMapper\Pipeline\FilterRegistry;
// Custom filters must implement FilterInterface
// See documentation for creating custom filters
FilterRegistry::register(SlugifyFilter::class);
$template = [
'slug' => '{{ title | slugify }}',
];
// Custom filters must implement FilterInterface
FilterRegistry::register(TruncateFilter::class);
$template = [
'excerpt' => '{{ content | truncate:100 }}',
];

Apply filters to array elements:

$sources = [
'users' => [
['name' => 'john'],
['name' => 'jane'],
],
];
$template = [
'names' => '{{ users.*.name | upper }}',
];
// Result: ['names' => ['JOHN', 'JANE']]

Filter array elements:

$template = [
'result' => [
'WHERE' => [
'{{ items.*.price }}' => ['>', 100],
],
'*' => [
'name' => '{{ items.*.name }}',
'price' => '{{ items.*.price }}',
],
],
];

Sort array elements:

$template = [
'result' => [
'ORDER BY' => [
'{{ items.*.price }}' => 'DESC',
],
'*' => [
'name' => '{{ items.*.name }}',
'price' => '{{ items.*.price }}',
],
],
];
$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" }}',
],
];
$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
// ]
// ]
$template = [
'order' => [
'customer' => [
'name' => '{{ order.customer.name | ucfirst }}',
'email' => '{{ order.customer.email | lower }}',
],
'items' => [
'*' => [
'name' => '{{ order.items.*.name | trim }}',
'price' => '{{ order.items.*.price | float }}',
],
],
],
];
$template = [
'firstName' => '{{ user.firstName | ucfirst }}',
'lastName' => '{{ user.lastName | ucfirst }}',
'fullName' => '{{ @firstName }} {{ @lastName }}',
'greeting' => 'Hello, {{ @fullName }}!',
];
// ✅ Good
'{{ name | trim | ucfirst }}'
// ❌ Bad - Use callback instead
'{{ name }}' // Then transform in PHP
// ✅ Good
'{{ user.role ?? "guest" }}'
// ❌ Bad
'{{ user.role }}' // May be null
// ✅ Good - Logical order
'{{ name | trim | lower | ucfirst }}'
// ❌ Bad - Illogical order
'{{ name | ucfirst | lower | trim }}'