Skip to content

Adicionar vistas HTML

Este guia explica como adicionar respostas HTML renderizadas no servidor a uma aplicação NENE2 usando NativePhpViewRenderer e HtmlResponseFactory.

Pré-requisito: Você tem uma aplicação NENE2 funcionando com pelo menos uma rota. Caso contrário, comece por Adicionar uma rota personalizada.


Visão geral

NENE2 fornece uma camada de renderização HTML mínima sem dependências externas:

ClasseFunção
NativePhpViewRendererRenderiza templates .php em escopo isolado
HtmlEscaperEscapa valores para saída HTML segura (htmlspecialchars / UTF-8 / aspas completas)
HtmlResponseFactoryEncapsula o HTML renderizado em uma resposta PSR-7 text/html; charset=utf-8
TemplateNotFoundExceptionLançada quando o arquivo de template não existe ou o caminho é inválido

As respostas HTML coexistem com endpoints JSON. Você pode adicioná-las à mesma aplicação sem remover rotas existentes.


1. Criar os templates

NENE2 espera que os arquivos de template PHP nativo fiquem dentro de um único diretório raiz. O local padrão é templates/ na raiz do projeto.

my-app/
├── templates/
│   ├── layout.php       (layout compartilhado opcional)
│   ├── home.php
│   └── notes/
│       ├── index.php
│       └── show.php

O que cada template recebe:

  • $e — helper de escape (HtmlEscaper::escape()). Use sempre para valores do usuário.
  • Cada chave do array $data passado para render() é injetada como variável individual.

templates/home.php

php
<!doctype html>
<html lang="pt-BR">
<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="pt-BR">
<head><meta charset="utf-8"><title>Lista de notas</title></head>
<body>
  <h1>Notas</h1>
  <ul>
    <?php foreach ($notes as $note): ?>
      <li><a href="/notes/<?= $e($note['id']) ?>"><?= $e($note['title']) ?></a></li>
    <?php endforeach; ?>
  </ul>
</body>
</html>

Segurança: Use sempre $e(...) para valores provenientes do usuário, banco de dados ou sistemas externos. Omitir isso cria vulnerabilidades XSS.


2. Registrar o renderer no ServiceProvider

Registre NativePhpViewRenderer e HtmlResponseFactory no ServiceProviderInterface da sua aplicação:

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. Usar HtmlResponseFactory em um handler

Injete HtmlResponseFactory no seu handler e chame 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'       => 'Bem-vindo',
            'description' => 'Aplicação construída com NENE2.',
        ]);
    }
}

Assinatura de create():

php
public function create(
    string $template,           // caminho relativo a partir de templateRoot
    array  $data    = [],       // variáveis passadas ao template
    int    $status  = 200,      // código de status HTTP
    array  $headers = [],       // cabeçalhos de resposta adicionais
): ResponseInterface

4. Registrar as rotas

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

5. Tratar TemplateNotFoundException

NativePhpViewRenderer::render() lança TemplateNotFoundException nos seguintes casos:

  • O arquivo de template não existe
  • O caminho do template está vazio
  • O caminho do template contém .. (traversal de diretório bloqueado)

Registre um handler de exceção de domínio para retornar uma resposta HTTP adequada:

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. Misturar endpoints HTML e 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));

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

7. Testar os 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>Bem-vindo</h1>', (string) $response->getBody());

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

Notas de design

  • Os templates são executados dentro de uma closure, portanto não têm acesso a $this nem aos internos da classe. As variáveis são injetadas via extract() + EXTR_SKIP (variáveis existentes não são sobrescritas).
  • HtmlEscaper usa ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5. " e ' são ambos escapados, e sequências UTF-8 inválidas são substituídas em vez de descartadas.
  • Traversal de diretório (../) é bloqueado na etapa de resolução de caminho. Nenhum acesso ao sistema de arquivos ocorre antes dessa verificação.

Para as garantias de API estável de Nene2\View\*, consulte ADR 0009.


Próximos passos

Publicado sob a licença MIT.