Rifattorizzazione di alcune parti di un software PHP

aneddoto su PHP

The call

Ci sono dei giorni in cui ricevi la chiamata. Quando accade lo sai ancora prima di rispondere, senti un brivido correre dietro la schiena, sudore freddo, peli irti, tutti i sensi si attivano per avvisarti, ma tu ingenuamente rispondi con un sorriso ebete stampato in faccia come al gelataio in una serata di Ferragosto: «Buonasera!»
«C'è un lavoro per te e non puoi dirmi di no» - fa il cliente con voce decisa e funerea.
Ecco. Non c'è nemmeno un briciolo di dubbio, è una sorda, sonora, grandiosa, inchiappettata senza infiocchettamenti.
«Il software che uso per la gestione dei rifiuti, non mi genera il PDF, ed altri due o tre problemini...» (adoro quando i clienti usano il diminuitivo a sproposito)
Non c'è margine di trattativa, devo lavorare su un software scritto da altri e la cosa non mi entusiasma affatto. Il problema principale è: alcuni PDF non vengono generati. Tenetelo a mente.

Diving in

Non è che lavorare su di un progetto altrui mi causa orticarie di per sè, ma raramente ho visto dei lavori ad-hoc su cui valeva la pena mettere le mani. Il dramma è l'effetto domino: quando si scrive in modalità spaghetti-code tutto si tiene insieme con un equilibrio precario, solitamente con sputo, righe di codice e serie di include a catena; cambiando una riga c'è una forte probabilità di far esplodere parti logicamente non correlate, iniziando un effetto valanga.
Ma sono un inguaribile ottimista, so che stavolta sarà diverso!

Recupero il progetto e mi accerto di avere i diritti per modificarlo. Bene, della sana burocrazia per iniziare.
Apro il progetto con VSCode e mi accingo ad una visione panoramica per darmi un'idea del progetto...
La prima cosa che balza all'occhio è l'orgogliosamente sbandierata paternità dei file: tutti con la firma dell'autore, che per evitare imbarazzi, da ora in poi chiamerò SedicenteProgrammatore.

Continuo qualche giro di ricognizione...


    du -sh .
  551M	.
    

COSA?! Devo aver sbagliato qualcosa... Ah- ecco. Avevo già creato il repository di git, perciò non devo considerare la directory .git.
Fiuuu, sospiro di sollievo, mi ero spaventato per nulla.


    du --exclude=.git .
  369M	.
    

Inizio ad imprecare. Sono davvero tanti mega per essere un progetto PHP. Mi addentro ancora un po'...


    du -sh * | sort -h | tail
  6.0M	upload
  8.5M	contratti
  11M	accettazione
  12M	report
  14M	copia.tar.gz
  18M	fatturazione
  21M	admin
  22M	amministrazione
  23M	reparti
  198M	codifica
    
Inizio a rovistare circospetto tra le cartelle...

    cd accettazione
  du -sh * | sort -h | tail
  du: unrecognized option '----selezionelistini.php'
  Try 'du --help' for more information.

  ls
  # strip
  -rw-rw-r--+ 1 matteo matteo    642 Dec 22 18:42 selectest2.php
  -rw-rw-r--+ 1 matteo matteo    560 Dec 22 18:42 selectest.php
  -rw-rw-r--+ 1 matteo matteo   2805 Dec 22 18:42 select.php
  -rw-rw-r--+ 1 matteo matteo   2502 Dec 22 18:42 select-solo-jquery.php
  -rw-rw-r--+ 1 matteo matteo   1038 Dec 22 18:42 ----selezionelistini.php
  -rw-rw-r--+ 1 matteo matteo   1127 Dec 22 18:42 selezionelistini.php
  -rwxrwxr-x+ 1 matteo matteo  11934 Dec 22 18:42 stampa_fornitore.php
  -rw-rw-r--+ 1 matteo matteo   7416 Dec 22 18:42 standard.php
  drwxrwxr-x+ 2 matteo matteo   4096 Dec 22 18:42 styles
  -rw-rw-r--+ 1 matteo matteo  19069 Dec 22 18:42 table.html
  # strip
    

Questo non è affatto un bel presagio. SedicenteProgrammatore ha chiamato un file con il carattere "-" iniziale e dato che bash esplode "*" con la lista dei file presenti nella directory significa aver passato a du l'opzione "----selezionelistini.php" e da qui l'errore.

In questi casi c'è un vecchio trucco per maneggiare file con nomi -ehm- briosi. Me lo ricordo perchè un mio vecchio responsabile UnixVecchiaScuola mi fece questa domanda trabocchetto per darmi due lezioni: la prima la porto ancora nell'animo, la seconda è stampata nella mente:


    ## facciamo qualche tentativo...
  mv ----selezionelistini.php bau.php
  mv: unrecognized option '----selezionelistini.php'
  Try 'mv --help' for more information.

  ## ah, manca un po' di virgolettame! nada
  mv "----selezionelistini.php" bau.php
  mv: unrecognized option '----selezionelistini.php'
  Try 'mv --help' for more information.

  ## caratteri di escape venite a me!
  mv "\-\-\-\-selezionelistini.php" bau.php
  mv: cannot stat '\-\-\-\-selezionelistini.php': No such file or directory

  ## basterebbe leggersi ogni tanto qualche man file...
  mv -- "----selezionelistini.php" bau.php
  ls -l bau.php
  -rw-rw-r--+ 1 matteo matteo 1038 Dec 22 18:42 bau.php
    

Queste nomenclature eccentriche sono sparse un po' in giro per il progetto, elimino i relativi file perchè sembrano file orfani e mi attanaglia quell'orribile sensazione di aver pestato una cacca con l'infradito.
Cerco di farmi coraggio e scopro che buoni 186M appartengono alla cartella "images" con dentro i file caricati dall'utente. Sorvolo sulla mia faccia a punto interrogativo sul perchè quella cartella risieda dentro il progetto, ma con un saldo netto di soli 183M decido che è tempo di leggere il codice.

Control, control, you must learn control

Nascoste nella moltitudine di file del progetto abbiamo delle autentiche perle. Alcune sono talmente uniche nel loro genere che desidero condividerle, una fra tutte è la nomina dei file; un piccolo assaggio è nel paragrafo precedente, ma qui voglio darvi una visione d'insieme...
Tutti conosciamo le qualità che deve avere un buon sistema di revisione: deve essere predicibile, riproducibile, etc, etc. Ma come si lavorava prima di git o mercurial o addirittura di csv?
Qui ne abbiamo un piccolo estratto:


    ls -l accettazione*.php
  -rw-rw-r--+ 1 matteo matteo  37480 22 dic 18.42 accettazione1--adesso.php
  -rw-rw-r--+ 1 matteo matteo  42629 22 dic 18.42 accettazione1-buona-3-11-2015.php
  -rw-rw-r--+ 1 matteo matteo  42427 22 dic 18.42 accettazione1---BUONAAAAA.php
  -rw-rw-r--+ 1 matteo matteo  48698 22 dic 18.42 accettazione1-buona-oggi.php
  -rw-rw-r--+ 1 matteo matteo  46489 22 dic 18.42 accettazione1-buono.php
  -rw-rw-r--+ 1 matteo matteo  47172 22 dic 18.42 accettazione1-experiment.php
  -rw-rw-r--+ 1 matteo matteo  41964 22 dic 18.42 accettazione1-FUNZIONANTE-PERFETTO.php
  -rw-rw-r--+ 1 matteo matteo  41716 22 dic 18.42 accettazione1-FUNZIONANTE.php
  -rw-rw-r--+ 1 matteo matteo  45331 22 dic 18.42 accettazione1--------.php
  -rw-rw-r--+ 1 matteo matteo  37344 22 dic 18.42 accettazione1.php
  -rw-rw-r--+ 1 matteo matteo  43451 22 dic 18.42 accettazione1-pld.php
  -rw-rw-r--+ 1 matteo matteo  36120 22 dic 18.42 accettazione1--ultima-9-1-15.php
  -rw-rw-r--+ 1 matteo matteo  36452 22 dic 18.42 accettazione1-ve3-18122014.php
  -rw-rw-r--+ 1 matteo matteo  40032 22 dic 18.42 accettazione1-ver-19112014.php
  -rw-rw-r--+ 1 matteo matteo  43762 22 dic 18.42 accettazione1-ver-24112014.php
  -rw-rw-r--+ 1 matteo matteo  82542 22 dic 18.42 accettazione2-buona-06-12-2014.php
  -rw-rw-r--+ 1 matteo matteo  52585 22 dic 18.42 accettazione2---buona.php
  -rw-rw-r--+ 1 matteo matteo  77291 22 dic 18.42 accettazione2.php
  -rw-rw-r--+ 1 matteo matteo  53064 22 dic 18.42 accettazione2-ver-17122014.php
  -rw-rw-r--+ 1 matteo matteo  54736 22 dic 18.42 accettazione2-ver-18122014.php
  -rw-rw-r--+ 1 matteo matteo  54683 22 dic 18.42 accettazione2-ver-20122014.php
  -rw-rw-r--+ 1 matteo matteo  77016 22 dic 18.42 accettazione-b.php
    

Volete sapere quale è la versione attualmente in uso? Dai, provate ad indovinare. Fatto?
Ecco avete sbagliato, perchè non è nessuna di queste versioni, sono semplicemente file orfani ad imperitura memoria del tempo speso da SedicenteProgrammatore sul progetto.
E subito tutti diranno, ecco, sei sempre il solito maligno, uno ha una svista e lascia sparsi una manciata di file e subito fai il professorino.
No, no, non è così, è proprio un modus operandi e qui mi pare sia proprio azzeccato il termine in quanto SedicenteProgrammatore lo fa in maniera seriale e compulsiva:


    ls -l index*.php
  -rwxrwxr-x+ 1 matteo matteo    41 22 dic 18.42 index123123123123123.php
  -rw-rw-r--+ 1 matteo matteo  5009 22 dic 18.42 index19062015.php
  -rw-rw-r--+ 1 matteo matteo  5552 22 dic 18.42 index-27042015.php
  -rw-rw-r--+ 1 matteo matteo  4857 22 dic 18.42 index2.php
  -rw-rw-r--+ 1 matteo matteo 11167 22 dic 18.42 index-per-partire-19062015.php
  -rw-rw-r--+ 1 matteo matteo 11592 22 dic 18.42 index-per-partire-27042015.php
  -rw-rw-r--+ 1 matteo matteo 10613 22 dic 18.42 index-per-partire-----.php
  -rw-rw-r--+ 1 matteo matteo 11558 22 dic 18.42 index-per-partire---.php
  -rw-rw-r--+ 1 matteo matteo 11167 22 dic 18.42 index-per-partire.php
  -rw-rw-r--+ 1 matteo matteo  5374 22 dic 18.42 index.php
  -rwxrwxr-x+ 1 matteo matteo 11601 22 dic 18.42 index---ABC.php
    

oppure


    ls -l ricerca*.php
  -rw-rw-r--+ 1 matteo matteo 162408 22 dic 18.42 ricerca-26042015.php
  -rw-rw-r--+ 1 matteo matteo 150547 22 dic 18.42 ricerca-buona-05-11-2015.php
  -rw-rw-r--+ 1 matteo matteo 150492 22 dic 18.42 ricerca-buona-5-11-2015.php
  -rwxrwxr-x+ 1 matteo matteo 113039 22 dic 18.42 ricerca-----FUNZIONANTERIPULITO.php
  -rw-rw-r--+ 1 matteo matteo 514698 22 dic 18.42 ricerca.html
  -rw-rw-r--+ 1 matteo matteo 162373 22 dic 18.42 ricerca----mah.php
  -rw-rw-r--+ 1 matteo matteo 156380 22 dic 18.42 ricerca-mia-buona-5.php
  -rw-rw-r--+ 1 matteo matteo 167952 22 dic 18.42 ricerca-oggi-buona.php
  -rw-rw-r--+ 1 matteo matteo 167639 22 dic 18.42 ricerca-oggi-ok.php
  -rw-rw-r--+ 1 matteo matteo 158937 22 dic 18.42 ricerca---oggi.php
  -rw-rw-r--+ 1 matteo matteo 162258 22 dic 18.42 ricerca-okkkkk.php
  -rw-rw-r--+ 1 matteo matteo 162039 22 dic 18.42 ricerca-ok-oggi.php
  -rw-rw-r--+ 1 matteo matteo 167786 22 dic 18.42 ricerca-ok.php
  -rw-rw-r--+ 1 matteo matteo 111304 22 dic 18.42 ricerca-originale.php
  -rw-rw-r--+ 1 matteo matteo 162444 22 dic 18.42 ricerca----.php
  -rw-rw-r--+ 1 matteo matteo 157598 22 dic 18.42 ricerca.php
  -rw-rw-r--+ 1 matteo matteo    159 22 dic 18.42 ricercatest.php
    

Qui si apprendono le date del progetto, che non è così vecchio come qualcuno si aspetterebbe. Due anni fa l'informatica non viveva nell'oscurantismo, esistevano i framework, i VCS, il mondo girava ancora in senso antiorario e così via.


    ls -l creamodello*.php
  -rw-rw-r--+ 1 matteo matteo 26370 Dec 22 18:42 creamodello1.php
  -rw-rw-r--+ 1 matteo matteo 50933 Dec 22 18:42 creamodello2-BUONISSIMO.php
  -rw-rw-r--+ 1 matteo matteo 48297 Dec 22 18:42 creamodello2---.php
  -rw-rw-r--+ 1 matteo matteo 44894 Dec 22 18:42 creamodello2.php
  -rw-rw-r--+ 1 matteo matteo 48335 Dec 22 18:42 creamodello2q-q-q-q-q.php
  -rw-rw-r--+ 1 matteo matteo 27244 Dec 22 18:42 creamodello.php
    

Qui, invece, si riescono a scoprire i gusti culinari di SedicenteProgrammatore... Evidentemente attratto da creamodello2. Slurp.

Tutto questo per dare un'idea, ogni file che è stato modificato ha una sua "storia" e non vorrei riempire la pagina di liste e liste di file...

Things getting worse

È giunto il momento di leggere il codice, è tempo di andare a caccia del colpevole... e dopo un po' trovo il file che gestisce la generazione del PDF: ex.php. Uhm. Nome bizzarro, ma del resto...
Apro il file: 937 righe di codice. Accidenti, questo file è davvero ben farcito! È pur sempre vero (per la generazione del mio curriculum ne so qualcosa) che produrre un PDF è spesso una serie infinita di coordinate e output di variabili.
Inizio a leggere il listato; comunque la cosa bella di lavorare con un PDF è che non è Word:


    $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Image("images/" .$image1. ".jpg",10,$y_image2-10,90);$pdf->Image("images/" .$image2. ".jpg",110,$y_image2-10,90);$pdf->MultiCell(0,5," ",0,'C');
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
  $pdf->Cell(0,5,"\n",0,1);
    

Err- cioè, non tutti condividono i miei sentimenti. Evidentemente a qualcuno piace battere tante volte invio. Piace così tanto da volerlo emulare anche con un PDF.

Proseguo l'analisi e mi pietrifico di fronte a questo:


    $anonima=$row['anonima'];
  $attesa=$row['attesa'];
  $dlgs=$row['dlgs'];
  $tabellatab=$row['tabellatab'];
  $anagraficaID2=$row['anagraficaID2'];
  $estremi2=$row['estremi2'];
  $estremi3=$row['estremi3'];
  $numero=$row['numero'];
  $datadoc2=$row['datadoc2'];
  $textfield=$row['textfield'];
  $textfield2=$row['textfield2'];
  $textfield3=$row['textfield3'];
  $textarea=$row['textarea'];
  $textarea2=$row['textarea2'];
  $textarea3=$row['textarea3'];
  $textarea3bis=$row['textarea3bis'];
  $textarea4=$row['textarea4'];
  $textarea5=$row['textarea5'];
  $textarea6=$row['textarea6'];
  $radioa=$row['radioa'];
  $textarea7=$row['textarea7'];
  $textarea81=$row['textarea81'];
  $textfield4=$row['textfield4'];
  $textfield41=$row['textfield41'];
  $textfield410=$row['textfield410'];
  $textfield411=$row['textfield411'];
  $textfield412=$row['textfield412'];
  $textfield413=$row['textfield413'];
  $textarea8=$row['textarea8'];
  $textarea9=$row['textarea9'];
  $textarea10=$row['textarea10'];
  $parametro=$row['parametro'];
  $valorilimite=$row['valorilimite'];
  $textfield141=$row['textfield141'];
  $textfield142=$row['textfield142'];
  $textfield143=$row['textfield143'];
  $textfield144=$row['textfield144'];
  $textfield145=$row['textfield145'];
  $textfield146=$row['textfield146'];
  $textfield147=$row['textfield147'];
  $textfield148=$row['textfield148'];
  $textfield149=$row['textfield149'];
  $textfield150=$row['textfield150'];
  $textfield151=$row['textfield151'];
  $textfield152=$row['textfield152'];
  $textfield153=$row['textfield153'];
  $textfield154=$row['textfield154'];
  $textfield155=$row['textfield155'];
  $textfield156=$row['textfield156'];
  $textfield157=$row['textfield157'];
  $textfield158=$row['textfield158'];
  $textfield159=$row['textfield159'];
  $textfield160=$row['textfield160'];
  $textarea11=$row['textarea11'];
  $textarea12=$row['textarea12'];
  $textarea13=$row['textarea13'];
  $textarea14=$row['textarea14'];
  $textarea15=$row['textarea15'];
  $textarea16=$row['textarea16'];
  $textarea17=$row['textarea17'];
  $textarea18=$row['textarea18'];
  $textarea19=$row['textarea19'];
  $smaltimentofine=$row['smaltimentofine'];
  $checkbox22=$row['checkbox22'];
  $textarea23=$row['textarea23'];
  $checkbox33=$row['checkbox33'];
  $textarea24=$row['textarea24'];
  $checkbox44=$row['checkbox44'];
  $textfield444=$row['textfield444'];
  $textfield555=$row['textfield555'];
  $addettoID=$row['addettoID'];
  $revisione=$row['revisione'];
  $pier1=$row['pier1'];
  $pier2=$row['pier2'];
  $pier3=$row['pier3'];
  $pier4=$row['pier4'];
  $pld1=$row['pld1'];
  $pld2=$row['pld2'];
  $pld3=$row['pld3'];
  $pld4=$row['pld4'];
    

80 variabili. Ottanta. Ot-tan-ta. Letto bene? Ecco. Ma la cosa davvero meravigliosa, è l'espressività dei nomi: $textarea, $textarea2, e così via. Spettacolare. Di facile memorizzazione e un metodo robusto contro gli errori. Future-proof!
Ma da dove vengono queste variabili? Da un array (che era troppo comodo, evidentemente) creato così:


    $risultato = mysql_query("SELECT * FROM tabella" .$anno. " WHERE id='$id'");
  $row = mysql_fetch_assoc($risultato);
    

Okay. Qui stiamo ad usare una pleteora di variabili, forse non è il caso di scoperchiare l'argomento SQL Injection.
Anche perchè le cose divertenti devono ancora arrivare...

Le freak c'est chic

Avete presente quelle cose talmente tanto kitsch che acquistano un loro significato? Ecco, in questo progetto c'è condensato un piccolo universo e ho deciso di fare alcuni esempi.
Tipo questa riga l'adoro perchè è talmente pensata male da essere geniale.


    if ($_POST[datadoc2]<>""){$datadoc2=$_POST[datadoc2];} else {$datadoc2="vuoto";}
    

Intanto la sintassi "<>" era un pezzo che non la vedevo. Poi l'utilizzo della variabile anzichè un array, ma rischio di diventare pedante, eppoi il vero punto forte della riga: assegnare vuoto. Non un valore vuoto come null o stringa vuota, ma proprio la parola "vuoto". Come ho fatto a non pensarci prima?
Questo punto è importante perchè dopo SedicenteProgrammatore, il valore "vuoto" lo controlla:


    if ($checkbox44=="vuoto") {;} else {$pdf->SetFont('times','',10);$pdf->MultiCell(0,5,"" . $checkbox44 . "",0,'J');
    

Chiaramente per necessità di brevità ho riportato soltanto alcune righe, ma dovete moltiplicare sempre per un fattore di 80 (o giù di lì a seconda dei casi) perchè sono la quantità di variabili in uso.
Torniamo sulla riga, anche qui in pochi caratteri è distillata la sintesi di un pensiero un po' malsano: intanto controlla il valore "vuoto", ma diciamo che su questo sorvoliamo perchè trattato nella riga precedente e in questa non vale; punto due: il controllo è rovesciato, verifica se $checkbox44 è uguale a "vuoto" per... non fare nulla!
SedicenteProgrammatore utilizza l'else per svolgere i comandi; dopodichè non è facile da percepire ma ci sono due comandi uno di seguito all'altro: l'idillio dell'indentazione.
Una menzione va anche alla concatenazione di "" con $checkbox44 seguito da una ulteriore concatenazione con una stringa vuota. Notevole.
La tentazione di produrre qualcosa di razionale è altissima, ma tutti i miei sensi suggeriscono di non farlo, di limitarsi semplicemente alla correzione dei bug segnalati e tapparsi il naso.

Ora ci sono delle cose che mi fanno sospettare una personalità dissociata di SedicenteProgrammatore ed una di queste è che utilizza le classi. Okay, non fa file separati, non usa autoloader, non usa i namespace, tutto vero; però se gli è alieno il concetto di array come fa ad essergli chiaro il concetto di class che dovrebbe includere il concetto precedente?
Temo non avrò mai risposta.
Comunque ho a che fare con qualcosa del genere:


        $textarea = str_replace("&deg;", "�", $textarea);
    $textarea = str_replace("&ldquo;", "�", $textarea);
    $textarea = str_replace("&rdquo;", "�", $textarea);
    $textarea = str_replace("&bull;", "�", $textarea);
    $textarea = str_replace("&agrave;", "�", $textarea);
    $textarea = str_replace("&egrave;", "�", $textarea);
    $textarea = str_replace("&igrave;", "�", $textarea);
    $textarea = str_replace("&ograve;", "�", $textarea);
    $textarea = str_replace("&ugrave;", "�", $textarea);
    $textarea = str_replace("<br>", "\n", $textarea);
    $textarea2 = str_replace("&gt;", ">", $textarea2);


    $textarea2 = str_replace("&deg;", "�", $textarea2);
    $textarea2 = str_replace("&ldquo;", "�", $textarea2);
    $textarea2 = str_replace("&rdquo;", "�", $textarea2);
    $textarea2 = str_replace("&bull;", "�", $textarea2);
    $textarea2 = str_replace("&agrave;", "�", $textarea2);
    $textarea2 = str_replace("&egrave;", "�", $textarea2);
    $textarea2 = str_replace("&igrave;", "�", $textarea2);
    $textarea2 = str_replace("&ograve;", "�", $textarea2);
    $textarea2 = str_replace("&ugrave;", "�", $textarea2);
    //$textarea2 = str_replace("<br>", "\n", $textarea2);
    $textarea2 = str_replace("&gt;", ">", $textarea2);
      

E cose simili per molte, molte, molte, molte altre righe. In pratica SedicenteProgrammatore si è imbattuto nel poco simpatico problema dell'encoding che pensavo debellato con UTF-8 come la polio o il vaiolo. Invece, eccoci qui. E il nostro amico ha pensato bene di non farsi intimorire dal problema e distillare lui stesso un vaccino.

Save before it's too late... No, wait!

Sono provato: leggere il codice in questo file è una specie di gara di resistenza.
Ma sono riuscito a trovare la radice del problema:


      $date = new DateTime($row["datadoc2"]);
    $datadoc2 = $date->format('d/m/Y');
    

Se l'utente non valorizza la data, l'interprete schianta con un bel messaggio che si sta provando a manipolare un oggetto che in realtà non è un oggetto. Perchè? Perchè $row["datadoc2"] contiene il valore "vuoto"; se ci fosse stato un valore tipo null avrebbe funzionato, come dire quando uno se le va a cercare.
Mi prudono le mani di mandare una bella mail con scritto "valorizzate la data e tanti saluti", ma la mia etica impone di correggere il problema ed assolvere il compito o missione eroica in questo caso.
Aggiunto un if, salvo e provo compiaciuto il risultato: orrore e angoscia.
Il PDF contiene caratteri ostrogoti al posto delle lettere accentate ma come? cosa? Non ho toccato nessuna delle sciempiaggini di str_replace. Lo sapevo. L'ho sempre saputo fin dall'inizio... The butterfly effect.

Vi faccio un resoconto breve, da pochi secondi di lettura, ma dovete moltiplicare la cosa per molti più secondi, intervallati da varie ingiurie verso l'autore di cotanta barbarie.
In pratica, il buon VSCode converte il file in UTF-8 automaticamente:


    ## file originale prima del salvataggio
  file ./ex.php
  ex.php: PHP script, Non-ISO extended-ASCII text, with very long lines, with CRLF line terminators

  ## dopo salvataggio con VSCode
  file ./ex.php
  ex.php: PHP script, UTF-8 Unicode text, with very long lines, with CRLF line terminators
    

e avete presente tutte quelle "str_replace"?


    // strip
  $textarea = str_replace("&deg;", "�", $textarea);
  // strip
    

Trasformavano vari caratteri in un loro oscuro corrispettivo in qualche formato d'encoding imperscrutabile.
Lanciando i vari iconv o utilizzando vim per l'editing si arriva sempre alla solita mesta conclusione. È il momento di lavar via i rimpianti, di smettere di modificare poco e con parsimonia, è ora di affrontare il problema. È tempo di RIFATTORIZZARE. (tuoni e fulmini alle spalle per dare quel tocco scenico)

Nomen omen

Mentre vanamente tento di porre rimedio al problema dell'encoding una domanda oscilla tra i miei pensieri: ex.php ma perché questo nome?
Non che la cosa abbia un'importanza esistenziale, ma odio non capire le motivazioni dietro alcune scelte. Il condividerle o meno è altra faccenda, ma balenare nel buio mi ha sempre urtato i nervi.
Eppure un significato lo deve pur avere...

Durante questo arduo compito, riscarico la libreria per la gerazione dei PDF: FPDF, progetto fermo da qualche anno e dal dubbioso futuro, e come se non bastasse la versione in uso nel progetto risale al 2011.
L'oracolo StackOverflow sostiene che FPDF ha una speciale versione chiamata tFPDF in grado di supportare UTF-8. La scarico ed aprendo lo zip ho un'illuminazione:


    unzip tfpdf.zip
  Archive:  tfpdf.zip
    inflating: HelloWorld.txt
    inflating: README.txt
    inflating: ex.pdf
    inflating: ex.php
    inflating: font/courier.php
    inflating: font/courierb.php
    inflating: font/courierbi.php
    inflating: font/courieri.php
    inflating: font/helvetica.php
    inflating: font/helveticab.php
    inflating: font/helveticabi.php
    inflating: font/helveticai.php
    inflating: font/symbol.php
    inflating: font/times.php
    inflating: font/timesb.php
    inflating: font/timesbi.php
    inflating: font/timesi.php
    inflating: info.htm
    inflating: tfpdf.php
    

Ma certo! ex.php sta per example! In un rito di eccessiva fedeltà al limite del fantatismo religioso SedicenteProgrammatore ha deciso bene di mantenere il nome. Sia mai che la rinomina possa causare effetti collaterali impredicibili. Amen.

All'alba vincerò

La strada di tFPDF è un vicolo cieco e l'unico modo sembra convertire i valori da UTF-8 ad una codifica più arcaica e maneggevole.
Di sicuro non scrivo biblicamente per ottanta volte otto iconv, array_map è da escludere perchè non abbiamo a che fare con array, una strada più semplice ed elegante deve esserci.

Tadaaaaan!


    class PDF extends OTHERPDFCLASS
  {
    function Header()
    {
      // intestazione occultata per evitare che si possano trovare riferimenti al cliente
      // finale o a SedicenteProgrammatore™
    }

    function Footer()
    {
      // intestazione occultata per evitare che si possano trovare riferimenti al cliente
      // finale o a SedicenteProgrammatore™
    }

    function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='')
    {
      $txt = iconv('UTF-8', 'ISO-8859-1', $txt);
      $txt = str_replace("<br>", "\n", $txt);
      parent::Cell($w, $h, $txt, $border, $ln, $align, $fill, $link);
    }
  }
    

Elimino 427 righe di vari str_replace, risolvo il problema dell'encoding con 3 righe di codice in una classe. Chiamo il cliente allegro e soddisfatto.
La semplicità ha un bel valore.