8. Ejercicio práctico – versión 3
Retomamos el ejercicio ya estudiado anteriormente (párrafos 4.3 y 4.4) para resolverlo con un código PHP que utiliza una clase.
8.1. La estructura de los scripts

8.2. La excepción [ExceptionImpots]
En la versión 03, cuando un constructor o un método de clase encuentre un error, lanzará una excepción de tipo [ExceptionImpots] como se muestra a continuación:
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 la versión 02, los datos de la administración tributaria se han agrupado:
- primero en un archivo jSON;
- luego, de este archivo jSON a una tabla asociativa;
En la versión 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 la versión 02, este archivo se utilizaba para inicializar una tabla asociativa. En la versión 03, el archivo inicializará la siguiente clase [TaxAdminData]:
<?php
namespace Application;
class TaxAdminData {
// tramos impositivos
private $limites;
private $coeffR;
private $coeffN;
// constantes para el 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 verifica 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 verifica 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 registra 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);
}
// getter y setter
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 contendrá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 de los archivos jSON y [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 lo tanto, inicializar el objeto de otra manera. De aquí en adelante, nuestras clases no tendrán un constructor, sino varios métodos de tipo [setFromQqChose] que permitirán inicializarlas de diferentes maneras. La construcción de un objeto de tipo [TaxAdminData] se realiza entonces con la expresión:
- línea 23: el método [setFromJsonFile] inicializa los atributos de la clase con los que tienen el mismo nombre en el archivo [$jsonFilename];
- líneas 24-42: el archivo jSON se utiliza para construir el array asociativo [$arrayTaxAdminData]. Ya hemos visto este código en el script [main.php] de la versión 02;
- líneas 44-47: si se presenta 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 el array asociativo [$arrayTaxAdminData] y la clase [TaxAdminData] tienen atributos con los mismos nombres que los valores procedentes del archivo jSON;
- líneas 53-57: se verifica que todos los atributos de la clase [TaxAdminData] hayan sido inicializados;
- línea 53: la expresión [get_object_vars($this)] devuelve un array asociativo cuyos atributos son los del objeto [$this], es decir, los atributos de la clase [TaxAdminData]. Aquí hay que tener en cuenta que la operación de inicialización de las líneas 48 a 51 pudo haber agregado atributos al objeto [$this]. Por lo tanto, si escribimos:
entonces el atributo [x] se agrega al objeto [$this], aunque dicho atributo no se haya declarado en la clase [TaxAdminData]. Lo que sí es seguro es que los atributos de las líneas 6 a 20 sí forman parte del objeto [$this], pero es posible que no se hayan inicializado. Es un error fácil de cometer; basta con equivocarse en el nombre de un 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:
es posible, aunque el atributo [$plafondQfDemiPart] debería ser de tipo real;
- líneas 59-71: se verifica 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 hubo 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 tuvo é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: de lo contrario, se registra 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 un array de elementos o un único elemento
// se crea un array
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// se transforma el array de elementos de tipo desconocido en un array de números reales
$newTableau = [];
$result = new \stdClass();
// los elementos del array 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 número flotante en newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// se registra el error
$result->erreur = TRUE;
// salimos
return $result;
}
}
// se devuelve el resultado
$result->erreur = FALSE;
if (!\is_array($value)) {
// un solo 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. Además, se desconoce su tipo. El valor proviene del archivo [taxadmindata.json]. Según los valores registrados en este archivo, los valores leídos pueden ser enteros, números reales, cadenas o valores 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 se puede convertir en un número; en el caso 4, es de tipo [string], que no se puede convertir en un número;
- líneas 4-8: se crea una tabla a partir del parámetro [$value] recibido en la línea 1;
- línea 10: el array que se va a llenar 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 cero;
- líneas 14-24: se verifica que todos los elementos de la matriz [$tableau] sean números reales positivos o nulos y se llena la matriz [$newTableau] con estos elementos transformados al tipo [float] (línea 17);
- líneas 18-23: tan pronto como se detecta un elemento que no es un número real positivo o nulo, se registra 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 con los atributos y valores del objeto [$this].
Líneas 87-110: los métodos getter y setter de la clase;
Nota: a veces puede resultar un poco tedioso tener que escribir todos los get y set de una clase, especialmente cuando hay muchos atributos. NetBeans puede generarlos automáticamente, así como el constructor. Para ello, simplemente seleccione los atributos [1]:

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

- en [4], indique que desea generar el constructor;
- en [5], marca todos los atributos: esto significa que quieres que el constructor tenga un parámetro para cada uno de los atributos;
- en [6], adopta el estilo de los constructores de Java;
- en [7], indique que desea explícitamente la palabra clave [public] delante del constructor;
- en [8], presione «Aceptar»;

- en [9], NetBeans generó el constructor. Sin embargo, no pudo establecer el tipo de los parámetros porque no los conoce. Agrega los tipos tú mismo: [10];
Para generar los getters y setters, repite los pasos 2 a 4 y, en el paso 4, selecciona [Getter and Setter]:

- en [5], indique que desea los getter y setter 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], confirme;

- en [9], los getters y setters generados por NetBeans;
Elimine estos getters y setters y repita los pasos 2 a 7.
- en [8], marque la opción [Fluent Setter] que no habíamos marcado anteriormente;
El resultado obtenido es el siguiente:

Cada setter termina con una operación [return $this]. Esto permite inicializar los atributos de la siguiente manera:
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 invocar el 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, en la que los métodos que no deberían devolver nada devuelven 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 calcular el impuesto
// puede generar la excepción ExceptionImpots
public function getTaxAdminData(): TaxAdminData;
// la interfaz puede 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 los usuarios con información sobre estado civil, número de hijos y salario anual
// $resultsFilename: archivo de resultados con el estado civil, número de hijos, salario anual y monto del impuesto
// $errorsFilename: archivo de los errores encontrados
// puede generar la excepción ExceptionImpots
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
Comentarios
- línea 4: la interfaz se ubica 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á obtener los datos de la administración tributaria en un objeto de tipo [TaxAdminData] que acabamos de presentar. Dado que estos datos pueden estar en un archivo, en una base de datos o incluso en la red, el método [getTaxAdminData] podría no lograr obtener los datos. En ese caso, lanzará una excepción de tipo [ExceptionImpots]. Este es el método estándar en la programación orientada a objetos para señalar un error que se ha producido en un método o en un constructor;
- línea 13: el método [calculerImpot] permitirá calcular el impuesto de un usuario;
- línea 20: el método [executeBatchImpots] permitirá 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 monto 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 datos erróneos.
El contenido del archivo de texto [$resultsFileName] será entonces el siguiente:
y el del archivo de texto [$errorsFileName] será el siguiente:
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 cualquier carácter de fin de línea del texto que se le haya 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á en 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 verifica que se cuente con 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 los 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 toma 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-deducció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á desarrollando;
- 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: en el párrafo anterior vimos un ejemplo en el que los datos de la administración tributaria se obtuvieron de un archivo jSON. Veremos otro caso en el que los datos se buscarán en una base de datos. Corresponderá a las clases derivadas definir 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 clase misma en abstracta (línea 7);
- líneas 15-64: la función de cálculo del impuesto que ya se ha mencionado en los párrafos enlace y enlace;
- la versión 02 colocaba los datos de la administración tributaria en una tabla asociativa [$taxAdminData]. La versión 03 los coloca en el atributo [$this→taxAdminData]. La primera diferencia entre estas dos soluciones es una diferencia en la visibilidad de los datos tributarios:
- en la versión 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 la versión 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 la versión 03 reemplaza las 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 ejecución, 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, le 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 a 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 ocurrir bastantes errores al manejar 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);
}
// apertura del archivo de resultados
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// lectura de los datos del 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)) {
// depuración
// imprime "línea n.º " . ($i + 1) . " : " . $ligne;
// se elimina cualquier carácter de fin de línea
$ligne = Utilitaires::cutNewLineChar($ligne);
// se recuperan los tres campos «casado:hijos:salario» que forman $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// se verifican
// 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 centavos 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 registra 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 siguiente formato: estado civil (sí / no), número de hijos, salario anual:
- (continuación)
- [$resultsFileName]: el nombre del archivo de texto que contendrá los resultados. Cada línea de texto tendrá el siguiente formato:
- (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 ciertas operaciones pueden generar una excepción, se utiliza un bloque try / catch / finally para rodear todo el código del método;
- líneas 3-19: se abren los tres archivos. Se lanza una excepción tan pronto como falla la apertura de alguno;
- línea 24: las líneas del archivo [$data] se leen una por una, en bloques de hasta 100 caracteres (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 verifica la validez de los tres elementos. Aquí no se lanza una excepción si hubo un error, sino que se escribe el mensaje del error 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 agregan las claves [marié, enfants, salaire];
- línea 61: el resultado se registra 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 revisa el número de líneas con errores 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 de inmediato (línea 73). El objetivo de este recurso es poder contar con una cláusula [finally] en las líneas 74-79: independientemente de cómo termine la ejecución del código del método, los tres archivos que haya podido abrir este código se cierran. Cerrar un archivo que no se ha abierto no genera un 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 generar una excepción de tipo [ExceptionImpots]. Dicha excepción se propagará hasta el script principal [main.php];
- líneas 18-20: se le asigna 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 proceso por lotes 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 exige el cumplimiento 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 le indica al intérprete PHP dónde se encuentran las clases e interfaces que utiliza 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, esto 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 maneja 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 un error irrecuperable, se muestra el mensaje de error;
Resultados
Con el siguiente archivo de contribuyentes [taxpayersdata.txt]:
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]: