PHP Consumir APIs

Na aula anterior, a Ana criou uma API JSON e a testou com o curl do terminal. Agora é a vez do outro lado: o seu programa PHP fazendo o papel do cliente, pedindo dados a uma API e usando a resposta. É assim que um site consulta um CEP, calcula o frete nos Correios, confere um pagamento ou mostra a previsão do tempo. "Consumir" uma API é só isto: mandar uma requisição HTTP e ler o JSON que volta.

Você vai aprender
  • Buscar uma URL com file_get_contents e transformar o JSON em array com json_decode.
  • Configurar a requisição com stream_context_create: método, cabeçalhos, corpo, tempo limite.
  • Fazer o mesmo com as funções curl_*, o jeito mais completo.
  • Ler o código de status (200, 201, 404…) e tratar cada caso.
  • Lidar com o que dá errado: API desligada, resposta lenta, JSON inválido.
O editor do guia não acessa a rede. O PHP das caixas "Experimente" roda dentro do seu navegador, sem permissão para abrir conexões, então file_get_contents("http://…") e curl_exec não funcionam nelas. Todas as saídas de requisições desta aula vieram do computador da Ana, com a API rodando de verdade no servidor embutido. Nas caixas, você vai treinar a outra metade do trabalho: ler e tratar a resposta.

A ideia: pedir por telefone

Pense na Ana ligando para uma papelaria para saber o preço de uma caneta. Ela precisa saber o número (a URL), dizer o que quer (o método: "quero consultar" = GET, "quero fazer um pedido" = POST), às vezes dar detalhes (o corpo, em JSON), e então ouvir a resposta. Às vezes ninguém atende (a API está fora do ar). Às vezes atendem e dizem "esse produto não existe" (404). Às vezes a pessoa demora tanto que a Ana desliga (tempo limite). Um bom cliente de API prevê os três casos.

Preparando: a API da lojinha ligada

Para ter algo de verdade para consumir, a Ana deixou a API da aula anterior rodando num terminal, na pasta ~/api:

$ cd ~/api
$ php -S localhost:8000 index.php
[Fri Oct 16 20:54:58 2026] PHP 8.5.8 Development Server (http://localhost:8000) started

Esse terminal fica ocupado com o servidor. Os programas clientes rodam num segundo terminal, na pasta ~/cliente. Lembrete do que a API sabe fazer: GET /produtos (lista), GET /produtos/2 (um produto), POST /produtos (cria um produto a partir de um JSON com nome e preco).


1. O jeito mais simples: file_get_contents com uma URL

Você conhece o file_get_contents da aula Ler e Escrever Arquivos: ele lê um arquivo inteiro e devolve o texto. O detalhe é que ele também aceita uma URL. Nesse caso, o PHP faz uma requisição GET e devolve o corpo da resposta.

<?php

declare(strict_types=1);

$resposta = file_get_contents('http://localhost:8000/produtos');
$produtos = json_decode($resposta, true);

foreach ($produtos as $produto) {
    echo "{$produto['id']}. {$produto['nome']}: R$ ", number_format($produto['preco'], 2, ',', '.'), "\n";
}
file_get_contents('http://localhost:8000/produtos')
Faz GET /produtos e devolve o corpo da resposta como texto (uma string).
json_decode($resposta, true)
Transforma o texto JSON em dados do PHP. O true pede arrays associativos (sem ele, viriam objetos stdClass). Veja JSON.
foreach
A partir daqui é PHP comum: uma lista de arrays.
$ php listar.php
1. Caderno: R$ 25,50
2. Caneta azul: R$ 3,90

Repare que são dois passos: buscar o texto e decodificar. Para ver a diferença, este programa mostra o que chega em cada etapa:

<?php
$resposta = file_get_contents('http://localhost:8000/produtos/2');
var_dump($resposta);
var_dump(json_decode($resposta, true));
$ php cru.php
string(61) "{
    "id": 2,
    "nome": "Caneta azul",
    "preco": 3.9
}
"
array(3) {
  ["id"]=>
  int(2)
  ["nome"]=>
  string(11) "Caneta azul"
  ["preco"]=>
  float(3.9)
}

A resposta é uma string de 61 caracteres, igual ao que o curl mostrava. Só depois do json_decode ela vira um array, com id como int e preco como float: o JSON guarda os tipos dos números.

Experimente: a variável $resposta tem exatamente o texto que a API devolveu para GET /produtos. Rode, depois acrescente um terceiro produto no texto JSON (por exemplo, {"id":3,"nome":"Borracha","preco":1.5}, sem esquecer a vírgula) e rode de novo. Tente também apagar uma aspa do JSON para ver o que acontece quando a resposta vem quebrada.

<?php
// O texto abaixo é exatamente o que a API devolveu para GET /produtos
// (o editor do guia não acessa a rede, então copiamos a resposta).
$resposta = '[{"id":1,"nome":"Caderno","preco":25.5},{"id":2,"nome":"Caneta azul","preco":3.9}]';

$produtos = json_decode($resposta, true);

foreach ($produtos as $produto) {
    echo "{$produto['id']}. {$produto['nome']}: R$ ", number_format($produto['preco'], 2, ',', '.'), "\n";
}

Quando a API responde com erro

E se o produto não existir? A API responde 404 com um JSON explicando. Mas veja o que o file_get_contents faz:

<?php
$resposta = file_get_contents('http://localhost:8000/produtos/9');
var_dump($resposta);
$ php nao-existe.php

Warning: file_get_contents(http://localhost:8000/produtos/9): Failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found
 in /home/ana/cliente/nao-existe.php on line 2
bool(false)

Por padrão, qualquer status de erro (4xx ou 5xx) vira um Warning ("falhou ao abrir o fluxo: a requisição HTTP falhou! 404 Não Encontrado") e a função devolve false. O JSON {"erro": "Produto 9 não encontrado"} que a API mandou foi jogado fora. (A quebra de linha antes de in /home/… é real: a mensagem inclui o fim de linha da primeira linha da resposta HTTP.)

Configurando a requisição: stream_context_create

Para mudar o comportamento, você passa um contexto: um array de opções para o "fluxo" HTTP. Ele é o 3º argumento do file_get_contents (o 2º é sempre false nesse uso).

<?php

declare(strict_types=1);

$contexto = stream_context_create([
    'http' => [
        'timeout' => 5,
        'ignore_errors' => true,
    ],
]);

$resposta = file_get_contents('http://localhost:8000/produtos/9', false, $contexto);
$cabecalhos = http_get_last_response_headers();

echo $cabecalhos[0], "\n";
var_dump(json_decode($resposta, true));
stream_context_create([...])
Cria o contexto. A chave 'http' guarda as opções para URLs http:// e https://.
'timeout' => 5
Desiste se a API ficar 5 segundos sem mandar dados. Sempre defina: o padrão (default_socket_timeout) é 60 segundos, tempo demais para um visitante esperando a página.
'ignore_errors' => true
"Ignore os erros": devolve o corpo mesmo com status 4xx/5xx, sem Warning. Agora quem decide o que fazer é o seu código.
http_get_last_response_headers()
PHP 8.4+ Devolve os cabeçalhos da última resposta, um por posição. A posição 0 é a linha de status. Em versões antigas, usava-se a variável mágica $http_response_header, que no PHP 8.5 está obsoleta (veja os erros no fim da aula).
$ php contexto.php
HTTP/1.1 404 Not Found
array(1) {
  ["erro"]=>
  string(25) "Produto 9 não encontrado"
}

Agora o programa sabe o status (404) e tem a mensagem de erro da API para mostrar ao usuário.

Enviando dados: POST com JSON

O mesmo contexto define o método, os cabeçalhos e o corpo da requisição:

<?php

declare(strict_types=1);

$novo = ['nome' => 'Régua 30 cm', 'preco' => 4.5];

$contexto = stream_context_create([
    'http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/json\r\nAccept: application/json\r\n",
        'content' => json_encode($novo),
        'timeout' => 5,
        'ignore_errors' => true,
    ],
]);

$resposta = file_get_contents('http://localhost:8000/produtos', false, $contexto);

echo http_get_last_response_headers()[0], "\n";
echo $resposta;
'method' => 'POST'
O método HTTP (padrão: GET).
'header' => "…\r\n…\r\n"
Os cabeçalhos num texto só, cada um terminado em \r\n (por isso aspas duplas). Content-Type diz que o corpo é JSON; Accept diz que queremos JSON de volta.
'content' => json_encode($novo)
O corpo da requisição: o array convertido em texto JSON. É o que a API lê com php://input.
$ php post-fgc.php
HTTP/1.1 201 Created
{
    "id": 3,
    "nome": "Régua 30 cm",
    "preco": 4.5
}

Status 201 Created ("criado") e o produto novo, já com o id que a API escolheu.


2. O jeito completo: as funções curl_*

A extensão cURL ("client URL") é a mesma biblioteca do comando curl que você usou no terminal, agora dentro do PHP. Ela tem mais opções que o file_get_contents (tempo limite de conexão separado, mensagens de erro detalhadas, o status num número inteiro) e é o que quase todo projeto usa, diretamente ou por baixo de bibliotecas.

O roteiro é sempre o mesmo: iniciar, configurar, executar, ler as informações.

<?php

declare(strict_types=1);

$ch = curl_init('http://localhost:8000/produtos/2');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);

$resposta = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

echo "Status: $status\n";
$produto = json_decode($resposta, true);
echo "{$produto['nome']} custa R$ {$produto['preco']}\n";
LinhaO que faz
$ch = curl_init(URL)Cria uma requisição (um objeto CurlHandle; $ch é o nome tradicional, de "curl handle", "alça").
CURLOPT_RETURNTRANSFER, true"Devolva a resposta" em vez de imprimi-la na tela. Você quase sempre quer isto.
CURLOPT_TIMEOUT, 5Tempo máximo da requisição inteira, em segundos.
curl_exec($ch)Executa. Devolve o corpo (texto) ou false se não conseguiu falar com o servidor.
curl_getinfo($ch, CURLINFO_RESPONSE_CODE)O código de status, como int (200, 404…).
$ php curl-get.php
Status: 200
Caneta azul custa R$ 3.9

(O preço saiu 3.9 porque foi interpolado direto; para 3,90, use number_format como no listar.php.)

POST com cURL e curl_setopt_array

Com muitas opções, curl_setopt_array configura tudo de uma vez. Este cliente também trata os três casos: sem conexão, criado e recusado.

<?php

declare(strict_types=1);

$novo = ['nome' => 'Apontador', 'preco' => 2.75];

$ch = curl_init('http://localhost:8000/produtos');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($novo),
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
    CURLOPT_CONNECTTIMEOUT => 3,
    CURLOPT_TIMEOUT => 5,
]);

$resposta = curl_exec($ch);

if ($resposta === false) {
    echo 'Falha na conexão: ', curl_error($ch), ' (código ', curl_errno($ch), ")\n";
    exit(1);
}

$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$dados = json_decode($resposta, true);

if ($status === 201) {
    echo "Criado: produto {$dados['id']} ({$dados['nome']})\n";
} else {
    echo "A API respondeu $status: {$dados['erro']}\n";
}
CURLOPT_POST => true
Usa o método POST. (Para outros, como PUT e DELETE: CURLOPT_CUSTOMREQUEST => 'DELETE'.)
CURLOPT_POSTFIELDS
O corpo. Com um texto (o JSON), ele vai exatamente assim. Com um array, o cURL enviaria como formulário, que a nossa API não aceita.
CURLOPT_HTTPHEADER
Os cabeçalhos, agora como uma lista, um por item (sem \r\n).
CURLOPT_CONNECTTIMEOUT => 3
Tempo máximo só para conectar. Se o servidor nem atender em 3 segundos, desiste.
$resposta === false
Não houve resposta nenhuma (servidor fora do ar, nome errado, tempo esgotado). curl_error explica em texto; curl_errno dá o número do erro.
$status === 201
Houve resposta: agora o status diz se deu certo. Um 404 ou 400 não faz o curl_exec devolver false.
$ php curl-post.php
Criado: produto 4 (Apontador)

E se a Ana esquecer o preço ($novo = ['nome' => 'Apontador'];)? A API recusa com 400, e o cliente mostra a mensagem dela:

$ php curl-post-400.php
A API respondeu 400: Envie um JSON com "nome" e "preco"

3. Quando ninguém atende: conexão e tempo limite

A API está desligada

A Ana desligou o servidor (Ctrl+C no primeiro terminal) e rodou os dois clientes:

$ php curl-post.php
Falha na conexão: Failed to connect to localhost port 8000 after 0 ms: Could not connect to server (código 7)
$ php listar.php

Warning: file_get_contents(http://localhost:8000/produtos): Failed to open stream: Connection refused in /home/ana/cliente/listar.php on line 5

Fatal error: Uncaught TypeError: json_decode(): Argument #1 ($json) must be of type string, false given in /home/ana/cliente/listar.php:6
Stack trace:
#0 /home/ana/cliente/listar.php(6): json_decode(false, true)
#1 {main}
  thrown in /home/ana/cliente/listar.php on line 6

O curl-post.php conferiu o false e mostrou uma mensagem clara ("falhou ao conectar em localhost porta 8000"). O listar.php não conferiu: o file_get_contents avisou "Connection refused" (conexão recusada) e devolveu false, o false foi parar no json_decode, e os tipos estritos derrubaram o programa. Regra: toda requisição pode falhar; confira o resultado antes de usar.

A API está lenta

Para testar, a Ana acrescentou à API uma rota que demora de propósito:

// rota de teste: demora 3 segundos para responder
if ($caminho === '/lenta') {
    sleep(3);
    responder(200, ['ok' => true]);
}

E um cliente que só espera 1 segundo:

<?php

declare(strict_types=1);

$ch = curl_init('http://localhost:8000/lenta');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);

$resposta = curl_exec($ch);

if ($resposta === false) {
    echo 'Erro ', curl_errno($ch), ': ', curl_error($ch), "\n";
    exit(1);
}
echo $resposta;
$ php lenta-curl.php
Erro 28: Operation timed out after 1001 milliseconds with 0 bytes received

"A operação esgotou o tempo depois de 1001 milissegundos, com 0 bytes recebidos." Sem o CURLOPT_TIMEOUT, o cURL esperaria para sempre, e a página do visitante ficaria carregando até o PHP desistir. Com o file_get_contents, a opção é o 'timeout' do contexto, e a mensagem é menos detalhada:

<?php
$contexto = stream_context_create(['http' => ['timeout' => 1]]);
$resposta = file_get_contents('http://localhost:8000/lenta', false, $contexto);
var_dump($resposta);
$ php lenta-fgc.php

Warning: file_get_contents(http://localhost:8000/lenta): Failed to open stream: HTTP request failed! in /home/ana/cliente/lenta-fgc.php on line 3
bool(false)

Os erros de cURL mais comuns

curl_errnoConstanteSignifica
6CURLE_COULDNT_RESOLVE_HOSTO nome do site não existe (erro de digitação, sem internet).
7CURLE_COULDNT_CONNECTNinguém atendeu: servidor desligado ou porta errada.
28CURLE_OPERATION_TIMEDOUTTempo limite esgotado.
35CURLE_SSL_CONNECT_ERRORFalha ao negociar o HTTPS.
60CURLE_PEER_FAILED_VERIFICATIONO certificado HTTPS do site não é confiável. Não "resolva" desligando a verificação (CURLOPT_SSL_VERIFYPEER => false): isso abre a porta para alguém se passar pelo site.

4. O JSON da resposta: confie, mas confira

Mesmo com status 200, a resposta pode não ser JSON: uma API com defeito pode mandar um Warning em HTML antes do JSON (você viu isso acontecer na aula anterior), ou uma página de manutenção. O json_decode devolve null nesses casos, em silêncio. Com a opção JSON_THROW_ON_ERROR, ele lança uma JsonException, que você trata com try/catch:

<?php

declare(strict_types=1);

$resposta = '<br /><b>Warning</b>: ... {"id":null}';

try {
    $dados = json_decode($resposta, true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo 'A resposta não é JSON válido: ', $e->getMessage(), "\n";
}

var_dump(json_decode($resposta, true));
echo json_last_error_msg(), "\n";
$ php json-throw.php
A resposta não é JSON válido: Syntax error
NULL
Syntax error

A primeira linha veio do catch. As duas últimas mostram o jeito antigo: json_decode devolve NULL e você precisa lembrar de perguntar a json_last_error_msg() o que houve ("erro de sintaxe"). O flags: é um argumento nomeado, para não ter que escrever o 3º argumento (a profundidade, 512).


Juntando tudo: uma função para falar com a API

Em vez de repetir o roteiro do cURL em todo lugar, a Ana criou uma função que faz a requisição, confere a conexão, decodifica o JSON com segurança e devolve o status junto com os dados:

<?php

declare(strict_types=1);

const API = 'http://localhost:8000';

/**
 * Faz uma requisição e devolve [código de status, dados decodificados].
 * Lança RuntimeException se não conseguir falar com a API.
 */
function pedirJson(string $metodo, string $caminho, ?array $corpo = null): array
{
    $ch = curl_init(API . $caminho);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $metodo,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['Accept: application/json', 'Content-Type: application/json'],
        CURLOPT_CONNECTTIMEOUT => 3,
        CURLOPT_TIMEOUT => 10,
    ]);
    if ($corpo !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($corpo));
    }

    $resposta = curl_exec($ch);
    if ($resposta === false) {
        throw new RuntimeException('Sem resposta da API: ' . curl_error($ch));
    }

    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $dados = json_decode($resposta, true, flags: JSON_THROW_ON_ERROR);

    return [$status, $dados];
}

try {
    [$status, $produto] = pedirJson('GET', '/produtos/2');
    echo "$status → {$produto['nome']}\n";

    [$status, $erro] = pedirJson('GET', '/produtos/99');
    echo "$status → {$erro['erro']}\n";

    [$status, $novo] = pedirJson('POST', '/produtos', ['nome' => 'Cola', 'preco' => 6.2]);
    echo "$status → criado com id {$novo['id']}\n";
} catch (RuntimeException | JsonException $e) {
    echo $e->getMessage(), "\n";
    exit(1);
}

Com a API ligada (e a lista de volta aos dois produtos iniciais):

$ php loja-cliente.php
200 → Caneta azul
404 → Produto 99 não encontrado
201 → criado com id 3

Com a API desligada:

$ php loja-cliente.php
Sem resposta da API: Failed to connect to localhost port 8000 after 0 ms: Could not connect to server
CURLOPT_CUSTOMREQUEST => $metodo
Um só lugar para qualquer método: GET, POST, PUT, DELETE.
?array $corpo = null
O corpo é opcional: GET não tem corpo.
throw new RuntimeException(...)
Falha de conexão vira uma exceção: quem chama decide o que mostrar.
[$status, $produto] = …
Desestruturação: pega as duas posições do array devolvido.
catch (RuntimeException | JsonException $e)
Pega os dois tipos de problema: sem conexão e resposta que não é JSON.

Qual usar?

FerramentaBom paraCuidado
file_get_contents + contextoUm GET rápido, um script pequeno, sem depender de extensões.Precisa de allow_url_fopen=On. Erros viram Warning se você esquecer ignore_errors. Mensagens de erro pouco detalhadas.
curl_*O dia a dia: qualquer método, tempo de conexão separado, status em int, erros detalhados.Precisa da extensão curl (vem em quase toda instalação). Mais linhas para escrever.
Bibliotecas (Guzzle, Symfony HttpClient)Projetos grandes: várias requisições ao mesmo tempo, novas tentativas automáticas, testes.Instaladas com o Composer. Por baixo, usam cURL ou fluxos do PHP: o que você aprendeu aqui continua valendo.

Para uma API pública de verdade, nada muda além da URL (com https://) e, em geral, de uma chave de acesso enviada num cabeçalho, como Authorization: Bearer SUA-CHAVE. Guarde a chave fora do código (num arquivo de configuração fora da pasta pública ou numa variável de ambiente lida com getenv), nunca no repositório.


Quando dá errado

"Failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found"

Você viu esse acima. O que significa: o servidor respondeu, mas com status de erro, e o file_get_contents sem ignore_errors trata isso como falha e devolve false.

Como consertar: use um contexto com 'ignore_errors' => true e leia o status em http_get_last_response_headers()[0], ou troque por cURL.

"json_decode(): Argument #1 ($json) must be of type string, false given"

O que significa: a requisição falhou (conexão recusada, tempo esgotado, status de erro) e devolveu false, mas o código passou esse false adiante como se fosse o texto da resposta.

Como consertar: confira if ($resposta === false) logo depois do file_get_contents ou do curl_exec, como no curl-post.php.

"http:// wrapper is disabled in the server configuration by allow_url_fopen=0"
$ php -d allow_url_fopen=0 listar.php

Warning: file_get_contents(): http:// wrapper is disabled in the server configuration by allow_url_fopen=0 in /home/ana/cliente/listar.php on line 5

Warning: file_get_contents(http://localhost:8000/produtos): Failed to open stream: no suitable wrapper could be found in /home/ana/cliente/listar.php on line 5

Fatal error: Uncaught TypeError: json_decode(): Argument #1 ($json) must be of type string, false given in /home/ana/cliente/listar.php:6
Stack trace:
#0 /home/ana/cliente/listar.php(6): json_decode(false, true)
#1 {main}
  thrown in /home/ana/cliente/listar.php on line 6

O que significa: alguns servidores de hospedagem desligam a opção allow_url_fopen ("permitir abrir URLs como arquivos"), e então o file_get_contents só aceita arquivos locais. (Aqui a Ana simulou isso com -d.)

Como consertar: use cURL, que não depende dessa opção. Ou, se o servidor for seu, ligue-a no php.ini.

"Deprecated: Function curl_close() is deprecated since 8.5"
$ php fechar.php
{
    "id": 1,
    "nome": "Caderno",
    "preco": 25.5
}

Deprecated: Function curl_close() is deprecated since 8.5, as it has no effect since PHP 8.0 in /home/ana/cliente/fechar.php on line 5

O que significa: tutoriais antigos terminam com curl_close($ch);. Desde o PHP 8.0 a requisição é um objeto que se fecha sozinho quando não é mais usado, então a função não faz nada, e no 8.5 ela ganhou um aviso de obsoleta ("será removida no futuro").

Como consertar: apague a linha do curl_close.

"The predefined locally scoped $http_response_header variable is deprecated"
$ php velho.php

Deprecated: The predefined locally scoped $http_response_header variable is deprecated, call http_get_last_response_headers() instead in /home/ana/cliente/velho.php on line 3
HTTP/1.1 200 OK

O que significa: o código usa a variável $http_response_header, que o PHP criava "magicamente" depois de um file_get_contents com URL. Ela ainda funciona (a linha HTTP/1.1 200 OK saiu), mas está obsoleta no PHP 8.5.

Como consertar: troque por http_get_last_response_headers(), como a própria mensagem sugere.

A página do site fica carregando "para sempre"

O que significa: a API que o seu PHP chama está lenta ou travada, e a requisição não tem tempo limite (ou tem um muito alto). O visitante espera junto.

Como consertar: defina sempre CURLOPT_CONNECTTIMEOUT e CURLOPT_TIMEOUT (ou 'timeout' no contexto) com poucos segundos e trate o erro 28 mostrando uma mensagem amigável ("serviço indisponível, tente de novo").


Referência rápida

Função / opçãoPara quê
file_get_contents($url, false, $ctx)Faz a requisição e devolve o corpo (ou false)
stream_context_create(['http' => [...]])Opções: method, header, content, timeout, ignore_errors
http_get_last_response_headers()Cabeçalhos da última resposta ([0] = status) PHP 8.4+
curl_init($url)Cria a requisição
curl_setopt($ch, OPÇÃO, valor) / curl_setopt_arrayConfigura uma / várias opções
CURLOPT_RETURNTRANSFERDevolver a resposta em vez de imprimir
CURLOPT_POST / CURLOPT_CUSTOMREQUESTMétodo POST / qualquer método
CURLOPT_POSTFIELDSCorpo da requisição
CURLOPT_HTTPHEADERLista de cabeçalhos
CURLOPT_CONNECTTIMEOUT / CURLOPT_TIMEOUTTempo máximo para conectar / para tudo (segundos)
curl_exec($ch)Executa; corpo ou false
curl_getinfo($ch, CURLINFO_RESPONSE_CODE)Código de status (int)
curl_error($ch) / curl_errno($ch)Mensagem / número do erro de conexão
json_decode($txt, true, flags: JSON_THROW_ON_ERROR)JSON → array, com exceção se for inválido

Exercícios

1. Devolver em vez de imprimir

Complete a opção que faz o curl_exec devolver a resposta como texto:

curl_setopt($ch, , true);

2. O status da resposta

Complete para obter o código de status da requisição cURL:

$status = curl_getinfo($ch, );

3. Não jogar fora o corpo do erro

Complete a opção do contexto que faz o file_get_contents devolver o corpo mesmo com status 404:

'' => true,

4. A API respondeu 404 Not Found. O que o curl_exec devolve?

5. Por que definir um tempo limite em toda requisição?

6. O que json_decode('<br /><b>Warning</b>', true) devolve?

Missão final: a função mensagem recebe o status e o corpo de uma resposta da API (como viriam do curl_getinfo e do curl_exec) e deve devolver: Produto: NOME quando o status for 200; Erro STATUS: MENSAGEM (usando a chave erro do JSON) para outros status; e Resposta inválida se o corpo não for JSON. Rode primeiro para ver os avisos que a versão ingênua produz. Dica: JSON_THROW_ON_ERROR e catch (JsonException).

<?php

declare(strict_types=1);

function mensagem(int $status, string $corpo): string
{
    $dados = json_decode($corpo, true);
    return 'Produto: ' . $dados['nome'];
}

echo mensagem(200, '{"id":2,"nome":"Caneta azul","preco":3.9}'), "\n";
echo mensagem(404, '{"erro":"Produto 9 não encontrado"}'), "\n";
echo mensagem(200, '<br /><b>Warning</b>: Undefined array key'), "\n";
<?php

declare(strict_types=1);

function mensagem(int $status, string $corpo): string
{
    try {
        $dados = json_decode($corpo, true, flags: JSON_THROW_ON_ERROR);
    } catch (JsonException) {
        return 'Resposta inválida';
    }

    if ($status !== 200) {
        return "Erro $status: {$dados['erro']}";
    }
    return 'Produto: ' . $dados['nome'];
}

echo mensagem(200, '{"id":2,"nome":"Caneta azul","preco":3.9}'), "\n";
echo mensagem(404, '{"erro":"Produto 9 não encontrado"}'), "\n";
echo mensagem(200, '<br /><b>Warning</b>: Undefined array key'), "\n";

Rodando a versão ingênua no computador da Ana, os avisos são estes (no editor, o nome do arquivo muda):

$ php mensagem.php
Produto: Caneta azul

Warning: Undefined array key "nome" in /home/ana/cliente/mensagem.php on line 8
Produto:

Warning: Trying to access array offset on null in /home/ana/cliente/mensagem.php on line 8
Produto: 

Resumo

  • Consumir uma API = mandar uma requisição HTTP e decodificar o JSON da resposta com json_decode($texto, true).
  • file_get_contents($url) faz um GET simples. Com stream_context_create você define método, cabeçalhos, corpo, timeout e ignore_errors.
  • cURL: curl_init → curl_setopt/curl_setopt_array → curl_exec → curl_getinfo. Não precisa (e não deve, no 8.5) chamar curl_close.
  • false = não houve resposta (conexão, nome, tempo). Status 4xx/5xx = houve resposta, com erro. Trate os dois.
  • Sempre um tempo limite. Sempre confira o resultado antes de usar. Use JSON_THROW_ON_ERROR para não engolir JSON inválido.
  • O editor do guia não acessa a rede: treine o tratamento das respostas nele e as requisições no seu computador, com a sua própria API no php -S.