ap-lib / templa
AP\Templa is a lightweight, flexible PHP macro engine for safely injecting and transforming dynamic values inside arrays or strings, with built-in macro and modifier support.
Requires
- php: ^8.3
- ext-mbstring: *
- ap-lib/error-node: dev-main
- ap-lib/logger: dev-main
Requires (Dev)
- phpunit/phpunit: 10.5.*
This package is auto-updated.
Last update: 2025-07-01 22:38:15 UTC
README
AP\Templa is a lightweight, flexible PHP macro engine for safely injecting and transforming dynamic values inside arrays or strings, with built-in macro and modifier support.
Installation
composer require ap-lib/templa
Features
- Define named macros with type-safe values
- Support for macro parameters
- Powerful modifier chain (e.g.
| string | base64 | upper
) - Works with arrays and nested structures
- Macro documentation generation for transparency
Requirements
- PHP 8.3 or higher
Getting started
use AP\\Templa\\TemplaEngine; use AP\\Templa\\Macros\\Constant; use AP\\Templa\\Modifier\\ToString; use AP\\Templa\\Modifier\\Base64; use AP\\Templa\\Modifier\\ToUpper; use AP\\Templa\\Modifier\\ToLower; // create engine $template = new TemplaEngine(); // create macros $template->addMacros("fruit", new Constant("orange", "string", "sold fruit name")); $template->addMacros("price", new Constant(3.14, "float")); $template->addMacros("email", new Constant("gagarin@cosmos.ru", "string|null", "seller's email")); $template->addMacros("phone", new Constant(null, "string|null", "seller's phone")); // register modifiers $template->addModifier("string", new ToString()); $template->addModifier("base64", new Base64()); $template->addModifier("upper", new ToUpper()); $template->addModifier("lower", new ToLower());
Using user-provided format:
// user-defined input template $webhook_format = [ "what_fruit" => "{{ fruit }}", "price_origin" => "{{ price }}", "price_str" => "{{ price | string }}", "price_and_postfix" => "{{ price }} USD", "email" => "{{ email }}", "email_base64" => "{{ email | base64 }}", "email_upper_base64" => "{{ email | upper | base64 }}", "phone_base64_or_null" => "{{ phone | ?base64 }}", "phone_upper_base64_or_null" => "{{ phone | ?upper | ?base64 }}", ]; // process data $webhook_data = $template->array($webhook_format); print_r($webhook_data);
Output:
[ "what_fruit" => "orange", "price_origin" => 3.14, "price_str" => "3.14", "price_and_postfix" => "3.14 USD", "email" => "gagarin@cosmos.ru", "email_base64" => base64_encode("gagarin@cosmos.ru"), "email_upper_base64" => base64_encode(strtoupper("gagarin@cosmos.ru")), "phone_base64_or_null" => null, "phone_upper_base64_or_null" => null, ]
Documentation
You can also introspect the engine to get macro and modifier documentation:
$documentation = $template->getDocumentation();
Example output:
[ 'macros' => [ [ 'name' => 'fruit', 'type' => 'string', 'param' => ['allow' => false, 'list' => null], 'details' => 'sold fruit name', ], [ 'name' => 'price', 'type' => 'float', 'param' => ['allow' => false, 'list' => null], 'details' => '', ], [ 'name' => 'email', 'type' => 'string|null', 'param' => ['allow' => false, 'list' => null], 'details' => "seller's email", ], [ 'name' => 'phone', 'type' => 'string|null', 'param' => ['allow' => false, 'list' => null], 'details' => "seller's phone", ], ], 'modifiers' => [ [ 'name' => 'string', 'in_type' => 'string|int|float|bool|null', 'out_type' => 'string', 'details' => 'Converts the value to string', ], [ 'name' => 'base64', 'in_type' => 'string|int|float|bool|null', 'out_type' => 'string', 'details' => 'Unpadded base64 encoding, as defined in RFC 4648 section 3.2.', ], [ 'name' => 'lower', 'in_type' => 'string|int|float|bool|null', 'out_type' => 'string', 'details' => 'Converts the value to lowercase string', ], [ 'name' => 'upper', 'in_type' => 'string|int|float|bool|null', 'out_type' => 'string', 'details' => 'Converts the value to uppercase string', ], ], ]
JSON Injection Protection
When injecting user data into a JSON template, you must escape values properly. If you simply render a macro directly inside a JSON string, malicious values can break your JSON structure:
$user_input_click_id = 'e5a754f3-9a91-4273-b5c4-055c8bb244cc","price":100000,"hello":"world'; $template = new TemplaEngine(); $template->addMacros("price", new Constant(3.14, "float")); $template->addMacros("click_id", new Constant($user_input_click_id, "string")); $webhook_format = '{ "price": "{{ price }}", "click_id": "{{ click_id }}" }'; // unsafe: vulnerable to JSON injection echo $template->string($webhook_format);
Result (unsafe):
{ "price": 100000, "click_id": "e5a754f3-9a91-4273-b5c4-055c8bb244cc", "hello": "world" }
To protect against this, you should use the built-in JsonSubString
final modifier, which safely escapes values for JSON string inclusion:
$template->final_strings_modifier = new JsonSubString(); $safe_output = $template->string($webhook_format);
Safe result:
{ "price": "3.14", "click_id": "e5a754f3-9a91-4273-b5c4-055c8bb244cc\",\"price\":100000,\"hello\":\"world" }
Now, user-supplied values cannot break the JSON structure.
Lazy loading macros
If you need to fetch values from slow sources (like a database, Redis, API, or gRPC) you can define macros using a lazy loader. The macro will call a closure at render time, making it possible to defer expensive computations until they are actually needed.
For example:
$template = new TemplaEngine(); // define a lazy-loaded macro $template->addMacros("hard", new LazyLoad( function (?string $param, string $name, string $macro) { // simulate a long operation sleep(1); return "takeLongTimeToGet"; }, "string" )); // define a normal in-memory macro template->addMacros("easy", new Constant( "dataAllowedOnMemory", "string" )); // rendering easy macro is fast $template->string('{ "result": "{{ easy }}" }'); // rendering hard macro will execute the lazy closure at runtime $template->string('{ "result": "{{ hard }}" }');
Use lazy macros if values come from slow systems or expensive computations, and use constants if values are already in memory.