🔄 cURL em PHP

💡 O que é cURL?

cURL (Client URL Library) é uma extensão do PHP que permite fazer pedidos HTTP de forma flexível e poderosa. É a escolha preferida para consumo profissional de APIs.

Vantagens do cURL

✅ Controlo Total

Controlo granular sobre todos os aspetos do pedido: timeouts, SSL, cookies, redirects, etc.

✅ Suporte Completo

Suporta HTTP, HTTPS, FTP, cookies, autenticação, proxies, uploads de ficheiros.

✅ Debug Detalhado

Informações detalhadas sobre o pedido e resposta para facilitar debugging.

✅ Performance

Mais eficiente e rápido que file_get_contents para múltiplos pedidos.

Verificar se cURL está Instalado

Verificar Extensão
<?php
// Verificar se cURL está disponível
if (function_exists('curl_init')) {
    echo "cURL está instalado!";

    // Ver versão
    $version = curl_version();
    echo "Versão: " . $version['version'];
} else {
    echo "cURL não está instalado. Instale a extensão php-curl.";
}
?>

📝 Uso Básico

O fluxo básico de cURL em PHP segue 4 passos:

1. curl_init()

Inicializar sessão

→
2. curl_setopt()

Configurar opções

→
3. curl_exec()

Executar pedido

→
4. curl_close()

Fechar sessão

Exemplo Mínimo

GET Simples
<?php
// 1. Inicializar
$ch = curl_init();

// 2. Configurar opções
curl_setopt($ch, CURLOPT_URL, 'https://jsonplaceholder.typicode.com/posts/1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// 3. Executar
$response = curl_exec($ch);

// 4. Fechar
curl_close($ch);

// Processar resposta
$data = json_decode($response, true);
print_r($data);
?>

⚙️ Opções do cURL

Opções Mais Usadas

Opção Descrição Exemplo
CURLOPT_URL URL do pedido 'https://api.exemplo.com'
CURLOPT_RETURNTRANSFER Retornar resposta em vez de imprimir true
CURLOPT_POST Definir método POST true
CURLOPT_POSTFIELDS Dados do body json_encode($data)
CURLOPT_HTTPHEADER Headers personalizados ['Content-Type: application/json']
CURLOPT_TIMEOUT Timeout em segundos 30
CURLOPT_CONNECTTIMEOUT Timeout de conexão 10
CURLOPT_FOLLOWLOCATION Seguir redirects true
CURLOPT_SSL_VERIFYPEER Verificar SSL true
CURLOPT_USERAGENT User Agent 'MeuApp/1.0'

Configuração Completa

Configuração Típica
<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.exemplo.com/endpoint',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_MAXREDIRS => 5,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
    CURLOPT_USERAGENT => 'MeuApp/1.0',
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Accept: application/json'
    ]
]);

$response = curl_exec($ch);
curl_close($ch);
?>

🔍 Pedidos GET

GET com Parâmetros

GET com Query String
<?php
// Construir URL com parâmetros
$baseUrl = 'https://jsonplaceholder.typicode.com/posts';
$params = [
    'userId' => 1,
    '_limit' => 5
];

$url = $baseUrl . '?' . http_build_query($params);

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true
]);

$response = curl_exec($ch);
curl_close($ch);

$posts = json_decode($response, true);
foreach ($posts as $post) {
    echo $post['title'] . "\n";
}
?>

GET com Headers

GET com Autenticação
<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.exemplo.com/users',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer SEU_TOKEN_AQUI',
        'Accept: application/json',
        'X-API-Key: sua_api_key'
    ]
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
?>

📤 Pedidos POST

POST com JSON

POST JSON
<?php
$data = [
    'title' => 'Novo Post',
    'body' => 'Conteúdo do post',
    'userId' => 1
];

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://jsonplaceholder.typicode.com/posts',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($data),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Accept: application/json'
    ]
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode === 201) {
    $result = json_decode($response, true);
    echo "Post criado com ID: " . $result['id'];
}
?>

POST com Form Data

POST Form Data
<?php
// Dados de formulário
$data = [
    'nome' => 'João Silva',
    'email' => 'joao@exemplo.com',
    'mensagem' => 'Olá, mundo!'
];

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.exemplo.com/contact',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($data)  // Converte para form data
]);

$response = curl_exec($ch);
curl_close($ch);
?>

Upload de Ficheiros

Upload de Ficheiro
<?php
$filePath = '/caminho/para/ficheiro.jpg';

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.exemplo.com/upload',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile($filePath, 'image/jpeg', 'foto.jpg'),
        'descricao' => 'Minha foto'
    ]
]);

$response = curl_exec($ch);
curl_close($ch);
?>
💡 CURLFile

Para upload de ficheiros, usa a classe CURLFile. Não uses @ antes do caminho (deprecated desde PHP 5.5).

🔧 PUT e DELETE

Pedido PUT

PUT - Atualizar Recurso
<?php
$data = [
    'id' => 1,
    'title' => 'Título Atualizado',
    'body' => 'Conteúdo atualizado',
    'userId' => 1
];

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://jsonplaceholder.typicode.com/posts/1',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PUT',  // Método personalizado
    CURLOPT_POSTFIELDS => json_encode($data),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json'
    ]
]);

$response = curl_exec($ch);
curl_close($ch);
?>

Pedido DELETE

DELETE - Remover Recurso
<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://jsonplaceholder.typicode.com/posts/1',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE'
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode === 200 || $httpCode === 204) {
    echo "Recurso eliminado com sucesso!";
}
?>

Pedido PATCH

PATCH - Atualização Parcial
<?php
// Apenas atualizar o título
$data = [
    'title' => 'Novo Título'
];

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://jsonplaceholder.typicode.com/posts/1',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_POSTFIELDS => json_encode($data),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json'
    ]
]);

$response = curl_exec($ch);
curl_close($ch);
?>

📋 Obter Headers de Resposta

Capturar Headers
<?php
$ch = curl_init();

// Variável para armazenar headers
$responseHeaders = [];

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://jsonplaceholder.typicode.com/posts',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => true,  // Incluir headers na resposta
    // Função para processar headers
    CURLOPT_HEADERFUNCTION => function($curl, $header) use (&$responseHeaders) {
        $len = strlen($header);
        $header = explode(':', $header, 2);
        if (count($header) < 2) {
            return $len;
        }
        $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
        return $len;
    }
]);

$response = curl_exec($ch);
curl_close($ch);

// Separar body dos headers
$body = substr($response, curl_getinfo($ch, CURLINFO_HEADER_SIZE));

// Headers disponíveis
print_r($responseHeaders);
// Exemplo: ['content-type' => 'application/json', 'x-ratelimit-limit' => '1000']
?>

Informações do Pedido

curl_getinfo()
<?php
// Após curl_exec($ch)
$info = curl_getinfo($ch);

echo "Código HTTP: " . $info['http_code'] . "\n";
echo "Tempo total: " . $info['total_time'] . " segundos\n";
echo "Tamanho download: " . $info['size_download'] . " bytes\n";
echo "Velocidade: " . $info['speed_download'] . " bytes/s\n";
echo "URL final: " . $info['url'] . "\n";
echo "Content-Type: " . $info['content_type'] . "\n";
?>

🚨 Tratamento de Erros

Tratamento Completo
<?php
function curlRequest(string $url, array $options = []) {
    $ch = curl_init();

    // Opções padrão
    $defaultOptions = [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5,
        CURLOPT_SSL_VERIFYPEER => true
    ];

    curl_setopt_array($ch, $defaultOptions + $options);

    $response = curl_exec($ch);

    // Verificar erros do cURL
    if ($response === false) {
        $errorCode = curl_errno($ch);
        $errorMessage = curl_error($ch);
        curl_close($ch);

        return [
            'success' => false,
            'error_code' => $errorCode,
            'error_message' => $errorMessage,
            'http_code' => 0,
            'data' => null
        ];
    }

    // Obter informações
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    // Verificar código HTTP
    if ($httpCode >= 400) {
        return [
            'success' => false,
            'error_code' => $httpCode,
            'error_message' => "HTTP Error: $httpCode",
            'http_code' => $httpCode,
            'data' => json_decode($response, true)
        ];
    }

    return [
        'success' => true,
        'http_code' => $httpCode,
        'data' => json_decode($response, true),
        'raw' => $response
    ];
}

// Uso
$result = curlRequest('https://jsonplaceholder.typicode.com/posts/1');

if ($result['success']) {
    echo "Título: " . $result['data']['title'];
} else {
    echo "Erro: " . $result['error_message'];
}
?>

Códigos de Erro cURL Comuns

Código Constante Descrição
6 CURLE_COULDNT_RESOLVE_HOST Não conseguiu resolver o hostname (DNS)
7 CURLE_COULDNT_CONNECT Falha ao conectar ao servidor
28 CURLE_OPERATION_TIMEDOUT Timeout atingido
35 CURLE_SSL_CONNECT_ERROR Erro de conexão SSL
51 CURLE_PEER_FAILED_VERIFICATION Certificado SSL inválido

📦 Classe Wrapper Reutilizável

Classe ApiClient
<?php
class ApiClient {
    private string $baseUrl;
    private array $defaultHeaders = [];
    private int $timeout = 30;

    public function __construct(string $baseUrl = '') {
        $this->baseUrl = $baseUrl;
    }

    public function setHeaders(array $headers): self {
        $this->defaultHeaders = $headers;
        return $this;
    }

    public function setTimeout(int $seconds): self {
        $this->timeout = $seconds;
        return $this;
    }

    public function get(string $endpoint, array $params = []): array {
        $url = $this->baseUrl . $endpoint;
        if (!empty($params)) {
            $url .= '?' . http_build_query($params);
        }
        return $this->request('GET', $url);
    }

    public function post(string $endpoint, array $data = []): array {
        return $this->request('POST', $this->baseUrl . $endpoint, $data);
    }

    public function put(string $endpoint, array $data = []): array {
        return $this->request('PUT', $this->baseUrl . $endpoint, $data);
    }

    public function delete(string $endpoint): array {
        return $this->request('DELETE', $this->baseUrl . $endpoint);
    }

    private function request(string $method, string $url, array $data = []): array {
        $ch = curl_init();

        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => array_merge(
                ['Content-Type: application/json', 'Accept: application/json'],
                $this->defaultHeaders
            )
        ];

        if (in_array($method, ['POST', 'PUT', 'PATCH']) && !empty($data)) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data);
        }

        curl_setopt_array($ch, $options);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        return [
            'success' => $response !== false && $httpCode < 400,
            'status' => $httpCode,
            'data' => json_decode($response, true),
            'error' => $error
        ];
    }
}

// Uso
$api = new ApiClient('https://jsonplaceholder.typicode.com');

// GET
$result = $api->get('/posts/1');
print_r($result['data']);

// POST
$newPost = $api->post('/posts', [
    'title' => 'Meu Post',
    'body' => 'Conteúdo',
    'userId' => 1
]);
?>

📝 Exercícios

▼

Exercício 1: GET com cURL

Desafio: Usa cURL para obter todos os posts do utilizador ID 3 da API JSONPlaceholder.

<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://jsonplaceholder.typicode.com/posts?userId=3',
    CURLOPT_RETURNTRANSFER => true
]);

$response = curl_exec($ch);
curl_close($ch);

$posts = json_decode($response, true);
foreach ($posts as $post) {
    echo $post['title'] . "\n";
}
?>
▼

Exercício 2: POST com Tratamento de Erros

Desafio: Cria um pedido POST que trata erros de conexão e mostra o código HTTP.

<?php
$data = ['title' => 'Teste', 'body' => 'Conteúdo', 'userId' => 1];

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://jsonplaceholder.typicode.com/posts',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($data),
    CURLOPT_HTTPHEADER => ['Content-Type: application/json']
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($response === false) {
    echo "Erro cURL: " . curl_error($ch);
} elseif ($httpCode >= 400) {
    echo "Erro HTTP: $httpCode";
} else {
    $result = json_decode($response, true);
    echo "Post criado! ID: " . $result['id'];
}

curl_close($ch);
?>