Skip to content

8. Ejercicio de aplicación – version 3

Retomamos el ejercicio ya estudiado anteriormente (apartados 4.3 y 4.4) para resolverlo con un código PHP utilizando una clase.

8.1. El árbol de scripts

Image

8.2. La excepción [ExceptionImpots]

En version 03, cuando un constructor o un método de clase encuentre un error, lanzará una excepción de tipo [ExceptionImpots] como sigue:

<?php

// espacio de nombres
namespace Application;

class ExceptionImpots extends \RuntimeException {

  public function __construct(string $message, int $code=0) {
    parent::__construct($message, $code);
  }

}

Comentarios

  • línea 4: la clase [ExceptionImpots] se encuentra en el espacio de nombres [Application];
  • línea 6: la clase [ExceptionImpots] extiende la clase predefinida en PHP [RuntimeException];
  • línea 8: el constructor espera dos parámetros:
    • $message: es el mensaje de error asociado a la excepción;
    • $code: es el código de error asociado a la excepción. Si no está presente, se utilizará el código 0;

8.3. La clase [TaxAdminData]

En el version 02, se han recopilado los datos de la administración tributaria:

  • primero en un archivo jSON;
  • luego, de este archivo jSON a una tabla asociativa;

En el version 03, los datos de la administración tributaria siguen estando en el archivo [taxadmindata.json], pero con nombres de atributos diferentes:


{
    "limites": [
        9964,
        27519,
        73779,
        156244,
        0
    ],
    "coeffR": [
        0,
        0.14,
        0.3,
        0.41,
        0.45
    ],
    "coeffN": [
        0,
        1394.96,
        5798,
        13913.69,
        20163.45
    ],
    "plafondQfDemiPart": 1551,
    "plafondRevenusCelibatairePourReduction": 21037,
    "plafondRevenusCouplePourReduction": 42074,
    "valeurReducDemiPart": 3797,
    "plafondDecoteCelibataire": 1196,
    "plafondDecoteCouple": 1970,
    "plafondImpotCouplePourDecote": 2627,
    "plafondImpotCelibatairePourDecote": 1595,
    "abattementDixPourcentMax": 12502,
    "abattementDixPourcentMin": 437
}

En el version 02, este archivo se utilizaba para inicializar una tabla asociativa. En el version 03, el archivo inicializará la siguiente clase [TaxAdminData]:


<?php

namespace Application;

class TaxAdminData {
  // tramos impositivos
  private $limites;
  private $coeffR;
  private $coeffN;
  // constantes de cálculo del impuesto
  private $plafondQfDemiPart;
  private $plafondRevenusCelibatairePourReduction;
  private $plafondRevenusCouplePourReduction;
  private $valeurReducDemiPart;
  private $plafondDecoteCelibataire;
  private $plafondDecoteCouple;
  private $plafondImpotCouplePourDecote;
  private $plafondImpotCelibatairePourDecote;
  private $abattementDixPourcentMax;
  private $abattementDixPourcentMin;

  // inicialización
  public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
    // se recupera el contenido del archivo de datos fiscales
    $fileContents = \file_get_contents($taxAdminDataFilename);
    $erreur = FALSE;
    // ¿Error?
    if (!$fileContents) {
      // se registra el error
      $erreur = TRUE;
      $message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
    }
    if (!$erreur) {
      // se recupera el código jSON del archivo de configuración en una tabla asociativa
      $arrayTaxAdminData = \json_decode($fileContents, true);
      // ¿error?
      if ($arrayTaxAdminData === FALSE) {
        // se registra el error
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
      }
    }
    // ¿error?
    if ($erreur) {
      // se lanza una excepción
      throw new ExceptionImpots($message);
    }
    // inicialización de los atributos de la clase
    foreach ($arrayTaxAdminData as $key => $value) {
      $this->$key = $value;
    }
    // se comprueba que todas las claves se hayan inicializado
    $arrayOfAttributes = \get_object_vars($this);
    foreach ($arrayOfAttributes as $key => $value) {
      if (!isset($this->$key)) {
        throw new ExceptionImpots("L'attribut [$key] de [TaxAdminData] n'a pas été initialisé");
      }
    }
    // se comprueba que solo haya valores reales
    foreach ($this as $key => $value) {
      // $value debe ser un número real >=0 o una matriz de números reales >=0
      $result = $this->check($value);
      // ¿Error?
      if ($result->erreur) {
        // se lanza una excepción
        throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
      } else {
        // se anota el valor
        $this->$key = $result->value;
      }
    }
    // se devuelve el objeto
    return $this;
  }

  private function check($value): \stdClass {

    return $result;
  }

    // toString
  public function __toString() {
    // cadena Json del objeto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

  // getters y setters
  public function getLimites() {
    return $this->limites;
  }

  public function getCoeffR() {
    return $this->coeffR;
  }


  }

  public function setLimites($limites) {
    $this->limites = $limites;
    return $this;
  }

  public function setCoeffR($coeffR) {
    $this->coeffR = $coeffR;
    return $this;
  }



}

Comentarios

  • líneas 6-20: los atributos que alojarán los atributos del mismo nombre del archivo jSON [taxadmindata.json]. Este es un punto importante: los atributos de la clase [TaxAdminData] son idénticos a los del archivo jSON [taxadmindata.json]. Esta particularidad facilita mucho la escritura del código;
  • la clase [TaxAdminData] no tiene constructor. En PHP, no es posible tener varios constructores. Establecer uno impide, por tanto, inicializar el objeto de otra manera. En lo sucesivo, nuestras clases no tendrán constructor, sino varios métodos de tipo [setFromQqChose] que permitirán inicializarlo de diferentes maneras. La construcción de un objeto de tipo [TaxAdminData] se realiza entonces con la expresión:
(new TaxAdminData())→setFromQqChose(…)
  • línea 23: el método [setFromJsonFile] inicializa los atributos de la clase con los del mismo nombre en el archivo [$jsonFilename];
  • líneas 24-42: el archivo jSON se utiliza para construir la tabla asociativa [$arrayTaxAdminData]. Ya nos hemos encontrado con este código en el script [main.php] de version 02;
  • líneas 44-47: si se ha producido un error al procesar el archivo jSON, se lanza una excepción. Esta se propagará hasta el script principal [main.php];
  • líneas 48-51: se inicializan los atributos de la clase. Aquí se aprovecha el hecho de que la tabla asociativa [$arrayTaxAdminData] y la clase [TaxAdminData] tienen atributos con los mismos nombres que los valores procedentes del archivo jSON;
  • líneas 53-57: se comprueba que todos los atributos de la clase [TaxAdminData] se hayan inicializado;
  • línea 53: la expresión [get_object_vars($this)] devuelve una tabla asociativa cuyos atributos son los del objeto [$this], es decir, los atributos de la clase [TaxAdminData]. Aquí hay que entender que la operación de inicialización de las líneas 48-51 pudo haber añadido atributos al objeto [$this]. Así, si escribimos:
    $this->x = "1000";

entonces el atributo [x] se añade al objeto [$this], aunque dicho atributo no se haya declarado en la clase [TaxAdminData]. Lo que es seguro es que los atributos de las líneas 6-20 forman parte del objeto [$this], pero es posible que no se hayan inicializado. Es un error fácil de cometer, basta con equivocarse en un nombre de atributo en el archivo [taxadmindata.json];

  • líneas 54-57: se revisan todos los atributos de [$this] y, si alguno de ellos no se ha inicializado, se lanza una excepción;
  • un atributo puede inicializarse con un valor incorrecto. En PHP, no es posible asignar un tipo a los atributos. Por lo tanto, la operación:
$this→plafondQfDemiPart=’abcd’

es posible, mientras que el atributo [$plafondQfDemiPart] debería ser real;

  • líneas 59-71: se comprueba que cada uno de los atributos de la clase tenga un valor numérico real positivo o nulo. La función [check] de la línea 76 realiza esta tarea. Su parámetro [$value] es un valor único o una matriz de valores;
  • línea 62: la función [check] devuelve un objeto de tipo [\stdClass] con dos atributos:
    • [erreur]: a TRUE si se ha producido un error, a FALSE en caso contrario;
    • [value]: el valor numérico real correspondiente al parámetro [$value] pasado como parámetro, línea 62;
  • línea 64: se comprueba si la verificación ha tenido éxito o no;
  • línea 66: si un atributo no es un número real positivo o cero, se lanza una excepción;
  • línea 69: en caso contrario, se anota su valor numérico;
  • línea 73: se devuelve el objeto [$this] como resultado;

La función [check] es la siguiente:


private function check($value): \stdClass {
    // $value es o bien una matriz de elementos o bien un único elemento
    // se crea una matriz
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // se transforma la matriz de elementos de tipo desconocido en una matriz de números reales
    $newTableau = [];
    $result = new \stdClass();
    // los elementos de la matriz deben ser números decimales positivos o nulos
    $modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
    for ($i = 0; $i < count($tableau); $i ++) {
      if (preg_match($modèle, $tableau[$i])) {
        // se coloca el float en newTableau
        $newTableau[] = (float) $tableau[$i];
      } else {
        // se anota el error
        $result->erreur = TRUE;
        // salimos
        return $result;
      }
    }
    // se devuelve el resultado
    $result->erreur = FALSE;
    if (!\is_array($value)) {
      // un único valor
      $result->value = $newTableau[0];
    } else {
      // una lista de valores
      $result->value = $newTableau;
    }
    return $result;
  }

Comentarios

  • línea 1: el parámetro [$value] es una matriz o un único elemento. Por otra parte, se desconoce su tipo. El valor procede del archivo [taxadmindata.json]. Según los valores registrados en este archivo, los valores leídos pueden ser enteros, reales, cadenas o booleanos. Por ejemplo:

"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",

En el caso 1, el valor es de tipo [entier]; en el caso 2, de tipo [réel]; en el caso 3 es de tipo [string], que puede convertirse en un número, y en el caso 4 es de tipo [string], que no puede convertirse en un número;

  • líneas 4-8: se crea una matriz a partir del parámetro [$value] recibido en la línea 1;
  • línea 10: la matriz que vamos a rellenar con números reales;
  • línea 11: el resultado será un objeto de tipo [\stdClass];
  • línea 13: expresión relacional de un número real positivo o nulo;
  • líneas 14-24: se comprueba que todos los elementos de la matriz [$tableau] sean números reales positivos o nulos y se rellena la matriz [$newTableau] con estos elementos transformados al tipo [float] (línea 17);
  • líneas 18-23: en cuanto se detecta un elemento que no es un número real positivo o nulo, se anota el error en el resultado y se devuelve este;
  • líneas 25-34: caso en el que todos los elementos de la matriz [$tableau] se han declarado correctos;
  • línea 32: el valor devuelto [$result→value] es una matriz de números reales [float] o un único número real;

La función [__toString] de las líneas 82-85 devuelve la cadena jSON de los atributos y valores del objeto [$this].

Líneas 87-110: los getter y setter de la clase;

Nota: a veces puede resultar un poco tedioso tener que escribir todos los getter y setter de una clase, sobre todo cuando hay muchos atributos. Netbeans puede generarlos automáticamente, así como el constructor. Para ello, basta con introducir los atributos [1]:

Image

  • en [2], haz clic con el botón derecho donde quieras insertar el código y luego elige el option [Insert Code];

Image

  • en [4], indique que desea generar el constructor;
  • en [5], marque todos los atributos: esto significa que desea que el constructor tenga un parámetro para cada uno de los atributos;
  • en [6], utilice el estilo de los constructores Java;
  • en [7], indique que desea explícitamente la palabra clave [public] delante del constructor;
  • en [8], valide;

Image

  • en [9], Netbeans ha generado el constructor. Sin embargo, no ha podido establecer el tipo de los parámetros porque no los conoce. Añádalos usted mismo [10];

Para generar los getters y setters, repita los pasos 2-4 y, en el paso 4, seleccione [Getter and Setter]:

Image

  • en [5], indique que desea los getters y setters para cada uno de los atributos;
  • en [6], indique que desea los getter y setter en el estilo utilizado por Java: setAttribut, getAttribut;
  • en [7], indique que desea que estos getters y setters sean públicos;
  • en [8], valida;

Image

  • en [9], los getters y setters generados por Netbeans;

Elimine estos getters y setters y vuelva a realizar los pasos 2-7.

  • en [8], marque option y [Fluent Setter], que no habíamos marcado anteriormente;

El resultado obtenido es el siguiente:

Image

Cada setter termina con una operación [return $this]. Esto permite inicializar los atributos de la siguiente manera:

$data→setLimites($limites)→setCoeffR($coeffR)→setCoeffN($coeffN) ;

De hecho, el valor de [$data→setLimites($limites)] (línea 32 del código) es [$this], por lo que aquí es [$data]. Por lo tanto, se puede llamar al método [setCoeffR($coeffR)] de este objeto y así sucesivamente, ya que, a su vez, este método también devuelve [$this] (línea 37 del código). Esta forma de escribir los métodos de una clase, que hace que los métodos que no deberían devolver nada devuelvan el objeto [$this], se denomina escritura fluida. Facilita el uso de estos métodos.

8.4. La interfaz [InterfaceImpots]

Ahora definimos la siguiente interfaz [InterfaceImpots] [InterfaceImpots.php]:


<?php

// espacio de nombres
namespace Application;

interface InterfaceImpots {

  // recuperar los datos de los tramos impositivos que permiten el cálculo del impuesto
  // puede lanzar la excepción ExceptionImpots
  public function getTaxAdminData(): TaxAdminData;

  // la interfaz sabe calcular un impuesto
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // la interfaz sabe procesar datos de archivos de texto
  // $usersFilename: archivo de datos de usuario con el estado civil, el número de hijos y el salario anual
  // $resultsFilename: archivo de resultados con el estado civil, el número de hijos, el salario anual y el importe del impuesto
  // $errorsFilename: archivo de errores detectados
  // puede lanzar la excepción ExceptionImpots
  public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}

Comentarios

  • línea 4: la interfaz se coloca en el espacio de nombres [Application];
  • línea 6: la interfaz que permite el cálculo de los impuestos;
  • línea 10: el método [getTaxAdminData] permitirá adquirir los datos de la administración tributaria en un objeto de tipo [TaxAdminData] que acabamos de presentar. Dado que estos datos pueden encontrarse en un archivo, en una base de datos o incluso en la red, el método [getTaxAdminData] puede no conseguir obtener los datos. En ese caso, lanzará una excepción de tipo [ExceptionImpots]. Este es el método estándar en programación orientada a objetos para señalar un error encontrado en un método o un constructor;
  • línea 13: el método [calculerImpot] permitirá calcular el impuesto de un usuario;
  • línea 20: el método [executeBatchImpots] permite calcular el impuesto de varios contribuyentes;
    • [$usersFileName] es el nombre del archivo de texto que contiene los datos de los contribuyentes;
    • [$resultsFileName] es el nombre del archivo de texto que contiene el importe del impuesto para estos contribuyentes;
    • [$errorsFileName] es el nombre del archivo de texto que contiene los errores encontrados al procesar estos archivos;

El contenido del archivo de texto [$usersFileName] podría ser el siguiente:


oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

Cabe señalar que las líneas 5 y 7 contienen elementos erróneos.

El contenido del archivo de texto [$resultsFileName] será entonces el siguiente:

1
2
3
4
5
6
7
8
9
{"marié":"oui","enfants":2,"salaire":55555,"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":2,"salaire":100000,"impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":3,"salaire":100000,"impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"non","enfants":0,"salaire":100000,"impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":2,"salaire":30000,"impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":0,"salaire":200000,"impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":3,"salaire":200000,"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}

y el del archivo de texto [$errorsFileName], el siguiente:

la ligne 5 du fichier taxpayersdata.txt est erronée
la ligne 7 du fichier taxpayersdata.txt est erronée

8.5. La clase [Utilitaires]

Además, definimos una clase [Utilitaires] en un archivo [Utilitaires.php]:


<?php

// espacio de nombres
namespace Application;

// una clase de funciones de utilidad
abstract class Utilitaires {

  public static function cutNewLinechar(string $ligne): string {
    // se elimina el marcador de fin de línea de $ligne si existe
    $longueur = strlen($ligne);  // longitud de la línea
    while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
      $ligne = substr($ligne, 0, $longueur - 1);
      $longueur--;
    }
    // fin: se devuelve la línea
    return($ligne);
  }
}

Comentarios

  • línea 4: la clase [Utilitaires] también se coloca en el espacio de nombres [Exemples];
  • línea 9: el método [cutNewLinechar] elimina el posible carácter de fin de línea del texto que se le ha pasado como parámetro. Devuelve la nueva línea así formada. Cabe señalar que se trata de un método estático, es decir, que se invocará con la forma [Utilitaires::cutNewLineChar];

8.6. La clase abstracta [AbstractBaseImpots]

La interfaz [InterfaceImpots] será implementada por la siguiente clase abstracta [AbstractBaseImpots] [AbstractBaseImpots.php]:


<?php

// espacio de nombres
namespace Application;

// definición de una clase abstracta AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
  // datos de la administración tributaria
  private $taxAdminData = NULL;

  // datos necesarios para el cálculo del impuesto
  abstract function getTaxAdminData(): TaxAdminData;

// cálculo del impuesto
// --------------------------------------------------------------------------
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    // $marié: sí, no
    // $enfants: número de hijos
    // $salaire: salario anual
    // $this->taxAdminData: datos de la administración tributaria
    //
    // se comprueba que se dispone de los datos de la administración tributaria
    if ($this->taxAdminData === NULL) {
      $this->taxAdminData = $this->getTaxAdminData();
    }
    // cálculo del impuesto con hijos
    $result1 = $this->calculerImpot2($marié, $enfants, $salaire);
    $impot1 = $result1["impôt"];
    // cálculo del impuesto sin hijos
    if ($enfants != 0) {
      $result2 = $this->calculerImpot2($marié, 0, $salaire);
      $impot2 = $result2["impôt"];
      // aplicación del límite máximo del coeficiente familiar
      $plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
      if ($enfants < 3) {
        // $PLAFOND_QF_DEMI_PART euros para los dos primeros hijos
        $impot2 = $impot2 - $enfants * $plafonDemiPart;
      } else {
        // $PLAFOND_QF_DEMI_PART euros para los dos primeros hijos, el doble para los siguientes
        $impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
      }
    } else {
      $impot2 = $impot1;
      $result2 = $result1;
    }
    // se aplica el impuesto más alto
    if ($impot1 > $impot2) {
      $impot = $impot1;
      $taux = $result1["taux"];
      $surcôte = $result1["surcôte"];
    } else {
      $surcôte = $impot2 - $impot1 + $result2["surcôte"];
      $impot = $impot2;
      $taux = $result2["taux"];
    }
    // cálculo de una posible deducción
    $décôte = $this->getDecôte($marié, $salaire, $impot);
    $impot -= $décôte;
    // cálculo de una posible reducción de impuestos
    $réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
    $impot -= $réduction;
    // resultado
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }

// --------------------------------------------------------------------------
  private function calculerImpot2(string $marié, int $enfants, float $salaire): array {

    // resultado
    return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
  }

  // revenuImposable=salarioAnual-desgravación
  // la deducción tiene un mínimo y un máximo
  private function getRevenuImposable(float $salaire): float {

    // resultado
    return floor($revenuImposable);
  }

// calcula una posible reducción
  private function getDecôte(string $marié, float $salaire, float $impots): float {

    // resultado
    return ceil($décôte);
  }

// calcula una posible reducción
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

    // resultado
    return ceil($réduction);
  }

  public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {

  }

}

Comentarios

  • línea 4: la clase [AbstractBaseImpots] estará en el espacio de nombres [Application], al igual que los demás elementos de la aplicación que se está escribiendo;
  • línea 7: la clase [AbstractBaseImpots] implementa la interfaz [InterfaceImpots];
  • línea 9: los datos de la administración tributaria se colocarán en el atributo [$taxAdminData];
  • línea 12: implementación del método [getTaxAdminData] de la interfaz. Aún no sabemos cómo definir este método: hemos visto un ejemplo en el que los datos de la administración fiscal se han tomado de un archivo jSON en el párrafo anterior. Veremos otro caso en el que los datos se buscarán en una base de datos. Serán las clases derivadas las que definan el contenido del método [getTaxAdminData]. Los dos casos anteriores darán lugar a dos clases derivadas. Por lo tanto, el método [getTaxAdminData] se declara abstracto, lo que automáticamente convierte a la propia clase en abstracta (línea 7);
  • líneas 15-64: la función de cálculo del impuesto ya vista en los apartados enlace y enlace;
  • la version 02 colocaba los datos de la administración tributaria en una tabla asociativa [$taxAdminData]. La version 03 los coloca en el atributo [$this→taxAdminData]. La primera diferencia entre estas dos soluciones es una diferencia en la visibilidad de los datos fiscales:
    • en version 02, la tabla asociativa [$taxAdminData] no tenía visibilidad global. Por lo tanto, se pasaba como parámetro a todas las funciones de cálculo del impuesto;
    • en version 03, el atributo [$this→taxAdminData] tiene visibilidad global para todos los métodos de la clase. Por lo tanto, no se pasa como parámetro a todas las funciones de cálculo del impuesto;
  • una segunda diferencia radica en que version 03 sustituye funciones por métodos de clase. Cada llamada al método se realiza ahora con una expresión [$this→getMéthode(…)] (líneas 27, 31, 57, 60);
  • una tercera diferencia es que, cuando el método [calculerImpot] inicia su trabajo, no sabe si el atributo [private $taxAdminData] que necesita ha sido inicializado. De hecho, el constructor de la clase no lo inicializa. Por lo tanto, corresponde al método [calculerImpot] hacerlo mediante el método [getTaxAdminData] de la línea 12. Esto es lo que se hace en las líneas 23-25;
  • aparte de estas diferencias, los métodos de cálculo del impuesto siguen siendo los mismos que en las versiones anteriores;

La función [executeBatchImpots] es la siguiente:


public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
    // pueden producirse bastantes errores al gestionar archivos
    try {
      // errores al abrir el archivo
      $errors = fopen($errorsFileName, "w");
      if (!$errors) {
        throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
      }
      // resultados de la apertura del archivo
      $results = fopen($resultsFileName, "w");
      if (!$results) {
        throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
      }
      // lectura de datos de usuario
      // cada línea tiene el formato estado civil, número de hijos, salario anual
      $data = fopen($usersFileName, "r");
      if (!$data) {
        throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
      }
      // se procesa la línea actual del archivo de datos de usuario
      // que tiene el formato estado civil, número de hijos, salario anual
      $num = 1;         // n.º de la línea actual
      $nbErreurs = 0;   // número de errores encontrados
      while ($ligne = fgets($data, 100)) {
        // debug
        //  print "línea n.º " . ($i + 1) . " : " . $ligne;
        // se elimina el posible carácter de fin de línea
        $ligne = Utilitaires::cutNewLineChar($ligne);
        // se recuperan los 3 campos casado:hijos:salario que forman $ligne
        list($marié, $enfants, $salaire) = explode(",", $ligne);
        // se comprueban
        // el estado civil debe ser sí o no
        $marié = trim(strtolower($marié));
        $erreur = ($marié !== "oui" and $marié !== "non");
        if (!$erreur) {
          // el número de hijos debe ser un número entero
          $enfants = trim($enfants);
          if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
            $erreur = TRUE;
          } else {
            $enfants = (int) $enfants;
          }
        }
        if (!$erreur) {
          // el salario es un número entero sin céntimos de euro
          $salaire = trim($salaire);
          if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
            $erreur = TRUE;
          } else {
            $salaire = (int) $salaire;
          }
        }
        // ¿error?
        if ($erreur) {
          fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
          $nbErreurs++;
        } else {
          // se calcula el impuesto
          $result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
          // se introduce el resultado en el archivo de resultados
          $result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
          fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
        }
        // línea siguiente
        $num++;
      }
      // ¿Hay errores?
      if ($nbErreurs > 0) {
        throw new ExceptionImpots("Il y a eu des erreurs", 15);
      }
    } catch (ExceptionImpots $ex) {
      // se vuelve a lanzar la excepción
      throw $ex;
    } finally {
      // se cierran todos los archivos
      fclose($data);
      fclose($results);
      fclose($errors);
    }
  }

Comentarios del código

  • línea 1: la función recibe tres parámetros:
    • [$usersFileName]: el nombre del archivo de texto que contiene los datos de los contribuyentes. Cada línea de texto contiene los datos de un contribuyente en el formato: estado civil (sí/no), número de hijos, salario anual:
oui,2,55555
oui,2,50000
  • (continuación)
    • [$resultsFileName]: el nombre del archivo de texto que contendrá los resultados. Cada línea de texto tendrá el siguiente formato:
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
  • (continuación)
    • [$errorsFileName]: el nombre del archivo de texto de los errores:

la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
  • línea 3: dado que algunas operaciones pueden lanzar una excepción, se envuelve todo el código del método con un try / catch / finally;
  • líneas 3-19: se abren los tres archivos. Se lanza una excepción en cuanto falla una apertura;
  • línea 24: las líneas del archivo [$data] se leen una a una a razón de 100 caracteres como máximo (todas las líneas tienen menos de 100 caracteres);
  • línea 28: se utiliza el método estático [Utilitaires::cutNewLineChar] para eliminar cualquier carácter de fin de línea;
  • línea 30: se recuperan los tres elementos de la línea leída;
  • líneas 33-52: se comprueba la validez de los tres elementos. En este caso, no se lanza una excepción si se ha producido un error, sino que se escribe el mensaje del mismo en el archivo de texto [$errors] (línea 55);
  • línea 59: si la línea leída es válida, se realiza el cálculo del impuesto. Se obtiene un resultado en forma de tabla asociativa ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  • línea 61: al resultado obtenido se le añaden las claves [marié, enfants, salaire];
  • línea 61: el resultado se escribe en el archivo de texto [$results] en forma de la cadena jSON del resultado obtenido;
  • líneas 68-70: al finalizar el procesamiento del archivo [$data], se comprueba el número de líneas erróneas encontradas. Si hay al menos una, se lanza una excepción;
  • líneas 71-74: se intercepta la excepción que haya podido lanzar el código y se vuelve a lanzar inmediatamente (línea 73). El objetivo de este artificio es poder disponer de una cláusula [finally] en las líneas 74-79: independientemente de cómo finalice la ejecución del código del método, se cierran los tres archivos que haya podido abrir dicho código. Cerrar un archivo que no se ha abierto no provoca ningún error;

8.7. La clase [ImpotsWithTaxAdminDataInJsonFile]

La clase abstracta [AbstractBaseImpots] no implementa el método [getTaxAdminData] de la interfaz [InterfaceImpots]. Por lo tanto, debemos definirlo en una clase derivada. Lo hacemos en la siguiente clase derivada [ImpotsWithTaxAdminDataInJsonFile]:


<?php

// espacio de nombres
namespace Application;

// definición de una clase ImpotsWithDataInArrays
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
  // un atributo de tipo Data
  private $taxAdminData;

  // el constructor
  public function __construct(string $jsonFileName) {
    // se inicializa $this->taxAdminData a partir del archivo jSON
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
  }

  // devuelve los datos que permiten calcular el impuesto
  public function getTaxAdminData(): TaxAdminData {
    // se devuelve el atributo [$this->taxAdminData]
    return $this->taxAdminData;
  }

}

Comentarios

  • línea 7: la clase [ImpotsWithTaxAdminDataInJsonFile] extiende la clase abstracta [AbstractBaseImpots]. Deberá definir el método [getTaxAdminData] que su clase padre no ha definido;
  • línea 9: el atributo [$taxAdminData] contendrá los datos de la administración tributaria;
  • líneas 12-15: el constructor recibe como único parámetro el nombre del archivo jSON que contiene los datos fiscales;
  • línea 14: se crea y se inicializa un objeto de tipo [TaxAdminData]. Esta operación puede lanzar una excepción de tipo [ExceptionImpots]. Esta se propagará hasta el script principal [main.php];
  • líneas 18-20: se le da un cuerpo al método [getTaxAdminData] que la clase padre no había definido. Aquí basta con hacer que el atributo [$this->taxAdminData] sea inicializado por el constructor;

8.8. El script [main.php]

Estas clases e interfaz son utilizadas por el siguiente script [main.php]:


<?php

// cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare(strict_types = 1);

// espacio de nombres
namespace Application;

// Inclusión de interfaces y clases
require_once __DIR__ . '/InterfaceImpots.php';
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . '/ExceptionImpots.php';
require_once __DIR__ . '/Utilitaires.php';
require_once __DIR__ . '/AbstractBaseImpots.php';
require_once __DIR__ . "/ImpotsWithTaxAdminDataInJsonFile.php";

// prueba -----------------------------------------------------
// definición de constantes
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // se crea un objeto ImpotsWithTaxAdminDataInJsonFile
  $impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // se ejecuta el lote de impuestos
  $impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // se muestra el error
  print $ex->getMessage() . "\n";
}
// fin
print "Terminé\n";
exit();


Comentarios

  • línea 4: se impone el respeto estricto de los tipos de los parámetros de las funciones;
  • línea 7: el script [main.php] también se coloca en el espacio de nombres [Application];
  • líneas 10-15: se indica al intérprete PHP dónde se encuentran las clases e interfaces utilizadas por el script. Cabe señalar que aquí no hemos utilizado la instrucción use para declarar el nombre completo de las clases utilizadas por el script. De hecho, es innecesario porque el script y las clases se encuentran en el mismo espacio de nombres [Application];
  • líneas 18-22: los nombres de los archivos de texto utilizados en el script;
  • líneas 24-29: se crea un objeto [ImpotsWithTaxAdminDataInJsonFile] y se gestiona cualquier excepción que pueda surgir;
  • línea 28: se ejecuta el método [executeBatchImpots], que calculará los impuestos para todos los contribuyentes del archivo [TAXPAYERSDATA_FILENAME]. Los resultados se guardarán en el archivo [RESULTS_FILENAME] y los posibles errores en el archivo [ERRORS_FILENAME];
  • líneas 29-32: en caso de error irrecuperable, se muestra el mensaje de error;

Resultados

Con el archivo de contribuyentes [taxpayersdata.txt] siguiente:


oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

se obtiene el siguiente archivo de errores [errors.txt]:


la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée

y el siguiente archivo de resultados [resultats.txt]:

1
2
3
4
5
6
7
8
9
{"marié":"oui","enfants":2,"salaire":55555,"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":2,"salaire":100000,"impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":3,"salaire":100000,"impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"non","enfants":0,"salaire":100000,"impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":2,"salaire":30000,"impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":0,"salaire":200000,"impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":3,"salaire":200000,"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}