PHP Dentro do HTML

O PHP nasceu para isto: um arquivo que é quase todo HTML, com "buracos" onde o PHP coloca informações que mudam. Nesta aula você vai abrir e fechar o PHP no meio de uma página, escrever valores com o atalho <?= ?>, gerar listas e tabelas a partir de dados e conferir, no código-fonte, exatamente o que chega ao navegador.

Você vai aprender
  • Alternar entre HTML e PHP com <?php e ?>.
  • Escrever um valor no meio do HTML com <?= … ?>.
  • Gerar os itens de uma lista e as linhas de uma tabela a partir de dados.
  • A sintaxe "de modelo": foreach: … endforeach; e if: … endif;.
  • Ler o código-fonte que o navegador recebe.
  • Proteger a página com htmlspecialchars.
Um pouquinho de HTML

HTML é feito de marcas (tags) entre < > que dizem o que cada pedaço do texto é. As desta aula: <h1> e <h2> (títulos), <p> (parágrafo), <ul> (lista) com <li> (item), <table> (tabela) com <tr> (linha), <th> (cabeçalho) e <td> (célula). Quase toda marca abre e fecha: <p>texto</p>.


A ideia: um formulário de papel com lacunas

Pense numa carta-modelo do banco: "Prezado(a)       , seu saldo é R$     ." O texto fixo é igual para todos; só as lacunas mudam. Uma página PHP é essa carta: o HTML é o texto fixo, e os trechos de PHP preenchem as lacunas antes de a carta ser enviada.

Você já viu na aula Como o PHP Funciona que o PHP lê o arquivo de cima para baixo. A regra completa é simples:

  • Tudo o que está fora de <?php … ?> é copiado para a saída como está.
  • Tudo o que está dentro é executado. Só o que o código manda escrever (com echo ou <?=) vai para a saída.

Abrindo e fechando o PHP

Num arquivo, você pode entrar e sair do PHP quantas vezes quiser. Esta é a página de perfil da Ana:

<?php
$nome = "Ana";
$cidade = "Recife";
$idade = 29;
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>Perfil de <?= $nome ?></title>
</head>
<body>
  <h1><?= $nome ?></h1>
  <p>Mora em <?= $cidade ?> e tem <?= $idade ?> anos.</p>
  <p>Daqui a 10 anos, terá <?= $idade + 10 ?>.</p>
</body>
</html>
LinhasO que acontece
1 a 5Um bloco de PHP no topo prepara os dados: três variáveis (caixinhas com nome, começando com $; aula Variáveis). Nada é escrito ainda. O ?> da linha 5 fecha o PHP.
6 a 9HTML comum, copiado como está.
10<?= $nome ?> escreve o valor de $nome dentro do título da aba.
13 a 15Mais lacunas preenchidas. Na linha 15, o PHP faz uma conta antes de escrever: $idade + 10.

O atalho <?= … ?>

<?=
Abre o PHP e já manda escrever. É um atalho para <?php echo.
$idade + 10
O que escrever: uma variável, um texto entre aspas, uma conta, o resultado de uma função…
?>
Fecha o PHP. Aqui o ; é dispensável: o ?> já termina a instrução.

Estas três linhas escrevem a mesma coisa:

<p><?= $nome ?></p>
<p><?php echo $nome; ?></p>
<p><?php echo $nome ?></p>

Dentro do HTML, prefira o <?=: é mais curto e deixa claro que ali há "uma lacuna". Ele funciona sempre, independente da configuração do PHP (ao contrário da marca curta <?, vista em Olá, Mundo).

O que o navegador recebe

Com o servidor embutido ligado na pasta (php -S localhost:8000), o curl mostra o HTML que sai do PHP, o mesmo que o navegador mostra em "Exibir código-fonte da página" (Ctrl+U):

$ curl localhost:8000/perfil.php
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>Perfil de Ana</title>
</head>
<body>
  <h1>Ana</h1>
  <p>Mora em Recife e tem 29 anos.</p>
  <p>Daqui a 10 anos, terá 39.</p>     « a conta virou o resultado
</body>
</html>

Nenhum <?, nenhum $: só HTML puro, com as lacunas preenchidas. Repare também que o bloco do topo (linhas 1 a 5) não deixou nem uma linha em branco: o PHP "engole" a quebra de linha que vem logo depois de um ?>.

Experimente a mesma página. Depois de rodar, alterne entre as abas Resultado e Código-fonte HTML da caixa:

Missão: a Ana se mudou e fez aniversário. Mude só as variáveis do topo para que a página diga Mora em Salvador e tem 30 anos. Repare que a conta da linha de baixo se ajusta sozinha.

<?php
$nome = "Ana";
$cidade = "Recife";
$idade = 29;
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>Perfil de <?= $nome ?></title>
</head>
<body>
  <h1><?= $nome ?></h1>
  <p>Mora em <?= $cidade ?> e tem <?= $idade ?> anos.</p>
  <p>Daqui a 10 anos, terá <?= $idade + 10 ?>.</p>
</body>
</html>
<?php
$nome = "Ana";
$cidade = "Salvador";
$idade = 30;
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>Perfil de <?= $nome ?></title>
</head>
<body>
  <h1><?= $nome ?></h1>
  <p>Mora em <?= $cidade ?> e tem <?= $idade ?> anos.</p>
  <p>Daqui a 10 anos, terá <?= $idade + 10 ?>.</p>
</body>
</html>

Gerando uma lista

O poder de verdade aparece quando os dados são muitos. A Ana guarda a lista de compras numa lista do PHP (um array, aula Arrays Indexados) e quer um item <li> para cada coisa. Em vez de escrever cada <li> à mão, ela pede ao PHP para repetir a linha do item uma vez para cada elemento:

<?php
$compras = ["pão", "leite", "café", "banana"];
?>
<h2>Lista de compras</h2>
<ul>
<?php foreach ($compras as $item): ?>
  <li><?= $item ?></li>
<?php endforeach; ?>
</ul>
<p>Total: <?= count($compras) ?> itens.</p>
["pão", "leite", "café", "banana"]
Uma lista com quatro textos, guardada em $compras.
<?php foreach ($compras as $item): ?>
"Para cada elemento de $compras, chamado de $item a cada volta…" (for each = "para cada"). Os dois-pontos : no fim abrem o trecho que vai se repetir.
<li><?= $item ?></li>
O trecho repetido: HTML com uma lacuna. A cada volta, $item vale um elemento diferente.
<?php endforeach; ?>
"Fim do para cada": marca onde termina o trecho repetido.
count($compras)
Uma função que conta os elementos da lista: 4.
$ curl localhost:8000/lista.php
<h2>Lista de compras</h2>
<ul>
  <li>pão</li>           « uma volta para cada item
  <li>leite</li>
  <li>café</li>
  <li>banana</li>
</ul>
<p>Total: 4 itens.</p>

Três linhas de modelo viraram quatro itens. Com mil produtos, seriam mil itens, sem mudar o modelo. O foreach tem uma aula inteira, foreach; aqui basta entender a ideia de "repetir um pedaço do HTML".

Missão: acrescente "arroz" à lista (depois de "banana", separado por vírgula) e rode. Confira na aba Código-fonte HTML que apareceu um <li>arroz</li> novo e que o total mudou sozinho.

<?php
$compras = ["pão", "leite", "café", "banana"];
?>
<h2>Lista de compras</h2>
<ul>
<?php foreach ($compras as $item): ?>
  <li><?= $item ?></li>
<?php endforeach; ?>
</ul>
<p>Total: <?= count($compras) ?> itens.</p>
<?php
$compras = ["pão", "leite", "café", "banana", "arroz"];
?>
<h2>Lista de compras</h2>
<ul>
<?php foreach ($compras as $item): ?>
  <li><?= $item ?></li>
<?php endforeach; ?>
</ul>
<p>Total: <?= count($compras) ?> itens.</p>

Gerando uma tabela, com decisão

As notas da turma estão guardadas com o nome de cada aluno (um array associativo, aula Arrays Associativos). A tabela tem uma linha por aluno e, na última coluna, uma decisão: "Aprovado" se a nota for 7 ou mais, "Recuperação" se não.

<?php
$notas = [
    "Ana" => 9.5,
    "Bruno" => 6.0,
    "Carla" => 7.5,
];
?>
<table>
  <tr><th>Aluno</th><th>Nota</th><th>Situação</th></tr>
<?php foreach ($notas as $aluno => $nota): ?>
  <tr>
    <td><?= $aluno ?></td>
    <td><?= $nota ?></td>
    <td><?php if ($nota >= 7): ?>Aprovado<?php else: ?>Recuperação<?php endif; ?></td>
  </tr>
<?php endforeach; ?>
</table>
TrechoO que faz
"Ana" => 9.5Guarda a nota 9.5 com o nome "Ana" (em PHP, decimais usam ponto).
foreach ($notas as $aluno => $nota):Para cada par, coloca o nome em $aluno e a nota em $nota.
if ($nota >= 7): ?>AprovadoSe a nota for maior ou igual a 7, escreve "Aprovado" (aula if, elseif, else).
<?php else: ?>RecuperaçãoSenão, escreve "Recuperação".
<?php endif; ?>Fim da decisão.
$ curl localhost:8000/tabela.php
<table>
  <tr><th>Aluno</th><th>Nota</th><th>Situação</th></tr>
  <tr>
    <td>Ana</td>
    <td>9.5</td>
    <td>Aprovado</td>
  </tr>
  <tr>
    <td>Bruno</td>
    <td>6</td>                 « 6.0 é escrito como 6
    <td>Recuperação</td>
  </tr>
  <tr>
    <td>Carla</td>
    <td>7.5</td>
    <td>Aprovado</td>
  </tr>
</table>

Repare na nota do Bruno: 6.0 saiu como 6, porque o PHP escreve números da forma mais curta. Para mostrar sempre com casas decimais (e com vírgula, como no Brasil), use number_format($nota, 1, ",", ".") (aula Formatar Números).

Missão: a turma ganhou um aluno novo. Acrescente "Davi" => 5.5, à lista de notas e rode: a tabela deve ganhar uma linha Davi | 5.5 | Recuperação.

<?php
$notas = [
    "Ana" => 9.5,
    "Bruno" => 6.0,
    "Carla" => 7.5,
];
?>
<table border="1">
  <tr><th>Aluno</th><th>Nota</th><th>Situação</th></tr>
<?php foreach ($notas as $aluno => $nota): ?>
  <tr>
    <td><?= $aluno ?></td>
    <td><?= $nota ?></td>
    <td><?php if ($nota >= 7): ?>Aprovado<?php else: ?>Recuperação<?php endif; ?></td>
  </tr>
<?php endforeach; ?>
</table>
<?php
$notas = [
    "Ana" => 9.5,
    "Bruno" => 6.0,
    "Carla" => 7.5,
    "Davi" => 5.5,
];
?>
<table border="1">
  <tr><th>Aluno</th><th>Nota</th><th>Situação</th></tr>
<?php foreach ($notas as $aluno => $nota): ?>
  <tr>
    <td><?= $aluno ?></td>
    <td><?= $nota ?></td>
    <td><?php if ($nota >= 7): ?>Aprovado<?php else: ?>Recuperação<?php endif; ?></td>
  </tr>
<?php endforeach; ?>
</table>

Formas diferentes de misturar

Há dois estilos para gerar HTML com PHP. O primeiro é o que você viu: HTML com lacunas. O segundo é o contrário: PHP que escreve HTML com echo:

<?php
$nome = "Ana";
echo "<h1>Olá, $nome!</h1>\n";
echo "<p>Esta página foi montada só com echo.</p>\n";
$ curl localhost:8000/echohtml.php
<h1>Olá, Ana!</h1>
<p>Esta página foi montada só com echo.</p>

Dentro de aspas duplas, o PHP troca $nome pelo valor (isso se chama interpolação, aula Interpolação). Funciona, mas com páginas grandes o HTML fica preso dentro de aspas, sem as cores do editor, e as próprias aspas do HTML (class="…") brigam com as do PHP.

EstiloExemploQuando usar
HTML com lacunas <?= ?><p>Olá, <?= $nome ?>!</p>Páginas e modelos. O HTML fica legível e o editor o colore.
Sintaxe de modelo<?php foreach (…): ?> … <?php endforeach; ?>Repetições e decisões no meio do HTML. As palavras endforeach/endif deixam claro o que está fechando.
echo com HTMLecho "<p>Olá, $nome!</p>";Trechos curtos dentro de um bloco PHP maior.
Chaves { }<?php foreach (…) { ?> … <?php } ?>Funciona igual à sintaxe de modelo, mas um <?php } ?> solto no meio do HTML é difícil de acompanhar. Prefira a de modelo.

Em projetos maiores, o costume é separar: um arquivo PHP prepara os dados (consulta o banco, faz as contas) e outro arquivo, o modelo (template), só tem HTML com lacunas. Os frameworks levam essa ideia adiante com linguagens de modelo próprias (o Blade, do Laravel; o Twig, do Symfony).


Segurança desde o começo: htmlspecialchars

Até aqui, os dados foram escritos por você. Num site de verdade, muitos vêm de visitantes: nomes, comentários, buscas. E um visitante mal-intencionado pode digitar HTML, ou até JavaScript, no lugar de um nome. Veja um comentário assim sendo escrito na página com e sem proteção:

<?php
$comentario = "<b>Adorei!</b> <script>alert('invadido')</script>";
?>
<p>Sem proteção: <?= $comentario ?></p>
<p>Com proteção: <?= htmlspecialchars($comentario) ?></p>
$ curl localhost:8000/xss.php
<p>Sem proteção: <b>Adorei!</b> <script>alert('invadido')</script></p>             « o script entrou na página
<p>Com proteção: &lt;b&gt;Adorei!&lt;/b&gt; &lt;script&gt;alert(&#039;invadido&#039;)&lt;/script&gt;</p>   « virou texto inofensivo

Sem proteção, o <script> foi parar no HTML, e o navegador de todo visitante executaria esse código (aqui, só uma janelinha; na vida real, roubar a sessão de quem está logado). Esse ataque se chama XSS (cross-site scripting). Com htmlspecialchars, os caracteres especiais do HTML viram entidades: < vira &lt;, > vira &gt;, a aspa simples vira &#039;. O navegador mostra esses códigos como os próprios caracteres, na tela, mas não os trata como marcas. O visitante vê o texto exatamente como foi digitado, sem nada ser executado.

A regra do guia

Todo dado que não foi escrito por você no código (o que vem de formulários, do endereço, do banco de dados, de arquivos) passa por htmlspecialchars antes de ir para o HTML. Os exemplos do guia seguem essa regra a partir daqui. A aula Escapar a Saída (XSS) mostra os detalhes.


Quando dá errado

O <?= $nome ?> aparece na página, ou a lacuna fica vazia

O que aconteceu: o arquivo não passou pelo PHP: ele termina em .html, ou foi aberto com dois cliques (file://). No código-fonte aparece o <?= cru; na tela, o navegador esconde a marca que não conhece e a lacuna fica vazia.

Como resolver: extensão .php e abrir pelo servidor (http://localhost:8000/…). Veja O que é PHP.

"syntax error, unexpected end of file" num modelo
<?php $compras = ["pão", "leite"]; ?>
<ul>
<?php foreach ($compras as $item): ?>
  <li><?= $item ?></li>
</ul>
$ php lista-erro.php

Parse error: syntax error, unexpected end of file in /home/ana/html/lista-erro.php on line 6

O que aconteceu: "fim de arquivo inesperado". O foreach: abriu um trecho de repetição e o arquivo acabou (a linha 6 é a linha vazia depois do </ul>) sem o endforeach;. A mensagem não diz o que falta; quando aparecer "unexpected end of file", procure algo que abriu e não fechou. Rode a caixa abaixo para ver o mesmo erro no editor.

Como resolver: todo foreach (…): precisa de um <?php endforeach; ?>, e todo if (…):, de um <?php endif; ?>.

<?php $compras = ["pão", "leite"]; ?>
<ul>
<?php foreach ($compras as $item): ?>
  <li><?= $item ?></li>
</ul>
Espaços e linhas em branco a mais no código-fonte

O que aconteceu: o PHP engole só a quebra de linha que vem imediatamente depois de um ?>. Se houver espaços entre o ?> e o fim da linha, a quebra não é engolida e uma linha só de espaços aparece na saída:

$ curl -s localhost:8000/espaco.php | cat -A
<p>A</p>$
<p>B</p>$
     $                    « espaços depois do ?> da linha 4
<p>C</p>$

(O cat -A mostra o fim de cada linha como $, para enxergar os espaços.) No navegador, isso não muda nada na tela. Mas, antes de um header(), até um espaço causa o "headers already sent" visto em Olá, Mundo.

Como resolver: configure o editor para apagar espaços no fim das linhas (no VS Code, "files.trimTrailingWhitespace": true).


Referência

FormaO que faz
<?php … ?>Executa o código; só o que for escrito vai para a saída.
<?= valor ?>Escreve o valor (atalho de <?php echo valor ?>).
<?php foreach ($lista as $item): ?> … <?php endforeach; ?>Repete o trecho para cada elemento.
<?php foreach ($lista as $chave => $valor): ?>Idem, com a chave e o valor.
<?php if (condição): ?> … <?php else: ?> … <?php endif; ?>Escreve um trecho ou outro.
htmlspecialchars($texto)Troca < > & " ' por entidades: o texto aparece, mas não vira HTML.
count($lista)Quantos elementos a lista tem.
Ctrl+U no navegador / curl URLVer o HTML que o PHP gerou.

Para fechar, proteja uma lacuna. O nome do produto tem um &, um dos caracteres especiais do HTML:

Missão: passe $produto por htmlspecialchars antes de escrever. Na tela, nada muda (continua Café & Pão); mas na aba Código-fonte HTML o & deve aparecer como &amp;, o jeito correto de escrevê-lo em HTML.

<?php
$produto = "Café & Pão";
$preco = "12,90";
?>
<h1>Promoção do dia</h1>
<p><?= $produto ?> por R$ <?= $preco ?></p>
<?php
$produto = "Café & Pão";
$preco = "12,90";
?>
<h1>Promoção do dia</h1>
<p><?= htmlspecialchars($produto) ?> por R$ <?= htmlspecialchars($preco) ?></p>

Exercícios

1. O atalho

Complete para escrever o valor de $nome no meio do HTML:

<p> $nome ?></p>

2. Fim da repetição

Complete a linha que fecha um foreach (…): no meio do HTML:

<?php ?>

3. O arquivo tem <p><?= 2 + 3 ?></p>. O que aparece no código-fonte, no navegador?

4. Por que usar htmlspecialchars ao escrever o nome que um visitante digitou?


Resumo

  • Fora de <?php … ?>, tudo é copiado para a saída; dentro, só sai o que for escrito.
  • <?= valor ?> preenche uma lacuna no HTML.
  • foreach (…): … endforeach; repete um pedaço do HTML para cada elemento; if (…): … else: … endif; escolhe um pedaço.
  • O navegador recebe só o HTML gerado: confira com Ctrl+U, com o curl ou com a aba Código-fonte HTML do editor.
  • Prefira HTML com lacunas a echo com HTML dentro de aspas.
  • Dados que não foram escritos por você passam por htmlspecialchars antes de ir para a página.
  • Próximo passo: as regras gerais da linguagem, em Sintaxe.