$context * @return array{subject:string,message:string,pdf_attachment:string,warnings:list} */ public function render(string $subjectTpl, string $messageTpl, string $pdfTpl, array $context): array { if (!class_exists('Twig\\Environment')) { throw new \RuntimeException('Twig is not available. Ensure vendor dependencies are installed.'); } $loader = new \Twig\Loader\ArrayLoader([ 'subject' => $subjectTpl, 'message' => $messageTpl, 'pdf' => $pdfTpl, ]); $twig = new \Twig\Environment($loader, [ 'autoescape' => 'html', 'strict_variables' => false, 'cache' => false, ]); // Provide both original keys and canonicalized token keys. $safeContext = $this->canonicalizeContext($context); return [ 'subject' => (string) $twig->render('subject', $safeContext), 'message' => (string) $twig->render('message', $safeContext), 'pdf_attachment' => (string) $twig->render('pdf', $safeContext), 'warnings' => [], ]; } /** @param array $row @return array */ private function canonicalizeContext(array $row): array { $out = $row; foreach ($row as $key => $value) { $lower = strtolower((string) $key); $canon = preg_replace('/[^a-z0-9_]+/', '_', $lower) ?? $lower; $canon = trim((string) preg_replace('/_+/', '_', $canon), '_'); if ($canon !== '' && !array_key_exists($canon, $out)) { $out[$canon] = $value; } } return $out; } }