Skip to content

Repository files navigation

Laravel Mustache Resolver

Latest Version Total Downloads Pipeline Coverage PHPStan level 8 PHP Version Laravel Version License

Development happens on gitlab.castris.com. The GitHub repository is a read-only distribution mirror: issues and pull requests opened there are not seen.

A framework-agnostic, fully testable, SOLID-compliant mustache template resolver for PHP applications with first-class Laravel integration.

Features

  • 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

Compatibility

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

Requirements

  • PHP 8.2+
  • Laravel 12.x or 13.x (optional)

Installation

composer require aichadigital/laravel-mustache-resolver

Laravel

The package auto-discovers the service provider. Optionally publish the config:

php artisan vendor:publish --tag="mustache-resolver-config"

Standalone (without Laravel)

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()
);

Usage

Basic Usage with Laravel Facade

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."
}

Relation Navigation

$template = "Manager: {{User.department.manager.name}}";
$result = Mustache::translate($template, $user);

Collection Access

// 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}}";

With Variables

$template = "Report for {{$period}}: {{User.name}}";
$result = Mustache::translate($template, $user, ['period' => '2024-Q1']);

Batch Processing

$templates = [
    "Name: {{User.name}}",
    "Email: {{User.email}}",
    "Department: {{User.department.name}}",
];

$results = Mustache::translateBatch($templates, $user);

Non-strict Mode

// Missing fields return empty string instead of failing
$result = Mustache::translate($template, $user, [], strict: false);

Configuration

// 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' => [
        'mode' => 'report',     // 'off' | 'report' | 'enforce'
        'max_depth' => 10,
        'blacklisted_attributes' => ['password', 'remember_token'],
    ],
];

Security

The service provider builds a SecurityValidator from config('mustache-resolver.security') and applies it to every resolution path, both for Eloquent models and array data. Every segment of a dot-notation path is checked against blacklisted_attributes, so a blacklisted attribute is also blocked behind a relation ({{User.relationship.password}}), and paths deeper than max_depth are rejected.

The security.mode setting controls what happens on a violation:

  • report (default): the violation is logged via Log::warning() and resolution proceeds. Non-breaking: use it to discover what enforcement would block before upgrading. This will become enforce in v3.0.0.
  • enforce: violations block the path (resolves to empty) and disallowed models throw ModelNotAllowedException. Blocked attempts are also logged, as an audit trail.
  • off: no checks are applied.

Blacklist matching is case-insensitive and covers every resolution path, including collection tokens ({{User.posts.*.author.password}}). Serializing a whole relation ({{User.department}}) filters blacklisted attributes out of the serialized output in enforce mode, preserving escapeWhenCastingToString(). Resolved arrays and collections containing blacklisted attributes are reported in report mode (their filtering is deferred to v3.0.0). Repeated violations of the same path are logged once per request/job cycle. An invalid security.mode value fails closed (enforce) with a warning.

Custom Resolvers

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,
],

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The AGPL-3.0-or-later License. Please see License File for more information.

About

Distribution mirror — development happens on gitlab.castris.com/aichadigital/mustache. Read-only: no issues, no PRs, no CI.

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages