Skip to content

14. Exercício prático – versão 6

Image

Acabamos de implementar a seguinte estrutura em camadas:

Image

O SGBD utilizado nos exemplos era o MySQL. No parágrafo sobre links, observamos que nada na classe que implementa a camada [dao] indicava que estávamos utilizando um SGBD específico. É isso que vamos verificar agora, utilizando outro SGBD, o SGBD PostgreSQL. A arquitetura em camadas passa a ser a seguinte:

Image

14.1. Instalação do SGBD e do PostgreSQL

As distribuições do SGBD e do PostgreSQL estão disponíveis no URL e no [https://www.postgresql.org/download/] (maio de 2019). Mostramos a instalação da versão para Windows de 64 bits:

Image

Image

  • no [1-4], baixe o instalador do SGBD;

Execute o instalador baixado:

Image

  • no [6], indique uma pasta de instalação;

Image

  • no [8], a opção [Stack Builder] é desnecessária para o que queremos fazer aqui;
  • em [10], mantenha o valor que for exibido;

Image

  • em [12-13], colocamos aqui a senha [root]. Essa será a senha do administrador do SGBD, que se chama [postgres]. O PostgreSQL também o chama de superusuário;
  • no [15], mantenha o valor padrão: essa é a porta de escuta do SGBD;

Image

  • em [17], mantenha o valor padrão;
  • em [19], o resumo da configuração da instalação;

Image

Image

No Windows, o SGBD PostgreSQL é instalado como um serviço do Windows iniciado automaticamente. Na maioria das vezes, isso não é desejável. Vamos alterar essa configuração. Digite [services] na barra de pesquisa do Windows [24-26]:

Image

  • em [29], vemos que o serviço SGBD PostgreSQL está no modo automático. Alteramos isso acessando as propriedades do serviço [30]:

Image

  • no [31-32], defina o modo de inicialização como manual;
  • no [33], pare o serviço;

Quando quiser iniciar manualmente o SGBD, volte ao aplicativo [services], clique com o botão direito do mouse no serviço [postgresql] (34) e inicie-o (35).

14.2. Ativação da extensão PDO do SGBD PostgreSQL

Vamos modificar o arquivo [php.ini], que configura o PHP (ver parágrafo com o link):

Image

  • para [2]; verifique se a extensão PDO do PostgreSQL está ativada. Feito isso, salve a alteração e reinicie o Laragon para garantir que a alteração seja aplicada. Em seguida, verifique a configuração de PHP diretamente no Laragon [3-5].

14.3. Gerenciar o PostgreSQL com a ferramenta [pgAdmin]

Inicie o serviço do Windows do SGBD PostgreSQL (consulte o parágrafo com o link). Em seguida, da mesma forma que você iniciou a ferramenta [services], inicie a ferramenta [pgadmin], que permite administrar o SGBD, PostgreSQL e [1-3]:

Image

É possível que, em determinado momento, seja solicitada a senha do superusuário. Ela é [postgres]. Você definiu essa senha durante a instalação do SGBD. Neste documento, atribuímos a senha [root] ao superusuário durante a instalação.

  • em [4], [pgAdmin] é um aplicativo web;
  • em [5], a lista de servidores PostgreSQL detectados pelo [pgAdmin], neste caso 1;
  • em [6], o servidor PostgreSQL que iniciamos;
  • em [7], os bancos de dados do SGBD, neste caso 1;
  • em [8], o banco de dados [postgresql] é gerenciado pelo superusuário [postgres];

Vamos, primeiro, criar um usuário [admimpots] com a senha [mdpimpots]:

Image

Image

  • em [17], colocamos [mdpimpots];

Image

  • em [21], o código SQL que será gerado pela ferramenta [pgAdmin] para o SGBD PostgreSQL. Essa é uma maneira de aprender a linguagem SQL, propriedade da PostgreSQL;
  • no [22], após a validação do assistente [Save], o usuário [admimpots] foi criado;

Agora, criamos o banco de dados [dbimpots-2019]:

Image

Clicamos com o botão direito do mouse em [23] e, em seguida, em [24-25] para criar um novo banco de dados. Na guia [26], definimos o nome do banco de dados [27] e seu proprietário [admimpots] [28].

Image

  • em [30], o código SQL de criação do banco de dados;
  • em [31], após a validação do assistente [Save], o banco de dados [dbimpots-2019] é criado;

Agora, vamos criar a tabela [tbtranches] com as colunas [id, limites, coeffr, coeffn]. Uma particularidade da tabela PostgreSQL é que os nomes das colunas diferenciam maiúsculas de minúsculas, o que normalmente não ocorre nas outras tabelas SGBD. Assim, com o MySQL, a ordem [select limites, coeffR, coeffN from tbtranches] funcionará mesmo que as colunas reais da tabela [tbtranches] sejam [LIMITES, COEFFR, COEFFN]. Com PostgreSQL, a ordem SQL não funcionará. Seria possível, então, escrever [select LIMITES, COEFFR, COEFFN from tbtranches], mas isso ainda não funcionaria, pois PostgreSQL executará o comando [select limites, coeffr, coeffn from tbtranches]: por padrão, ele converte os nomes das colunas para letras minúsculas. Para que isso não aconteça, é preciso escrever: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], ou seja, é preciso colocar os nomes das colunas entre aspas. Por esses motivos, vamos dar aos nomes das colunas nomes em letras minúsculas. Os nomes dos objetos de um banco de dados podem ser uma fonte de incompatibilidade entre SGBD, já que certos nomes são palavras reservadas em alguns SGBD e não em outros.

Criamos a tabela [tbtranches]:

Image

  • use o botão [40] para criar colunas;

Image

Image

  • após concluir o assistente de criação por meio de [Save], a tabela [tbtranches] é criada [52-53];

Precisamos indicar ao SGBD que ele mesmo deve gerar a chave primária [id] ao inserir uma linha na tabela:

Image

  • no [56], acessamos as propriedades da chave primária [id];
  • em [59], indica-se que a coluna é do tipo [Identity]. Isso fará com que o SGBD gere os valores da chave primária;

Image

  • em [62], o código SQL gerado para essa operação;

A tabela [tbtranches] está agora pronta.

Repetimos as mesmas operações para criar a tabela [tbconstantes]. Especificamos o resultado a ser obtido:

Image

Image

Image

A base de dados [dbimpots-2019] já está pronta. Vamos preenchê-la com dados.

Assim como fizemos com o MySQL, é possível exportar o banco de dados [dbimpots-2019] para um arquivo SQL. Em seguida, é possível importar esse arquivo SQL para recriar o banco de dados caso ele tenha sido perdido ou danificado. Aqui, exportaremos apenas a estrutura do banco de dados e não seus dados:

Image

Image

O arquivo gerado é o seguinte:


--
-- dump do banco de dados PostgreSQL
--

-- Exportado da versão 11.2 do banco de dados
-- Exportado pela versão 11.2 do pg_dump

-- Iniciado em 04/07/2019 às 08:20:31

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;

SET default_tablespace = '';

SET default_with_oids = false;

--
-- Entrada 198 do TOC (classe 1259 OID 16408)
-- Nome: tbconstantes; Tipo: TABLE; Esquema: public; Proprietário: postgres
--

CREATE TABLE public.tbconstantes (
    plafond_qf_demi_part double precision NOT NULL,
    id integer NOT NULL,
    plafond_revenus_celibataire_pour_reduction double precision NOT NULL,
    plafond_revenus_couple_pour_reduction double precision NOT NULL,
    valeur_reduc_demi_part double precision NOT NULL,
    plafond_decote_celibataire double precision NOT NULL,
    plafond_decote_couple double precision NOT NULL,
    plafond_impot_celibataire_pour_decote double precision NOT NULL,
    plafond_impot_couple_pour_decote double precision NOT NULL,
    abattement_dix_pourcent_max double precision NOT NULL,
    abattement_dix_pourcent_min double precision NOT NULL
);


ALTER TABLE public.tbconstantes OWNER TO postgres;

--
-- Entrada 199 de TOC (classe 1259 OID 16411)
-- Nome: tbconstantes_id_seq; Tipo: SEQUENCE; Esquema: public; Proprietário: postgres
--

ALTER TABLE public.tbconstantes ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.tbconstantes_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Entrada 196 de TOC (classe 1259 OID 16399)
-- Nome: tbtranches; Tipo: TABLE; Esquema: public; Proprietário: admimpots
--

CREATE TABLE public.tbtranches (
    limites double precision NOT NULL,
    id integer NOT NULL,
    coeffr double precision NOT NULL,
    coeffn double precision NOT NULL
);


ALTER TABLE public.tbtranches OWNER TO admimpots;

--
-- Entrada 197 do TOC (classe 1259 OID 16404)
-- Nome: tbimpots_id_seq; Tipo: SEQUENCE; Esquema: público; Proprietário: admimpots
--

ALTER TABLE public.tbtranches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.tbimpots_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Entrada 2694 do TOC (classe 2606 OID 16429)
-- Nome: tbconstantes tbconstantes_pkey; Tipo: CONSTRAINT; Esquema: público; Proprietário: postgres
--

ALTER TABLE ONLY public.tbconstantes
    ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);


--
-- Entrada TOC 2692 (classe 2606 OID 16403)
-- Nome: tbtranches tbimpots_pkey; Tipo: CONSTRAINT; Esquema: público; Proprietário: admimpots
--

ALTER TABLE ONLY public.tbtranches
    ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);


--
-- Entrada 2821 do TOC (classe 0 OID 0)
-- Dependências: 198
-- Nome: TABLE tbconstantes; Tipo: ACL; Esquema: public; Proprietário: postgres
--

GRANT ALL ON TABLE public.tbconstantes TO admimpots;


-- Concluído em 04/07/2019 às 08:20:32

--
-- Dump do banco de dados PostgreSQL concluído
--

14.4. Preenchimento da tabela [tbtranches]

Já realizamos esse trabalho com os arquivos SGBD e MySQL no parágrafo “link”. Basta modificarmos o arquivo [database.json], que descreve o banco de dados:

Image

O arquivo [database.json] fica da seguinte forma:


{
    "dsn": "pgsql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "public.tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "public.tbconstantes",
    "colPlafondQfDemiPart": "plafond_qf_demi_part",
    "colPlafondRevenusCelibatairePourReduction": "plafond_revenus_celibataire_pour_reduction",
    "colPlafondRevenusCouplePourReduction": "plafond_revenus_couple_pour_reduction",
    "colValeurReducDemiPart": "valeur_reduc_demi_part",
    "colPlafondDecoteCelibataire": "plafond_decote_celibataire",
    "colPlafondDecoteCouple": "plafond_decote_couple",
    "colPlafondImpotCelibatairePourDecote": "plafond_impot_celibataire_pour_decote",
    "colPlafondImpotCouplePourDecote": "plafond_impot_couple_pour_decote",
    "colAbattementDixPourcentMax": "abattement_dix_pourcent_max",
    "colAbattementDixPourcentMin": "abattement_dix_pourcent_min"
}
  • linha 2: o DSN foi alterado; [pgsql] indica que estamos lidando com o SGBD do Postgres;
  • linhas 5 e 9: os nomes das tabelas foram precedidos pelo nome do esquema ao qual pertencem, [public]. Isso não era indispensável, uma vez que [public] é o esquema usado por padrão quando nenhum esquema é especificado no nome da tabela;
  • linhas 6-8, 10-19: os nomes das colunas foram alterados;

O script [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] para preenchimento do banco de dados [dbimpots-2019] é o seguinte:


<?php

// Rigorosa observância dos 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__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../../version-05/Dao/DaoTransferAdminDataFromJsonFile2Database.php";
//
// definição das 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

Apenas as linhas 12-21, que carregam os arquivos necessários para a execução do aplicativo, são alteradas. Elas são alteradas porque o valor [__DIR__] muda: agora ele designa a pasta [version-07/Main].

Ao executar esse script, obtém-se o seguinte resultado na tabela [tbtranches]:

Image

  • clica-se com o botão direito do mouse em [1] e, em seguida, em [2-3];
  • em [4], os dados das faixas de imposto estão corretamente presentes;

Repetimos o mesmo procedimento para a tabela de constantes [tbconstantes]:

Image

Image

Image

Observe que, para a execução do script, o aplicativo Laragon não precisa estar ativo: não é necessário nem o servidor Apache, nem o SGBD MySQL. É necessário apenas o SGBD PostgreSQL, cujo serviço do Windows foi iniciado.

14.5. Cálculo do imposto

Image

As camadas [dao] (3) e [métier] (2) já foram criadas. Já escrevemos o script principal para o SGBD MySQL no parágrafo com o link. Basta retomarmos o script [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] e adaptá-lo para o SGBD e PostgreSQL. Agora ele se chama [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Image

O script [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.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;

// gestão de erros por PHP
//ini_set("display_errors", "0");
// inclusão de interface e classes
require_once __DIR__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../../version-05/Métier/InterfaceMetier.php";
require_once __DIR__ . "/../../version-05/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

Apenas as linhas 12 a 22, que carregam os arquivos necessários para a execução do aplicativo, são alteradas. Elas são alteradas porque o valor [__DIR__] muda: agora ele indica a pasta [version-07/Main].

Resultados da execução

Os mesmos obtidos nas versões anteriores.

14.6. Testes [Codeception]

Assim como nas versões anteriores, validamos esta versão com os testes [Codeception]:

Image

14.6.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-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusão de interfaces e classes
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";

// bibliotecas de terceiros
require_once VENDOR . "/autoload.php";

// definição de constantes
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.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() {

  }

}

Comentários

  • linhas 9-28: definição do ambiente de teste. Utilizamos o mesmo ambiente, sem a camada [métier], que o utilizado pelo script principal [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] descrito no parágrafo do link;
  • linhas 34-39: construção da camada [dao];
  • linha 38: o atributo [$this→taxAdminData] contém os dados a serem testados;
  • linhas 42-44: o método [testTaxAdminData] é aquele descrito no parágrafo do link;

Os resultados do teste são os seguintes:

Image

14.6.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-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusão de interfaces e classes
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/../version-05/Métier/InterfaceMetier.php";
require_once ROOT . "/../version-05/Métier/Metier.php";
// bibliotecas de terceiros
require_once VENDOR . "/autoload.php";
// definição de constantes
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.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() {

  }
--------------------------------------------------------------------
  public function test11() {

  }

}

Comentários

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

Os resultados do teste são os seguintes:

Image