$variables Variables for placeholder substitution * @param array $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 $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 $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 $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 ''; } }