Disclaimer per i contenuti del Blog


DISCLAIMER:Questo blog non rappresenta una testata giornalistica in quanto viene aggiornato senza alcuna periodicità . Non può pertanto considerarsi un prodotto editoriale ai sensi della legge n° 62 del 7.03.2001.
L'autore non è responsabile per quanto pubblicato dai lettori nei commenti ad ogni post. Verranno cancellati i commenti ritenuti offensivi o lesivi dell’immagine o dell’onorabilità di terzi, di genere spam, razzisti o che contengano dati personali non conformi al rispetto delle norme sulla Privacy. Alcuni testi o immagini inserite in questo blog sono tratte da internet e, pertanto, considerate di pubblico dominio; qualora la loro pubblicazione violasse eventuali diritti d'autore, vogliate comunicarlo via email. Saranno immediatamente rimossi. L'autore del blog non è responsabile dei siti collegati tramite link, del loro contenuto che può essere soggetto a variazioni nel tempo ne degli eventuali danni derivanti dall'utilizzo proprio od improprio delle informazioni presenti nei post.

venerdì 20 settembre 2013

Gestione e verifica del CODICE FISCALE in PHP


Il codice fiscale è stato introdotto con il decreto del Presidente della Repubblica n. 605 del 29 settembre 1973 per rendere più efficiente l'amministrazione finanziaria con la creazione dell'Anagrafe tributaria [Fonte]. In questo articolo tratteremo il codice fiscale attribuito alle persone fisiche, un codice alfanumerico di 16 caratteri il cui fine è identificare in maniera univoca noi cittadini. 

Quando si lavora in ambito gestionale è inevitabile che prima o poi  ci si scontri con la necessità di verificare la sua validità. Prima di entrare nel dettaglio del modulo di controllo, è opportuno capire come tale codice venga calcolato; come prima fonte citerei l'Agenzia delle Entrate, di seguito la pagina delle FAQ, quindi Wikipedia.

Al fine di semplificare il lavoro di verifica della sua validità ho realizzato una piccola classe PHP che si incarica del lavoro; ho scelto l'approccio Object Oriented per poter astrarre il suo funzionamento dal resto dell'applicazione. 

La classe dispone di un metodo 'setter' con il quale specifichiamo il codice fiscale da analizzare e una serie di metodi 'getter' che ci restituiscono le informazioni richieste:

Metodi SETTER

  • SetCF("CodiceDaAnalizzare") 


Metodi GETTER

  • GetCodiceValido() : restituisce true o false a seconda che il codice sia valido o meno;
  • GetErrore() : qualora il metodo GetCodiceValido() dovesse ritornare false questo metodo permette di venire a conoscenza del motivo per il quale il codice non è stato validato:
  • GetSesso() : restituisce il sesso della persona M=maschio F=femmina
  • GetComuneNascita() : restituisce il codice del comune di nascita secondo la codifica catastale (1 carattere alfabetico + 3 caratteri numerici, es. H501 per il comune di Roma)
  • GetAANascita() : restituisce l'anno della data di nascita in formato da due caratteri; questo purtroppo limita molto l'utilità dell'informazione in quanto non ci permette di capire a quale secolo si riferisca la data; 
  • GetMMNascita() : restituisce il mese della data di nascita
  • GetGGNascita() : restituisce il giorno della data di nascita

Tutti i metodi 'setter' restituiscono null qualora non si sia prima provveduto ad istanziare il metodo SetCF.

<?php
// Classe controllo CODICE FISCALE per persone fisiche
// ----------------------------------------------------
//
// 2014-01-13 - Modificata sezione calcolo giorno di nascita per sesso=F 
//
// Metodi "setter":
//  SetCF(CodiceFiscaleDaAnalizzare)
//
// Metodi "getter":
//  GetCodiceValido()  TRUE/FALSE
//  GetErrore()   Motivo dell'errore nel caso in cui GetCodiceValido() dovesse ritornare false, altrimenti stringa vuota;
//  GetSesso()    Sesso (M o F)
//  GetComuneNascita()  Codice comune di nascita secondo la codifica catastale (1 carattere alfabetico + 3 caratteri numerici es. H501 per Roma)
//  GetAANascita()  Anno di nascita (ATTENZIONE: solo 2 caratteri!!! Non potremo mai sapere con sicurezza in che secolo è nato l'intestatario del CF!!!)
//  GetMMNascita()  Mese di nascita
//  GetGGNascita()  Giorno di nascita
//
// - Vengono gestiti correttamente anche i casi di OMOCODIA
//
class CodiceFiscale {

 private $codiceValido = null;
 private $sesso = null;
 private $comuneNascita = null;
 private $ggNascita = null;
 private $mmNascita = null;
 private $aaNascita = null;
 private $errore = null;
 private $TabDecOmocodia = null;
 private $TabSostOmocodia = null;
 private $TabCaratteriPari = null;
 private $TabCaratteriDispari = null;
 private $TabCodiceControllo = null;
 private $TabDecMesi = null;
 private $TabErrori = null;

 public function __construct() {
  // Tabella sostituzioni per omocodia
  $this->TabDecOmocodia = array("A" => "!", "B" => "!", "C" => "!", "D" => "!", "E" => "!", "F" => "!", "G" => "!", "H" => "!", "I" => "!", "J" => "!", "K" => "!", "L" => "0", "M" => "1", "N" => "2", "O" => "!", "P" => "3", "Q" => "4", "R" => "5", "S" => "6", "T" => "7", "U" => "8", "V" => "9", "W" => "!", "X" => "!", "Y" => "!", "Z" => "!", );

  // Posizioni caratteri interessati ad 
  // alterazione di codifica in caso di omocodia
  $this->TabSostOmocodia = array(6,7,9,10,12,13,14);

  // Tabella peso caratteri PARI
  $this->TabCaratteriPari = array("0" => 0 , "1" => 1 , "2" => 2 , "3" => 3 , "4" => 4 , "5" => 5 , "6" => 6 , "7" => 7 , "8" => 8 , "9" => 9 , "A" => 0 , "B" => 1 , "C" => 2 , "D" => 3 , "E" => 4 , "F" => 5 , "G" => 6 , "H" => 7 , "I" => 8 , "J" => 9, "K" => 10, "L" => 11, "M" => 12, "N" => 13, "O" => 14, "P" => 15, "Q" => 16, "R" => 17, "S" => 18, "T" => 19, "U" => 20, "V" => 21, "W" => 22, "X" => 23, "Y" => 24, "Z" => 25);

  // Tabella peso caratteri DISPARI
  $this->TabCaratteriDispari = array("0" => 1 , "1" => 0 , "2" => 5 , "3" => 7 , "4" => 9 , "5" => 13, "6" => 15, "7" => 17, "8" => 19, "9" => 21, "A" => 1 , "B" => 0 , "C" => 5 , "D" => 7 , "E" => 9 , "F" => 13, "G" => 15, "H" => 17, "I" => 19, "J" => 21, "K" => 2 , "L" => 4 , "M" => 18, "N" => 20, "O" => 11, "P" => 3 , "Q" => 6 , "R" => 8 , "S" => 12, "T" => 14, "U" => 16, "V" => 10, "W" => 22, "X" => 25, "Y" => 24, "Z" => 23  );

  // Tabella calcolo codice CONTOLLO (carattere 16)
  $this->TabCodiceControllo = array( 0 => "A",  1 => "B",  2 => "C",  3 => "D",  4 => "E",  5 => "F",  6 => "G",  7 => "H",  8 => "I",  9 => "J", 10 => "K", 11 => "L", 12 => "M", 13 => "N", 14 => "O", 15 => "P", 16 => "Q", 17 => "R", 18 => "S", 19 => "T", 20 => "U", 21 => "V", 22 => "W", 23 => "X", 24 => "Y", 25 => "Z");

  // Array per il calcolo del mese
  $this->TabDecMesi = array("A" => "01", "B" => "02", "C" => "03", "D" => "04", "E" => "05", "H" => "06", "L" => "07", "M" => "08", "P" => "09", "R" => "10", "S" => "11", "T" => "12");

  // Tabella messaggi di errore
  $this->TabErrori = array(0 => "Codice da analizzare assente", 1 => "Lunghezza codice da analizzare non corretta", 2 => "Il codice da analizzare contiene caratteri non corretti", 3 => "Carattere non valido in decodifica omocodia", 4 => "Codice fiscale non corretto");
 }

 public function SetCF($cf)
 {
  // Azzero le property
  $this->codiceValido = null;
  $this->sesso = null;
  $this->comuneNascita = null;
  $this->ggNascita = null;
  $this->mmNascita = null;
  $this->aaNascita = null;
  $this->errore = null;

  // Verifica esistenza codice passato
  if ($cf==="") {
   $this->codiceValido = false;
   $this->errore = $this->TabErrori[0];
   return false;
  }

  // Verifica lunghezza codice passato: 
  // 16 caratteri per CF standard 
  // (non gestisco i CF provvisori da 11 caratteri...)
  if (strlen($cf) !== 16) {
   $this->codiceValido = false;
   $this->errore = $this->TabErrori[1];
   return false;
  }

  // Converto in maiuscolo
  $cf = strtoupper($cf); 

  // Verifica presenza di caratteri non validi
  // nel codice passato
  // if( ! ereg("^[A-Z0-9]+$", $cf) ) {
  // ******* Funzione deprecata e, come 
  // ******* suggerito da Gabriele,
  // ******* sostituita con preg_match
  if( ! preg_match("/^[A-Z0-9]+$/", $cf) ) {
   $this->codiceValido = false;
   $this->errore = $this->TabErrori[2];
   return false;
  }

  // Converto la stringa in array
  $cfArray = str_split($cf);

  // Verifica correttezza alterazioni per omocodia
  // (al posto dei numeri sono accettabili solo le
  // lettere da "L" a "V", "O" esclusa, che
  // sostituiscono i numeri da 0 a 9)
  for ($i = 0; $i < count($this->TabSostOmocodia); $i++) 
   if (!is_numeric($cfArray[$this->TabSostOmocodia[$i]])) 
    if ($this->TabDecOmocodia[$cfArray[$this->TabSostOmocodia[$i]]]==="!") {
     $this->codiceValido = false;
     $this->errore = $this->TabErrori[3];
     return false;
    }

  // Tutti i controlli formali sono superati.
  // Inizio la fase di verifica vera e propria del CF
  $pari = 0;
  $dispari = $this->TabCaratteriDispari[$cfArray[14]];  // Calcolo subito l'ultima cifra dispari (pos. 15) per comodita'...

  // Loop sui primi 14 elementi
  // a passo di due caratteri alla volta
  for ($i = 0; $i < 13; $i+=2)     
  {
   $dispari = $dispari + $this->TabCaratteriDispari[$cfArray[$i]];
   $pari = $pari + $this->TabCaratteriPari[$cfArray[$i+1]];
  }

  // Verifica congruenza dei valori calcolati
  // sui primi 15 caratteri con il codice di
  // controllo (carattere 16)
  if (!($this->TabCodiceControllo[($pari+$dispari) % 26]===$cfArray[15])) {
   $this->codiceValido = false;
   $this->errore = $this->TabErrori[4];
   return false;
  }
  else {
   // Opero la sostituzione se necessario
   // utilizzando la tabella $this->TabDecOmocodia
   // (per risolvere eventuali omocodie)
   for ($i = 0; $i < count($this->TabSostOmocodia); $i++) 
    if (!is_numeric($cfArray[$this->TabSostOmocodia[$i]])) 
     $cfArray[$this->TabSostOmocodia[$i]] = $this->TabDecOmocodia[$cfArray[$this->TabSostOmocodia[$i]]];

   // Converto l'array di nuovo in stringa
   $CodiceFiscaleAdattato = implode($cfArray);

   // Comunico che il codice è valido...
   $this ->codiceValido = true;
   $this ->errore = "";

   // ...ed estraggo i dati dal codice verificato
   $this ->sesso = (substr($CodiceFiscaleAdattato,9,2) > "40" ? "F" : "M");
   $this ->comuneNascita = substr($CodiceFiscaleAdattato, 11,4);
   $this ->aaNascita = substr($CodiceFiscaleAdattato,6,2);
   $this ->mmNascita = $this->TabDecMesi[substr($CodiceFiscaleAdattato,8,1)];

   // 2014-01-13 Modifica per corretto recupero giorno di nascita se sesso=F
   $this ->ggNascita = substr($CodiceFiscaleAdattato,9,2);
   if($this->sesso === "F") {
      $this ->ggNascita = $this ->ggNascita - 40;
      if (strlen($this ->ggNascita)===1)
         $this ->ggNascita = "0".$this ->ggNascita; 
   }
  }
 }

 public function GetCodiceValido() {
  return $this->codiceValido;
 }

 public function GetErrore() {
  return $this->errore;
 }

 public function GetSesso() {
  return $this->sesso;
 }

 public function GetComuneNascita() {
  return $this->comuneNascita;
 }

 public function GetAANascita() {
  return $this->aaNascita;
 }

 public function GetMMNascita() {
  return $this->mmNascita;
 }

 public function GetGGNascita() {
  return $this->ggNascita;
 }
}
?>

Questo è un piccolo script PHP utilizzabile per testare la classe. Molto spartano...
<?php
// Esempio di utilizzo della classe cf.class.php
include("cf.class.php");
$cf = new CodiceFiscale();
// Mario Rossi, nato ad Orgosolo il 23/09/1981
$cf ->SetCF("RSSMRA81P23G097T"); 
if ($cf->GetCodiceValido()) {
 echo  "Codice VALIDO - Data di nascita:" . 
  $cf->GetGGNascita() . 
  "-" . 
  $cf->GetMMNascita() . 
  "-" . 
  $cf->GetAANascita() . 
  " Sesso:" . 
  $cf->GetSesso() . 
  " Comune di nascita:". 
  $cf->GetComuneNascita();
}
else {
 echo  "Codice ERRATO - Motivo:" . 
  $cf->GetErrore();
}


La classe in formato .zip può essere scaricata facendo click qui.

Aggiornamento del 11/10/2017

La classe CodiceFiscale è stata inclusa anche nel pacchetto validateForm che permette la convalida dei campi presenti in <FORM></FORM> utilizzando Javascript/PHP/Ajax. La versione stand-alone sarà comunque sempre disponibile al solito link.


27 commenti:

  1. Corretto In data odierna un bug che, nel caso di donne nate dal 1 al 9 del mese, restituiva il giorno di nascita lungo un solo byte.
    Ora il campo viene correttamente formattato.

    RispondiElimina
  2. Piccolo errore! bisogna sostituire:
    if( ! ereg("^[A-Z0-9]+$", $cf) )
    con questa:
    if(!preg_match("/^[A-Z0-9]+$/", $cf))

    la funzione è deprecata

    RispondiElimina
    Risposte
    1. Grazie dell'ottimo suggerimento. Classe aggiornata. :)

      Elimina
  3. Buongiorno,
    io ho provato, ma ho un problema.

    Una volta richiesto il Codice fiscale in un form html con:
    input name="cf" type"text" id="cf"

    Ed elaborato in uno script php di verifica,

    facendo un richiamo al file cf.class.php:

    include("cf.class.php");
    $cf = ($_POST['cf']);
    if ($cf->GetCodiceValido())
    { eco.......

    Mi da errore. Dove ho sbagliato?

    Grazie per il supporto.
    Saluti,
    Maurizio

    RispondiElimina
    Risposte
    1. Ciao Maurizio,

      la sequenza corretta è:

      include("cf.class.php"); // Include la classe
      $cf = new CodiceFiscale(); // crea una istanza $cf dell'oggetto CodiceFiscale
      $cf ->SetCF($_GET["cf""]); // Imposta il codice da esaminare
      if ($cf->GetCodiceValido()) { // Analizza la sua validità
      ... quello che vuoi... // Codice valido!
      }

      Ho utilizzato $_GET direttamente come parametro della SetCF ma sarebbe bene verificare il contenuto della $_GET prima di passarla alla routine.

      Elimina
    2. PERDONAMI!!!
      C'è un doppio apice di troppo in
      $cf ->SetCF($_GET["cf""]);
      la sequenza corretta è
      $cf ->SetCF($_GET["cf"]);

      Elimina
    3. Grazie mille. Nel frattempo mi ero intestardito e, riprovandoci mille volte, ci ero arrivato.
      Complimenti, funziona davvero bene!
      Un saluto, Maurizio

      Elimina
  4. Salve, complimenti funziona benissimo... GetAANascita() : restituisce l'anno della data di nascita in formato da due caratteri... come posso risolvere questo problema... in formato da quattro caratteri? Grazie di tutto

    RispondiElimina
    Risposte
    1. Purtroppo non si può risolvere con la certezza che il dato sia valido al 100% in quanto nel CF sono presenti solo due caratteri. Se in 7a ed 8a posizione trovi ad esempio 15 potresti avere a che fare con un 99enne o un bambino di un anno...
      Se ti viene in mente qualcosa fammelo sapere.

      Elimina
  5. Ciao a tutti, volevo chiedere se qualcuno mi può aiutare con questo script, molto bello, anzi ringrazio Paolo Bertinetti che l'ha realizzato, ma ho un problema nella data di nascita, mi servirebbe far restituire le 4 cifre dell'anno (1981) e non solo (81), perché devo fare un controllo sull'età e se risulta minorenne devo creare una condizione, come si può fare?? qualcuno mi sa aiutare??

    RispondiElimina
  6. Ciao, come già scritto in un precedente commento non è possibile estrapolare l'anno in formato a 4 cifre... non è inoltre possibile stabilire se l'intestatario di un CF sia maggiorenne o meno con una sicurezza del 100%...

    RispondiElimina
  7. Risolto problema anno a 4 cifre

    // Mario Rossi, nato il 23/09/1981
    $cf ->SetCF("RSSMRA81P23G097T")

    function Anno4($AnnoN2){

    $AnnoOggi = date('y');
    $diff = $AnnoOggi - $AnnoN2;

    if ($diff >0) {
    $annoN4 = "20".$annoN2;
    }
    if ($diff <0) {
    $annoN4 = "19".$annoN2;
    }
    return $AnnoN4;
    }

    echo Anno4($cf->GetAANascita());

    RispondiElimina
    Risposte
    1. Ciao,
      Perdonami ma tutto dipende da quanti compromessi vuoi/puoi accettare. E se uno è nato nel 1916?? Magari per te va bene ma non è una soluzione universale. Quando un dato manca... manca.
      A proposito, nel tuo codice non prendi in considerazione la condizione di $diff===0...

      Elimina
    2. Ciao Paolo, allora nello script che ho fatto, in effetti, c'è un errore nel primo $diff ho messo >0 ma andrebbe modificato con >=0, questo risolvere l'inconveniente, ma specifico ancora meglio, la tua classe è ottima ed effettivamente la soluzione che ho adottato ha degli inconvenienti, come persone ultra centenarie, ma diciamocela tutta, dipende da che cosa uno lo usa lo script, ti facci un esempio, nel mio caso l'ho usata per controllare in primis il codice fiscale e come secondo, per verificare se era maggiorenne, fatto questo controllo, si può fare l’acquisto nel mio store, adesso dubito che ci siano centenari che acquistano on-line.

      Codice Modificato

      // Mario Rossi, nato il 23/09/1981
      $cf ->SetCF("RSSMRA81P23G097T")

      function Anno4($AnnoN2){

      $AnnoOggi = date('y');
      $diff = $AnnoOggi - $AnnoN2;

      if ($diff >=0) {
      $annoN4 = "20".$annoN2;
      }
      if ($diff <0) {
      $annoN4 = "19".$annoN2;
      }
      return $AnnoN4;
      }

      echo Anno4($cf->GetAANascita());

      Elimina
    3. Certo, l'importante è che sia chiaro per tutti che purtroppo si deve scendere a compromessi e che gli ultracentenari possono essere un problema. Buona codifica... :)

      Elimina
  8. Ciao Paolo,
    ti ringrazio per questa ottima funzione che ci hai messo a disposizione, vacendoci risparmiare un sacco di tempo nel doverla creare da noi. :)

    RispondiElimina
  9. Paolo! Grazie mille. Ho aggiunto il controllo in 30 secondi, ho perso più tempo su google che per far andare tutto!
    Have a nice day!
    WiggiX

    RispondiElimina
  10. Ciao Paolo, innanzitutto complimenti per questo codice. :-)
    Vorrei chiederti una cosa: una volta inclusa la classe c'è un modo per sapere "solo" se il codice di controllo (l'ultima lettera) è corretto?
    Grazie anticipatamente :-)

    RispondiElimina
  11. Grazie!
    Puoi fare così:
    SetCF($CodiceDaEsaminare);
    if ($cf->GetCodiceValido()) {
    // CODICE VALIDO
    } else {
    // CODICE ERRATO
    }

    RispondiElimina
    Risposte
    1. Grazie Paolo. :-) ma dimmi, tu lavori freelance? Ho visto che hai anche una bellissima passione per la fotografia.

      Elimina
    2. Si, sono un freelance: https://www.paolobertinetti.it ;)

      Elimina
  12. Buongiorno, grazie per avere reso disponibile questo utile script, una domanda. Non ho ancora testato lo script ma preliminarmente vorrei sapere se il controllo è di tipo formale. Se io inserisco dati formalmente corretti ma sostanzialmente falsi, lo script evidenzia questa anomalia?
    Roberto

    RispondiElimina
    Risposte
    1. Ciao Roberto,
      Ovviamente il controllo é solo formale: viene controllata la congruenza dei primi 15 caratteri rispetto al 16o applicando gli algoritmi ufficiali. Verificare l’effettiva esistenza di un codice fiscale presupporrebbe un collegamento con l’archivio anagrafe...

      Elimina
    2. Grazie Paolo, se deciderò di usare lo script sarò molto lieto di fare una piccola donazione :)

      Elimina