A framework-agnostic, fully testable, SOLID-compliant mustache template resolver for PHP applications with first-class Laravel integration.
- Simple field resolution:
{{User.name}} - Relation navigation:
{{User.department.manager.name}} - Dynamic fields:
{{Device.$manufacturer.field_parameter}} - Collection access:
{{User.posts.0.title}},{{User.addresses.*.city}} - Built-in functions:
{{now()}},{{format(User.date, 'Y-m-d')}} - Null coalescing:
{{User.nickname ?? 'Anonymous'}} - Framework-agnostic core with optional Laravel integration
- 100% testable without database
| Package version | PHP | Laravel | Status |
|---|---|---|---|
| 2.x | 8.2, 8.3, 8.4 | 12.x, 13.x | Active development |
| 1.x | 8.2, 8.3, 8.4 | 10.x, 11.x, 12.x | Security fixes only |
- PHP 8.2+
- Laravel 12.x or 13.x (optional)
composer require aichadigital/laravel-mustache-resolverThe package auto-discovers the service provider. Optionally publish the config:
php artisan vendor:publish --tag="mustache-resolver-config"use AichaDigital\MustacheResolver\Core\MustacheResolver;
use AichaDigital\MustacheResolver\Core\Parser\MustacheParser;
use AichaDigital\MustacheResolver\Core\Pipeline\PipelineBuilder;
use AichaDigital\MustacheResolver\Cache\NullCache;
$resolver = new MustacheResolver(
new MustacheParser(),
PipelineBuilder::create()->build(),
new NullCache()
);use AichaDigital\MustacheResolver\Laravel\Facades\Mustache;
$template = "Hello, {{User.name}}! Your email is {{User.email}}.";
$user = User::find(1);
$result = Mustache::translate($template, $user);
if ($result->isSuccess()) {
echo $result->getTranslated();
// "Hello, John! Your email is john@example.com."
}$template = "Manager: {{User.department.manager.name}}";
$result = Mustache::translate($template, $user);// Access by index
$template = "First post: {{User.posts.0.title}}";
// Access first/last
$template = "Latest: {{User.posts.last.title}}";
// Wildcard (returns array)
$template = "All cities: {{User.addresses.*.city}}";$template = "Report for {{$period}}: {{User.name}}";
$result = Mustache::translate($template, $user, ['period' => '2024-Q1']);$templates = [
"Name: {{User.name}}",
"Email: {{User.email}}",
"Department: {{User.department.name}}",
];
$results = Mustache::translateBatch($templates, $user);// Missing fields return empty string instead of failing
$result = Mustache::translate($template, $user, [], strict: false);// config/mustache-resolver.php
return [
'strict' => true, // Throw on unresolvable mustaches
'keep_unresolved' => false, // Keep mustaches if not resolved (non-strict)
'cache' => [
'enabled' => false,
'ttl' => 3600,
],
'security' => [
'max_depth' => 10,
'blacklisted_attributes' => ['password', 'remember_token'],
],
];use AichaDigital\MustacheResolver\Contracts\ResolverInterface;
class CustomResolver implements ResolverInterface
{
public function supports(TokenInterface $token, ContextInterface $context): bool
{
return $token->getPrefix() === 'Custom';
}
public function resolve(TokenInterface $token, ContextInterface $context): mixed
{
// Your resolution logic
}
public function priority(): int
{
return 150; // Higher than built-in resolvers
}
public function name(): string
{
return 'custom';
}
}Register in config:
'resolvers' => [
\App\Resolvers\CustomResolver::class,
],composer testPlease see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The AGPL-3.0-or-later License. Please see License File for more information.