Search and replace dell'URL nel database WordPress senza rompere i dati serializzati
Cambia l'URL di un sito WordPress nel database senza svuotare widget, menu o impostazioni del tema. Il comando WP-CLI che mantiene intatti i dati serializzati.
Pubblicato
Non lanciare mai un semplice UPDATE ... SET column = REPLACE(column, 'oldurl', 'newurl') su un database WordPress. Cambierà il testo correttamente e nello stesso momento distruggerà le tue impostazioni, perché WordPress salva array e oggetti come stringhe serializzate PHP, e quel formato registra la lunghezza in byte di ogni stringa che contiene. Una sostituzione SQL grezza riscrive il testo ma non tocca mai la lunghezza registrata, e il valore smette di essere leggibile.
Usa invece uno strumento che conosce la serializzazione. Da riga di comando è wp search-replace. Fai prima il backup del database, lancialo in modalità di prova, poi eseguilo davvero.
Cosa si rompe davvero
Una stringa serializzata PHP si presenta così:
s:18:"http://example.com"
Il 18 è la lunghezza in byte del testo che segue. Contali pure — http://example.com è esattamente 18 caratteri.
Ora immagina un’impostazione del tema salvata a database:
a:1:{s:4:"logo";s:18:"http://example.com";}
Esegui una semplice sostituzione SQL da http:// a https:// e ottieni:
a:1:{s:4:"logo";s:19:"https://example.com";}
Solo che non è vero. Ottieni questo:
a:1:{s:4:"logo";s:18:"https://example.com";}
La stringa ora è di 19 byte ma continua a dichiararne 18. PHP legge la lunghezza dichiarata, avanza di 18 byte, non trova le virgolette di chiusura e il punto e virgola dove se li aspetta, e rinuncia. L’intero array non viene deserializzato.
Perché fallisce in silenzio
È questa la parte che rende il problema così costoso. WordPress non genera un errore e non si ferma. Quando legge un’opzione passa il valore attraverso la propria funzione di deserializzazione, PHP restituisce false per il dato malformato e WordPress consegna al tuo codice un valore che si comporta come se l’opzione non fosse mai stata impostata. Il plugin o il tema ripiega quindi sul proprio default.
Il sintomo, quindi, non è una pagina di errore. Il sintomo è:
- I widget spariscono dalle sidebar
- Le impostazioni del customizer tornano ai valori predefiniti del tema
- I dati di layout di un page builder tornano vuoti e la pagina si carica bianca
- Le pagine di configurazione dei plugin sembrano appena installate
- I menu perdono le posizioni assegnate
Nulla nella bacheca segnala che qualcosa non va. La riga è ancora nel database, ancora piena di quello che sembra testo corretto. Solo il prefisso di lunghezza è sbagliato di un byte o due. PHP una notice quando la deserializzazione fallisce la emette, ma su un sito in produzione con la visualizzazione degli errori disattivata non la vede nessuno. Attivare WP_DEBUG con WP_DEBUG_LOG abilitato e WP_DEBUG_DISPLAY impostato a false scrive quelle notice in un file di log dove puoi leggerle.
Anche il danno non è distribuito in modo uniforme. I valori scalari semplici — siteurl, home, il contenuto normale di un articolo, un URL che sta in una colonna tutta sua — sopravvivono benissimo a una sostituzione ingenua. Si rompono solo i valori serializzati. Ed è per questo che una sostituzione sbagliata, a prima vista, sembra spesso aver funzionato.
Controlla wp-config.php prima di toccare qualsiasi cosa
Se wp-config.php definisce WP_HOME o WP_SITEURL, quelle costanti vincono su qualunque valore presente nel database. Modificare wp_options sembrerà non produrre assolutamente nulla. Cerca righe come queste prima di concludere che il problema è il database:
define( 'WP_HOME', 'http://example.com' );
define( 'WP_SITEURL', 'http://example.com' );
O le aggiorni al nuovo URL, oppure le rimuovi e lasci decidere al database.
Fai il backup, e sul serio
Una rottura della serializzazione non è sempre facile da annullare. Una volta che il prefisso di lunghezza e il contenuto non concordano più, il valore originale non è recuperabile a partire da quello rotto: non puoi sapere quale fosse la lunghezza corretta senza conoscere la stringa di partenza. Il backup è l’unico vero annulla.
wp db export backup-before-replace.sql
Oppure con mysqldump, se WP-CLI non è disponibile:
mysqldump -u USER -p DBNAME > backup-before-replace.sql
Se puoi, tienilo fuori dal server. Un backup che sta nella web root è insieme un punto di ripristino e un problema di sicurezza.
Il comando sicuro
Il search-replace di WP-CLI deserializza ogni valore, percorre la struttura decodificata, sostituisce al suo interno e riserializza con le lunghezze corrette. Prima la prova a vuoto:
wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid
Leggi il report. Ti dice quante sostituzioni avverranno per ogni tabella e colonna. Se l’elenco delle tabelle sembra sbagliato, o se una tabella che non riconosci registra migliaia di occorrenze, fermati e guarda prima di eseguirlo davvero.
Poi lancialo:
wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only
Qualche nota onesta sui flag:
--all-tablescopre le tabelle che WordPress non registra da sé, ed è proprio lì che spesso vivono i dati dei plugin. Senza questo flag alcune tabelle dei plugin vengono saltate del tutto.--skip-columns=guidconta. Ilguidè un identificatore permanente per i lettori di feed, non un URL attivo. Riscriverlo può far apparire ai tuoi iscritti l’intero archivio come articoli nuovi.- Cerca senza la barra finale e senza le varianti di protocollo che non vuoi toccare. Sostituire il nudo
example.comcolpirà anche gli indirizzi email e ogni menzione nel testo degli articoli.
Se non puoi usare la riga di comando, i plugin di migrazione consapevoli della serializzazione fanno lo stesso lavoro di decodifica dalla bacheca. Il comportamento che cerchi viene descritto esplicitamente come gestione dei dati serializzati; se uno strumento non lo dichiara, dai per scontato che non lo faccia.
Quali tabelle e colonne sono coinvolte
| Tabella | Colonna | Rischio serializzazione |
|---|---|---|
| wp_options | option_value | Alto — widget, theme mods, transient, gran parte delle impostazioni dei plugin |
| wp_postmeta | meta_value | Alto — layout dei page builder, array di campi personalizzati |
| wp_usermeta | meta_value | Alto — capability, preferenze di schermata e bacheca |
| wp_termmeta | meta_value | Medio — dipende dai plugin |
| wp_posts | post_content, guid | Basso per il contenuto, ma attributi degli shortcode e markup dei blocchi contengono URL |
| wp_comments | comment_content, comment_author_url | Basso |
| wp_links | link_url, link_image | Basso, e spesso inutilizzata |
In multisito aggiungi wp_blogs, wp_site, wp_sitemeta e le tabelle per sito wp_2_options, wp_3_options e così via. I cambi di URL in multisito hanno regole ulteriori oltre a un search and replace: consideralo un lavoro a parte.
I casi che search-replace non risolve del tutto
Onestamente, i limiti sono questi:
JSON salvato dentro il database. Alcuni page builder salvano i dati di layout come JSON con le barre in avanti escapate, quindi il tuo URL compare come https:\/\/example.com anziché https://example.com. Una ricerca sulla forma normale non lo troverà. Potresti aver bisogno di un secondo passaggio sulla stringa escapata. Provalo su una copia.
Valori codificati in Base64. Se un plugin codifica le impostazioni prima di salvarle, nessuna sostituzione testuale può vedere l’URL. Quelle impostazioni di solito vanno reinserite dalla schermata del plugin stesso.
URL scritti a mano nei file. Tutto ciò che è scritto dentro functions.php, un template del tema child o un percorso di asset fissato nel codice non sta nel database e non verrà toccato. Fai un grep separato nella cartella del tema.
Output in cache e generato. Object cache, cache di pagina e copie sulla CDN continuano a servire i vecchi URL anche dopo una sostituzione corretta. Svuotale, poi ricontrolla prima di dare per scontato che la sostituzione sia fallita.
Se hai già lanciato la query sbagliata
Ripristina il backup. La risposta è questa, e vale la pena dirla chiaramente invece di proporre una procedura di riparazione che nella maggior parte dei casi non funziona.
Se il backup non c’è, la strada percorribile è stretta: i valori mai serializzati stanno bene, mentre quelli serializzati e rotti vanno ricostruiti a mano oppure azzerati risalvando la relativa schermata di impostazioni. Risalvare un’area widget, riselezionare la posizione di un menu o reinserire le impostazioni di un plugin scrive un valore nuovo e serializzato correttamente sopra quello rotto. Noioso, ma funziona, ed è meglio che lasciare righe corrotte lì dove confonderanno la prossima persona che metterà mano al sito.
Per generare le istruzioni corrette prima di eseguire qualsiasi cosa, costruiscile con il tool per il search and replace SQL di WordPress: ti mostra quali colonne si possono toccare con SQL semplice e quali richiedono una gestione consapevole della serializzazione, così vedi la divisione prima di impegnarti in un comando.
FAQ
Domande
Perché una semplice REPLACE in SQL rompe un sito WordPress?
Perché WordPress salva array e oggetti come testo serializzato PHP, e quel formato registra la lunghezza in byte di ogni stringa che contiene. Una REPLACE grezza cambia il testo ma lascia il vecchio numero di lunghezza al suo posto. PHP a quel punto si rifiuta di deserializzare il valore, WordPress ripiega su un default e le impostazioni appaiono vuote invece che sbagliate.
Qual è il modo più sicuro per cambiare l'URL di un sito WordPress nel database?
Fai un backup completo del database, poi lancia il comando WP-CLI wp search-replace prima in modalità di prova. WP-CLI deserializza ogni valore, sostituisce all'interno della struttura decodificata e riserializza con le lunghezze corrette. I plugin di migrazione che gestiscono la serializzazione fanno lo stesso lavoro dalla bacheca, se non hai accesso alla riga di comando.
Quali tabelle contengono il vecchio URL?
Principalmente wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta e wp_comments. Il pericolo della serializzazione vive in wp_options e in qualsiasi colonna meta_value, perché widget, theme mods, transient e dati dei page builder sono salvati come array. In multisito anche wp_blogs, wp_site e wp_sitemeta contengono il dominio.
Modificare il database cambia l'URL se è definito in wp-config.php?
No. Se wp-config.php definisce WP_HOME o WP_SITEURL, quelle costanti hanno la meglio su qualunque valore di siteurl e home in wp_options. Modificare il database non produce alcun effetto visibile finché le costanti non vengono aggiornate o rimosse. Controlla prima wp-config.php: quella singola riga spiega parecchie migrazioni fallite.
Devo sostituire anche la colonna guid?
No. Il guid è un identificatore permanente per i lettori di feed, non un URL funzionante, e riscriverlo può far vedere agli iscritti tutti i vecchi articoli come se fossero nuovi. Passa skip-columns con guid quando usi WP-CLI. L'unica eccezione comune è un sito che non ha mai avuto feed pubblici né iscritti.
Come capisco se un valore si è rotto dopo una sostituzione sbagliata?
I valori serializzati rotti sembrano normali nel database ma tornano vuoti in WordPress. I widget spariscono, le impostazioni del customizer si azzerano e le opzioni dei plugin tornano ai default senza alcun messaggio di errore. Attivare WP_DEBUG e WP_DEBUG_LOG fa emergere la notice di unserialize. Confrontare la lunghezza dichiarata con il conteggio reale dei byte lo conferma in modo diretto.