add new guide service

This commit is contained in:
team2
2026-02-26 20:40:42 +01:00
parent 12f2a48f88
commit a68f5182e4
11 changed files with 1017 additions and 12 deletions

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Service\MarkdownRenderer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
#[Route('/admin/guides')]
final class GuideController extends AbstractController
{
private string $guidePath;
public function __construct(ParameterBagInterface $params)
{
$this->guidePath = rtrim(
$params->get('kernel.project_dir'),
'/'
);
}
#[Route('', name: 'admin_guides_index')]
public function index(): Response
{
if (!is_dir($this->guidePath)) {
return $this->render('admin/guides/index.html.twig', [
'guides' => [],
]);
}
$files = glob($this->guidePath . '/*.md') ?: [];
$guides = [];
foreach ($files as $file) {
$slug = basename($file, '.md');
if (!$this->isValidSlug($slug)) {
continue;
}
$content = file_get_contents($file) ?: '';
$title = $this->extractTitleFromMarkdown($content)
?? $this->humanizeSlug($slug);
$guides[] = [
'slug' => $slug,
'title' => $title,
];
}
usort($guides, fn ($a, $b) => strcasecmp($a['title'], $b['title']));
return $this->render('admin/guides/index.html.twig', [
'guides' => $guides,
]);
}
#[Route('/{slug}', name: 'admin_guides_view')]
public function view(string $slug, MarkdownRenderer $renderer): Response
{
if (!$this->isValidSlug($slug)) {
throw $this->createNotFoundException();
}
$file = $this->guidePath . '/' . $slug . '.md';
if (!is_file($file)) {
throw $this->createNotFoundException();
}
$content = file_get_contents($file) ?: '';
return $this->render('admin/guides/view.html.twig', [
'slug' => $slug,
'title' => $this->extractTitleFromMarkdown($content)
?? $this->humanizeSlug($slug),
'html' => $renderer->render($content),
]);
}
// =========================================================
// Helper
// =========================================================
private function extractTitleFromMarkdown(string $content): ?string
{
if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
return trim($matches[1]);
}
return null;
}
private function humanizeSlug(string $slug): string
{
return ucfirst(str_replace('-', ' ', $slug));
}
private function isValidSlug(string $slug): bool
{
return (bool) preg_match('/^[a-zA-Z0-9\-_]+$/', $slug);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\Service;
use League\CommonMark\MarkdownConverter;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
final class MarkdownRenderer
{
private MarkdownConverter $converter;
public function __construct()
{
$config = [
'html_input' => 'strip',
'allow_unsafe_links' => false,
];
$environment = new Environment($config);
// Core Markdown
$environment->addExtension(new CommonMarkCoreExtension());
// GitHub Flavored Markdown (Tables, Strikethrough, Autolinks, Task Lists)
$environment->addExtension(new GithubFlavoredMarkdownExtension());
$this->converter = new MarkdownConverter($environment);
}
public function render(string $markdown): string
{
return (string) $this->converter->convert($markdown);
}
}