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);