Files
portal-api/tests/check-email-template-placeholders.php
2026-09-23 15:17:18 +07:00

112 lines
4.2 KiB
PHP

<?php
/**
* Soát toàn bộ template email đang bật trong tbl.template:
* 1. Mọi {{placeholder}} trong subject/message phải được khai báo ở metadata.variables,
* nếu không EmailTemplateRenderer sẽ ném InvalidTemplateVariableException lúc gửi.
* 2. Không còn placeholder kiểu blade {{ $var }} — renderer không hiểu, sẽ in ra nguyên văn.
* 3. Mọi scenario mà Mailable đang trỏ tới đều phải có row tương ứng.
*
* Chạy: php -d zend.assertions=1 tests/check-email-template-placeholders.php
*/
require __DIR__ . '/../vendor/autoload.php';
$app = require __DIR__ . '/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$failures = 0;
// ── 1 + 2. Từng template phải render được ────────────────────────────────────
$templates = App\Models\Template::where('status', 1)->get();
echo "Soát {$templates->count()} template đang bật.\n\n";
foreach ($templates as $template) {
$name = $template->scenario ?: ($template->key ?: "#{$template->id}");
$content = (string) $template->subject . ' ' . (string) $template->message;
preg_match_all('/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/', $content, $matches);
$used = array_unique($matches[1]);
$declared = array_keys($template->variables());
$undeclared = array_values(array_diff($used, $declared));
// Layout dùng {{$content}} làm chỗ cắm nội dung — đó là quy ước riêng, không phải biến.
$bladeish = $template->scenario === 'layout'
? []
: array_values(array_filter(
preg_match_all('/\{\{\s*\$[a-zA-Z_]+\s*\}\}/', $content, $m2) ? $m2[0] : []
));
if ($undeclared !== []) {
echo " ✘ {$name}: biến chưa khai báo → " . implode(', ', $undeclared) . "\n";
$failures++;
continue;
}
if ($bladeish !== []) {
echo " ✘ {$name}: còn placeholder blade → " . implode(', ', array_unique($bladeish)) . "\n";
$failures++;
continue;
}
echo " ✔ {$name}\n";
}
// ── 3. Mailable nào trỏ tới scenario chưa có row ─────────────────────────────
echo "\nSoát scenario mà Mailable đang dùng.\n\n";
$mailFiles = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . '/../app/Mail'));
$referenced = [];
foreach ($mailFiles as $file) {
if ($file->isDir() || $file->getExtension() !== 'php') {
continue;
}
if (preg_match_all("/renderFromTemplate\(\s*'([a-z0-9_]+)'/", file_get_contents($file->getPathname()), $m)) {
foreach ($m[1] as $scenario) {
$referenced[$scenario] = basename($file->getPathname());
}
}
}
$existing = App\Models\Template::where('status', 1)
->get(['scenario', 'key'])
->flatMap(fn ($t) => array_filter([$t->scenario, $t->key]))
->unique()
->all();
foreach ($referenced as $scenario => $mailable) {
if (in_array($scenario, $existing, true)) {
echo " ✔ {$scenario} ({$mailable})\n";
} else {
echo " ✘ {$scenario} ({$mailable}): KHÔNG có template — gửi sẽ ném ModelNotFoundException\n";
$failures++;
}
}
// ── 4. Không còn Mailable nào render qua Blade ───────────────────────────────
echo "\nSoát Blade trong app/Mail.\n\n";
$bladeMailables = [];
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . '/../app/Mail')) as $file) {
if ($file->isDir() || $file->getExtension() !== 'php') {
continue;
}
if (preg_match('/\bview:\s*\'|\bmarkdown:\s*\'|->view\(|->markdown\(/', file_get_contents($file->getPathname()))) {
$bladeMailables[] = basename($file->getPathname());
}
}
if ($bladeMailables === []) {
echo " ✔ Không Mailable nào dùng Blade.\n";
} else {
echo " ✘ Còn dùng Blade: " . implode(', ', $bladeMailables) . "\n";
$failures += count($bladeMailables);
}
echo "\n" . ($failures === 0 ? "TẤT CẢ ĐẠT.\n" : "CÓ {$failures} LỖI.\n");
assert($failures === 0, "Có {$failures} template/mailable không đạt");
exit($failures === 0 ? 0 : 1);