Skip to content

Ajouter des vues HTML

Ce guide explique comment ajouter des réponses HTML rendues côté serveur à une application NENE2 en utilisant NativePhpViewRenderer et HtmlResponseFactory.

Prérequis : Vous avez une application NENE2 fonctionnelle avec au moins une route. Sinon, commencez par Ajouter une route personnalisée.


Vue d'ensemble

NENE2 fournit une couche de rendu HTML minimale sans dépendances externes :

ClasseRôle
NativePhpViewRendererRend les templates .php dans une portée isolée
HtmlEscaperÉchappe les valeurs pour une sortie HTML sûre (htmlspecialchars / UTF-8 / guillemets complets)
HtmlResponseFactoryEncapsule le HTML rendu dans une réponse PSR-7 text/html; charset=utf-8
TemplateNotFoundExceptionLevée si le fichier template n'existe pas ou si le chemin est invalide

Les réponses HTML coexistent avec les endpoints JSON. Vous pouvez les ajouter à la même application sans supprimer les routes existantes.


1. Créer les templates

NENE2 s'attend à ce que les fichiers templates PHP natifs soient placés sous un seul répertoire racine. L'emplacement standard est templates/ à la racine du projet.

my-app/
├── templates/
│   ├── layout.php       (layout partagé optionnel)
│   ├── home.php
│   └── notes/
│       ├── index.php
│       └── show.php

Ce que chaque template reçoit :

  • $e — assistant d'échappement (HtmlEscaper::escape()). Toujours l'utiliser pour les valeurs utilisateur.
  • Chaque clé du tableau $data passé à render() est injectée comme variable individuelle.

templates/home.php

php
<!doctype html>
<html lang="fr">
<head><meta charset="utf-8"><title><?= $e($title) ?></title></head>
<body>
  <h1><?= $e($title) ?></h1>
  <p><?= $e($description) ?></p>
</body>
</html>

templates/notes/index.php

php
<!doctype html>
<html lang="fr">
<head><meta charset="utf-8"><title>Liste des notes</title></head>
<body>
  <h1>Notes</h1>
  <ul>
    <?php foreach ($notes as $note): ?>
      <li><a href="/notes/<?= $e($note['id']) ?>"><?= $e($note['title']) ?></a></li>
    <?php endforeach; ?>
  </ul>
</body>
</html>

Sécurité : Utilisez toujours $e(...) pour les valeurs provenant de l'utilisateur, de la base de données ou de systèmes externes. L'omettre crée des vulnérabilités XSS.


2. Câbler le renderer dans le ServiceProvider

Enregistrez NativePhpViewRenderer et HtmlResponseFactory dans le ServiceProviderInterface de votre application :

php
use Nene2\DependencyInjection\ContainerBuilder;
use Nene2\DependencyInjection\ServiceProviderInterface;
use Nene2\View\HtmlResponseFactory;
use Nene2\View\NativePhpViewRenderer;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;

final readonly class AppServiceProvider implements ServiceProviderInterface
{
    public function register(ContainerBuilder $builder): void
    {
        $builder
            ->set(
                NativePhpViewRenderer::class,
                static function (ContainerInterface $container): NativePhpViewRenderer {
                    return new NativePhpViewRenderer(dirname(__DIR__) . '/templates');
                },
            )
            ->set(
                HtmlResponseFactory::class,
                static function (ContainerInterface $container): HtmlResponseFactory {
                    $responseFactory = $container->get(ResponseFactoryInterface::class);
                    $streamFactory   = $container->get(StreamFactoryInterface::class);
                    $renderer        = $container->get(NativePhpViewRenderer::class);

                    return new HtmlResponseFactory($responseFactory, $streamFactory, $renderer);
                },
            );
    }
}

3. Utiliser HtmlResponseFactory dans un handler

Injectez HtmlResponseFactory dans votre handler et appelez create() :

php
use Nene2\View\HtmlResponseFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final readonly class HomeHandler
{
    public function __construct(private HtmlResponseFactory $html) {}

    public function __invoke(ServerRequestInterface $request): ResponseInterface
    {
        return $this->html->create('home.php', [
            'title'       => 'Bienvenue',
            'description' => 'Application construite avec NENE2.',
        ]);
    }
}

Signature de create() :

php
public function create(
    string $template,           // chemin relatif depuis templateRoot
    array  $data    = [],       // variables transmises au template
    int    $status  = 200,      // code de statut HTTP
    array  $headers = [],       // en-têtes de réponse supplémentaires
): ResponseInterface

4. Enregistrer les routes

php
$router->get('/', new HomeHandler($container->get(HtmlResponseFactory::class)));
$router->get('/notes', new NoteListHandler($container->get(HtmlResponseFactory::class)));

5. Gérer TemplateNotFoundException

NativePhpViewRenderer::render() lève TemplateNotFoundException dans ces cas :

  • Le fichier template n'existe pas
  • Le chemin du template est vide
  • Le chemin du template contient .. (traversée de répertoire bloquée)

Enregistrez un handler d'exception de domaine pour retourner une réponse HTTP appropriée :

php
use Nene2\Error\DomainExceptionHandlerInterface;
use Nene2\Error\ProblemDetailsResponseFactory;
use Nene2\View\TemplateNotFoundException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final readonly class TemplateNotFoundExceptionHandler implements DomainExceptionHandlerInterface
{
    public function __construct(private ProblemDetailsResponseFactory $problemDetails) {}

    public function handles(\Throwable $exception): bool
    {
        return $exception instanceof TemplateNotFoundException;
    }

    public function handle(\Throwable $exception, ServerRequestInterface $request): ResponseInterface
    {
        return $this->problemDetails->create($request, 'not-found', 'Not Found', 404);
    }
}

6. Mélanger des endpoints HTML et JSON

php
$registerRoutes = static function (Router $router) use ($container): void {
    // API JSON
    $router->get('/api/notes',     $container->get(NoteListJsonHandler::class));
    $router->post('/api/notes',    $container->get(NoteCreateHandler::class));

    // Vues HTML
    $router->get('/',              $container->get(HomeHandler::class));
    $router->get('/notes',         $container->get(NoteListHtmlHandler::class));
    $router->get('/notes/{id}',    $container->get(NoteShowHtmlHandler::class));
};

7. Tester les handlers HTML

php
use Nene2\View\HtmlResponseFactory;
use Nene2\View\NativePhpViewRenderer;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\ServerRequest;

public function testHomeReturnsHtml(): void
{
    $templateRoot = sys_get_temp_dir() . '/test-templates-' . bin2hex(random_bytes(4));
    mkdir($templateRoot);
    file_put_contents($templateRoot . '/home.php', '<h1><?= $e($title) ?></h1>');

    $psr17   = new Psr17Factory();
    $factory = new HtmlResponseFactory($psr17, $psr17, new NativePhpViewRenderer($templateRoot));
    $handler = new HomeHandler($factory);

    $response = $handler(new ServerRequest('GET', '/'));

    self::assertSame(200, $response->getStatusCode());
    self::assertStringContainsString('text/html', $response->getHeaderLine('Content-Type'));
    self::assertStringContainsString('<h1>Bienvenue</h1>', (string) $response->getBody());

    unlink($templateRoot . '/home.php');
    rmdir($templateRoot);
}

Notes de conception

  • Les templates s'exécutent dans une portée de closure, ils n'ont donc pas accès à $this ni aux internes de classe. Les variables sont injectées via extract() + EXTR_SKIP (les variables existantes ne sont pas écrasées).
  • HtmlEscaper utilise ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5. " et ' sont tous deux échappés, et les séquences UTF-8 invalides sont substituées plutôt que supprimées.
  • La traversée de répertoire (../) est bloquée à l'étape de résolution de chemin. Aucun accès au système de fichiers n'a lieu avant cette vérification.

Pour les garanties d'API stable de Nene2\View\*, consultez ADR 0009.


Étapes suivantes

Publié sous licence MIT.