7 min de leituraEngenharia

Como encurtar uma URL em PHP com curl, Guzzle ou WordPress

Encurte uma URL em PHP com um pequeno bloco de curl, a mesma chamada em Guzzle ou Laravel, uma versão para WordPress com wp_remote_post, além de retries e encurtamento em massa.

Ana Kowalska
Marketing solutions engineering
Como encurtar uma URL em PHP: um POST com curl enviando uma URL de destino para uma API de encurtador e decodificando o link curto da resposta JSON, ao lado dos caminhos com Guzzle e WordPress

Encurtar uma URL em PHP é um POST HTTP. Envie o link longo para a API de um encurtador com a extensão curl, passe sua chave de API como um token Bearer e extraia o link curto da resposta com json_decode. Nenhum pacote do Composer é necessário, embora o Guzzle e o cliente HTTP do Laravel reduzam a mesma chamada a três linhas.

A maioria dos tutoriais de PHP sobre o assunto é peça de museu. Eles configuram urlshortener/v1, que o Google encerrou depois de interromper a criação de novos links em 2019, com os links goo.gl inativos deixando de funcionar em 25 de agosto de 2025, ou usam o endpoint bit.ly v3, que desapareceu anos atrás. O código abaixo tem como alvo uma API atual.

Esta é a versão em PHP do guia geral sobre como encurtar uma URL. Se você ainda não escolheu um serviço, a visão geral da API gratuita de encurtador de URLs explica o formato da solicitação, o modelo de autenticação e os limites considerados aqui. Os nomes dos campos são os do Elido; o formato pode ser adaptado.

A maneira mais rápida: um POST com curl

Coloque a chave no ambiente, codifique o corpo por conta própria e leia o código de status antes de confiar no payload:

<?php

$ch = curl_init('https://api.elido.app/v1/links');

curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('ELIDO_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'destination_url' => 'https://example.com/spring-sale?utm_source=newsletter',
    ]),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($body === false || $status >= 400) {
    throw new RuntimeException("shorten failed: HTTP {$status}");
}

$link = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $link['short_url']; // https://s.elido.me/ab12cd

Duas linhas existem ali por causa de bugs que todo mundo encontra pelo menos uma vez. CURLOPT_RETURNTRANSFER tem false como padrão, então, sem ele, o curl imprime o JSON diretamente na saída e entrega true, que é a verdadeira causa da maioria das perguntas do tipo "minha resposta está vazia". E CURLOPT_POSTFIELDS muda para a codificação de formulário multipart assim que você passa um array, portanto o json_encode é o que realmente transforma a solicitação em JSON.

CURLINFO_RESPONSE_CODE é igualmente importante. O curl fica satisfeito em retornar um corpo cheio de {"error": "unauthorized"} sem reclamar.

Um POST PHP com curl enviando uma URL de destino e um token Bearer para o endpoint de links do encurtador, a API retornando HTTP 201 com um short_url que json_decode lê e um loop de retry aguardando após respostas 429 e 5xx

A mesma chamada em Guzzle ou Laravel

Se o projeto já inclui Guzzle, o código repetitivo diminui:

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.elido.app',
    'timeout'  => 10,
    'headers'  => ['Authorization' => 'Bearer ' . getenv('ELIDO_API_KEY')],
]);

$response = $client->post('/v1/links', [
    'json'    => ['destination_url' => $destination],
    'headers' => ['Idempotency-Key' => hash('sha256', $destination)],
]);

$short = json_decode((string) $response->getBody(), true)['short_url'];

O Guzzle lança ClientException em respostas 4xx e ServerException em respostas 5xx por padrão, o oposto do silêncio do curl e também do comportamento de fetch no JavaScript. Capture essas exceções ou defina http_errors como false e verifique o status por conta própria.

No Laravel, o cliente HTTP encapsula o Guzzle e oferece retries gratuitamente:

$short = Http::withToken(config('services.elido.key'))
    ->timeout(10)
    ->retry(3, 200, throw: false)
    ->post('https://api.elido.app/v1/links', ['destination_url' => $destination])
    ->json('short_url');

Esse retry(3, 200) representa três tentativas com uma pausa de 200 ms. É suficiente para uma instabilidade passageira, mas não para um limite de taxa, que é o assunto da próxima seção.

O WordPress não usa curl diretamente. Ele tem wp_remote_post, que escolhe um transporte por você e funciona em hosts onde o curl está desativado. Conecte-o à publicação e armazene o resultado como metadado do post, para que templates e feeds possam lê-lo:

add_action('publish_post', function (int $post_id): void {
    if (get_post_meta($post_id, '_elido_short_url', true)) {
        return; // already shortened
    }

    $response = wp_remote_post('https://api.elido.app/v1/links', [
        'timeout' => 10,
        'headers' => [
            'Authorization'   => 'Bearer ' . ELIDO_API_KEY,
            'Content-Type'    => 'application/json',
            'Idempotency-Key' => 'post-' . $post_id,
        ],
        'body' => wp_json_encode(['destination_url' => get_permalink($post_id)]),
    ]);

    if (is_wp_error($response)) {
        error_log('shorten failed: ' . $response->get_error_message());
        return;
    }

    $link = json_decode(wp_remote_retrieve_body($response), true);
    update_post_meta($post_id, '_elido_short_url', $link['short_url']);
}, 10, 1);

ELIDO_API_KEY pertence ao wp-config.php, não a um arquivo de plugin que acaba em um repositório público nem a uma linha de opções que todo administrador pode ler. Observe a chave de idempotência: post-123 é estável, portanto, se o hook for executado duas vezes em uma republicação, a segunda chamada retorna o link que já existe em vez de criar outro.

Se você preferir não manter o hook, o guia de encurtador para WordPress compara essa opção com o plugin e com os caminhos sem código.

Quer executar estes snippets como estão? Crie uma chave no plano gratuito, exporte-a como ELIDO_API_KEY, e o bloco de curl acima funcionará sem alterações.

Retries, limites de taxa e idempotência

PHP sem supervisão - um job do cron, um worker de fila ou um importador em massa - precisa sobreviver a um 429 e a um 5xx temporário. Também precisa sobreviver ao caso incômodo em que o POST chega à API, mas a resposta se perde no caminho de volta. Seu código vê um timeout, tenta novamente e cria um segundo link curto para o mesmo destino, a menos que uma chave de idempotência impeça isso.

function shorten(string $destination, int $retries = 3): string
{
    $key = hash('sha256', $destination); // stable across retries and re-runs

    for ($attempt = 0; $attempt < $retries; $attempt++) {
        [$body, $status, $retryAfter] = post_link($destination, $key);

        if ($status === 429) {
            sleep(max(1, (int) $retryAfter));
            continue;
        }
        if ($status >= 500) {
            sleep(2 ** $attempt); // exponential backoff
            continue;
        }
        if ($status >= 400) {
            throw new RuntimeException("shorten failed: HTTP {$status} {$body}");
        }

        return json_decode($body, true, 512, JSON_THROW_ON_ERROR)['short_url'];
    }

    throw new RuntimeException("shorten failed after {$retries} attempts");
}

Um 401 ou 403 nunca se corrige sozinho, então o código lança uma exceção na primeira tentativa em vez de dormir durante três tentativas. Um 429 espera o tempo solicitado pelo cabeçalho Retry-After. Somente respostas 5xx recebem o backoff duplicado. O guia detalhado sobre limites de taxa e idempotência explica todo o raciocínio, inclusive por que um hash do destino costuma ser uma chave melhor que um UUID aleatório quando o mesmo lote pode ser executado novamente.

Uma armadilha específica do PHP: sleep() dentro de uma solicitação web mantém um worker php-fpm ocupado durante todo o período. Retries pertencem a scripts CLI, jobs de fila ou WP-Cron, não à solicitação que renderiza uma página.

Encurtamento em massa sem esperas sequenciais

Um loop foreach que encurta 500 URLs faz 500 viagens de ida e volta consecutivas. A 120 ms cada, isso representa um minuto inteiro de espera. O PHP não tem um runtime assíncrono, mas tem HTTP concorrente, e o Pool do Guzzle mantém um número fixo de solicitações em andamento:

use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

$requests = static function (array $urls) {
    foreach ($urls as $url) {
        yield new Request('POST', '/v1/links', [
            'Content-Type'    => 'application/json',
            'Idempotency-Key' => hash('sha256', $url),
        ], json_encode(['destination_url' => $url]));
    }
};

$short = [];
$pool  = new Pool($client, $requests($urls), [
    'concurrency' => 8,
    'fulfilled'   => function ($response, $i) use (&$short, $urls) {
        $short[$urls[$i]] = json_decode((string) $response->getBody(), true)['short_url'];
    },
    'rejected'    => function ($reason, $i) use (&$short, $urls) {
        $short[$urls[$i]] = 'ERROR: ' . $reason->getMessage();
    },
]);

$pool->promise()->wait();
Três caminhos em PHP para o mesmo endpoint de encurtador: curl puro em hospedagem compartilhada, Guzzle ou Laravel em uma aplicação e wp_remote_post dentro do WordPress, todos lendo a chave de API do ambiente

Indexar os resultados pela URL original mantém uma falha parcial visível e permite executar o processo novamente. Sem Composer, curl_multi_init faz o mesmo trabalho com mais controle manual: adicione handles, itere com curl_multi_exec e colete cada resposta. Comece com uma concorrência de cerca de oito e deixe os cabeçalhos das respostas indicarem quando reduzi-la.

Qual abordagem escolher

Combine a ferramenta com o host. Hospedagem compartilhada sem Composer: o bloco simples de curl. Uma aplicação Symfony ou Laravel: Guzzle ou o cliente HTTP, com retries configurados uma vez no nível do cliente. WordPress: wp_remote_post em um hook de publicação. Uma importação noturna de milhares de links: o pool, com uma chave de idempotência estável para que uma nova execução não tenha custo.

Seja qual for sua escolha, quatro coisas permanecem iguais: a chave vem do ambiente, o curl recebe um timeout explícito, o código de status é lido antes do corpo e tudo que pode ser executado duas vezes leva uma chave de idempotência. Equipes que passam do primeiro script geralmente querem a API e os SDKs ou o que mais a plataforma oferece aos desenvolvedores: cliques, tags, expiração, webhooks para eventos de links em vez de polling.

Leia a série principal

Este post faz parte do cluster de engenharia. Comece pelo guia gratuito da API de encurtador de URLs para entender o endpoint e a autenticação; depois, leia o material sobre limites de taxa e idempotência para operar bem sob carga. A referência atual é a documentação da API, e, se você herdou links goo.gl, a alternativa ao Google URL Shortener explica para onde eles vão agora.

Relacionados no blog

Perguntas frequentes

Como encurto uma URL em PHP?

Envie a URL longa por POST para a API de um encurtador com curl, usando sua chave de API como um cabeçalho Bearer e o destino em um corpo JSON; depois, use json_decode na resposta e leia o campo short_url. São cerca de quinze linhas com curl_setopt_array, ou três com Guzzle ou com o cliente HTTP do Laravel, se o projeto já tiver um deles.

Como encurto uma URL em PHP sem uma biblioteca externa?

Use a extensão curl que acompanha o PHP. curl_init, curl_setopt_array, curl_exec e json_decode cobrem todo o fluxo sem uma dependência do Composer, o que é importante em hospedagem compartilhada onde você não pode executar o Composer. Defina CURLOPT_RETURNTRANSFER como true, ou curl_exec imprimirá a resposta em vez de retorná-la.

A API do Google URL Shortener ainda funciona em PHP?

Não. O Google parou de aceitar novos links pela API URL Shortener em 2019 e encerrou o serviço; os links goo.gl que estavam inativos deixaram de ser resolvidos em 25 de agosto de 2025. Qualquer tutorial de PHP baseado em urlshortener/v1 é código obsoleto, e os exemplos de bit.ly v3 da mesma época também desapareceram.

Posso encurtar uma URL dentro do WordPress com PHP?

Sim. Chame wp_remote_post a partir de um hook executado na publicação e armazene o link curto retornado com update_post_meta, para que o restante do tema possa lê-lo. Mantenha a chave de API em wp-config.php, e não em um arquivo de plugin ou no banco de dados; verifique também is_wp_error na resposta, pois o WordPress retorna um objeto WP_Error em vez de lançar uma exceção.

Como encurto muitas URLs de uma vez em PHP?

Envie as solicitações simultaneamente com um pool limitado, em vez de usar um loop foreach que espera cada resposta. O Pool do Guzzle com uma concorrência de cerca de oito é o caminho mais curto; curl_multi faz o mesmo sem Composer, e uma Idempotency-Key derivada de cada URL impede que uma nova execução crie links duplicados.

Por que minha solicitação PHP com curl para um encurtador de URLs retorna 401 ou uma resposta vazia?

Um 401 significa que o cabeçalho Authorization está ausente ou malformado - ele deve ser exatamente 'Authorization: Bearer YOUR_KEY' como uma única string no array CURLOPT_HTTPHEADER. Um valor de retorno vazio geralmente significa que CURLOPT_RETURNTRANSFER nunca foi definido, então o corpo foi direto para a saída e curl_exec retornou true.

Experimente Elido

Cole uma URL, obtenha um link curto

Sem cadastro. O link vive 30 dias. Cadastre-se para mantê-lo para sempre.

Grátis, sem necessidade de registo · 2 por dia

Experimente o Elido

Encurtador de URL hospedado na UE: domínios personalizados, análises profundas e API aberta. Plano gratuito - sem cartão de crédito.

Tags
how to shorten a url in php
php url shortener
shorten url php curl
url shortener api php
wordpress shorten url php
bulk shorten urls php

Continuar lendo