Skip to content

13. Ejercicio práctico – versión 5

Image

Ya hemos escrito varias versiones de este ejercicio. La última versión utilizaba una arquitectura en capas:

Image

La capa [dao] implementa una interfaz [InterfaceDao]. Hemos creado una clase que implementa esta interfaz:

  • [DaoImpotsWithTaxAdminDataInJsonFile], que obtenía los datos fiscales de un archivo jSON;

Vamos a implementar la interfaz [InterfaceDao] mediante una nueva clase [DaoImpotsWithTaxAdminDataInDatabase] que obtendrá los datos de la administración tributaria de una base de datos MySQL.

13.1. Creación de la base de datos [dbimpots-2019]

Siguiendo el ejemplo del párrafo anterior, creamos una base de datos MySQL denominada [dbimpots-2019], cuyo propietario será [admimpots] con la contraseña [mdpimpots]:

Image

  • En [1-4], arriba, vemos la base de datos [dbimpots-2019], que por el momento no tiene tablas;

Image

  • en [1-5], arriba, vemos que el usuario [admimpots] tiene todos los derechos sobre la base de datos [dbimpots-2019]. Lo que no vemos aquí es que este usuario tiene la contraseña [admimpots];

Ahora creamos la tabla [tbtranches], que contendrá los tramos impositivos:

Image

  • En [1-7], creamos una tabla llamada [tbtranches] con 4 columnas;

Image

  • en [3-6] definimos una columna llamada [id] (3), de tipo entero [int] (4), que será la clave primaria [6] de la tabla y se autoincrementará [5] mediante SGBD. Esto significa que MySQL gestionará por sí mismo los valores de la clave primaria al momento de las inserciones. Asignará el valor 1 a la clave primaria de la primera inserción, luego el 2 a la siguiente, y así sucesivamente;
  • en [7], el asistente nos ofrece otras opciones de configuración de la clave primaria. Aquí nos limitamos a aceptar los valores predeterminados en [7];

Image

  • En [8-16], se definen las otras tres columnas de la tabla:
    • [limites] (8), de tipo número decimal (9) de 10 dígitos, de los cuales 2 son decimales (10), contendrá los elementos de la columna 17 de los tramos de impuestos;
    • [coeffR] (11), de tipo número decimal (12) de 6 cifras, 2 de las cuales son decimales (13), contendrá los elementos de la columna 18 de los tramos impositivos;
    • [coeffN] (14), de tipo número decimal (15) de 10 cifras, de las cuales 2 son decimales (16), contendrá los elementos de la columna 19 de los tramos impositivos;

Tras validar esta estructura, obtenemos el siguiente resultado:

Image

  • En [5], el ícono de la clave indica que la columna [id] es la clave primaria. También se observa que esta clave primaria tiene valores enteros (6) y que es administrada (autoincrementada) por MySQL;

De la misma manera que creamos la tabla [tbtranches], creamos la tabla [tbconstantes], que contendrá las constantes para el cálculo del impuesto:

Image

Es posible exportar la estructura de la base de datos a un archivo de texto en forma de una secuencia de órdenes SQL:

Image

La opción [5] exporta aquí únicamente la estructura de la base de datos y no su contenido. En nuestro caso, la base aún no tiene contenido.

Image

Image

Image

La opción [11] genera el siguiente archivo SQL [dbimpots-2019.sql]:


-- phpMyAdmin SQL Volcado
-- versión 4.8.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Fecha de generación: 30 de junio de 2019 a las 01:10 PM
-- Versión del servidor: 5.7.24
-- PHP Versión: 7.2.11

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Base de datos: `dbimpots-2019`
--
CREATE DATABASE IF NOT EXISTS `dbimpots-2019` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `dbimpots-2019`;

-- --------------------------------------------------------

--
-- Estructura de la tabla `tbconstantes`
--

DROP TABLE IF EXISTS `tbconstantes`;
CREATE TABLE `tbconstantes` (
  `id` int(11) NOT NULL,
  `plafondQfDemiPart` decimal(10,2) NOT NULL,
  `plafondRevenusCelibatairePourReduction` decimal(10,2) NOT NULL,
  `plafondRevenusCouplePourReduction` decimal(10,2) NOT NULL,
  `valeurReducDemiPart` decimal(10,2) NOT NULL,
  `plafondDecoteCelibataire` decimal(10,2) NOT NULL,
  `plafondDecoteCouple` decimal(10,2) NOT NULL,
  `plafondImpotCelibatairePourDecote` decimal(10,2) NOT NULL,
  `plafondImpotCouplePourDecote` decimal(10,2) NOT NULL,
  `abattementDixPourcentMax` decimal(10,2) NOT NULL,
  `abattementDixPourcentMin` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Estructura de la tabla `tbtranches`
--

DROP TABLE IF EXISTS `tbtranches`;
CREATE TABLE `tbtranches` (
  `id` int(11) NOT NULL,
  `limites` decimal(10,2) NOT NULL,
  `coeffR` decimal(10,2) NOT NULL,
  `coeffN` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Índices de las tablas volcadas
--

--
-- Índices de la tabla `tbconstantes`
--
ALTER TABLE `tbconstantes`
  ADD PRIMARY KEY (`id`);

--
-- Índices de la tabla `tbtranches`
--
ALTER TABLE `tbtranches`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT para las tablas volcadas
--

--
-- AUTO_INCREMENT para la tabla `tbconstantes`
--
ALTER TABLE `tbconstantes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT para la tabla `tbtranches`
--
ALTER TABLE `tbtranches`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Puede utilizar este archivo SQL para regenerar la base de datos [dbimpots-2019] si esta se ha destruido o alterado. No es necesario eliminar la base de datos antes de regenerarla, ya que el script SQL se encarga de hacerlo por sí mismo:

Image

Image

13.2. Organización del código

Para mostrar mejor la función de los diferentes scripts PHP que estamos escribiendo, organizaremos nuestro código en carpetas:

Image

  • en [1], visión general de la versión 05;
  • en [2], las entidades de la aplicación, entidades intercambiadas entre capas;
  • en [3], las utilidades de la aplicación;
  • en [4], los datos utilizados o generados por la aplicación. Aquí decidimos utilizar únicamente archivos jSON para los archivos de texto. Estos presentan varias ventajas:
    • son reconocidos por muchas herramientas;
    • estas herramientas cuentan con resaltado de sintaxis. Además, la notación jSON sigue ciertas reglas. Cuando estas no se respetan, las herramientas lo señalan. Por ejemplo, un error difícil de detectar en un archivo de texto básico es el uso de la letra O mayúscula o minúscula en lugar de ceros. Si se produce este error, se señalará. De hecho, en el código jSON:

"plafondRevenusCouplePourReduction": 42O74

donde, por descuido, se ha puesto una «O» mayúscula en lugar del cero en [42074], NetBeans señala el error:

Image

De hecho, NetBeans reconoce la O mayúscula, lo que convierte a [49O74] en una cadena de caracteres. Por ello, concluye que la sintaxis debería ser [4-5]: la cadena [47O74] debería estar entre comillas. De este modo, se le llama la atención al desarrollador sobre el error y puede corregirlo: ya sea agregando las comillas o reemplazando la O por un cero;

Los demás elementos de la versión 05 son los siguientes:

Image

  • en [6], las interfaces y clases de la capa [Dao];
  • en [7], las interfaces y clases de la capa [métier];
  • en [8], los scripts principales de la versión 05;

La versión 05 tiene dos objetivos distintos:

  • llenar la base MySQL [dbimpots-2019] con el contenido del archivo jSON [Data/txadmindata.json];
  • implementar el cálculo del impuesto con datos fiscales que ahora provienen de la base de datos MySQL [dbimpots-2019];

Abordaremos estos dos objetivos por separado.

13.3. Llenado de la base de datos [dbimpots-2019]

13.3.1. Objetivo

El archivo de texto taxadmindata.json contiene los datos de la administración tributaria:


{
    "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
}

Nuestro objetivo es transferir estos datos a la base de datos MySQL [dbimpots-2019] creada anteriormente.

13.3.2. Las entidades

Image

La entidad [Database] servirá para encapsular los datos del siguiente archivo jSON [database.json]:


{
    "dsn": "mysql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "tbconstantes",
    "colPlafondQfDemiPart": "plafondQfDemiPart",
    "colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
    "colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
    "colValeurReducDemiPart": "valeurReducDemiPart",
    "colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
    "colPlafondDecoteCouple": "plafondDecoteCouple",
    "colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
    "colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
    "colAbattementDixPourcentMax": "abattementDixPourcentMax",
    "colAbattementDixPourcentMin": "abattementDixPourcentMin"
}

La entidad [TaxAdminData] servirá para encapsular los datos del siguiente archivo jSON [taxadmindata.json]:


{
    "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
}

La entidad [TaxPayerData] servirá para encapsular los datos del siguiente archivo jSON [taxpayerdata.json]:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

13.3.2.1. La clase base [BaseEntity]

Para simplificar el código de las entidades, adoptaremos la siguiente regla: los atributos de una entidad tienen los mismos nombres que los atributos del archivo jSON que la entidad debe encapsular. Siguiendo esta regla, las entidades [Database, TaxAdminData, TaxPayerData] tienen puntos en común que pueden factorizarse en una clase padre. Esta será la siguiente clase [BaseEntity]:


<?php

namespace Application;

class BaseEntity {
  // atributo
  protected $arrayOfAttributes;

  // inicialización a partir de un archivo jSON
  public function setFromJsonFile(string $jsonFilename) {
    // se recupera el contenido del archivo de datos fiscales
    $fileContents = \file_get_contents($jsonFilename);
    $erreur = FALSE;
    // ¿Error?
    if (!$fileContents) {
      // se registra el error
      $erreur = TRUE;
      $message = "Le fichier des données [$jsonFilename] n'existe pas";
    }
    if (!$erreur) {
      // se recupera el código jSON del archivo de configuración en una tabla asociativa
      $this->arrayOfAttributes = \json_decode($fileContents, true);
      // ¿error?
      if ($this->arrayOfAttributes === FALSE) {
        // se registra el error
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$jsonFilename] 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 ($this->arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // se devuelve el objeto
    return $this;
  }

  public function checkForAllAttributes() {
    // se verifica que todas las claves se hayan inicializado
    foreach (\array_keys($this->arrayOfAttributes) as $key) {
      if ($key !== "arrayOfAttributes" && !isset($this->$key)) {
        throw new ExceptionImpots("L'attribut [$key] de la classe "
          . get_class($this) . " n'a pas été initialisé");
      }
    }
  }

  public function setFromArrayOfAttributes(array $arrayOfAttributes) {
    // se inicializan ciertos atributos de la clase
    foreach ($arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // se devuelve el objeto
    return $this;
  }

  // toString
  public function __toString() {
    // atributos del objeto
    $arrayOfAttributes = \get_object_vars($this);
    // se elimina el atributo de la clase padre
    unset($arrayOfAttributes["arrayOfAttributes"]);
    // cadena JSON del objeto
    return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
  }

  // getter
  public function getArrayOfAttributes() {
    return $this->arrayOfAttributes;
  }

}

Comentarios

  • línea 5: la clase [BaseEntity] está destinada a ser extendida por las clases [Database, TaxAdminData, TaxPayerData];
  • línea 7: el atributo [$arrayOfAttributes] es una matriz que contiene todos los atributos de la clase hija que ha extendido a [BaseEntity], así como sus valores;
  • líneas 9-41: el atributo [$arrayOfAttributes] se inicializa a partir del archivo jSON [$jsonFilename] pasado como parámetro. Se lanza una excepción de tipo [ExceptionImpot] si no se pudo leer el archivo jSON o si no es un archivo jSON válido;
  • líneas 36-38: aquí hay un código especial si lo ejecuta una clase hija. En este caso, [$this] representa una instancia de la clase hija [Database, TaxAdminData, TaxPayerData] y, en ese caso, las líneas 36-38 inicializan los atributos de esta clase hija, siempre y cuando dichos atributos tengan visibilidad «protected» (o «public») (véase el párrafo del enlace). De hecho, se ha dicho que los atributos de las entidades [Database, TaxAdminData, TaxPayerData] eran los mismos que los atributos del archivo jSON que encapsulaban. Finalmente, el método [setFromJsonFile] permite que una clase hija se inicialice a partir de un archivo jSON;
  • línea 40: se devuelve el objeto [$this], es decir, una instancia de una clase hija, si el método [setFromJsonFile] ha sido llamado por una clase hija;
  • líneas 43-51: el método [checkForAllAttributes] permite a una clase hija verificar que todos sus atributos se hayan inicializado. Si no es así, se lanza una excepción [ExceptionImpots]. Este método permite a la clase hija verificar que su archivo jSON no haya omitido ciertos atributos;
  • líneas 53-60: el método [setFromArrayOfAttributes] permite a una clase hija inicializar todos o parte de sus atributos a partir de un array asociativo cuyas claves tienen los mismos nombres que los atributos de la clase hija que se va a inicializar;
  • líneas 63-70: el método [__toString] permite obtener la representación jSON de una clase hija;

13.3.2.2. La entidad [Database]

La entidad [Database] es la siguiente:


<?php

namespace Application;

class Database extends BaseEntity {
  // atributos
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;



}

La clase [Database] se utiliza para encapsular los datos del siguiente archivo jSON [database.json]:


{
    "dsn": "mysql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "tbconstantes",
    "colPlafondQfDemiPart": "plafondQfDemiPart",
    "colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
    "colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
    "colValeurReducDemiPart": "valeurReducDemiPart",
    "colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
    "colPlafondDecoteCouple": "plafondDecoteCouple",
    "colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
    "colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
    "colAbattementDixPourcentMax": "abattementDixPourcentMax",
    "colAbattementDixPourcentMin": "abattementDixPourcentMin"
}

La clase y el archivo jSON tienen los mismos atributos. Estos describen las características de la base de datos MySQL [dbimpots-2019]:

dsn
Nombre de la base de datos DSN
id
Propietario de la base
pwd
Su contraseña
tableTranches
Nombre de la tabla que contiene los tramos de impuestos
colLimites
colCoeffR
colCoeffN
Nombres de las columnas de la tabla [tableTranches]
tableConstantes
Nombre de la tabla que contiene las constantes para el cálculo del impuesto
colPlafondQfDemiPart
colPlafondRevenusCelibatairePourReduction
colPlafondRevenusCouplePourReduction
colValeurReducDemiPart
colPlafondDecoteCelibataire
colPlafondDecoteCouple
colPlafondImpotCelibatairePourDecote
colPlafondImpotCouplePourDecote
colAbattementDixPourcentMax
colAbattementDixPourcentMin
Nombres de las columnas de la tabla [tableConstantes] que contiene las constantes para el cálculo del impuesto

¿Por qué nombrar las tablas y las columnas si ya conocemos sus nombres y no es algo que vaya a cambiar? Después de SGBD y MySQL, vamos a utilizar SGBD y PostgreSQL para almacenar los datos de la administración tributaria. Sin embargo, los nombres de las columnas y tablas de Postgres no siguen las mismas reglas que MySQL. Nos veremos obligados a usar otros nombres. Lo mismo ocurre con otros SGBD. Si queremos que el código sea portable entre SGBD, es preferible utilizar parámetros en lugar de los nombres fijos de las tablas y columnas.

Volvamos al código de la clase [Database]:


<?php

namespace Application;

class Database extends BaseEntity {
  // atributos
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;

  // setter
  // inicialización
  public function setFromJsonFile(string $jsonFilename) {
    // padre
    parent::setFromJsonFile($jsonFilename);
    // se verifica que todos los atributos se hayan inicializado
    parent::checkForAllAttributes();
    // se devuelve el objeto
    return $this;
  }

  // getter y setter
  public function getDsn() {
    return $this->dsn;
  }



  public function setDsn($dsn) {
    $this->dsn = $dsn;
    return $this;
  }



}

Comentarios

  • líneas 7-24: todos los atributos de la clase tienen visibilidad [protected]. Esta es la condición para que puedan modificarse desde la clase padre [BaseEntity] (véase el párrafo sobre enlaces);
  • líneas 28-35: el método [setFromJsonFile] permite inicializar los atributos de la clase [Database] a partir del contenido de un archivo jSON pasado como parámetro. Los atributos del archivo jSON y los de la clase [Database] deben ser idénticos. Si el archivo jSON no es válido, se lanza una excepción;
  • línea 30: es la clase padre la que realiza la inicialización;
  • línea 32: se le pide a la clase padre que verifique que todos los atributos de la clase [Database] se hayan inicializado. Si no es así, se lanza una excepción;
  • línea 34: se devuelve la instancia [Database] que acaba de ser inicializada;
  • líneas 37 y siguientes: los métodos getter y setter de los atributos de la clase;

13.3.2.3. La entidad [TaxAdminData]

La entidad [TaxAdminData] es la siguiente:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // tramos impositivos
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constantes para el cálculo del impuesto
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  
}

La clase [TaxAdminData] se utiliza para encapsular los datos del siguiente archivo jSON [taxadmindata.json]:


{
    "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
}

La clase y el archivo jSON tienen los mismos atributos. Estos representan los datos de la administración tributaria. El resto del código de la clase [TaxAdminData] es el siguiente:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // tramos impositivos
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constantes para el cálculo del impuesto
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  // inicialización
  public function setFromJsonFile(string $taxAdminDataFilename) {
    // padre
    parent::setFromJsonFile($taxAdminDataFilename);
    // se verifica que todos los atributos se hayan inicializado
    parent::checkForAllAttributes();
    // se verifica que los valores de los atributos sean números reales >=0
    foreach ($this as $key => $value) {
      if ($key !== "arrayOfAttributes") {
        // $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;
  }

  protected function check($value): \stdClass {
    // $value es una matriz de elementos de tipo cadena o un único elemento
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // se transforma la matriz de cadenas en una matriz 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;
  }

  // getter y setter

}

Comentarios

  • línea 23: el método [setFromJsonFile] sirve para inicializar los atributos de la clase [TaxAdminData] a partir de un archivo jSON pasado como parámetro. Los atributos del archivo jSON deben existir con el mismo nombre en la clase;
  • línea 25: es la clase padre la que realiza esta tarea;
  • línea 27: se le pide a la clase padre que verifique que todos los atributos de la clase hija se hayan inicializado;
  • líneas 29-42: se verifica localmente que todos los atributos tengan un valor real positivo o nulo. Esta verificación ya se ha discutido en el párrafo «enlace» de la versión 03;

13.3.3. La capa [dao]

Ahora podemos escribir el código que transferirá los datos del archivo de texto [taxadmindata.json] a las tablas [tbtranches, tbconstantes] de la base de datos MySQL [dbimpots-2019]. Adoptaremos la siguiente arquitectura:

Image

Image

La capa [dao] implementará la siguiente interfaz [InterfaceDao4TransferAdminDataFromFile2Database]:


<?php

// espacio de nombres
namespace Application;

interface InterfaceDao4TransferAdminData2Database {

  public function transferAdminData2Database(): void;
}

Comentarios

  • línea 8: el método [transferAdminData2Database] tiene como función almacenar los datos de la administración tributaria en una base de datos;

La interfaz [InterfaceDao4TransferAdminData2Database] será implementada por la siguiente clase [DaoTransferAdminDataFromJsonFile2Database]:


<?php

// espacio de nombres
namespace Application;

// definición de una clase TransferAdminDataFromFile2DatabaseDao
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
  // atributos de la base de datos de destino
  private $database;
  // datos de la administración tributaria
  private $taxAdminData;

  // fabricante
  public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
    // se guarda la configuración de la base de datos
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // se guardan los datos fiscales
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // transfiere los datos de los tramos impositivos desde un archivo de texto
  // a la base de datos
  public function transferAdminData2Database(): void {
    // se trabaja en la base
    $database = $this->database;
    try {
      // se abre la conexión a la base de datos
      $connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
      // se desea que, ante cada error de SGBD, se lance una excepción
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // iniciamos una transacción
      $connexion->beginTransaction();
      // se completa la tabla de tramos impositivos
      $this->fillTableTranches($connexion);
      // se completa la tabla de constantes
      $this->fillTableConstantes($connexion);
      // se finaliza la transacción con éxito
      $connexion->commit();
    } catch (\PDOException $ex) {
      // ¿Hay alguna transacción en curso?
      if (isset($connexion) && $connexion->inTransaction()) {
        // se termina la transacción con un error
        $connexion->rollBack();
      }
      // se devuelve la excepción al código que la invocó
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // se cierra la conexión
      $connexion = NULL;
    }
  }


  // Se completa la tabla de tramos impositivos
  private function fillTableTranches($connexion): void {

  }

  // Se completa la tabla de constantes
  private function fillTableConstantes($connexion): void {

  }

}

Comentarios

Aquí aplicamos lo que aprendimos en el capítulo sobre MySQL.

  • línea 7: la clase [DaoTransferAdminDataFromJsonFile2Database] implementa la interfaz [InterfaceDao4TransferAdminData2Database];
  • línea 9: el atributo [$database] es el objeto de tipo [Database] que encapsula los datos del archivo [database.json];
  • línea 11: el atributo [$taxAdminData] es el objeto de tipo [TaxAdminData] que encapsula los datos del archivo [taxadmindata.json];
  • líneas 14-19: el constructor recibe como parámetros los nombres de los archivos [database.json, taxadmindata.json];
  • línea 16: inicialización del atributo [$database];
  • línea 18: inicialización del atributo [$taxAdminData];
  • línea 23: se implementa el único método de la interfaz [InterfaceDao4TransferAdminData2Database];
  • líneas 26-38: se llenan las tablas [tbtranches, tbconstantes] en dos pasos:
    • línea 34: primero se llena la tabla [tbtranches]. Esto se realiza dentro de una transacción (líneas 32, 38). El método [fillTableTranches] (línea 55) lanza una excepción tan pronto como algo sale mal. En ese caso, la ejecución continúa con el bloque catch / finally de las líneas 39-50;
    • línea 36: se llena la tabla [tbconstantes] de la misma manera mediante el método [fillTableConstantes] (línea 60);
  • líneas 39-47: caso en el que el código ha lanzado una excepción;
  • líneas 41-44: si existe una transacción, se cancela;
  • línea 46: se lanza una excepción de tipo [ExceptionImpots] con el mensaje de la excepción original, que a su vez es de cualquier tipo;
  • líneas 47-50: en la cláusula [finally], se cierra la conexión;

El código del método [fillTableTranches] es el siguiente:


private function fillTableTranches($connexion): void {
    // acceso directo a la base de datos
    $database = $this->database;
    // los datos que se deben insertar en la base de datos
    $limites = $this->taxAdminData->getLimites();
    $coeffR = $this->taxAdminData->getCoeffR();
    $coeffN = $this->taxAdminData->getCoeffN();
    // se vacía la tabla por si hubiera algo en ella
    $statement = $connexion->prepare("delete from " . $database->getTableTranches());
    $statement->execute();
    // se preparan las inserciones
    $sqlInsert = "insert into {$database->getTableTranches()} "
      . "({$database->getColLimites()}, {$database->getColCoeffR()},"
      . " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
    $statement = $connexion->prepare($sqlInsert);
    // ejecutamos la orden preparada con los valores de las escalas de impuestos
    for ($i = 0; $i < count($limites); $i++) {
      $statement->execute([
        "limites" => $limites[$i],
        "coeffR" => $coeffR[$i],
        "coeffN" => $coeffN[$i]]);
    }
  }

Comentarios

  • línea 1: el método [fillTableTranches] recibe como parámetro una conexión abierta. Además, se sabe que se ha iniciado una transacción dentro de esta conexión;
  • líneas 5-7: los valores que se insertarán en la tabla son proporcionados por el atributo [$taxAdminData];
  • líneas 9-10: se elimina el contenido actual de la tabla [tbtranches];
  • líneas 12-15: se prepara la inserción de filas en la tabla. Aquí se utilizan los nombres de las columnas proporcionados por el atributo [$database];
  • líneas 17-22: se ejecuta, tantas veces como sea necesario, la instrucción de inserción preparada en las líneas 12-15;

El código del método [fillTableConstantes] es el siguiente:


private function fillTableConstantes($connexion): void {
    // atajo
    $database = $this->database;
    // se vacía la tabla por si acaso hubiera algo en ella
    $statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
    $statement->execute();
    // se prepara la inserción
    $taxAdminData = $this->taxAdminData;
    $sqlInsert = "insert into {$database->getTableConstantes()}"
      . " ({$database->getColPlafondQfDemiPart()},"
      . " {$database->getColPlafondRevenusCelibatairePourReduction()},"
      . " {$database->getColPlafondRevenusCouplePourReduction()},"
      . " {$database->getColValeurReducDemiPart()},"
      . " {$database->getColPlafondDecoteCelibataire()},"
      . " {$database->getColPlafondDecoteCouple()},"
      . " {$database->getColPlafondImpotCelibatairePourDecote()},"
      . " {$database->getColPlafondImpotCouplePourDecote()},"
      . " {$database->getColAbattementDixPourcentMax()},"
      . " {$database->getColAbattementDixPourcentMin()})"
      . " values ("
      . ":plafondQfDemiPart,"
      . ":plafondRevenusCelibatairePourReduction,"
      . ":plafondRevenusCouplePourReduction,"
      . ":valeurReducDemiPart,"
      . ":plafondDecoteCelibataire,"
      . ":plafondDecoteCouple,"
      . ":plafondImpotCelibatairePourDecote,"
      . ":plafondImpotCouplePourDecote,"
      . ":abattementDixPourcentMax,"
      . ":abattementDixPourcentMin)";
    $statement = $connexion->prepare($sqlInsert);
    // se ejecuta la orden preparada
    $statement->execute([
      "plafondQfDemiPart" => $taxAdminData->getPlafondQfDemiPart(),
      "plafondRevenusCelibatairePourReduction" => $taxAdminData->getPlafondRevenusCelibatairePourReduction(),
      "plafondRevenusCouplePourReduction" => $taxAdminData->getPlafondRevenusCouplePourReduction(),
      "valeurReducDemiPart" => $taxAdminData->getValeurReducDemiPart(),
      "plafondDecoteCelibataire" => $taxAdminData->getPlafondDecoteCelibataire(),
      "plafondDecoteCouple" => $taxAdminData->getPlafondDecoteCouple(),
      "plafondImpotCelibatairePourDecote" => $taxAdminData->getPlafondImpotCelibatairePourDecote(),
      "plafondImpotCouplePourDecote" => $taxAdminData->getPlafondImpotCouplePourDecote(),
      "abattementDixPourcentMax" => $taxAdminData->getAbattementDixPourcentMax(),
      "abattementDixPourcentMin" => $taxAdminData->getAbattementDixPourcentMin()
    ]);
  }

Comentarios

  • línea 1: el método [fillTableConstantes] recibe como parámetro una conexión abierta. Además, se sabe que se ha iniciado una transacción dentro de esta conexión;
  • líneas 5-6: se vacía la tabla [tbconstantes];
  • líneas 9-31: preparación de la orden de inserción SQL. Es compleja debido a que hay 10 columnas que inicializar en esta operación de inserción y es necesario buscar los nombres de las columnas en el atributo [$database];
  • líneas 33-44: ejecución de la orden de inserción. Solo hay una fila que insertar. Una vez más, el código se complica porque hay que buscar los valores a insertar en el atributo [$taxAdminData];

13.3.4. El script principal

Image

Image

El script principal se basa en la capa [dao] para realizar la transferencia de datos:


<?php

// se respetan estrictamente los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// gestión de errores mediante PHP
// ini_set("display_errors", "0");
// Inclusión de la interfaz y las clases
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../Dao/DaoTransferAdminDataFromJsonFile2Database.php";
//
// definición de constantes
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // creación de la capa [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // transferencia de datos a la base de datos
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // se muestra el error
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fin
print "Terminé\n";
exit;

Comentarios

  • líneas 12-21: carga de las clases e interfaces de la aplicación;
  • líneas 24-24: los dos archivos jSON;
  • línea 30: se instancia la capa [dao] pasando al constructor los dos archivos jSON;
  • línea 32: se realiza la transferencia de datos;

Al ejecutar este código, obtenemos el siguiente resultado en la base de datos:

Image

En la columna [3], se ven los valores asignados por MySQL a la clave primaria [id]. La numeración comienza en 1. La captura de pantalla anterior se obtuvo tras varias ejecuciones del script.

Image

Image

13.4. Cálculo del impuesto

Image

13.4.1. Arquitectura

La versión 04 de la aplicación de cálculo de impuestos utilizaba una arquitectura en capas:

Image

La capa [dao] implementa una interfaz [InterfaceDao]. Creamos una clase que implementa esta interfaz:

  • [DaoImpotsWithTaxAdminDataInJsonFile], que obtenía los datos fiscales de un archivo jSON. Esa era la versión 04;

Vamos a implementar la interfaz [InterfaceDao] mediante una nueva clase [DaoImpotsWithTaxAdminDataInDatabase], que obtendrá los datos de la administración tributaria de una base de datos MySQL. La capa [dao], al igual que antes, escribirá los resultados y los errores en archivos de texto y obtendrá los datos de los contribuyentes también de un archivo de texto. Solo que esta vez, esos archivos de texto serán archivos jSON. Además, sabemos que si seguimos respetando la interfaz [InterfaceDao], no será necesario modificar la capa [métier].

Image

13.4.2. La entidad [TaxPayerData]

Image

La clase [TaxPayerData] sirve para encapsular en una clase los datos del siguiente archivo jSON [taxpayersdata.json]:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

La clase [TaxPayerData] es la siguiente:


<?php

// espacio de nombres
namespace Application;

// la clase de datos
class TaxPayerData extends BaseEntity {
  // datos necesarios para el cálculo del impuesto del contribuyente
  protected $marié;
  protected $enfants;
  protected $salaire;
  // resultados del cálculo del impuesto
  protected $impôt;
  protected $surcôte;
  protected $décôte;
  protected $réduction;
  protected $taux;

  // métodos getter y setter

}

Comentarios

  • línea 7: la clase [TaxPayerData] extiende la clase [BaseEntity]. Dado que los métodos de su clase padre son suficientes, la clase [TaxPayerData] no define ninguno propio. Cabe recordar que los atributos de la clase [TaxPayerData] son idénticos a los del archivo jSON [taxpayersdata.json];

13.4.3. La capa [dao]

13.4.3.1. El rasgo [TraitDao]

El rasgo [TraitDao] implementa una parte de la interfaz [InterfaceDao]. Recordemos esta última:


<?php

// espacio de nombres
namespace Application;

interface InterfaceDao {

  // lectura de los datos de los contribuyentes
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // lectura de los datos de la administración tributaria (tramos impositivos)
  public function getTaxAdminData(): TaxAdminData;

  // registro de resultados
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

El rasgo [TraitDao] implementa los métodos [getTaxPayersData, saveResults] de la interfaz [InterfaceDao]. Dado que entre las versiones 04 y 05 se modificó la definición de la entidad [TaxPayerData], debemos revisar el código de [TraitDao]:


<?php

// espacio de nombres
namespace Application;

trait TraitDao {

  // lectura de datos de contribuyentes
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // se recuperan los datos de los contribuyentes en una tabla
    $baseEntity = new BaseEntity();
    $baseEntity->setFromJsonFile($taxPayersFilename);
    $arrayOfAttributes = $baseEntity->getArrayOfAttributes();
    // tabla de datos de los contribuyentes
    $taxPayersData = [];
    // tabla de errores
    $errors = [];
    // recorrido por la tabla de atributos de elementos de tipo [TaxPayerData]
    $i = 0;
    foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
      // verificación
      $error = $this->check($attributesOfTaxPayerData);
      if (!$error) {
        // un contribuyente de +
        $taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
      } else {
        // un error de + - se anota el número del dato inválido
        $error = ["numéro" => $i] + $error;
        $errors[] = $error;
      }
      // siguiente
      $i++;
    }
    // se guardan los errores en un archivo JSON
    $string = "";
    foreach ($errors as $error) {
      $string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
    }
    $this->saveString($errorsFilename, $string);
    // resultado de la función
    return $taxPayersData;
  }

  private function check(array $attributesOfTaxPayerData): array {
    // se verifican los datos de [$taxPayerData]
    // la lista de atributos con errores
    $attributes = [];
    // el estado civil debe ser «sí» o «no»
    $marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
    $erreur = ($marié !== "oui" and $marié !== "non");
    if ($erreur) {
      // se anota el error
      $attributes[] = ["marié" => $marié];
    }
    // el número de hijos debe ser un número entero positivo o cero
    $enfants = trim($attributesOfTaxPayerData["enfants"]);
    if (!preg_match("/^\d+$/", $enfants)) {
      // se anota el error
      $erreur = TRUE;
      $attributes[] = ["enfants" => $enfants];
    } else {
      $enfants = (int) $enfants;
    }

    // el salario debe ser un número entero positivo o cero (sin centavos de euro)
    $salaire = trim($attributesOfTaxPayerData["salaire"]);
    if (!preg_match("/^\d+$/", $salaire)) {
      // se registra el error
      $erreur = TRUE;
      $attributes[] = ["salaire" => $salaire];
    } else {
      $salaire = (int) $salaire;
    }

    // ¿Error?
    if ($erreur) {
      // respuesta con error
      return ["erreurs" => $attributes];
    } else {
      // respuesta sin error
      return [];
    }
  }

  // registro de los resultados
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // almacenamiento de la tabla [$taxPayersData] en el archivo de texto [$resultsFileName]
    // si el archivo de texto [$resultsFileName] no existe, se crea
    // construcción de la cadena jSON con los resultados
    $string = "[" . implode(",
", $taxPayersData) . "]";
    // almacenamiento de esta cadena
    $this->saveString($resultsFilename, $string);
  }

  // almacenamiento de los resultados de una tabla en un archivo de texto
  private function saveString(string $fileName, string $data): void {
    // almacenamiento de la cadena [$data] en el archivo de texto [$fileName]
    // si el archivo de texto [$fileName] no existe, se crea
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Comentarios

  • [TraitDao] implementa los métodos [getTaxPayersData] (línea 9) y [saveResults] (línea 86) de la interfaz [InterfaceDao];
  • línea 9: el método [getTaxPayersData] recibe como parámetros:
    • [$taxPayersFilename]: el nombre del archivo jSON con los datos de los contribuyentes [taxpayersdata.json];
    • [$errorsFilename]: el nombre del archivo jSON de los errores [errors.json];
  • líneas 11-13: el contenido del archivo jSON con los datos de los contribuyentes se transfiere a una tabla asociativa [$arrayOfAttributes]. Si el archivo jSON resulta inutilizable, se ha lanzado una excepción [ExceptionImpots];
  • línea 15: la tabla [$taxPayersData] contendrá los datos de los contribuyentes encapsulados en objetos de tipo [TaxPayerData];
  • línea 17: se acumularán los errores en la tabla [$errors];
  • líneas 99-33: construcción de la tabla [$taxPayersData];
  • línea 22: antes de encapsularse en un tipo [TaxPayerData], se verifican los datos. El método [check] devuelve:
    • un array [‘erreurs’=>[…]] con los atributos erróneos si los datos son incorrectos;
    • un array vacío si los datos son correctos;
  • línea 25: caso en el que los datos son válidos. Se crea un nuevo objeto [TaxPayerData] y se agrega al array [$taxPayersData];
  • líneas 26-30: caso en el que los datos no son válidos. En el error se anota el número del objeto [TaxPayerData] erróneo en el archivo jSON para que el usuario pueda localizarlo; luego, el error se agrega a la tabla [$errors];
  • líneas 35-39: se registran los errores encontrados en el archivo jSON [$errorsFilename] pasado como parámetro, línea 9;
  • línea 41: se devuelve la tabla de objetos [TaxPayerData] creados: ese era el objetivo del método;
  • líneas 44-83: el método privado [check] verifica la validez de los parámetros [marié, enfants, salaire] de la tabla [$attributesOfTaxPayerData] pasada como parámetro en la línea 44. Si hay atributos erróneos, los acumula en la tabla [$attributes] (líneas 47, 53, 60, 70) en forma de una tabla [‘attribut erroné’=> valeur de l’attribut erroné];
  • línea 78: si hay errores, se devuelve una tabla [‘erreurs’=>$attributes];
  • línea 81: si no hay errores, se devuelve una tabla de errores vacía;
  • líneas 86-93: implementación del método [saveResults] de la interfaz [InterfaceDao];
  • línea 90: se construye la cadena jSON para guardarla en el archivo jSON [$resultsFilename] pasado como parámetro en la línea 86. Se debe construir la cadena jSON a partir de un array:
    • cada elemento de la tabla está separado del siguiente por una coma y un salto de línea;
    • todo el array está entre corchetes [];
  • línea 92: la cadena jSON se guarda en el archivo jSON [$resultsFilename];

13.4.3.2. La clase [DaoImpotsWithTaxAdminDataInDatabase]

La clase [DaoImpotsWithTaxAdminDataInDatabase] implementa la interfaz [InterfaceDao] de la siguiente manera:


<?php

// espacio de nombres
namespace Application;

// definición de una clase ImpotsWithDataInDatabase
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
  // uso de un rasgo
  use TraitDao;
  // el objeto de tipo TaxAdminData que contiene los datos de los tramos impositivos
  private $taxAdminData;
  // el objeto de tipo [Database] que contiene las características de BD
  private $database;

  // fabricante
  public function __construct(string $databaseFilename) {
    //: se guarda la configuración jSON de la base de datos
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // se prepara el atributo
    $this->taxAdminData = new TaxAdminData();
    try {
      // se abre la conexión a la base de datos
      $connexion = new \PDO(
        $this->database->getDsn(),
        $this->database->getId(),
        $this->database->getPwd());
      // se desea que, ante cada error de SGBD, se lance una excepción
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // se inicia una transacción
      $connexion->beginTransaction();
      // se completa la tabla de tramos impositivos
      $this->getTranches($connexion);
      // se completa la tabla de constantes
      $this->getConstantes($connexion);
      // se finaliza la transacción con éxito
      $connexion->commit();
    } catch (\PDOException $ex) {
      // ¿Hay alguna transacción en curso?
      if (isset($connexion) && $connexion->inTransaction()) {
        // se termina la transacción con un error
        $connexion->rollBack();
      }
      // se devuelve la excepción al código que la invocó
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // se cierra la conexión
      $connexion = NULL;
    }
  }

  // lectura de los datos de la base de datos
  private function getTranches($connexion): void {

  }

  // lectura de la tabla de constantes
  private function getConstantes($connexion): void {

  }

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

}

Comentarios

  • línea 4: se mantiene el espacio de nombres ya utilizado para las otras implementaciones de la capa [dao];
  • línea 7: la clase [DaoImpotsWithTaxAdminDataInDatabase] implementa la interfaz [InterfaceDao];
  • línea 9: se importa el rasgo [TraitDao]. Sabemos que este rasgo implementa una parte de la interfaz. El único método que queda por implementar es el método [getTaxAdminData] de las líneas 62 a 64. Este método simplemente devuelve el atributo privado [taxAdminData] de la línea 11. De ello se deduce que el constructor deberá inicializar este atributo. Esa es su única función;
  • línea 16: el constructor recibe como único parámetro [$databaseFilename], que es el nombre del archivo jSON [database.json] que define la base de datos MySQL [dbimpots-2019] ;
  • línea 18: el archivo jSON [$databaseFilename] se utiliza para crear un objeto de tipo [Database], el cual se construye y se almacena en el atributo [$database] de la línea 13. Si no se pudo procesar correctamente el archivo jSON, se lanzó una excepción [ExceptionImpots];
  • línea 20: se crea el objeto [$this→taxAdminData], que el constructor debe inicializar;
  • líneas 22-26: se abre la conexión a la base de datos. Obsérvese la notación [\PDO] para referirse a la clase [PDO] de PHP. De hecho, como nos encontramos en el espacio de nombres [Application], si simplemente escribiéramos [PDO], a ese nombre relativo se le añadiría como prefijo el espacio de nombres actual y, por lo tanto, daría como resultado la clase [Application\PDO], que no existe;
  • línea 28: en caso de error, SGBD lanzará un \PDOException (línea 37);
  • línea 30: se inicia una transacción. Esta no es realmente necesaria, ya que solo se ejecutarán dos órdenes SQL, las cuales no modifican la base de datos. No obstante, se hace para aislarnos de los demás usuarios de la base de datos;
  • línea 32: la lectura de la tabla de tramos impositivos [tbtranches] se realiza mediante el método privado [getTranches] de la línea 52;
  • línea 34: la lectura de la tabla de constantes de cálculo [tbconstantes] se realiza mediante el método privado [getConstantes] de la línea 57;
  • línea 36: si llegamos a esta línea, significa que todo ha salido bien. Por lo tanto, validamos la transacción;
  • líneas 37-42: si llegamos hasta aquí, es porque se ha producido una excepción. Por lo tanto, se invalida la transacción si había alguna en curso (líneas 39-42). Línea 44: para que las excepciones sean homogéneas, se vuelve a lanzar el mensaje de la excepción recibida, esta vez en forma de una excepción de tipo [ExceptionImpots];
  • líneas 45-48: en todos los casos (haya o no excepción) se cierra la conexión;

El método [getTranches] es el siguiente:


private function getTranches($connexion): void {
     // atajos
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
     // se prepara la consulta SELECT
    $statement = $connexion->prepare(
      "select {$database->getColLimites()}," .
      " {$database->getColCoeffR()}," .
      " {$database->getColCoeffN()}" .
      " from {$database->getTableTranches()}");
     // se ejecuta la orden preparada con los valores de los tramos impositivos
    $statement->execute();
     // se procesa el resultado
    $limites = [];
    $coeffR = [];
    $coeffN = [];
     // Se completan las tres tablas
    while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
      $limites[] = (float) $tranche->{$database->getColLimites()};
      $coeffR[] = (float) $tranche->{$database->getColCoeffR()};
      $coeffN[] = (float) $tranche->{$database->getColCoeffN()};
    }
    // se guardan los datos en el atributo [$taxAdminData] de la clase
    $taxAdminData->setFromArrayOfAttributes([
      "limites" => $limites,
      "coeffR" => $coeffR,
      "coeffN" => $coeffN
    ]);
  }

Comentarios

  • línea 1: el método recibe como parámetro [$connexion], que es una conexión abierta en la que hay una transacción en curso;
  • líneas 2-4: se crean dos atajos para evitar tener que escribir [$this->database] y [$taxAdminData = $this->taxAdminData] en todo el código. Se trata de copias de referencias a objetos y no de una copia de los objetos mismos;
  • líneas 6-10: se prepara la orden SELECT, que luego se ejecuta en la línea 12;
  • líneas 13-22: se procesa el resultado de SELECT. La información recibida se acumula en tres tablas [limites, coeffR, coeffN];
  • líneas 24-28: los tres tableros se utilizan para inicializar el atributo [$this->taxAdminData] de la clase;

El método privado [getConstantes] es el siguiente:


private function getConstantes($connexion): void {
    // atajos
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
    // se prepara la consulta SELECT
    $select = "select {$database->getColPlafondQfDemiPart()}," .
      "{$database->getColPlafondRevenusCelibatairePourReduction()}," .
      "{$database->getColPlafondRevenusCouplePourReduction()}," . "{$database->getColValeurReducDemiPart()}," .
      "{$database->getColPlafondDecoteCelibataire()}," . "{$database->getColPlafondDecoteCouple()}," .
      "{$database->getColPlafondImpotCelibatairePourDecote()}," . "{$database->getColPlafondImpotCouplePourDecote()}," .
      "{$database->getColAbattementDixPourcentMax()}," . "{$database->getColAbattementDixPourcentMin()}" .
      " from {$database->getTableConstantes()}";
    $statement = $connexion->prepare($select);
    // se ejecuta la orden preparada
    $statement->execute();
    // se procesa el resultado: aquí solo hay una línea
    $row = $statement->fetch(\PDO::FETCH_OBJ);
    // se inicializa el atributo [$taxAdminData]
    $taxAdminData->setPlafondQfDemiPart($row->{$database->getColPlafondQfDemiPart()});
    $taxAdminData->setPlafondRevenusCelibatairePourReduction(
      $row->{$database->getColPlafondRevenusCelibatairePourReduction()});
    $taxAdminData->setPlafondRevenusCouplePourReduction($row->{$database->getColPlafondRevenusCouplePourReduction()});
    $taxAdminData->setValeurReducDemiPart($row->{$database->getColValeurReducDemiPart()});
    $taxAdminData->setPlafondDecoteCelibataire($row->{$database->getColPlafondDecoteCelibataire()});
    $taxAdminData->setPlafondDecoteCouple($row->{$database->getColPlafondDecoteCouple()});
    $taxAdminData->setPlafondImpotCelibatairePourDecote($row->{$database->getColPlafondImpotCelibatairePourDecote()});
    $taxAdminData->setPlafondImpotCouplePourDecote($row->{$database->getColPlafondImpotCouplePourDecote()});
    $taxAdminData->setAbattementDixPourcentMax($row->{$database->getColAbattementDixPourcentMax()});
    $taxAdminData->setAbattementDixPourcentMin($row->{$database->getColAbattementDixPourcentMin()});
  }

Comentarios

  • línea 1: el método recibe como parámetro [$connexion], que es una conexión abierta en la que hay una transacción en curso;
  • líneas 2-4: se crean dos atajos para evitar tener que escribir [$this->database] y [$taxAdminData = $this->taxAdminData] en todo el código. Se trata de copias de referencias a objetos y no de una copia de los objetos mismos;
  • líneas 6-15: se prepara la orden SELECT, y luego se ejecuta en la línea 15;
  • líneas 17-29: se procesa el resultado de SELECT. La información recuperada se utiliza para inicializar el atributo [$this->taxAdminData] de la clase;

Nota: cabe señalar que la clase no depende de SGBD ni de MySQL. Es el código que realiza la llamada el que establece el SGBD utilizado a través del DSN de la base de datos.

13.4.4. La capa [métier]

Image

  • Acabamos de implementar la capa [dao] (3);
  • dado que hemos respetado la interfaz [InterfaceDao], la capa [métier] (2) podría, en teoría, permanecer sin cambios. Sin embargo, no solo hemos modificado la capa [dao]. También hemos modificado las entidades que son compartidas por todas las capas;

La capa [métier] implementa la siguiente interfaz [InterfaceMetier]:


<?php

// espacio de nombres
namespace Application;

interface InterfaceMetier {

  // cálculo de los impuestos de un contribuyente
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // cálculo de impuestos en modo por lotes
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
  • línea 12: el método [executeBatchImpots] ahora utiliza el archivo jSON [$taxPayersFileName], mientras que en la versión 04 era un archivo de texto básico.;

En la versión 04, el método [executeBatchImpots] era el siguiente:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // se permiten excepciones que provienen de la capa [dao]
    // se recuperan los datos de los contribuyentes
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabla de resultados
    $results = [];
    // se procesan
    foreach ($taxPayersData as $taxPayerData) {
      // se calcula el impuesto
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // se completa [$taxPayerData]
      $taxPayerData->setMontant($result["impôt"]);
      $taxPayerData->setDécôte($result["décôte"]);
      $taxPayerData->setSurCôte($result["surcôte"]);
      $taxPayerData->setTaux($result["taux"]);
      $taxPayerData->setRéduction($result["réduction"]);
      // se ingresa el resultado en la tabla de resultados
      $results [] = $taxPayerData;
    }
    // registro de los resultados
    $this->dao->saveResults($resultsFileName, $results);
  }
  • la línea 15 ahora está incorrecta. En la nueva definición de la clase [TaxPayerData], el método [setMontant] ya no existe;

En la versión 05, el método [executeBatchImpots] será el siguiente:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // se permiten las excepciones que provienen de la capa [dao]
    // se recuperan los datos de los contribuyentes
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabla de resultados
    $results = [];
    // se analizan
    foreach ($taxPayersData as $taxPayerData) {
      // se calcula el impuesto
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // se completa [$taxPayerData]
      $taxPayerData->setFromArrayOfAttributes($result);
      // se ingresa el resultado en la tabla de resultados
      $results [] = $taxPayerData;
    }
    // registro de los resultados
    $this->dao->saveResults($resultsFileName, $results);
  }

Comentarios

  • línea 15: en lugar de utilizar los setters individuales de la clase [TaxPayerData], se utiliza su setter global [setFromArrayOfAttributes];
  • el resto del código no necesita modificarse;

13.4.5. El script principal

Image

  • Acabamos de implementar las capas [dao] (3) y [métier] (2);
  • ahora nos queda escribir el script principal (1);

El script principal es similar al de la versión 04:


<?php

// Se respetan estrictamente los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// gestión de errores mediante PHP
//ini_set("display_errors", "0");
// Inclusión de la interfaz y las clases
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../Métier/InterfaceMetier.php";
require_once __DIR__ . "/../Métier/Metier.php";
//
// definición de constantes
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
const RESULTS_FILENAME = "../Data/resultats.json";
const ERRORS_FILENAME = "../Data/errors.json";
const TAXPAYERSDATA_FILENAME = "../Data/taxpayersdata.json";

try {
  // creación de la capa [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // creación de la capa [métier]
  $métier = new Metier($dao);
  // cálculo de impuestos en modo por lotes
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // se muestra el error
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fin
print "Terminé\n";
exit;


Comentarios

  • líneas 12-22: carga de todos los archivos de la versión 05;
  • líneas 25-29: los nombres de los distintos archivos jSON de la aplicación;
  • línea 33: construcción de la capa [dao];
  • línea 35: construcción de la capa [métier];
  • línea 37: llamada al método [executeBatchImpots] de la capa [métier];

Resultados

La aplicación genera dos archivos jSON:

  • [resultats.json]: los resultados de los distintos cálculos de impuestos;
  • [errors.json]: que señala los errores encontrados en los archivos jSON y [taxpayersdata.json];

El archivo [errors.json] es el siguiente:


{
    "numéro": 1,
    "erreurs": [
        {
            "marié": "ouix"
        },
        {
            "enfants": "2x"
        },
        {
            "salaire": "55555x"
        }
    ]
}

Esto significa que en [taxpayersdata.json], el elemento n.º 1 de la tabla de contribuyentes es incorrecto. El archivo [taxpayersdata.json] era el siguiente:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

El archivo de resultados [resultats.json] es el siguiente:


[
    {
        "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é": "non",
        "enfants": 3,
        "salaire": 100000,
        "impôt": 16782,
        "surcôte": 7176,
        "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é": "oui",
        "enfants": 5,
        "salaire": 100000,
        "impôt": 4230,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "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": 20000,
        "impôt": 0,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0
    }
]

Estos resultados coinciden con los de la versión 04.

13.5. Pruebas [Codeception]

Al igual que se hizo en el párrafo sobre la versión 04, vamos a escribir pruebas [Codeception] para la versión 05.

Image

13.5.1. Prueba de la capa [dao]

La prueba [DaoTest.php] es la siguiente:


<?php

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

// espacio de nombres
namespace Application;

// directorios raíz
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// Inclusión de interfaces y clases
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// bibliotecas de terceros
require_once VENDOR . "/autoload.php";

// definición de constantes
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";

class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;

  public function __construct() {
    parent::__construct();
    // creación de la capa [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // pruebas
  public function testTaxAdminData() {
    // constantes de cálculo
    $this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());

  }

}

Comentarios

  • líneas 9-33: definición del entorno de la prueba. Utilizamos el mismo que el utilizado por el script principal [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] descrito en el párrafo del enlace;
  • líneas 39-44: construcción de la capa [dao];
  • línea 43: el atributo [$this→taxAdminData] contiene los datos a probar;
  • líneas 47-51: el método [testTaxAdminData] es el descrito en el párrafo del enlace;

Los resultados de la prueba son los siguientes:

Image

13.5.2. Prueba de la capa [métier]

La prueba [MetierTest.php] es la siguiente:


<?php

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

// espacio de nombres
namespace Application;

// directorios raíz
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusión de interfaces y clases
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// bibliotecas de terceros
require_once VENDOR . "/autoload.php";

// definición de constantes
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";

class MetierTest extends \Codeception\Test\Unit {
  // capa de negocio
  private $métier;

  public function __construct() {
    parent::__construct();
    // creación de la capa [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // creación de la capa [métier]
    $this->métier = new Metier($dao);
  }

  // pruebas
  public function test1() {
    $result = $this->métier->calculerImpot("oui", 2, 55555);
    $this->assertEqualsWithDelta(2815, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.14, $result["taux"]);
  }
…………………………………………………………………………………………………………………..
public function test11() {
    $result = $this->métier->calculerImpot("oui", 3, 200000);
    $this->assertEqualsWithDelta(42842, $result["impôt"], 1);
    $this->assertEqualsWithDelta(17283, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.41, $result["taux"]);
  }

}

Comentarios

  • líneas 9-33: definición del entorno de prueba. Utilizamos el mismo que el utilizado por el script principal [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] descrito en el párrafo del enlace;
  • líneas 39-45: construcción de las capas [dao] y [métier];
  • línea 44: el atributo [$this→métier] hace referencia a la capa [métier];
  • líneas 47-64: los métodos [test1, test2…, test11] son los descritos en el párrafo enlace;

Los resultados de la prueba son los siguientes:

Image