PHP

Gerar slug de URL em PHP: acento, espaço e símbolo fora do endereço

Você precisa que Camiseta Preta Tamanho GG vire camiseta-preta-tamanho-gg, e que Ação & Cia vire acao-cia. São três coisas na sequência: transliterar o acento, trocar o resto por hífen e limpar as pontas. Nessa ordem, sempre.

<?php
function gerar_slug(string $texto, string $fallback = 'item'): string
{
    $tr = Transliterator::create('Any-Latin; Latin-ASCII; Lower()');
    if ($tr === null) {
        throw new RuntimeException('intl fora do ar: ' . intl_get_error_message());
    }
    $texto = preg_replace('/[^a-z0-9]+/', '-', $tr->transliterate($texto));
    $slug  = trim((string) $texto, '-');

    return $slug === '' ? $fallback : $slug;
}

echo gerar_slug('Camiseta Preta Tamanho GG'); // camiseta-preta-tamanho-gg
echo gerar_slug('Ação & Cia — 50% OFF');      // acao-cia-50-off

O Any-Latin; Latin-ASCII; Lower() é um encadeamento do ICU: o primeiro converte qualquer escrita para o alfabeto latino, o segundo joga fora os acentos, o terceiro baixa a caixa. Como o ICU vem com as tabelas dentro, o resultado é igual no seu Mac e no servidor. Precisa da extensão intl ligada, confira com php -m | grep intl.

E repare no $slug === '' no lugar de um ?:: produto chamado só 0 gera o slug 0, que é falsy, e o operador curto trocaria por item sem você perceber.

Por que eu abandonei o iconv //TRANSLIT

O truque antigo era iconv('UTF-8', 'ASCII//TRANSLIT', $texto). Ele funciona na sua máquina e explode em produção porque o resultado depende do locale do processo. Com LC_ALL=C, que é o padrão de muito container e de muito job de cron, o glibc não sabe o que fazer com o ç e devolve ? ou 'c com apóstrofo. Aí o slug sai a-ao e ninguém entende por quê.

A armadilha do slug repetido

Dois produtos chamados Camiseta Preta geram o mesmo slug. Antes de gravar, teste se já existe e vá somando sufixo: camiseta-preta-2, -3. E ponha um índice UNIQUE na coluna, porque duas requisições simultâneas passam pelo mesmo teste no mesmo milissegundo.

Se travou nisso e precisa de ajuda, me chama aqui.

Post Recentes

Layout XML do Magento 2: remover bloco de módulo de terceiro

Um módulo de terceiro plantou um banner no topo da loja e você não quer desinstalar nada: tire o bloco…

3 horas atrás

Restaurar dump do Magento 2 em outro servidor: trocar a base_url no SQL

Importou o banco de produção e o site pula para o domínio antigo? Rode o UPDATE em core_config_data, limpe o…

1 dia atrás

Criar exceção customizada em PHP: quando estender Exception compensa

Seu catch trata pedido inexistente e banco fora do ar do mesmo jeito. Veja como criar exceção customizada em PHP…

2 dias atrás

DataProvider Magento 2: form do admin abre com campos vazios ao editar

O form do admin salva mas abre em branco na edição: veja o getData() indexado pelo id da entidade e…

3 dias atrás

register_post_meta com show_in_rest: campo personalizado no /wp-json

O meta está no banco mas some do /wp-json/wp/v2/posts. Veja o register_post_meta certo, com single, type, schema de array e…

3 dias atrás

IntlDateFormatter: data por extenso em pt-BR sem array de meses

Escreva 31 de agosto de 2026 em PHP 8.3 sem aquele array com os doze meses, usando IntlDateFormatter com pattern…

4 dias atrás