Construir uma API REST é uma habilidade fundamental para qualquer desenvolvedor PHP moderno. Nesta aula, vamos mergulhar nos pilares do REST e aplicá-los na prática, criando uma API simples, mas completa, sem depender de frameworks. Você aprenderá a lidar com os verbos HTTP, a estruturar rotas, a trabalhar com JSON e a responder com os códigos de status adequados, garantindo que sua API seja robusta, previsível e fácil de consumir.

Embora existam frameworks como Laravel e Slim que facilitam esse processo, entender o funcionamento por baixo dos panos é crucial. Isso permite que você tenha controle total sobre o comportamento da sua aplicação e cria uma base sólida para aprender qualquer framework. Vamos começar!

Verbos HTTP

Os verbos HTTP (ou métodos) são a forma de indicar a intenção da requisição feita ao servidor. Em uma API REST, cada verbo está associado a uma operação específica: GET para consultar, POST para criar, PUT/PATCH para atualizar, DELETE para remover. Essa padronização permite que a API seja intuitiva e que clientes (como aplicações web ou mobile) saibam exatamente o que esperar de cada endpoint.

Na prática, o PHP identifica o verbo utilizado através da variável superglobal $_SERVER['REQUEST_METHOD']. É a partir dela que decidimos qual ação executar. Vamos criar um exemplo simples que responde de forma diferente para cada verbo:

<?php
// api.php

$method = $_SERVER['REQUEST_METHOD'];

switch ($method) {
    case 'GET':
        echo "Você fez uma requisição GET";
        break;
    case 'POST':
        echo "Você fez uma requisição POST";
        break;
    case 'PUT':
        echo "Você fez uma requisição PUT";
        break;
    case 'DELETE':
        echo "Você fez uma requisição DELETE";
        break;
    default:
        http_response_code(405);
        echo "Método não permitido";
}

Observe que para métodos não suportados, retornamos o código 405 (Method Not Allowed). Isso é uma boa prática: a API deve comunicar claramente quando uma operação não é suportada.

Rotas

Rotas são a forma de mapear URLs para ações específicas. Em uma API REST, cada rota geralmente corresponde a um recurso (como 'usuarios', 'produtos') e o verbo HTTP define a operação. Como não usaremos um framework, precisamos interpretar a URL manualmente.

A variável $_SERVER['REQUEST_URI'] contém o caminho solicitado. Podemos extrair o caminho e dividi-lo em segmentos. Vamos criar um roteador simples que responde a 'usuarios' e 'produtos':

<?php
// router.php

$uri = $_SERVER['REQUEST_URI'];
$path = parse_url($uri, PHP_URL_PATH);
$segments = explode('/', trim($path, '/'));

$resource = $segments[0] ?? null;
$id = $segments[1] ?? null;

switch ($resource) {
    case 'usuarios':
        if ($id) {
            // rota /usuarios/{id}
            echo "Usuário com ID $id";
        } else {
            // rota /usuarios
            echo "Lista de usuários";
        }
        break;
    case 'produtos':
        echo "Produtos";
        break;
    default:
        http_response_code(404);
        echo "Rota não encontrada";
}

Esse roteador é básico, mas suficiente para entender o conceito. Em uma aplicação real, você provavelmente usaria um arquivo de rotas mais sofisticado, mas o princípio é o mesmo: analisar a URL e direcionar para o handler correto.

JSON

JSON (JavaScript Object Notation) é o formato padrão para troca de dados em APIs REST. Ele é leve, legível e suportado nativamente pelo PHP. Para enviar uma resposta JSON, usamos a função json_encode() e definimos o cabeçalho Content-Type: application/json. Para receber dados JSON (em POST, PUT, etc.), usamos json_decode() no corpo da requisição.

Vamos criar um exemplo de API que retorna um usuário em JSON e também recebe dados JSON para criar um novo usuário:

<?php
// json_example.php

// Definir cabeçalho para JSON
header('Content-Type: application/json; charset=utf-8');

$method = $_SERVER['REQUEST_METHOD'];

if ($method === 'GET') {
    // Retorna um usuário fictício
    $usuario = ['id' => 1, 'nome' => 'Maria', 'email' => 'maria@exemplo.com'];
    echo json_encode($usuario);
} elseif ($method === 'POST') {
    // Recebe dados JSON do corpo da requisição
    $dados = json_decode(file_get_contents('php://input'), true);

    if ($dados === null) {
        http_response_code(400);
        echo json_encode(['erro' => 'JSON inválido']);
    } else {
        // Aqui você salvaria no banco de dados
        echo json_encode(['status' => 'sucesso', 'recebido' => $dados]);
    }
}

Note que usamos file_get_contents('php://input') para ler o corpo da requisição, que contém os dados JSON. A função json_decode com o segundo argumento true converte o JSON em array associativo.

Status codes

Os códigos de status HTTP são essenciais para que o cliente saiba o resultado da requisição. Eles são divididos em categorias: 2xx para sucesso, 3xx para redirecionamento, 4xx para erros do cliente e 5xx para erros do servidor. Em uma API REST, é crucial usar os códigos corretamente para que o cliente possa tratar erros de forma adequada.

No PHP, usamos http_response_code() para definir o código. Vamos ver alguns exemplos:

  • 200 OK - Requisição bem-sucedida (GET, PUT, DELETE).
  • 201 Created - Recurso criado com sucesso (POST).
  • 204 No Content - Sucesso, mas sem corpo na resposta (DELETE).
  • 400 Bad Request - Requisição inválida (dados mal formatados).
  • 401 Unauthorized - Autenticação necessária.
  • 403 Forbidden - Sem permissão para acessar.
  • 404 Not Found - Recurso não encontrado.
  • 405 Method Not Allowed - Método não suportado para a rota.
  • 422 Unprocessable Entity - Dados válidos, mas falharam na validação.
  • 500 Internal Server Error - Erro no servidor.

Abaixo, um exemplo de API que utiliza status codes de forma adequada:

<?php
// status_codes.php

header('Content-Type: application/json');

$method = $_SERVER['REQUEST_METHOD'];

// Simula um banco de dados
$usuarios = [
    1 => ['id' => 1, 'nome' => 'João'],
    2 => ['id' => 2, 'nome' => 'Ana']
];

if ($method === 'GET') {
    $id = $_GET['id'] ?? null;
    if ($id && isset($usuarios[$id])) {
        http_response_code(200);
        echo json_encode($usuarios[$id]);
    } else {
        http_response_code(404);
        echo json_encode(['erro' => 'Usuário não encontrado']);
    }
} elseif ($method === 'POST') {
    $dados = json_decode(file_get_contents('php://input'), true);
    if (!$dados || !isset($dados['nome'])) {
        http_response_code(422);
        echo json_encode(['erro' => 'Campo nome é obrigatório']);
    } else {
        // Cria novo usuário (simulado)
        http_response_code(201);
        echo json_encode(['id' => 3, 'nome' => $dados['nome']]);
    }
} else {
    http_response_code(405);
    echo json_encode(['erro' => 'Método não permitido']);
}

Perceba que cada situação retorna um código adequado, facilitando o tratamento de erros no cliente.

Boas práticas

Ao construir uma API REST, siga estas boas práticas: use substantivos no plural para recursos (ex.: /usuarios), mantenha a consistência nas rotas, sempre valide os dados de entrada, retorne mensagens de erro claras, e utilize HTTPS em produção. Além disso, considere versionar sua API (ex.: /v1/usuarios) para evitar quebrar clientes existentes.

Outra prática importante é documentar sua API, seja com OpenAPI/Swagger ou manualmente. Isso facilita o consumo por outros desenvolvedores e melhora a manutenção.

Referências

Exercícios

  1. Crie um script PHP que responda com o método HTTP usado (GET, POST, etc.) e, se for POST, leia o corpo JSON e retorne os dados recebidos em formato JSON.

    ✓ Resposta:
    <?php
    header('Content-Type: application/json');
    $method = $_SERVER['REQUEST_METHOD'];
    if ($method === 'POST') {
        $dados = json_decode(file_get_contents('php://input'), true);
        echo json_encode(['metodo' => $method, 'dados' => $dados]);
    } else {
        echo json_encode(['metodo' => $method]);
    }
  2. Implemente um roteador simples que responda a /produtos e /produtos/{id}, onde {id} é um número. Se a rota não existir, retorne 404.

    ✓ Resposta:
    <?php
    $uri = $_SERVER['REQUEST_URI'];
    $path = parse_url($uri, PHP_URL_PATH);
    $segments = explode('/', trim($path, '/'));
    if ($segments[0] === 'produtos') {
        if (isset($segments[1]) && is_numeric($segments[1])) {
            echo "Produto ID: {$segments[1]}";
        } else {
            echo "Lista de produtos";
        }
    } else {
        http_response_code(404);
        echo "Rota não encontrada";
    }
  3. Escreva uma função que receba dados de um formulário (simulados como array) e retorne em JSON com status 201. Se os dados estiverem vazios, retorne 400.

    ✓ Resposta:
    <?php
    function criarUsuario($dados) {
        if (empty($dados)) {
            http_response_code(400);
            echo json_encode(['erro' => 'Dados não fornecidos']);
        } else {
            http_response_code(201);
            echo json_encode($dados);
        }
    }
    // Exemplo de uso:
    criarUsuario(['nome' => 'Carlos']);
  4. Crie um endpoint que aceite GET e DELETE para /usuario/{id}. Para GET, retorne os dados do usuário (pode ser fixo); para DELETE, retorne 204 No Content.

    ✓ Resposta:
    <?php
    $method = $_SERVER['REQUEST_METHOD'];
    $uri = $_SERVER['REQUEST_URI'];
    $path = parse_url($uri, PHP_URL_PATH);
    $segments = explode('/', trim($path, '/'));
    if ($segments[0] === 'usuario' && isset($segments[1])) {
        if ($method === 'GET') {
            http_response_code(200);
            echo json_encode(['id' => $segments[1], 'nome' => 'Fulano']);
        } elseif ($method === 'DELETE') {
            http_response_code(204);
        } else {
            http_response_code(405);
        }
    } else {
        http_response_code(404);
    }
  5. Implemente um endpoint que receba um JSON com campos 'nome' e 'email' via POST. Se o e-mail não contiver '@', retorne 422. Caso contrário, retorne 201 e os dados.

    ✓ Resposta:
    <?php
    header('Content-Type: application/json');
    $dados = json_decode(file_get_contents('php://input'), true);
    if (!isset($dados['nome'], $dados['email'])) {
        http_response_code(422);
        echo json_encode(['erro' => 'Campos nome e email são obrigatórios']);
    } elseif (strpos($dados['email'], '@') === false) {
        http_response_code(422);
        echo json_encode(['erro' => 'Email inválido']);
    } else {
        http_response_code(201);
        echo json_encode($dados);
    }