Skip to content

13. Exercício prático – versão 5

Image

Já escrevemos várias versões deste exercício. A última versão utilizava uma arquitetura em camadas:

Image

A camada [dao] implementa uma interface [InterfaceDao]. Criamos uma classe que implementa essa interface:

  • [DaoImpotsWithTaxAdminDataInJsonFile], que buscava os dados fiscais em um arquivo jSON;

Vamos implementar a interface [InterfaceDao] por meio de uma nova classe [DaoImpotsWithTaxAdminDataInDatabase], que irá buscar os dados da administração fiscal em um banco de dados MySQL.

13.1. Criação do banco de dados [dbimpots-2019]

Seguindo o exemplo do parágrafo com o link, criamos um banco de dados MySQL denominado [dbimpots-2019], cujo proprietário será [admimpots] com a senha [mdpimpots]:

Image

  • no [1-4] acima, vemos o banco de dados [dbimpots-2019], que, por enquanto, não possui tabelas;

Image

  • no [1-5] acima, vemos que o usuário [admimpots] possui todos os direitos sobre o banco de dados [dbimpots-2019]. O que não vemos aqui é que esse usuário tem a senha [admimpots];

Agora criamos a tabela [tbtranches], que conterá as faixas de tributação:

Image

  • em [1-7], criamos uma tabela chamada [tbtranches] com 4 colunas;

Image

  • em [3-6], definimos uma coluna chamada [id] (3), do tipo inteiro [int] (4), que será a chave primária [6] da tabela e será autoincrementada [5] pelo SGBD. Isso significa que MySQL irá gerenciar por conta própria os valores da chave primária no momento das inserções. Ele atribuirá o valor 1 à chave primária da primeira inserção, depois 2 à seguinte, e assim por diante;
  • no [7], o assistente nos oferece outras opções de configuração da chave primária. Aqui, basta confirmar os valores padrão no [7];

Image

  • no [8-16], definem-se as outras três colunas da tabela:
    • [limites] (8), do tipo número decimal (9) com 10 dígitos, dos quais 2 decimais (10), conterá os elementos da coluna 17 das faixas de imposto;
    • [coeffR] (11), do tipo número decimal (12) com 6 dígitos, dos quais 2 decimais (13), conterá os elementos da coluna 18 das faixas de imposto;
    • [coeffN] (14), do tipo número decimal (15) com 10 dígitos, dos quais 2 são decimais (16), conterá os elementos da coluna 19 das faixas de imposto;

Após validar essa estrutura, obtemos o seguinte resultado:

Image

  • em [5], o ícone da chave indica que a coluna [id] é a chave primária. Observa-se também que essa chave primária possui valores inteiros (6) e que é gerenciada (autoincrementada) por MySQL;

Da mesma forma que criamos a tabela [tbtranches], criamos a tabela [tbconstantes], que conterá as constantes do cálculo do imposto:

Image

É possível exportar a estrutura do banco de dados para um arquivo de texto na forma de uma sequência de comandos SQL:

Image

A opção [5] exporta, neste caso, apenas a estrutura do banco de dados e não seu conteúdo. No nosso caso, o banco de dados ainda não possui conteúdo.

Image

Image

Image

A opção [11] gera o seguinte arquivo SQL [dbimpots-2019.sql]:


-- phpMyAdmin SQL Dump
-- versão 4.8.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Hora de geração: 30 de junho de 2019 às 01:10 PM
-- Versão do servidor: 5.7.24
-- PHP Versão: 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 */;

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

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

--
-- Estrutura da tabela `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;

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

--
-- Estrutura da tabela `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 das tabelas exportadas
--

--
-- Índices da tabela `tbconstantes`
--
ALTER TABLE `tbconstantes`
  ADD PRIMARY KEY (`id`);

--
-- Índices da tabela `tbtranches`
--
ALTER TABLE `tbtranches`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT para tabelas exportadas
--

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

--
-- AUTO_INCREMENT para a tabela `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 */;

Você pode usar este arquivo SQL para regenerar o banco de dados [dbimpots-2019] caso ele tenha sido destruído ou alterado. Não é necessário excluir o banco de dados antes de regenerá-lo, pois o script SQL se encarrega de fazer isso por conta própria:

Image

Image

13.2. Organização do código

Para ilustrar melhor a função dos diferentes scripts PHP que estamos escrevendo, vamos organizar nosso código em pastas:

Image

  • em [1], visão geral da versão 05;
  • em [2], as entidades do aplicativo, entidades trocadas entre camadas;
  • em [3], os utilitários do aplicativo;
  • em [4], os dados utilizados ou produzidos pela aplicação. Decidimos aqui utilizar apenas arquivos jSON para os arquivos de texto. Estes apresentam várias vantagens:
    • são reconhecidos por muitas ferramentas;
    • essas ferramentas possuem destaque de sintaxe. Além disso, a notação jSON segue regras específicas. Quando essas regras não são respeitadas, as ferramentas as sinalizam. Por exemplo, um erro difícil de detectar em um arquivo de texto básico é o uso de letras O maiúsculas ou minúsculas no lugar de zeros. Se esse erro ocorrer, ele será sinalizado. De fato, no código jSON:

"plafondRevenusCouplePourReduction": 42O74

onde, por engano, colocamos um “O” maiúsculo no lugar do zero em [42074], o NetBeans sinaliza o erro:

Image

De fato, o NetBeans reconhece o O maiúsculo, o que transforma [49O74] em uma sequência de caracteres. Ele conclui que a sintaxe deveria ser [4-5]: a sequência [47O74] deveria estar entre aspas. A atenção do desenvolvedor é, portanto, chamada para o erro, e ele pode corrigi-lo: seja colocando as aspas, seja substituindo o O por um zero;

Os demais elementos da versão 05 são os seguintes:

Image

  • em [6], as interfaces e classes da camada [Dao];
  • em [7], as interfaces e classes da camada [métier];
  • em [8], os scripts principais da versão 05;

A versão 05 tem dois objetivos distintos:

  • preencher a base MySQL [dbimpots-2019] com o conteúdo do arquivo jSON [Data/txadmindata.json];
  • implementar o cálculo do imposto com dados fiscais provenientes, a partir de agora, do banco de dados MySQL [dbimpots-2019];

Vamos tratar esses dois objetivos separadamente.

13.3. Preenchimento do banco de dados [dbimpots-2019]

13.3.1. Objetivo

O arquivo de texto taxadmindata.json contém os dados da administração fiscal:


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

Nosso objetivo é transferir esses dados para a base de dados MySQL [dbimpots-2019] criada anteriormente.

13.3.2. As entidades

Image

A entidade [Database] servirá para encapsular os dados do seguinte arquivo 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"
}

A entidade [TaxAdminData] servirá para encapsular os dados dos seguintes arquivos: jSON e [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
}

A entidade [TaxPayerData] servirá para encapsular os dados dos seguintes arquivos: jSON e [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. A classe base [BaseEntity]

Para simplificar o código das entidades, adotaremos a seguinte regra: os atributos de uma entidade têm os mesmos nomes que os atributos do arquivo jSON que a entidade deve encapsular. Com base nessa regra, as entidades [Database, TaxAdminData, TaxPayerData] apresentam pontos em comum que podem ser fatorizados em uma classe pai. Essa será a seguinte classe [BaseEntity]:


<?php

namespace Application;

class BaseEntity {
  // atributo
  protected $arrayOfAttributes;

  // inicialização a partir de um arquivo jSON
  public function setFromJsonFile(string $jsonFilename) {
    // recupera-se o conteúdo do arquivo de dados fiscais
    $fileContents = \file_get_contents($jsonFilename);
    $erreur = FALSE;
    // erro?
    if (!$fileContents) {
      // registra-se o erro
      $erreur = TRUE;
      $message = "Le fichier des données [$jsonFilename] n'existe pas";
    }
    if (!$erreur) {
      // recupera-se o código jSON do arquivo de configuração em uma tabela associativa
      $this->arrayOfAttributes = \json_decode($fileContents, true);
      // erro?
      if ($this->arrayOfAttributes === FALSE) {
        // registra-se o erro
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$jsonFilename] n'a pu être exploité correctement";
      }
    }
    // erro?
    if ($erreur) {
      // lança-se uma exceção
      throw new ExceptionImpots($message);
    }
    // inicialização dos atributos da classe
    foreach ($this->arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // retornando o objeto
    return $this;
  }

  public function checkForAllAttributes() {
    // verifica-se se todas as chaves foram inicializadas
    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) {
    // inicializam-se alguns atributos da classe
    foreach ($arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // retornando o objeto
    return $this;
  }

  // toString
  public function __toString() {
    // atributos do objeto
    $arrayOfAttributes = \get_object_vars($this);
    // remove-se o atributo da classe pai
    unset($arrayOfAttributes["arrayOfAttributes"]);
    // cadeia JSON do objeto
    return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
  }

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

}

Comentários

  • linha 5: a classe [BaseEntity] destina-se a ser estendida pelas classes [Database, TaxAdminData, TaxPayerData];
  • linha 7: o atributo [$arrayOfAttributes] é uma matriz que contém todos os atributos da classe filha que estendeu [BaseEntity], bem como seus valores;
  • linhas 9-41: o atributo [$arrayOfAttributes] é inicializado a partir do arquivo jSON [$jsonFilename] passado como parâmetro. Uma exceção do tipo [ExceptionImpot] é lançada se o arquivo jSON não puder ser lido ou se não for um arquivo jSON válido;
  • linhas 36-38: trata-se de um código especial caso seja executado por uma classe filha. Nesse caso, [$this] representa uma instância da classe filha [Database, TaxAdminData, TaxPayerData] e, nesse caso, as linhas 36-38 inicializam os atributos dessa classe filha, desde que esses atributos tenham visibilidade protected (ou public) (ver parágrafo com link). De fato, foi dito que os atributos das entidades [Database, TaxAdminData, TaxPayerData] eram os mesmos que os atributos do arquivo jSON que elas encapsulavam. Por fim, o método [setFromJsonFile] permite que uma classe filha seja inicializada a partir de um arquivo jSON;
  • linha 40: retorna o objeto [$this], ou seja, uma instância de uma classe filha, se o método [setFromJsonFile] tiver sido chamado por uma classe filha;
  • linhas 43-51: o método [checkForAllAttributes] permite que uma classe filha verifique se todos os seus atributos foram inicializados. Caso contrário, é lançada uma exceção [ExceptionImpots]. Esse método permite que a classe filha verifique se seu arquivo jSON não omitiu alguns atributos;
  • linhas 53-60: o método [setFromArrayOfAttributes] permite que uma classe filha inicialize todos ou parte de seus atributos a partir de um array associativo cujas chaves têm os mesmos nomes que os atributos da classe filha a ser inicializada;
  • linhas 63-70: o método [__toString] permite obter a representação jSON de uma classe filha;

13.3.2.2. A entidade [Database]

A entidade [Database] é a seguinte:


<?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;



}

A classe [Database] é utilizada para encapsular os dados do arquivo jSON [database.json] a seguir:


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

A classe e o arquivo jSON possuem os mesmos atributos. Estes descrevem as características do banco de dados MySQL [dbimpots-2019]:

dsn
Nome do banco de dados DSN
id
Proprietário do banco de dados
pwd
Sua senha
tableTranches
Nome da tabela que contém as faixas de tributação
colLimites
colCoeffR
colCoeffN
Nomes das colunas da tabela [tableTranches]
tableConstantes
Nome da tabela que contém as constantes de cálculo do imposto
colPlafondQfDemiPart
colPlafondRevenusCelibatairePourReduction
colPlafondRevenusCouplePourReduction
colValeurReducDemiPart
colPlafondDecoteCelibataire
colPlafondDecoteCouple
colPlafondImpotCelibatairePourDecote
colPlafondImpotCouplePourDecote
colAbattementDixPourcentMax
colAbattementDixPourcentMin
Nomes das colunas da tabela [tableConstantes] que contêm as constantes de cálculo do imposto

Por que nomear as tabelas e as colunas se já sabemos seus nomes e isso não é algo que venha a mudar? Após o SGBD e o MySQL, vamos utilizar o SGBD e o PostgreSQL para armazenar os dados da administração fiscal. No entanto, os nomes das colunas e tabelas do Postgres não seguem as mesmas regras do MySQL. Seremos obrigados a usar outros nomes. Isso também se aplica a outros SGBD. Se quisermos ter um código portável entre SGBD, é preferível usar parâmetros em vez dos nomes fixos das tabelas e colunas.

Voltemos ao código da classe [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
  // inicialização
  public function setFromJsonFile(string $jsonFilename) {
    // pai
    parent::setFromJsonFile($jsonFilename);
    // verifica-se se todos os atributos foram inicializados
    parent::checkForAllAttributes();
    // retorna o objeto
    return $this;
  }

  // getters e setters
  public function getDsn() {
    return $this->dsn;
  }



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



}

Comentários

  • linhas 7-24: todos os atributos da classe têm visibilidade [protected]. Essa é a condição para que possam ser modificados a partir da classe pai [BaseEntity] (ver parágrafo sobre links);
  • linhas 28-35: o método [setFromJsonFile] permite inicializar os atributos da classe [Database] a partir do conteúdo de um arquivo jSON passado como parâmetro. É necessário que os atributos do arquivo jSON e os da classe [Database] sejam idênticos. Se o arquivo jSON não for utilizável, uma exceção é lançada;
  • linha 30: é a classe pai que realiza a inicialização;
  • linha 32: solicita-se à classe pai que verifique se todos os atributos da classe [Database] foram inicializados. Caso contrário, é lançada uma exceção;
  • linha 34: retorna-se a instância [Database] que acaba de ser inicializada;
  • linhas 37 e seguintes: os getters e setters dos atributos da classe;

13.3.2.3. A entidade [TaxAdminData]

A entidade [TaxAdminData] é a seguinte:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // faixas de imposto
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constantes para cálculo do imposto
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  
}

A classe [TaxAdminData] é utilizada para encapsular os dados do arquivo jSON [taxadmindata.json] a seguir:


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

A classe e o arquivo jSON possuem os mesmos atributos. Estes representam os dados da administração fiscal. O restante do código da classe [TaxAdminData] é o seguinte:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // faixas de imposto
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constantes de cálculo do imposto
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  // inicialização
  public function setFromJsonFile(string $taxAdminDataFilename) {
    // pai
    parent::setFromJsonFile($taxAdminDataFilename);
    // verifica-se se todos os atributos foram inicializados
    parent::checkForAllAttributes();
    // verifica-se se os valores dos atributos são números reais >=0
    foreach ($this as $key => $value) {
      if ($key !== "arrayOfAttributes") {
        // $value deve ser um número real >=0 ou uma matriz de números reais >=0
        $result = $this->check($value);
        // erro?
        if ($result->erreur) {
          // lança-se uma exceção
          throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
        } else {
          // registramos o valor
          $this->$key = $result->value;
        }
      }
    }
    // retornamos o objeto
    return $this;
  }

  protected function check($value): \stdClass {
    // $value é uma matriz de elementos do tipo string ou um único elemento
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // transforma-se a matriz de strings em uma matriz de números reais
    $newTableau = [];
    $result = new \stdClass();
    // os elementos da matriz devem ser números decimais positivos ou nulos
    $modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
    for ($i = 0; $i < count($tableau); $i ++) {
      if (preg_match($modèle, $tableau[$i])) {
        // coloca-se o float em newTableau
        $newTableau[] = (float) $tableau[$i];
      } else {
        // registra-se o erro
        $result->erreur = TRUE;
        // sai do programa
        return $result;
      }
    }
    // retornamos o resultado
    $result->erreur = FALSE;
    if (!\is_array($value)) {
      // um único valor
      $result->value = $newTableau[0];
    } else {
      // uma lista de valores
      $result->value = $newTableau;
    }
    return $result;
  }

  // getters e setters

}

Comentários

  • linha 23: o método [setFromJsonFile] serve para inicializar os atributos da classe [TaxAdminData] a partir de um arquivo jSON passado como parâmetro. É necessário que os atributos do arquivo jSON existam com o mesmo nome na classe;
  • linha 25: é a classe pai que realiza essa tarefa;
  • linha 27: solicita-se à classe pai que verifique se todos os atributos da classe filha foram inicializados;
  • linhas 29-42: verifica-se localmente se todos os atributos receberam um valor real positivo ou nulo. Essa verificação já foi discutida no parágrafo “link” da versão 03;

13.3.3. A camada [dao]

Agora podemos escrever o código que transferirá os dados do arquivo de texto [taxadmindata.json] para as tabelas [tbtranches, tbconstantes] do banco de dados MySQL [dbimpots-2019]. Adotaremos a seguinte arquitetura:

Image

Image

A camada [dao] implementará a seguinte interface [InterfaceDao4TransferAdminDataFromFile2Database]:


<?php

// espaço de nomes
namespace Application;

interface InterfaceDao4TransferAdminData2Database {

  public function transferAdminData2Database(): void;
}

Comentários

  • linha 8: o método [transferAdminData2Database] tem como função armazenar os dados da administração fiscal em um banco de dados;

A interface [InterfaceDao4TransferAdminData2Database] será implementada pela seguinte classe [DaoTransferAdminDataFromJsonFile2Database]:


<?php

// espaço de nomes
namespace Application;

// definição de uma classe TransferAdminDataFromFile2DatabaseDao
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
  // atributos do banco de dados de destino
  private $database;
  // dados da administração fiscal
  private $taxAdminData;

  // fabricante
  public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
    // a configuração do banco de dados é armazenada
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // os dados fiscais são armazenados
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // transfere os dados das faixas de imposto de um arquivo de texto
  // para o banco de dados
  public function transferAdminData2Database(): void {
    // trabalha-se com o banco de dados
    $database = $this->database;
    try {
      // abre-se a conexão com o banco de dados
      $connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
      // deseja-se que, a cada erro do SGBD, seja lançada uma exceção
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // iniciamos uma transação
      $connexion->beginTransaction();
      // preenche-se a tabela de faixas de imposto
      $this->fillTableTranches($connexion);
      // preenche-se a tabela de constantes
      $this->fillTableConstantes($connexion);
      // a transação é encerrada com sucesso
      $connexion->commit();
    } catch (\PDOException $ex) {
      // há alguma transação em andamento?
      if (isset($connexion) && $connexion->inTransaction()) {
        // encerra-se a transação com falha
        $connexion->rollBack();
      }
      // a exceção é repassada ao código chamador
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // a conexão é encerrada
      $connexion = NULL;
    }
  }


  // preenchimento da tabela de faixas de imposto
  private function fillTableTranches($connexion): void {

  }

  // preenchimento da tabela de constantes
  private function fillTableConstantes($connexion): void {

  }

}

Comentários

Aqui, aplicamos o que aprendemos no capítulo sobre MySQL.

  • linha 7: a classe [DaoTransferAdminDataFromJsonFile2Database] implementa a interface [InterfaceDao4TransferAdminData2Database];
  • linha 9: o atributo [$database] é o objeto do tipo [Database] que encapsula os dados do arquivo [database.json];
  • linha 11: o atributo [$taxAdminData] é o objeto do tipo [TaxAdminData] que encapsula os dados do arquivo [taxadmindata.json];
  • linhas 14-19: o construtor recebe como parâmetros os nomes dos arquivos [database.json, taxadmindata.json];
  • linha 16: inicialização do atributo [$database];
  • linha 18: inicialização do atributo [$taxAdminData];
  • linha 23: implementa-se o único método da interface [InterfaceDao4TransferAdminData2Database];
  • linhas 26-38: preenche-se a tabela [tbtranches, tbconstantes] em duas etapas:
    • linha 34: primeiro, preenche-se a tabela [tbtranches]. Isso é feito dentro de uma transação (linhas 32, 38). O método [fillTableTranches] (linha 55) lança uma exceção assim que algo dá errado. Nesse caso, a execução continua com o bloco catch / finally das linhas 39-50;
    • linha 36: preenche-se a tabela [tbconstantes] da mesma forma, utilizando o método [fillTableConstantes] (linha 60);
  • linhas 39-47: caso em que uma exceção tenha sido lançada pelo código;
  • linhas 41-44: se houver uma transação, ela é cancelada;
  • linha 46: é lançada uma exceção do tipo [ExceptionImpots] com a mensagem da exceção original, que, por sua vez, é de um tipo qualquer;
  • linhas 47-50: na cláusula [finally], a conexão é encerrada;

O código do método [fillTableTranches] é o seguinte:


private function fillTableTranches($connexion): void {
    // atalho para o banco de dados
    $database = $this->database;
    // os dados a serem inseridos no banco de dados
    $limites = $this->taxAdminData->getLimites();
    $coeffR = $this->taxAdminData->getCoeffR();
    $coeffN = $this->taxAdminData->getCoeffN();
    // esvaziamos a tabela caso haja algo nela
    $statement = $connexion->prepare("delete from " . $database->getTableTranches());
    $statement->execute();
    // prepara-se as inserções
    $sqlInsert = "insert into {$database->getTableTranches()} "
      . "({$database->getColLimites()}, {$database->getColCoeffR()},"
      . " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
    $statement = $connexion->prepare($sqlInsert);
    // executa-se o comando preparado com os valores das faixas de imposto
    for ($i = 0; $i < count($limites); $i++) {
      $statement->execute([
        "limites" => $limites[$i],
        "coeffR" => $coeffR[$i],
        "coeffN" => $coeffN[$i]]);
    }
  }

Comentários

  • linha 1: o método [fillTableTranches] recebe como parâmetro uma conexão aberta. Sabe-se, além disso, que uma transação foi iniciada nessa conexão;
  • linhas 5-7: os valores a serem inseridos na tabela são fornecidos pelo atributo [$taxAdminData];
  • linhas 9-10: o conteúdo atual da tabela [tbtranches] é excluído;
  • linhas 12-15: prepara-se a inserção de linhas na tabela. Aqui, utilizam-se os nomes das colunas fornecidos pelo atributo [$database];
  • linhas 17-22: executa-se, quantas vezes forem necessárias, a instrução de inserção preparada nas linhas 12-15;

O código do método [fillTableConstantes] é o seguinte:


private function fillTableConstantes($connexion): void {
    // atalho
    $database = $this->database;
    // esvaziamos a tabela, caso haja algo nela
    $statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
    $statement->execute();
    // prepara-se a inserção
    $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);
    // executa-se a ordem 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()
    ]);
  }

Comentários

  • linha 1: o método [fillTableConstantes] recebe como parâmetro uma conexão aberta. Sabe-se, além disso, que uma transação foi iniciada nessa conexão;
  • linhas 5-6: a tabela [tbconstantes] é esvaziada;
  • linhas 9-31: preparação da ordem de inserção SQL. Ela é complexa, pois há 10 colunas a serem inicializadas nessa operação de inserção e é necessário buscar os nomes das colunas no atributo [$database];
  • linhas 33-44: execução da ordem de inserção. Há apenas uma linha a ser inserida. Mais uma vez, o código se torna complexo devido à necessidade de buscar os valores a serem inseridos no atributo [$taxAdminData];

13.3.4. O script principal

Image

Image

O script principal utiliza a camada [dao] para realizar a transferência de dados:


<?php

// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);

// espaço de nomes
namespace Application;

// gerenciamento de erros por PHP
// ini_set("display_errors", "0");
// inclusão de interface e classes
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";
//
// definição de constantes
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // criação da camada [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // transferência de dados para o banco de dados
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // exibição do erro
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fim
print "Terminé\n";
exit;

Comentários

  • linhas 12-21: carregamento das classes e interfaces do aplicativo;
  • linhas 24-24: os dois arquivos jSON;
  • linha 30: instanciamos a camada [dao], passando ao construtor os dois arquivos jSON;
  • linha 32: realiza-se a transferência de dados;

Ao executarmos esse código, obtemos o seguinte resultado no banco de dados:

Image

Na coluna [3], vemos os valores atribuídos por MySQL à chave primária [id]. A numeração começa em 1. A captura de tela acima foi obtida após várias execuções do script.

Image

Image

13.4. Cálculo do imposto

Image

13.4.1. Arquitetura

A versão 04 do aplicativo de cálculo de impostos utilizava uma arquitetura em camadas:

Image

A camada [dao] implementa uma interface [InterfaceDao]. Criamos uma classe que implementa essa interface:

  • [DaoImpotsWithTaxAdminDataInJsonFile], que buscava os dados fiscais em um arquivo jSON. Essa era a versão 04;

Vamos implementar a interface [InterfaceDao] por meio de uma nova classe [DaoImpotsWithTaxAdminDataInDatabase], que irá buscar os dados da administração fiscal em um banco de dados MySQL. A camada [dao], assim como anteriormente, gravará os resultados e os erros em arquivos de texto e buscará os dados dos contribuintes também em um arquivo de texto. Só que, desta vez, esses arquivos de texto serão arquivos jSON. Além disso, sabemos que, se continuarmos a respeitar a interface [InterfaceDao], a camada [métier] não precisará ser alterada.

Image

13.4.2. A entidade [TaxPayerData]

Image

A classe [TaxPayerData] serve para encapsular em uma classe os dados do arquivo jSON [taxpayersdata.json] a seguir:


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

A classe [TaxPayerData] é a seguinte:


<?php

// espaço de nomes
namespace Application;

// a classe dos dados
class TaxPayerData extends BaseEntity {
  // dados necessários para o cálculo do imposto do contribuinte
  protected $marié;
  protected $enfants;
  protected $salaire;
  // resultados do cálculo do imposto
  protected $impôt;
  protected $surcôte;
  protected $décôte;
  protected $réduction;
  protected $taux;

  // getters e setters

}

Comentários

  • linha 7: a classe [TaxPayerData] estende a classe [BaseEntity]. Como os métodos de sua classe pai são suficientes, a classe [TaxPayerData] não define nenhum método próprio. Vale lembrar que os atributos da classe [TaxPayerData] são idênticos aos do arquivo jSON [taxpayersdata.json];

13.4.3. A camada [dao]

13.4.3.1. A característica [TraitDao]

O traço [TraitDao] implementa uma parte da interface [InterfaceDao]. Vale lembrar que esta é:


<?php

// espaço de nomes
namespace Application;

interface InterfaceDao {

  // leitura dos dados do contribuinte
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // leitura dos dados da administração tributária (faixas de imposto)
  public function getTaxAdminData(): TaxAdminData;

  // gravação dos resultados
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

A classe [TraitDao] implementa os métodos [getTaxPayersData, saveResults] da interface [InterfaceDao]. Como, entre as versões 04 e 05, a definição da entidade [TaxPayerData] foi alterada, precisamos revisar o código de [TraitDao]:


<?php

// espaço de nomes
namespace Application;

trait TraitDao {

  // leitura dos dados dos contribuintes
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // os dados dos contribuintes são recuperados em uma tabela
    $baseEntity = new BaseEntity();
    $baseEntity->setFromJsonFile($taxPayersFilename);
    $arrayOfAttributes = $baseEntity->getArrayOfAttributes();
    // tabela de dados dos contribuintes
    $taxPayersData = [];
    // tabela de erros
    $errors = [];
    // iteração pela tabela de atributos de elementos do tipo [TaxPayerData]
    $i = 0;
    foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
      // verificação
      $error = $this->check($attributesOfTaxPayerData);
      if (!$error) {
        // um contribuinte com saldo positivo
        $taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
      } else {
        // um erro de + - registra-se o número do dado inválido
        $error = ["numéro" => $i] + $error;
        $errors[] = $error;
      }
      // próximo
      $i++;
    }
    // os erros são salvos em um arquivo JSON
    $string = "";
    foreach ($errors as $error) {
      $string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
    }
    $this->saveString($errorsFilename, $string);
    // resultado da função
    return $taxPayersData;
  }

  private function check(array $attributesOfTaxPayerData): array {
    // verifica-se os dados de [$taxPayerData]
    // a lista de atributos com erros
    $attributes = [];
    // o estado civil deve ser “sim” ou “não”
    $marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
    $erreur = ($marié !== "oui" and $marié !== "non");
    if ($erreur) {
      // o erro é registrado
      $attributes[] = ["marié" => $marié];
    }
    // o número de filhos deve ser um número inteiro positivo ou zero
    $enfants = trim($attributesOfTaxPayerData["enfants"]);
    if (!preg_match("/^\d+$/", $enfants)) {
      // o erro é registrado
      $erreur = TRUE;
      $attributes[] = ["enfants" => $enfants];
    } else {
      $enfants = (int) $enfants;
    }

    // o salário deve ser um número inteiro positivo ou zero (sem centavos de euro)
    $salaire = trim($attributesOfTaxPayerData["salaire"]);
    if (!preg_match("/^\d+$/", $salaire)) {
      // registrando o erro
      $erreur = TRUE;
      $attributes[] = ["salaire" => $salaire];
    } else {
      $salaire = (int) $salaire;
    }

    // erro?
    if ($erreur) {
      // retorno com erro
      return ["erreurs" => $attributes];
    } else {
      // retorno sem erro
      return [];
    }
  }

  // gravação dos resultados
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // gravação da tabela [$taxPayersData] no arquivo de texto [$resultsFileName]
    // se o arquivo de texto [$resultsFileName] não existir, ele será criado
    // construção da sequência jSON a partir dos resultados
    $string = "[" . implode(",
", $taxPayersData) . "]";
    // gravação dessa sequência
    $this->saveString($resultsFilename, $string);
  }

  // gravação dos resultados de uma tabela em um arquivo de texto
  private function saveString(string $fileName, string $data): void {
    // gravação da sequência [$data] no arquivo de texto [$fileName]
    // se o arquivo de texto [$fileName] não existir, ele será criado
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Comentários

  • [TraitDao] implementa os métodos [getTaxPayersData] (linha 9) e [saveResults] (linha 86) da interface [InterfaceDao];
  • linha 9: o método [getTaxPayersData] recebe como parâmetros:
    • [$taxPayersFilename]: o nome do arquivo jSON contendo os dados dos contribuintes [taxpayersdata.json];
    • [$errorsFilename]: o nome do arquivo jSON contendo os erros [errors.json];
  • linhas 11-13: o conteúdo do arquivo jSON com os dados dos contribuintes é transferido para uma tabela associativa [$arrayOfAttributes]. Se o arquivo jSON for considerado inutilizável, uma exceção [ExceptionImpots] foi lançada;
  • linha 15: a tabela [$taxPayersData] conterá os dados dos contribuintes encapsulados em objetos do tipo [TaxPayerData];
  • linha 17: os erros serão acumulados na tabela [$errors];
  • linhas 99-33: construção da tabela [$taxPayersData];
  • linha 22: antes de serem encapsulados no tipo [TaxPayerData], os dados são verificados. O método [check] retorna:
    • um array [‘erreurs’=>[…]] com os atributos incorretos, caso os dados estejam incorretos;
    • um array vazio, caso os dados estejam corretos;
  • linha 25: caso em que os dados são válidos. Um novo objeto [TaxPayerData] é criado e adicionado ao array [$taxPayersData];
  • linhas 26-30: caso em que os dados sejam inválidos. No erro, é registrado o número do objeto [TaxPayerData] com erro no arquivo jSON para que o usuário possa localizá-lo; em seguida, o erro é adicionado à tabela [$errors];
  • linhas 35-39: registram-se os erros encontrados no arquivo jSON [$errorsFilename] passado como parâmetro, linha 9;
  • linha 41: retorna-se a matriz dos objetos [TaxPayerData] criados: esse era o objetivo do método;
  • linhas 44-83: o método privado [check] verifica a validade dos parâmetros [marié, enfants, salaire] da matriz [$attributesOfTaxPayerData] passada como parâmetro na linha 44. Se houver atributos incorretos, ela os acumula na tabela [$attributes] (linhas 47, 53, 60, 70) na forma de uma tabela [‘attribut erroné’=> valeur de l’attribut erroné];
  • linha 78: se houver erros, é retornada uma tabela [‘erreurs’=>$attributes];
  • linha 81: se não houver erros, retorna-se uma tabela de erros vazia;
  • linhas 86-93: implementação do método [saveResults] da interface [InterfaceDao];
  • linha 90: constrói-se a string jSON a ser gravada no arquivo jSON [$resultsFilename] passado como parâmetro na linha 86. Deve-se construir a string jSON a partir de uma matriz:
    • cada elemento da matriz é separado do seguinte por uma vírgula e um salto de linha;
    • a matriz inteira está entre colchetes [];
  • linha 92: a sequência jSON é gravada no arquivo jSON [$resultsFilename];

13.4.3.2. A classe [DaoImpotsWithTaxAdminDataInDatabase]

A classe [DaoImpotsWithTaxAdminDataInDatabase] implementa a interface [InterfaceDao] da seguinte maneira:


<?php

// espaço de nomes
namespace Application;

// definição de uma classe ImpotsWithDataInDatabase
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
  // uso de uma característica
  use TraitDao;
  // o objeto do tipo TaxAdminData que contém os dados das faixas de imposto
  private $taxAdminData;
  // o objeto do tipo [Database] que contém as características do BD
  private $database;

  // fabricante
  public function __construct(string $databaseFilename) {
    // armazena-se a configuração jSON do banco de dados
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // prepara-se o atributo
    $this->taxAdminData = new TaxAdminData();
    try {
      // abrimos a conexão com o banco de dados
      $connexion = new \PDO(
        $this->database->getDsn(),
        $this->database->getId(),
        $this->database->getPwd());
      // deseja-se que, a cada erro de SGBD, seja lançada uma exceção
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // inicia-se uma transação
      $connexion->beginTransaction();
      // preenche-se a tabela de faixas de imposto
      $this->getTranches($connexion);
      // preenche-se a tabela de constantes
      $this->getConstantes($connexion);
      // a transação é encerrada com sucesso
      $connexion->commit();
    } catch (\PDOException $ex) {
      // há alguma transação em andamento?
      if (isset($connexion) && $connexion->inTransaction()) {
        // encerra-se a transação com falha
        $connexion->rollBack();
      }
      // a exceção é repassada ao código chamador
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // a conexão é encerrada
      $connexion = NULL;
    }
  }

  // Leitura dos dados do banco de dados
  private function getTranches($connexion): void {

  }

  // leitura da tabela de constantes
  private function getConstantes($connexion): void {

  }

  // retorna os dados necessários para o cálculo do imposto
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }

}

Comentários

  • linha 4: mantém-se o espaço de nomes já utilizado para as outras implementações da camada [dao];
  • linha 7: a classe [DaoImpotsWithTaxAdminDataInDatabase] implementa a interface [InterfaceDao];
  • linha 9: importa-se o traço [TraitDao]. Sabe-se que esse traço implementa uma parte da interface. O único método que resta a ser implementado é o método [getTaxAdminData] das linhas 62-64. Esse método se limita a definir o atributo privado [taxAdminData] da linha 11. Deduz-se que o construtor deverá inicializar esse atributo. Essa é sua única função;
  • linha 16: o construtor recebe como único parâmetro [$databaseFilename], que é o nome do arquivo jSON [database.json] que define o banco de dados MySQL [dbimpots-2019] ;
  • linha 18: o arquivo jSON [$databaseFilename] é utilizado para criar um objeto do tipo [Database], construído e armazenado no atributo [$database] da linha 13. Se o arquivo jSON não puder ser processado corretamente, uma exceção [ExceptionImpots] foi lançada;
  • linha 20: cria-se o objeto [$this→taxAdminData], que o construtor deve inicializar;
  • linhas 22-26: abre-se a conexão com o banco de dados. Observe a notação [\PDO] para designar a classe [PDO] de PHP. De fato, como estamos no espaço de nomes [Application], se escrevêssemos simplesmente [PDO], esse nome relativo seria prefixado pelo espaço de nomes atual e, portanto, resultaria na classe [Application\PDO], que não existe;
  • linha 28: em caso de erro, o SGBD iniciará um \PDOException (linha 37);
  • linha 30: inicia-se uma transação. Ela não é realmente útil, pois apenas duas ordens SQL serão executadas, ordens que não alteram o banco de dados. No entanto, faz-se isso para isolar-se dos demais usuários do banco de dados;
  • linha 32: a leitura da tabela de faixas de tributação [tbtranches] é feita pelo método privado [getTranches] da linha 52;
  • linha 34: a leitura da tabela de constantes de cálculo [tbconstantes] é feita pelo método privado [getConstantes] da linha 57;
  • linha 36: se chegarmos a esta linha, significa que tudo correu bem. Portanto, validamos a transação;
  • linhas 37-42: se chegarmos até aqui, significa que ocorreu uma exceção. Portanto, invalidamos a transação, caso houvesse uma em andamento (linhas 39-42). Linha 44: para garantir que as exceções sejam homogêneas, reenviamos a mensagem da exceção recebida, desta vez na forma de uma exceção do tipo [ExceptionImpots];
  • linhas 45-48: em todos os casos (haja exceção ou não), encerra-se a conexão;

O método [getTranches] é o seguinte:


private function getTranches($connexion): void {
     // atalhos
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
     // prepara-se a consulta SELECT
    $statement = $connexion->prepare(
      "select {$database->getColLimites()}," .
      " {$database->getColCoeffR()}," .
      " {$database->getColCoeffN()}" .
      " from {$database->getTableTranches()}");
     // executa-se a ordem preparada com os valores das faixas de imposto
    $statement->execute();
     // análise do resultado
    $limites = [];
    $coeffR = [];
    $coeffN = [];
     // preenchimento das três tabelas
    while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
      $limites[] = (float) $tranche->{$database->getColLimites()};
      $coeffR[] = (float) $tranche->{$database->getColCoeffR()};
      $coeffN[] = (float) $tranche->{$database->getColCoeffN()};
    }
    // os dados são armazenados no atributo [$taxAdminData] da classe
    $taxAdminData->setFromArrayOfAttributes([
      "limites" => $limites,
      "coeffR" => $coeffR,
      "coeffN" => $coeffN
    ]);
  }

Comentários

  • linha 1: o método recebe como parâmetro [$connexion], que é uma conexão aberta e na qual uma transação está em andamento;
  • linhas 2-4: criam-se dois atalhos para evitar ter que escrever [$this->database] e [$taxAdminData = $this->taxAdminData] em todo o código. Trata-se de cópias de referências a objetos e não de uma cópia dos próprios objetos;
  • linhas 6-10: a ordem SELECT é preparada e, em seguida, executada na linha 12;
  • linhas 13-22: o resultado do SELECT é processado. As informações recebidas são acumuladas em três tabelas [limites, coeffR, coeffN];
  • linhas 24-28: as três tabelas são utilizadas para inicializar o atributo [$this->taxAdminData] da classe;

O método privado [getConstantes] é o seguinte:


private function getConstantes($connexion): void {
    // atalhos
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
    // prepara-se a 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);
    // executa-se a ordem preparada
    $statement->execute();
    // analisamos o resultado — há apenas uma linha aqui
    $row = $statement->fetch(\PDO::FETCH_OBJ);
    // inicializamos o 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()});
  }

Comentários

  • linha 1: o método recebe como parâmetro [$connexion], que é uma conexão aberta na qual uma transação está em andamento;
  • linhas 2-4: criam-se dois atalhos para evitar ter que escrever [$this->database] e [$taxAdminData = $this->taxAdminData] em todo o código. Trata-se de cópias de referências a objetos e não de uma cópia dos próprios objetos;
  • linhas 6-15: a ordem SELECT é preparada e, em seguida, executada na linha 15;
  • linhas 17-29: o resultado de SELECT é processado. As informações recuperadas são usadas para inicializar o atributo [$this->taxAdminData] da classe;

Observação: note-se que a classe não depende do SGBD nem do MySQL. É o código chamador que define o SGBD utilizado por meio do DSN do banco de dados.

13.4.4. A camada [métier]

Image

  • acabamos de implementar a camada [dao] (3);
  • como respeitamos a interface [InterfaceDao], a camada [métier] (2) pode, em teoria, permanecer inalterada. No entanto, não modificamos apenas a camada [dao]. Também modificamos as entidades que são compartilhadas por todas as camadas;

A camada [métier] implementa a seguinte interface [InterfaceMetier]:


<?php

// espaço de nomes
namespace Application;

interface InterfaceMetier {

  // cálculo dos impostos de um contribuinte
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // cálculo de impostos em modo batch
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
  • linha 12: o método [executeBatchImpots] agora utiliza o arquivo jSON [$taxPayersFileName], enquanto na versão 04 era um arquivo de texto simples.;

Na versão 04, o método [executeBatchImpots] era o seguinte:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // permite que as exceções provenientes da camada [dao] sejam propagadas
    // recuperação dos dados dos contribuintes
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabela de resultados
    $results = [];
    // analisamos os resultados
    foreach ($taxPayersData as $taxPayerData) {
      // calcula-se o imposto
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // preenche-se [$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"]);
      // insere-se o resultado na tabela de resultados
      $results [] = $taxPayerData;
    }
    // gravação dos resultados
    $this->dao->saveResults($resultsFileName, $results);
  }
  • a linha 15 agora está incorreta. Na nova definição da classe [TaxPayerData], o método [setMontant] não existe mais;

Na versão 05, o método [executeBatchImpots] será o seguinte:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // deixa-se que as exceções provenientes da camada [dao] sejam reportadas
    // recuperam-se os dados dos contribuintes
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabela de resultados
    $results = [];
    // análise dos resultados
    foreach ($taxPayersData as $taxPayerData) {
      // calcula-se o imposto
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // preenche-se [$taxPayerData]
      $taxPayerData->setFromArrayOfAttributes($result);
      // insere-se o resultado na tabela de resultados
      $results [] = $taxPayerData;
    }
    // gravação dos resultados
    $this->dao->saveResults($resultsFileName, $results);
  }

Comentários

  • linha 15: em vez de usar os setters individuais da classe [TaxPayerData], utiliza-se seu setter global [setFromArrayOfAttributes];
  • o restante do código não precisa ser alterado;

13.4.5. O script principal

Image

  • acabamos de implementar as camadas [dao] (3) e [métier] (2);
  • resta-nos escrever o script principal (1);

O script principal é semelhante ao da versão 04:


<?php

// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);

// espaço de nomes
namespace Application;

// gestão de erros por PHP
//ini_set("display_errors", "0");
// inclusão de interface e classes
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";
//
// definição 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 {
  // criação da camada [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // criação da camada [métier]
  $métier = new Metier($dao);
  // cálculo de impostos em modo batch
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // exibição do erro
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fim
print "Terminé\n";
exit;


Comentários

  • linhas 12-22: carregamento de todos os arquivos da versão 05;
  • linhas 25-29: os nomes dos diferentes arquivos jSON do aplicativo;
  • linha 33: construção da camada [dao];
  • linha 35: construção da camada [métier];
  • linha 37: chamada do método [executeBatchImpots] da camada [métier];

Resultados

O aplicativo gera dois arquivos jSON:

  • [resultats.json]: os resultados dos diversos cálculos de impostos;
  • [errors.json]: que sinaliza os erros encontrados nos arquivos jSON e [taxpayersdata.json];

O arquivo [errors.json] é o seguinte:


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

Isso significa que, no arquivo [taxpayersdata.json], o elemento nº 1 da tabela de contribuintes está incorreto. O arquivo [taxpayersdata.json] era o seguinte:


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

Já o arquivo de resultados [resultats.json] é o seguinte:


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

Esses resultados estão em conformidade com os da versão 04.

13.5. Testes [Codeception]

Assim como foi feito no parágrafo sobre a versão 04, vamos escrever testes [Codeception] para a versão 05.

Image

13.5.1. Teste da camada [dao]

O teste [DaoTest.php] é o seguinte:


<?php

// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);

// espaço de nomes
namespace Application;

// diretórios raiz
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusão de interfaces e classes
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 terceiros
require_once VENDOR . "/autoload.php";

// definição 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();
    // criação da camada [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

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

  }

}

Comentários

  • linhas 9-33: definição do ambiente de teste. Utilizamos o mesmo ambiente usado pelo script principal [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], descrito no parágrafo com o link;
  • linhas 39-44: construção da camada [dao];
  • linha 43: o atributo [$this→taxAdminData] contém os dados a serem testados;
  • linhas 47-51: o método [testTaxAdminData] é aquele descrito no parágrafo “link”;

Os resultados do teste são os seguintes:

Image

13.5.2. Teste da camada [métier]

O teste [MetierTest.php] é o seguinte:


<?php

// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);

// espaço de nomes
namespace Application;

// diretórios raiz
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusão de interfaces e classes
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 terceiros
require_once VENDOR . "/autoload.php";

// definição 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 {
  // camada de negócios
  private $métier;

  public function __construct() {
    parent::__construct();
    // criação da camada [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // criação da camada [métier]
    $this->métier = new Metier($dao);
  }

  // testes
  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"]);
  }

}

Comentários

  • linhas 9-33: definição do ambiente de teste. Utilizamos o mesmo ambiente usado pelo script principal [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], descrito no parágrafo com o link;
  • linhas 39-45: construção das camadas [dao] e [métier];
  • linha 44: o atributo [$this→métier] faz referência à camada [métier];
  • linhas 47-64: os métodos [test1, test2…, test11] são os descritos no parágrafo “link”;

Os resultados do teste são os seguintes:

Image