Plugin WP

Importer idempotente em PHP, por que seu script deveria poder rodar N vezes sem medo

A diferença entre um script que "funciona no primeiro run" e um script que você roda com confiança em produção é uma linha de hash. E vai muito além de WordPress.

· 6 min de leitura

Todo dev que já importou planilha em produção conhece o frio na espinha do segundo run. Você rodou o script, 114 páginas foram criadas, aparentemente deu certo. Aí o time de conteúdo pede pra você rodar de novo porque mudou uma linha. Você cruza os dedos e espera que o script não duplique tudo.

Esse frio na espinha é sintoma de um script que não foi projetado pra idempotência. E idempotência não é detalhe, é a propriedade que separa um script de uso único de uma ferramenta operacional.

O que significa idempotente

Um script idempotente pode ser executado N vezes com o mesmo input e o estado final é igual ao de uma única execução. Se rodar 1 vez ou 50 vezes, o banco fica do mesmo jeito.

Na prática, isso significa que o script precisa detectar o que já foi feito antes de decidir o que fazer. Três estratégias em ordem de robustez:

1. Chave natural no banco

Se você está importando pra uma tabela com constraint de unicidade (ou um CPT do WordPress com post_name único por tipo), use INSERT ... ON DUPLICATE KEY UPDATE ou wp_insert_post com ID resolvido via get_page_by_path. Custo: mínimo. Ganho: você não duplica.

Armadilha: isso não protege contra updates desnecessários. Se você rodar o script toda noite com a mesma planilha, vai reescrever 114 posts intocados toda noite.

2. Hash da linha como checksum

Aqui mora o truque. Antes de atualizar um post, você calcula um hash da linha de entrada (md5, sha1, não importa, desde que seja determinístico) e compara com o hash que foi persistido na última importação. Se for igual, pula. Se for diferente, atualiza e persiste o novo hash.

$hash = md5( serialize( $row ) );
$existing = get_page_by_path( $row['slug'], OBJECT, 'exame_medico' );

if ( $existing && get_post_meta( $existing->ID, '_source_hash', true ) === $hash ) {
    continue; // Nenhuma mudança, pula
}

$id = wp_insert_post( $this->map_fields( $row, $existing->ID ?? 0 ) );
update_post_meta( $id, '_source_hash', $hash );

Custo: 1 chamada extra ao meta por linha. Ganho: você pode rodar o script N vezes e só mexe no que de fato mudou.

3. Log de execução auditável

Pra scripts críticos, eu persisto em uma tabela própria cada execução: id, timestamp, total_rows, rows_inserted, rows_updated, rows_skipped, rows_failed. Isso vira auditoria e também vira debug quando o time reclama que "não importou direito".

Por que isso importa fora do WordPress

O mesmo princípio aparece em:

  • ETL. Reprocessar um batch do S3 não pode duplicar linhas no data warehouse.
  • Migração de dados. Você vai rodar o script em dev, em staging e em produção. Preferencialmente com o mesmo script.
  • Webhooks. Retries acontecem. Se seu endpoint não for idempotente, você vai cobrar duas vezes o cliente.
  • CI/CD. Migrações de banco (rails, django, laravel) são idempotentes por design, com hash de versão. Isso não é coincidência, é a mesma ideia.

O teste que todo importer deveria passar

Antes de considerar qualquer importer "pronto", rode ele duas vezes seguidas e compare o estado do banco. Se o diff for diferente de zero, ele não é idempotente. Se for zero, você acabou de eliminar metade dos bugs de produção que teria daqui a 3 meses.

Projetos relacionados: Arquitetura de conteúdo e scaffold PHP para import em escala e Plugin WordPress com fix de timezone (UTC).