- Create AnsibleDeployStage using framework's Process module for secure command execution - Integrate AnsibleDeployStage into DeploymentPipelineCommands for production deployments - Add force_deploy flag support in Ansible playbook to override stale locks - Use PHP deployment module as orchestrator (php console.php deploy:production) - Fix ErrorAggregationInitializer to use Environment class instead of $_ENV superglobal Architecture: - BuildStage → AnsibleDeployStage → HealthCheckStage for production - Process module provides timeout, error handling, and output capture - Ansible playbook supports rollback via rollback-git-based.yml - Zero-downtime deployments with health checks
197 lines
6.0 KiB
PHP
197 lines
6.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Notification\Templates;
|
|
|
|
use App\Framework\Notification\Notification;
|
|
use App\Framework\Notification\ValueObjects\NotificationChannel;
|
|
use App\Framework\Notification\ValueObjects\NotificationTypeInterface;
|
|
|
|
/**
|
|
* Template Renderer
|
|
*
|
|
* Renders notification templates with variable substitution
|
|
* Supports per-channel customization and placeholder replacement
|
|
*/
|
|
final readonly class TemplateRenderer
|
|
{
|
|
/**
|
|
* Render a notification from a template
|
|
*
|
|
* @param NotificationTemplate $template The template to render
|
|
* @param string $recipientId Recipient identifier
|
|
* @param array<string, mixed> $variables Variables for placeholder substitution
|
|
* @param array<NotificationChannel> $channels Target channels
|
|
* @param NotificationTypeInterface $type Notification type
|
|
* @return Notification Rendered notification
|
|
*/
|
|
public function render(
|
|
NotificationTemplate $template,
|
|
string $recipientId,
|
|
array $variables,
|
|
array $channels,
|
|
NotificationTypeInterface $type
|
|
): Notification {
|
|
// Validate required variables
|
|
$template->validateVariables($variables);
|
|
|
|
// Merge with default variables
|
|
$mergedVariables = [...$template->defaultVariables, ...$variables];
|
|
|
|
// Render title and body
|
|
$title = $this->replacePlaceholders($template->titleTemplate, $mergedVariables);
|
|
$body = $this->replacePlaceholders($template->bodyTemplate, $mergedVariables);
|
|
|
|
// Create base notification
|
|
$notification = Notification::create(
|
|
recipientId: $recipientId,
|
|
type: $type,
|
|
title: $title,
|
|
body: $body,
|
|
...$channels
|
|
)->withPriority($template->defaultPriority);
|
|
|
|
// Store template information in data
|
|
return $notification->withData([
|
|
'template_id' => $template->id->toString(),
|
|
'template_name' => $template->name,
|
|
'template_variables' => $mergedVariables,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Render for a specific channel with channel-specific template
|
|
*
|
|
* @param NotificationTemplate $template The template
|
|
* @param NotificationChannel $channel Target channel
|
|
* @param array<string, mixed> $variables Variables for substitution
|
|
* @return RenderedContent Rendered title and body for the channel
|
|
*/
|
|
public function renderForChannel(
|
|
NotificationTemplate $template,
|
|
NotificationChannel $channel,
|
|
array $variables
|
|
): RenderedContent {
|
|
// Validate required variables
|
|
$template->validateVariables($variables);
|
|
|
|
// Merge with default variables
|
|
$mergedVariables = [...$template->defaultVariables, ...$variables];
|
|
|
|
// Get channel-specific template if available
|
|
$channelTemplate = $template->getChannelTemplate($channel);
|
|
|
|
// Determine which templates to use
|
|
$titleTemplate = $channelTemplate?->titleTemplate ?? $template->titleTemplate;
|
|
$bodyTemplate = $channelTemplate?->bodyTemplate ?? $template->bodyTemplate;
|
|
|
|
// Render
|
|
$title = $this->replacePlaceholders($titleTemplate, $mergedVariables);
|
|
$body = $this->replacePlaceholders($bodyTemplate, $mergedVariables);
|
|
|
|
// Get channel metadata
|
|
$metadata = $channelTemplate?->metadata ?? [];
|
|
|
|
return new RenderedContent(
|
|
title: $title,
|
|
body: $body,
|
|
metadata: $metadata
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Replace placeholders in template string
|
|
*
|
|
* Supports {{variable}} and {{variable.nested}} syntax
|
|
*
|
|
* @param string $template Template string with placeholders
|
|
* @param array<string, mixed> $variables Variable values
|
|
* @return string Rendered string
|
|
*/
|
|
private function replacePlaceholders(string $template, array $variables): string
|
|
{
|
|
return preg_replace_callback(
|
|
'/\{\{([a-zA-Z0-9_.]+)\}\}/',
|
|
function ($matches) use ($variables) {
|
|
$key = $matches[1];
|
|
|
|
// Support nested variables like {{user.name}}
|
|
if (str_contains($key, '.')) {
|
|
$value = $this->getNestedValue($variables, $key);
|
|
} else {
|
|
$value = $variables[$key] ?? '';
|
|
}
|
|
|
|
// Convert to string
|
|
return $this->valueToString($value);
|
|
},
|
|
$template
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get nested value from array using dot notation
|
|
*
|
|
* @param array<string, mixed> $array Source array
|
|
* @param string $key Dot-notated key (e.g., 'user.name')
|
|
* @return mixed Value or empty string if not found
|
|
*/
|
|
private function getNestedValue(array $array, string $key): mixed
|
|
{
|
|
$keys = explode('.', $key);
|
|
$value = $array;
|
|
|
|
foreach ($keys as $segment) {
|
|
if (is_array($value) && array_key_exists($segment, $value)) {
|
|
$value = $value[$segment];
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Convert value to string for template substitution
|
|
*
|
|
* @param mixed $value Value to convert
|
|
* @return string String representation
|
|
*/
|
|
private function valueToString(mixed $value): string
|
|
{
|
|
if ($value === null) {
|
|
return '';
|
|
}
|
|
|
|
if (is_bool($value)) {
|
|
return $value ? 'true' : 'false';
|
|
}
|
|
|
|
if (is_scalar($value)) {
|
|
return (string) $value;
|
|
}
|
|
|
|
if (is_array($value)) {
|
|
return json_encode($value);
|
|
}
|
|
|
|
if (is_object($value)) {
|
|
// Handle objects with __toString
|
|
if (method_exists($value, '__toString')) {
|
|
return (string) $value;
|
|
}
|
|
|
|
// Handle objects with toArray
|
|
if (method_exists($value, 'toArray')) {
|
|
return json_encode($value->toArray());
|
|
}
|
|
|
|
return json_encode($value);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
}
|