Referência
Sintaxe de templates
Templates de rota, blocos reutilizáveis, valores e composição condicional.
Os templates usam shortcodes entre [# e #].
Templates não têm lógica, por princípio. Um template é markup limpo e simples: ele exibe valores, repete um template para uma lista e, no máximo, escolhe qual template vem a seguir ([#if … tpl=…#]). Decisões, cálculos e preparação de dados ficam em bibliotecas, onde podem ser lidos e testados num só lugar. Não há blocos de lógica, laços com condições nem expressões dentro dos templates, como engines como Twig ou Blade permitem; misturar lógica no markup é exatamente o que o Nimbly evita.
[#shortcode#]
[#shortcode param#]
[#shortcode key=value key2=value2#]
Os shortcodes podem ser aninhados: qualquer valor de parâmetro ou nome de shortcode pode conter um shortcode:
[#get data.users.[#get selected-id#]#]
Isso também funciona em parâmetros tpl= e em nomes de shortcode, o que permite compor nomes de template dinamicamente:
[#if current_image=(not-empty) tpl=card-image[#if current_image=(empty) echo=-placeholder#]#]
Resulta em tpl=card-image quando há uma imagem, e em tpl=card-image-placeholder quando não há.
Uma aplicação especialmente poderosa é a escolha por tipo de conteúdo: coloque um campo de tipo diretamente no nome do shortcode para ir a um template específico do tipo, sem nenhuma ramificação:
[#card-[#get item.type#]#]
Resulta em [#card-news#], [#card-event#], [#card-download#] etc., conforme o tipo do item. Esse padrão elimina cadeias de condições [#if item.type=X tpl=Y#] e cresce sem esforço quando novos tipos surgem.
Use default= para garantir um nome de shortcode válido quando o valor pode faltar ou não ser reconhecido; um shortcode não resolvido aparece como texto literal na saída:
[#card-[#get item.type default=unknown#]#]
Implemente [#card-unknown#] como alternativa genérica (ou deixe-o vazio para não exibir nada).
Os templates são arquivos simples no formato de saída desejado (HTML, JSON, CSS etc.). Eles têm a extensão .tpl.
Templates de rota
Toda URL corresponde a um arquivo em ext/uri/:
ext/uri/index.tpl → /
ext/uri/about/index.tpl → /about/
ext/uri/blog/(slug)/index.tpl → /blog/<anything>/
Um template de rota gera a página:
[#html#]
O main.tpl na mesma pasta contém o corpo da página, exibido dentro da shell HTML. Ele sempre pertence a essa rota; até ext/uri/main.tpl é só o corpo da página inicial (/), não um layout global.
Templates reutilizáveis
Ficam em ext/tpl/<name>/index.tpl. São chamados pelo nome como shortcode:
[#hero-section#]
Os templates não recebem parâmetros diretamente. Para passar dados a um template, use [#set ... overwrite#] antes de chamá-lo:
[#set back_url=/agenda overwrite#]
[#back-button#]
Dentro do template, leia a variável com [#get back_url default=/#]. Os shortcodes de biblioteca (ext/lib/<name>/) são diferentes: eles recebem $params e podem ser chamados com parâmetros.
Convenção de nomes de templates
Dê a cada template reutilizável um nome com o tipo primeiro, seguido da rota ou do assunto a que pertence:
{type}-{route-or-subject}
Tipos e exemplos:
| Tipo | Usado para | Exemplo |
|---|---|---|
page- |
Template de página inteira (um por rota) | page-about, page-projects |
section- |
Uma seção ou bloco dentro de uma página | section-home-header, section-page-header |
card- |
Um cartão repetível | card-project, card-team-member |
logo- |
Um componente que exibe um logo | logo-partner, logo-partner-image |
icon- |
Um ícone inline | icon-arrow-right, icon-chevron-right |
form- |
Um componente de formulário | form-registration |
Os subtemplates dentro de uma pasta de página seguem a mesma regra, com o tipo primeiro:
ext/tpl/page-projects/
section-project-list.tpl
card-project.tpl
card-project-image.tpl
Nunca coloque o tipo no final (project-card, about-page). Com o tipo primeiro, todos os cartões, todas as seções e todos os templates de página ficam juntos no sistema de arquivos.
Templates de uma rota
Os templates também podem ficar soltos dentro de uma pasta de URI (não numa subpasta). Eles não são rotas; são templates parciais incluídos a partir do index.tpl ou do main.tpl dessa rota.
ext/uri/new/page-settings-fields.tpl ← partial, not a route
ext/uri/new/index.tpl ← route
ext/uri/new/main.tpl ← route body
Importante: uma subpasta em ext/uri/ sempre cria uma nova rota. Um arquivo .tpl solto numa pasta de URI é um template parcial dessa rota.
route.inc
O route.inc só é usado em rotas dinâmicas: rotas com segmentos (param) na URL que precisam ser correspondidos, validados e aceitos ou recusados. As rotas estáticas (caminhos simples, sem parâmetros) não usam route.inc; o index.tpl delas é carregado diretamente.
As rotas dinâmicas não são descobertas sozinhas. Toda rota com um segmento (param) também precisa de um registro no recurso .routes; senão a requisição nunca chega ao route.inc e o resultado é um 404. Por exemplo, ext/uri/en/blog/(slug)/route.inc precisa de um registro em .routes com route: "en/blog/(slug)" e um order adequado, normalmente 200. Rode php core/cli/nimbly.php routes:sync para percorrer os arquivos ext/uri/**/route.inc e criar os registros de .routes que faltam.
Um route.inc fica ao lado do index.tpl e decide se a requisição pertence a esta rota:
<?php
$parts = router_match(__FILE__);
if ($parts === false) return;
$slug = $parts[0];
load_library('data');
load_library('util');
$records = data_read_index('articles', 'title_slug', md5_uuid($slug));
if (empty($records)) return;
set_variable_dot('record', reset($records));
router_accept();
Funções do roteador
| Função | Descrição |
|---|---|
router_match(__FILE__) |
Compara a URL atual com os segmentos dinâmicos desta rota. Devolve um array com os valores capturados (um por (param) no caminho), ou false se a URL não corresponde. Chame sempre primeiro. |
router_accept() |
Indica que este route.inc aceita a requisição. A engine de templates segue para o index.tpl. |
router_deny() |
Indica a recusa; o roteador continua procurando outra rota correspondente. Chamar return sem router_accept() tem o mesmo efeito. |
O padrão: chame router_match(), valide o resultado, carregue e valide os dados e só chame router_accept() quando tudo estiver certo. Se algo falhar, apenas return; a requisição passa para a próxima rota ou para um 404.
Valide toda a entrada da rota na fronteira
Não confie em nenhum valor que chega por uma URL. Isso inclui segmentos dinâmicos do caminho, parâmetros GET e de consulta, cabeçalhos usados pela rota e qualquer valor da requisição obtido pelo sistema de variáveis dos templates. Scanners automáticos enviam rotineiramente fragmentos de SQL, sintaxe de shell, sequências de travessia de pastas, números enormes e outras cargas de fuzzing a todo parâmetro público, mesmo que o Nimbly não use SQL.
Valide e normalize esses valores na rota antes de passá-los a uma biblioteca ou de usá-los em cálculos, buscas de recursos, caminhos de arquivo, chaves de cache, redirecionamentos ou comandos:
- Prefira uma allowlist com a sintaxe exata aceita; não tente remover fragmentos sabidamente ruins.
- Imponha tipo, tamanho, intervalo e valores permitidos, conforme o caso.
- Converta a entrada numérica validada em
intoufloatantes de calcular. - Mantenha o texto bruto da requisição fora de caminhos de arquivo e de cache, a menos que toda a sua sintaxe tenha sido validada por allowlist e faça parte de propósito da identidade do cache.
- Recuse a entrada inválida na hora, com a resposta de falha normal da rota. Rotas de assets parecidas com uma API podem devolver um HTTP 400 vazio quando a entrada malformada deve ficar visível para limitação de taxa ou detecção de bots.
- Valide no PHP por conta própria, mesmo quando o Apache, um proxy ou uma CDN já filtram o mesmo parâmetro; o filtro na borda é uma otimização, não a fronteira de confiança.
Por exemplo, um parâmetro ratio de miniatura precisa ser validado como um decimal positivo limitado e convertido em float antes dos cálculos da imagem. Um texto como ratio=1' OR 1=1 nunca deve chegar ao cálculo da miniatura nem à criação do arquivo de cache.
Mantenha o route.inc só com lógica de roteamento: corresponder, validar, definir uma ou duas variáveis, aceitar ou recusar. A lógica de negócio pertence a uma biblioteca carregada pelo index.tpl por meio de um shortcode.
Nunca acrescente um
route.inca uma rota estática. Uma rota estática (comologin/,about/) não tem segmentos(param)e é sempre aceita; acrescentar umroute.incsem chamarrouter_accept()causa um 404. Se precisar de lógica numa página estática, coloque-a num shortcode chamado a partir doindex.tpl, não noroute.inc.
routeé um nome de shortcode reservado. Não existe um shortcode[#route#], e não é possível criar um. O nome de arquivoroute.incpertence exclusivamente ao sistema de rotas.