Risma is not just another template engine. It's a lightweight, high-performance, zero-dependency string processing pipeline for PHP.
It completely transforms how you manipulate strings, handle dynamic templates, and sanitize data by introducing a flexible, deeply recursive, and bulletproof pipe-like syntax. No eval(), no spaghetti code; just pure, chained logic.
Ever tried chaining multiple string manipulations in native PHP? It gets ugly, fast.
❌ Native PHP (Hard to read, inside-out execution):
$output = sprintf("Status: %s", ucfirst(strtolower(trim($user['status'] ?? 'pending'))));✅ The Risma Way (Clean, pipeline execution):
$output = $risma->render("Status: {status.or('pending').trim.strtolower.ucfirst}", $user);- 🔗 Infinite Function Chaining: Pipe data through endless functions using simple dot notation.
- 🎯 Smart Argument Routing (
$): Pinpoint exactly where your piped value should land in the next function's arguments. - 🛡️ Bulletproof Zero-Eval Lexer: Advanced lookahead parser flawlessly handles internal unescaped quotes (e.g.,
"sal"am") and escaped quotes ("sal\"am") without breaking. - 🪆 Deep Recursion (Inception): Nest placeholders inside function arguments infinitely.
- 🔌 Ultimate Extensibility: Inject your own custom functions, static classes, or instantiated objects directly into the engine.
- 🛠️ Rich Built-in Arsenal: Comes loaded with logic, formatting, and conditional operators out of the box.
Install via Composer and you are ready to go:
composer require nabeghe/rismause Nabeghe\Risma\Risma;
// Initialize the engine
$risma = new Risma();Pass an associative array of data. Risma finds the placeholders and replaces them.
echo $risma->render("Hello {name}!", ['name' => 'Hadi']);
// Output: Hello Hadi!Transform data on the fly. By default, the output of the current step becomes the first argument of the next function.
$text = "Welcome back, {user.trim.strtoupper}!";
echo $risma->render($text, ['user' => ' alice ']);
// Output: Welcome back, ALICE!Pass arguments exactly like you would in PHP. Risma's internal lexer is incredibly smart. it handles inner quotes and commas inside strings flawlessly.
// Handles internal unescaped quotes effortlessly!
$text = "Message: {@sprintf(\"%s\", \"He said \"Hello\" to me\")}";
echo $risma->render($text, []);
// Output: Message: He said "Hello" to meNot every PHP function accepts the target string as the first argument (we're looking at you, str_replace). Use $ to tell Risma exactly where to inject the piped value!
// str_replace(search, replace, subject) -> Subject is the 3rd argument.
$text = "Formatted: {slug.str_replace('-', ' ', '$')}";
echo $risma->render($text, ['slug' => 'open-source-is-awesome']);
// Output: Formatted: open source is awesomeNeed to execute a function to generate a root value without relying on a variable? Start with @.
echo $risma->render("Copyright {@date('Y')} - {@rand(1, 100)}", []);
// Output: Copyright 2026 - 42Risma recursively resolves placeholders from the inside out. You can embed placeholders inside function arguments of other placeholders!
$template = '{@sprintf("%s %s", "{@ucfirst("{first}")}", "{@ucfirst("{last}")}")}';
echo $risma->render($template, ['first' => 'hadi', 'last' => 'akbarzadeh']);
// Output: Hadi AkbarzadehIf you need to render the literal {braces} without Risma parsing them, prefix with !.
echo $risma->render("Use !{variable} to write a variable.", []);
// Output: Use {variable} to write a variable.Risma includes a powerful suite of native helpers. You can chain them endlessly to achieve complex logical operations directly inside your string.
ok: Returns'1'if truthy,'0'if falsy. (Great for boolean flags)exists: Returns'1'if not null/empty, otherwise'0'.or('default'): Fallback value if the variable is empty.and('suffix'): Appends text only if the variable is not empty.if_empty('yes', 'no')/if_not_empty('yes', 'no'): Conditional text based on emptiness.if_blank('yes', 'no')/if_not_blank('yes', 'no'): Like empty, but also treats invisible unicode spaces as blank!if_equals('target', 'yes', 'no'): Strict comparison.if_numeric('yes', 'no'): Checks if the piped value is a number.
(Note: You can use %s in the 'yes' or 'no' arguments to inject the original value!)
prepend('prefix1', 'prefix2', ...): Adds strings to the beginning.append('suffix1', 'suffix2', ...): Adds strings to the end.flatten_lines: Squashes multi-line text (\n,\r\n) into a single, clean space-separated line.remove_lines: Completely strips line breaks.maybe_plural_s: Returns's'if the piped integer is > 1.line: Generates a raw\nline break.
Let's combine everything into a single, highly complex template that formats an email notification, handles missing data, flattens line breaks, and evaluates conditionals—all in one string!
$template = 'Subject: {@sprintf("New message from %s", "{sender.trim.or(\"Unknown User\")}")} ' .
'| Body: {message.flatten_lines.if_empty("No content.", ">> %s <<")} ' .
'| Attachments: {attach_count} file{attach_count.maybe_plural_s} ' .
'| Status: {is_vip.ok.if_equals("1", "[VIP Member]", "[Standard]")}';
$data = [
'sender' => ' Hadi "Danger" Akbarzadeh ', // Internal quotes & spaces
'message' => "Hello!\r\nThis is a test message\nwith \"quotes\".", // Messy multiline
'attach_count' => 3,
'is_vip' => true,
];
echo $risma->render($template, $data);Output:
Subject: New message from Hadi "Danger" Akbarzadeh | Body: >> Hello! This is a test message with "quotes". << | Attachments: 3 files | Status: [VIP Member]
Risma is designed to be your engine. You can mount your own functions, classes, and objects to the pipeline.
$risma->addFunc('mask_email', function($email) {
return substr($email, 0, 3) . '***@***.com';
});
echo $risma->render("{email.mask_email}", ['email' => 'hadi@nabeghe.com']);
// Output: had***@***.comMap an entire class. Risma will check this class for methods during the chain.
class TextUtils {
public static function bold($text) { return "<b>$text</b>"; }
}
$risma->addClass(TextUtils::class);
echo $risma->render("{title.bold}", ['title' => 'Risma']);
// Output: <b>Risma</b>You can even inject an instantiated object to preserve state!
$translator = new MyTranslator();
$risma->addObject($translator); // Now all public methods of $translator are available!| Method | Description |
|---|---|
render(string $text, array $vars, bool $default = true): string |
Core compiler. If $default is false, throws Exception on missing vars. |
addFunc(string $name, callable $callback): void |
Registers a custom pipeline function. |
addClass(string $className, bool $prepend = false): void |
Mounts a static class to the resolver. |
addObject(object $object, bool $prepend = false): void |
Mounts an instantiated object to the resolver. |
Created with ❤️ by Nabeghe. Licensed under the MIT License. Free to use, modify, and distribute!