phrity/util-interpolator
Lightweight PHP string interpolation helper. Replaces {key} tokens with values from an array/object, supports nested paths (default “.” separator, customizable), and can be used via Interpolator class or InterpolatorTrait. Uses Phrity Accessor/Transformer.
Installation:
composer require phrity/util-interpolator
Basic Usage:
use Phrity\Util\Interpolator\Interpolator;
$interpolator = new Interpolator();
$result = $interpolator->interpolate('Hello, {name}!', ['name' => 'John']);
// Output: "Hello, John!"
First Laravel Use Case:
$template = "Welcome, {user.name}! Your verification link: {verification.url}";
$data = [
'user' => ['name' => 'Jane Doe'],
'verification' => ['url' => url('/verify-email')]
];
$personalizedEmail = $interpolator->interpolate($template, $data);
public function showWelcome(Interpolator $interpolator, User $user)
{
$template = "Hi {user.name}, your balance is {user.balance}.";
$rendered = $interpolator->interpolate($template, ['user' => $user->toArray()]);
return view('welcome', ['content' => $rendered]);
}
class NotificationService {
public function __construct(private Interpolator $interpolator) {}
public function sendWelcomeEmail(User $user) {
$template = file_get_contents(storage_path('templates/welcome.txt'));
$data = ['user' => $user->toArray()];
$message = $this->interpolator->interpolate($template, $data);
Mail::send([], [], fn() => new Message($message));
}
}
class User extends Model {
use InterpolatorTrait;
public function getGreeting(): string {
return $this->interpolate(
'Welcome, {first_name}! Your ID is {id}.',
$this->attributes
);
}
}
$interpolator = new Interpolator(separator: '/');
$apiResponse = $interpolator->interpolate(
'Status: {response/data/status}',
['response' => json_decode(file_get_contents('api_response.json'), true)]
);
$transformer = new Phrity\Util\Transformer\JsonDecoder();
$interpolator = new Interpolator(transformer: $transformer);
$result = $interpolator->interpolate(
'User: {user.name}, Posts: {user.posts.length}',
['user' => '{"name": "Alice", "posts": [1, 2, 3]}']
);
// Output: "User: Alice, Posts: 3"
public function register()
{
$this->app->bind(Interpolator::class, function ($app) {
return new Interpolator(
transformer: new Phrity\Util\Transformer\FirstMatchResolver([
new Phrity\Util\Transformer\ReadableConverter(),
new Phrity\Util\Transformer\BasicTypeConverter(),
])
);
});
}
// In a service provider
Blade::directive('interpolate', function ($expression) {
return "<?php echo app(\\Phrity\\Util\\Interpolator\\Interpolator::class)->interpolate($expression[0], $expression[1]); ?>";
});
// Usage in Blade
@interpolate('Hello, {name}!', ['name' => $user->name])
public function renderTemplate(string $templatePath, array $data): string
{
$template = file_get_contents(resource_path("views/templates/{$templatePath}.txt"));
return $this->interpolator->interpolate($template, $data);
}
Path Resolution Failures:
{user.address.city} will fail if intermediate keys (e.g., address) are missing.Phrity\Util\Accessor\SafeAccessor to handle missing keys gracefully.
$interpolator = new Interpolator(accessor: new SafeAccessor());
$result = $interpolator->interpolate('City: {user.address.city}', ['user' => ['name' => 'Bob']]);
// Output: "City: "
Circular References:
user->posts->author->posts) may cause stack overflows.Transformer Conflicts:
null to an empty string).FirstMatchResolver to prioritize your transformers.
$transformer = new FirstMatchResolver([
new NullToEmptyStringTransformer(), // Custom
new ReadableConverter(),
]);
Performance with Large Templates:
$cached = Cache::remember("template_{$user->id}", now()->addHours(1), fn() =>
$interpolator->interpolate($template, $data)
);
Security:
{user.__proto__}) could expose sensitive data.$allowedPaths = ['user.name', 'user.email'];
$accessor = new AllowedPathsAccessor($allowedPaths);
Enable Verbose Logging:
$interpolator = new Interpolator(accessor: new DebugAccessor());
// Logs all attempted path accesses to storage/logs/debug.log
Validate Input Data:
$data = $this->validateData($replacers);
$result = $interpolator->interpolate($template, $data);
private function validateData(array $data): array {
foreach ($data as $key => $value) {
if (is_array($value) && empty($value)) {
throw new \InvalidArgumentException("Empty array for key: {$key}");
}
}
return $data;
}
Test Edge Cases:
$testCases = [
['{missing}', [], ''], // Missing key
['{user.name}', ['user' => null], ''], // Null value
['{0}', [0 => 'zero'], 'zero'], // Numeric keys
['{{literal}}', [], '{literal}'], // Escaped braces
];
Custom Accessor:
Phrity\Util\Accessor\AccessorInterface to implement custom path resolution logic.class EloquentAccessor implements AccessorInterface {
public function access($data, string $path): mixed {
return data_get($data, $path) ?? $data->{$path};
}
}
Custom Transformers:
Carbon, Collection).
class CarbonTransformer implements TransformerInterface {
public function transform($value): string {
return $value->format('Y-m-d H:i:s');
}
}
Interpolation Events:
event(new InterpolationPerformed($template, $data, $result));
Fallback Values:
$result = $interpolator->interpolate('Name: {name|default}', ['name' => null], fallback: 'Guest');
// Output: "Name: Guest"
How can I help you explore Laravel packages today?