PHP Criar uma API JSON
Até aqui, o PHP da Ana sempre respondeu com páginas HTML, feitas para uma pessoa ler no navegador. Uma API responde com dados, feitos para outro programa ler: um aplicativo de celular, um site em JavaScript, o sistema de um parceiro. O formato mais usado para esses dados é o JSON. Nesta aula, a Ana transforma a lista de produtos da lojinha numa API de verdade e a testa com o curl.
- O que é uma API e o que muda em relação a uma página HTML.
- Responder JSON:
json_encodee o cabeçalhoContent-Type: application/json. - Descobrir o que o cliente pediu:
$_SERVER['REQUEST_METHOD']e$_SERVER['REQUEST_URI']. - Ler um JSON enviado pelo cliente com
php://inputejson_decode(o$_POSTnão serve aqui). - Os códigos de status 200, 201, 400, 404 e 405, e como enviá-los com
http_response_code. - Testar cada caso com o
curl: GET, POST com JSON, 404 e 405.
A ideia: o balcão e o pedido por escrito
Na lojinha física, a Ana atende pessoas no balcão: mostra os produtos, conversa, embrulha. Isso é a página HTML. Agora imagine que uma escola quer fazer pedidos automaticamente todo mês. Ela não quer "conversar": quer mandar um formulário padronizado e receber de volta outro formulário padronizado, que o sistema dela consiga ler sozinho. Isso é a API (Application Programming Interface, "interface de programação de aplicações").
Uma API em PHP é um script PHP comum, com três diferenças:
- Responde JSON em vez de HTML, e avisa isso no cabeçalho
Content-Type. - Olha o método (
GET= consultar,POST= criar…) e o caminho (/produtos,/produtos/2) para decidir o que fazer. Isso se chama roteamento. - Usa o código de status HTTP para dizer se deu certo (
200,201) ou o que deu errado (404,400…).
1. A primeira resposta JSON
Comece pelo mais simples: um array do PHP convertido em JSON com json_encode (veja a aula JSON).
<?php $dados = ['mensagem' => 'Olá, API!', 'versao' => 1]; echo json_encode($dados), "\n";
A Ana liga o servidor embutido num terminal (php -S localhost:8000) e, em outro, pede a página com o curl -i. O -i ("incluir") mostra também os cabeçalhos da resposta, que o navegador esconde:
$ curl -i localhost:8000/ola.php
HTTP/1.1 200 OK
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:20 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Content-type: text/html; charset=UTF-8
{"mensagem":"Olá, API!","versao":1}
Duas coisas estão erradas:
Content-type: text/html: o PHP avisou que a resposta é uma página HTML, porque é o padrão dele. Um cliente que confere o tipo vai recusar ou tratar como texto.Olá: ojson_encodeescreveu oácomo código (á). É JSON válido (quem ler vai entenderá), mas fica difícil de ler. A opçãoJSON_UNESCAPED_UNICODEmantém os acentos.
O cabeçalho certo é enviado com header(), antes de qualquer echo (como em header e Redirecionamento):
<?php
header('Content-Type: application/json; charset=utf-8');
$dados = ['mensagem' => 'Olá, API!', 'versao' => 1];
echo json_encode($dados), "\n";
$ curl -i localhost:8000/ola2.php
HTTP/1.1 200 OK
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:21 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Content-Type: application/json; charset=utf-8
{"mensagem":"Olá, API!","versao":1}
- header('Content-Type: application/json; charset=utf-8')
- Diz ao cliente: "o corpo é JSON, em UTF-8".
application/jsoné o nome oficial do tipo JSON. - json_encode($dados)
- Array do PHP → texto JSON. Array associativo vira objeto
{…}; lista vira[…]. - , "\n"
- Só para o terminal ficar arrumado (o próximo prompt começa na linha de baixo). O cliente ignora.
As opções do json_encode usadas no resto da aula:
| Opção | Efeito |
|---|---|
JSON_UNESCAPED_UNICODE | Mantém acentos: "Olá" em vez de "Olá". |
JSON_UNESCAPED_SLASHES | Mantém as barras: "/produtos" em vez de "\/produtos". |
JSON_PRETTY_PRINT | Quebra linhas e indenta, para pessoas lerem. Opcional (os programas não precisam). |
Várias opções se juntam com | ("ou" de bits): JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES.
2. O que o cliente pediu: método e caminho
Numa API, a mesma "porta" atende vários pedidos diferentes. Para saber qual é, o PHP oferece, em $_SERVER:
$_SERVER['REQUEST_METHOD']: o método (GET,POST,PUT,DELETE…).$_SERVER['REQUEST_URI']: o caminho pedido, com a query string (?ordem=preco).
Para todas as URLs caírem no mesmo arquivo, o servidor embutido aceita um roteador: um script PHP passado depois do endereço, que recebe toda requisição. A Ana fez um "eco", que só devolve o que recebeu:
<?php
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'metodo' => $_SERVER['REQUEST_METHOD'],
'uri' => $_SERVER['REQUEST_URI'],
'caminho' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),
'post' => $_POST,
'corpo' => file_get_contents('php://input'),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
$ php -S localhost:8000 eco.php « eco.php é o roteador: recebe todas as URLs [Fri Oct 16 20:53:26 2026] PHP 8.5.8 Development Server (http://localhost:8000) started
No outro terminal, o -X do curl escolhe o método:
$ curl localhost:8000/produtos/2?ordem=preco
{
"metodo": "GET",
"uri": "/produtos/2?ordem=preco",
"caminho": "/produtos/2",
"post": [],
"corpo": ""
}
$ curl -X DELETE localhost:8000/produtos/2
{
"metodo": "DELETE",
"uri": "/produtos/2",
"caminho": "/produtos/2",
"post": [],
"corpo": ""
}
- /produtos/2?ordem=preco
- Não existe arquivo com esse nome: quem respondeu foi o roteador
eco.php. - "uri"
- O
REQUEST_URItraz a query string junto. Comparar$_SERVER['REQUEST_URI'] === '/produtos'falharia com/produtos?ordem=preco. - parse_url(…, PHP_URL_PATH)
- Separa só o caminho (
/produtos/2). É ele que usamos para rotear. A query string continua em$_GET.
O corpo da requisição: php://input
Num POST, o cliente manda dados no corpo. Veja a diferença entre mandar um formulário e mandar JSON:
$ curl -X POST localhost:8000/produtos -d 'nome=Lápis&preco=2'
{
"metodo": "POST",
"uri": "/produtos",
"caminho": "/produtos",
"post": {
"nome": "Lápis",
"preco": "2"
},
"corpo": "nome=Lápis&preco=2"
}
$ curl -X POST localhost:8000/produtos -H 'Content-Type: application/json' -d '{"nome": "Lápis", "preco": 2}'
{
"metodo": "POST",
"uri": "/produtos",
"caminho": "/produtos",
"post": [],
"corpo": "{\"nome\": \"Lápis\", \"preco\": 2}"
}
O -d manda um corpo; o -H acrescenta um cabeçalho. No primeiro caso, o corpo está no formato de formulário (nome=…&preco=…) e o PHP preencheu o $_POST sozinho. No segundo, o corpo é JSON: o PHP não sabe decodificá-lo automaticamente, e o $_POST ficou vazio. O texto cru do corpo está sempre disponível no fluxo especial php://input ("a entrada do PHP"), e é dele que a API lê:
$dados = json_decode(file_get_contents('php://input'), true);
- file_get_contents('php://input')
- Lê o corpo inteiro da requisição como texto.
- json_decode(…, true)
- Texto JSON → array associativo. Se o texto não for JSON válido, devolve
null.
3. Códigos de status: a resposta em um número
Toda resposta HTTP começa com um código de três dígitos. O cliente olha o número antes de ler o corpo. Por padrão o PHP manda 200; para outro, use http_response_code(404), também antes de qualquer saída.
| Código | Nome | Quando usar na API |
|---|---|---|
200 | OK | Deu certo (consulta, alteração). |
201 | Created ("criado") | Um POST criou algo. Costuma vir com o cabeçalho Location apontando para o item novo. |
204 | No Content ("sem conteúdo") | Deu certo e não há nada para devolver (comum no DELETE). |
400 | Bad Request ("pedido ruim") | O cliente mandou dados inválidos ou faltando. |
401 / 403 | Unauthorized / Forbidden | Falta login / está logado mas não tem permissão. |
404 | Not Found ("não encontrado") | A rota ou o item não existe. |
405 | Method Not Allowed ("método não permitido") | A rota existe, mas não aceita esse método. Mande junto o cabeçalho Allow com os métodos aceitos. |
500 | Internal Server Error | Erro no servidor (o PHP manda sozinho quando há um erro fatal). |
Regra prática: 2xx = deu certo; 4xx = o cliente errou; 5xx = o servidor errou. E mesmo no erro, responda JSON ({"erro": "…"}), para o cliente mostrar uma mensagem útil.
4. A API da lojinha
Juntando tudo. Os produtos ficam num arquivo produtos.json (no mundo real, num banco de dados):
[
{"id": 1, "nome": "Caderno", "preco": 25.5},
{"id": 2, "nome": "Caneta azul", "preco": 3.9}
]
A API atende três rotas: GET /produtos (lista), POST /produtos (cria) e GET /produtos/{id} (um produto).
<?php
declare(strict_types=1);
const ARQUIVO = __DIR__ . '/produtos.json';
function responder(int $status, array $dados): never
{
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($dados, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
exit;
}
$produtos = json_decode(file_get_contents(ARQUIVO), true);
$metodo = $_SERVER['REQUEST_METHOD'];
$caminho = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// /produtos
if ($caminho === '/produtos') {
if ($metodo === 'GET') {
responder(200, $produtos);
}
if ($metodo === 'POST') {
$dados = json_decode(file_get_contents('php://input'), true);
if (!is_array($dados) || !isset($dados['nome'], $dados['preco'])) {
responder(400, ['erro' => 'Envie um JSON com "nome" e "preco"']);
}
$novo = [
'id' => max(array_column($produtos, 'id')) + 1,
'nome' => (string) $dados['nome'],
'preco' => (float) $dados['preco'],
];
$produtos[] = $novo;
file_put_contents(ARQUIVO, json_encode($produtos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
header('Location: /produtos/' . $novo['id']);
responder(201, $novo);
}
header('Allow: GET, POST');
responder(405, ['erro' => "Método $metodo não permitido em /produtos"]);
}
// /produtos/2
if (preg_match('#^/produtos/(\d+)$#', $caminho, $partes)) {
$id = (int) $partes[1];
if ($metodo !== 'GET') {
header('Allow: GET');
responder(405, ['erro' => "Método $metodo não permitido em /produtos/$id"]);
}
foreach ($produtos as $produto) {
if ($produto['id'] === $id) {
responder(200, $produto);
}
}
responder(404, ['erro' => "Produto $id não encontrado"]);
}
responder(404, ['erro' => "Rota $caminho não encontrada"]);
| Trecho | O que faz |
|---|---|
function responder(…): never | Um lugar só para responder: status, cabeçalho, JSON e exit. O tipo de retorno never (PHP 8.1+) diz "esta função nunca volta": depois dela, o script acabou. Sem o exit, o código continuaria e poderia mandar uma segunda resposta grudada na primeira. |
$caminho === '/produtos' | A rota da coleção. Dentro dela, um if por método. |
!is_array($dados) || !isset(…) | Validação: JSON inválido (json_decode devolveu null) ou sem os campos → 400. Nunca confie no que o cliente manda (veja Validar Dados). |
max(array_column($produtos, 'id')) + 1 | O próximo id: o maior que existe, mais um. |
header('Location: …') + 201 | "Criado, e ele mora aqui." |
header('Allow: GET, POST') + 405 | Rota certa, método errado: diz quais métodos ela aceita. |
preg_match('#^/produtos/(\d+)$#', …) | Uma expressão regular (um "molde" de texto) que casa /produtos/ seguido só de dígitos. Os parênteses capturam o número em $partes[1]. |
último responder(404, …) | Nenhuma rota casou. |
A Ana liga a API com o index.php como roteador:
$ php -S localhost:8000 index.php [Fri Oct 16 20:53:38 2026] PHP 8.5.8 Development Server (http://localhost:8000) started
Testando com o curl: GET
$ curl localhost:8000/produtos
[
{
"id": 1,
"nome": "Caderno",
"preco": 25.5
},
{
"id": 2,
"nome": "Caneta azul",
"preco": 3.9
}
]
$ curl -i localhost:8000/produtos/2
HTTP/1.1 200 OK
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:39 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Content-Type: application/json; charset=utf-8
{
"id": 2,
"nome": "Caneta azul",
"preco": 3.9
}
404: o produto não existe
$ curl -i localhost:8000/produtos/9
HTTP/1.1 404 Not Found
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:39 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Content-Type: application/json; charset=utf-8
{
"erro": "Produto 9 não encontrado"
}
$ curl -i localhost:8000/clientes
HTTP/1.1 404 Not Found
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:39 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Content-Type: application/json; charset=utf-8
{
"erro": "Rota /clientes não encontrada"
}
POST com JSON: criando um produto
$ curl -i -X POST localhost:8000/produtos -H 'Content-Type: application/json' -d '{"nome": "Borracha", "preco": 1.5}'
HTTP/1.1 201 Created
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:39 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Location: /produtos/3
Content-Type: application/json; charset=utf-8
{
"id": 3,
"nome": "Borracha",
"preco": 1.5
}
$ curl localhost:8000/produtos/3
{
"id": 3,
"nome": "Borracha",
"preco": 1.5
}
Status 201 Created, o cabeçalho Location e o produto novo com id 3, que já pode ser consultado. O produtos.json agora tem três produtos.
405 e 400: método errado e dados errados
$ curl -i -X DELETE localhost:8000/produtos
HTTP/1.1 405 Method Not Allowed
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:39 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Allow: GET, POST
Content-Type: application/json; charset=utf-8
{
"erro": "Método DELETE não permitido em /produtos"
}
$ curl -i -X PUT localhost:8000/produtos/1
HTTP/1.1 405 Method Not Allowed
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:39 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Allow: GET
Content-Type: application/json; charset=utf-8
{
"erro": "Método PUT não permitido em /produtos/1"
}
$ curl -i -X POST localhost:8000/produtos -H 'Content-Type: application/json' -d '{nome: Lápis}'
HTTP/1.1 400 Bad Request
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:39 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Content-Type: application/json; charset=utf-8
{
"erro": "Envie um JSON com \"nome\" e \"preco\""
}
O {nome: Lápis} parece JSON, mas não é (no JSON, nomes e textos precisam de aspas duplas): o json_decode devolveu null e a validação respondeu 400. As aspas dentro da mensagem aparecem como \" porque é assim que o JSON escreve uma aspa dentro de um texto.
Experimente: o editor do guia não tem curl nem servidor rodando em segundo plano, então as duas primeiras linhas fazem o papel da requisição, preenchendo o $_SERVER à mão. Rode e depois troque o REQUEST_URI por '/produtos', '/produtos/9' e '/clientes?x=1'. A função responder desta caixa mostra também uma linha Status:, que no curl -i apareceria no cabeçalho.
<?php
declare(strict_types=1);
// No editor não existe curl: estas duas linhas fazem o papel da requisição.
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/produtos/2';
function responder(int $status, array $dados): never
{
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
echo "Status: $status\n"; // só no editor: o curl -i mostraria isto no cabeçalho
echo json_encode($dados, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
exit;
}
$produtos = [
['id' => 1, 'nome' => 'Caderno', 'preco' => 25.5],
['id' => 2, 'nome' => 'Caneta azul', 'preco' => 3.9],
];
$metodo = $_SERVER['REQUEST_METHOD'];
$caminho = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($caminho === '/produtos' && $metodo === 'GET') {
responder(200, $produtos);
}
if (preg_match('#^/produtos/(\d+)$#', $caminho, $partes) && $metodo === 'GET') {
$id = (int) $partes[1];
foreach ($produtos as $produto) {
if ($produto['id'] === $id) {
responder(200, $produto);
}
}
responder(404, ['erro' => "Produto $id não encontrado"]);
}
responder(404, ['erro' => "Rota $caminho não encontrada"]);
No computador da Ana, com o php do terminal, essa caixa mostra:
$ php api-editor.php
Status: 200
{
"id": 2,
"nome": "Caneta azul",
"preco": 3.9
}
Por que passar o roteador para o php -S
Sem o roteador (php -S localhost:8000, só isso), o servidor embutido entrega qualquer arquivo da pasta que for pedido pelo nome. Para caminhos que não existem, como /produtos, ele procura um index.php, então a API parece funcionar igual. Mas veja:
$ curl localhost:8000/produtos.json « servidor ligado SEM o roteador
[
{"id": 1, "nome": "Caderno", "preco": 25.5},
{"id": 2, "nome": "Caneta azul", "preco": 3.9}
]
O arquivo de dados foi entregue cru, sem passar pela API. Com php -S localhost:8000 index.php, toda requisição passa pelo index.php:
$ curl localhost:8000/produtos.json « servidor ligado COM o roteador
{
"erro": "Rota /produtos.json não encontrada"
}
Num servidor de verdade (Apache, Nginx), o mesmo efeito vem da configuração de "reescrita" que manda tudo para o index.php, e os arquivos de dados ficam fora da pasta pública.
Formas diferentes
| Forma | Como | Quando usar |
|---|---|---|
if + preg_match (esta aula) | Um bloco por rota. | APIs pequenas, aprender como funciona por dentro. |
match com o método | match ($metodo) { 'GET' => …, 'POST' => …, default => responder(405, …) } | Dentro de cada rota, quando há vários métodos (veja match). |
| Arquivos separados | produtos.php, clientes.php… e a URL com o nome do arquivo. | Muito simples, mas as URLs ficam feias (/produtos.php?id=2). |
| Micro-frameworks | Slim, ou os roteadores do Laravel e do Symfony, instalados com o Composer. | Projetos reais: rotas declaradas numa linha, validação, autenticação. Por baixo, fazem o que você fez aqui. |
O projeto API REST leva esta API adiante, com SQLite, PUT e DELETE. Para o outro lado (o PHP chamando uma API), veja a próxima aula, Consumir APIs.
Quando dá errado
Warning em HTML no meio do JSON
<?php
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['id' => $_GET['id']]), "\n";
$ curl localhost:8000/erro1.php
<br />
<b>Warning</b>: Undefined array key "id" in <b>/home/ana/api/erro1.php</b> on line <b>4</b><br />
{"id":null}
O que aconteceu: o cliente não mandou ?id=, o PHP avisou "chave de array indefinida" e o aviso saiu no corpo da resposta, em HTML, antes do JSON. Para o cliente, a resposta inteira deixou de ser JSON válido: o json_decode do outro lado devolve null.
Como consertar: valide a entrada ($_GET['id'] ?? null e responda 400 se faltar) e, em produção, desligue o display_errors (veja Boas Práticas).
<?php
echo "Começando...\n";
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => true]), "\n";
$ curl -i localhost:8000/erro2.php
HTTP/1.1 200 OK
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:53:45 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Content-type: text/html; charset=UTF-8
Começando...
<br />
<b>Warning</b>: Cannot modify header information - headers already sent by (output started at /home/ana/api/erro2.php:2) in <b>/home/ana/api/erro2.php</b> on line <b>3</b><br />
{"ok":true}
O que aconteceu: o echo da linha 2 já mandou o corpo, e com ele os cabeçalhos. O header() da linha 3 chegou tarde ("não dá para mudar os cabeçalhos: já foram enviados, a saída começou em erro2.php:2"), e a resposta saiu como text/html. O mesmo vale para http_response_code. Um espaço ou linha em branco antes do <?php causa o mesmo erro.
Como consertar: nenhuma saída antes de header() e http_response_code(). Por isso a função responder faz os dois antes do echo.
\/
$ curl -i -X DELETE localhost:8000/produtos
HTTP/1.1 405 Method Not Allowed
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:52:48 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Allow: GET, POST
Content-Type: application/json; charset=utf-8
{
"erro": "Método DELETE não permitido em \/produtos"
}
O que aconteceu: esta resposta veio de uma versão anterior da API, sem a opção JSON_UNESCAPED_SLASHES. Por padrão, o json_encode escreve / como \/. Não é erro (o cliente lê /produtos do mesmo jeito), só fica feio.
Como consertar: acrescente JSON_UNESCAPED_SLASHES às opções do json_encode.
$_POST está vazio
O que aconteceu: o cliente mandou JSON (Content-Type: application/json). O PHP só preenche o $_POST para corpos de formulário. Você viu isso no eco.php.
Como consertar: leia json_decode(file_get_contents('php://input'), true).
Referência rápida
| O quê | Como |
|---|---|
| Dizer que a resposta é JSON | header('Content-Type: application/json; charset=utf-8'); |
| Array → JSON | json_encode($dados, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) |
| Código de status | http_response_code(404); (antes de qualquer saída) |
| Método pedido | $_SERVER['REQUEST_METHOD'] |
| Caminho pedido | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) |
| Corpo JSON enviado | json_decode(file_get_contents('php://input'), true) |
| Parte variável da rota | preg_match('#^/produtos/(\d+)$#', $caminho, $partes) → $partes[1] |
| Métodos aceitos (com 405) | header('Allow: GET, POST'); |
| Onde mora o item criado (com 201) | header('Location: /produtos/3'); |
| Servidor com roteador | php -S localhost:8000 index.php |
| Testar | curl -i URL, curl -X POST URL -H 'Content-Type: application/json' -d '{…}' |
Exercícios
1. O tipo da resposta
Complete o cabeçalho que diz que a resposta é JSON:
2. Ler o corpo
Complete para ler o JSON enviado no corpo da requisição:
3. Produto não encontrado
Complete com o código de status certo:
4. Um POST /produtos criou o produto 7. Qual a melhor resposta?
5. Com curl -X POST … -H 'Content-Type: application/json' -d '{"nome":"Lápis"}', o que o PHP coloca em $_POST?
6. Para a URL /produtos?ordem=preco, o que vale $_SERVER['REQUEST_URI']?
Missão final: a requisição simulada agora é DELETE /produtos, e a API responde 404 Rota /produtos não encontrada, o que está errado: a rota existe, o método é que não é aceito. Mude o primeiro if para que, em /produtos, o GET continue devolvendo a lista e qualquer outro método receba 405 com {"erro": "Método DELETE não permitido"} (use a variável $metodo na mensagem) e o cabeçalho Allow: GET.
<?php
declare(strict_types=1);
// No editor não existe curl: estas duas linhas fazem o papel da requisição.
$_SERVER['REQUEST_METHOD'] = 'DELETE';
$_SERVER['REQUEST_URI'] = '/produtos';
function responder(int $status, array $dados): never
{
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
echo "Status: $status\n"; // só no editor: o curl -i mostraria isto no cabeçalho
echo json_encode($dados, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
exit;
}
$produtos = [
['id' => 1, 'nome' => 'Caderno', 'preco' => 25.5],
['id' => 2, 'nome' => 'Caneta azul', 'preco' => 3.9],
];
$metodo = $_SERVER['REQUEST_METHOD'];
$caminho = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($caminho === '/produtos' && $metodo === 'GET') {
responder(200, $produtos);
}
if (preg_match('#^/produtos/(\d+)$#', $caminho, $partes) && $metodo === 'GET') {
$id = (int) $partes[1];
foreach ($produtos as $produto) {
if ($produto['id'] === $id) {
responder(200, $produto);
}
}
responder(404, ['erro' => "Produto $id não encontrado"]);
}
responder(404, ['erro' => "Rota $caminho não encontrada"]);
<?php
declare(strict_types=1);
// No editor não existe curl: estas duas linhas fazem o papel da requisição.
$_SERVER['REQUEST_METHOD'] = 'DELETE';
$_SERVER['REQUEST_URI'] = '/produtos';
function responder(int $status, array $dados): never
{
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
echo "Status: $status\n"; // só no editor: o curl -i mostraria isto no cabeçalho
echo json_encode($dados, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
exit;
}
$produtos = [
['id' => 1, 'nome' => 'Caderno', 'preco' => 25.5],
['id' => 2, 'nome' => 'Caneta azul', 'preco' => 3.9],
];
$metodo = $_SERVER['REQUEST_METHOD'];
$caminho = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($caminho === '/produtos') {
if ($metodo === 'GET') {
responder(200, $produtos);
}
header('Allow: GET');
responder(405, ['erro' => "Método $metodo não permitido"]);
}
if (preg_match('#^/produtos/(\d+)$#', $caminho, $partes) && $metodo === 'GET') {
$id = (int) $partes[1];
foreach ($produtos as $produto) {
if ($produto['id'] === $id) {
responder(200, $produto);
}
}
responder(404, ['erro' => "Produto $id não encontrado"]);
}
responder(404, ['erro' => "Rota $caminho não encontrada"]);
Resumo
- Uma API responde dados (JSON) para programas, não páginas para pessoas.
header('Content-Type: application/json; charset=utf-8')+json_encode(comJSON_UNESCAPED_UNICODEeJSON_UNESCAPED_SLASHES).- Roteie pelo método (
$_SERVER['REQUEST_METHOD']) e pelo caminho (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)). - JSON enviado pelo cliente não vai para o
$_POST: leia comjson_decode(file_get_contents('php://input'), true)e valide. - O status conta o resultado: 200 OK, 201 criado, 400 dados ruins, 404 não existe, 405 método não aceito (com
Allow). Mesmo no erro, responda JSON. header()ehttp_response_code()vêm antes de qualquer saída; avisos do PHP no meio da resposta estragam o JSON.- Teste tudo com
curl -i, com o servidor ligado assim:php -S localhost:8000 index.php.