Skip to content

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

Image

Acabámos de implementar a seguinte estrutura em camadas:

Image

O SGBD utilizado nos exemplos era o MySQL. No parágrafo sobre ligações, tínhamos observado que nada na classe que implementa a camada [dao] sugeria que se estivesse a ser utilizado 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). Apresentamos a instalação da versão para Windows de 64 bits:

Image

Image

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

Executa-se o instalador descarregado:

Image

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

Image

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

Image

  • em [12-13], colocámos aqui a palavra-passe [root]. Esta será a palavra-passe do administrador do SGBD, que se chama [postgres]. O PostgreSQL também lhe chama «superutilizador»;
  • no [15], mantenha o valor por predefinição: trata-se da porta de escuta do SGBD;

Image

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

Image

Image

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

Image

  • em [29], verificamos que o serviço SGBD PostgreSQL está no modo automático. Alteramos isso acedendo às propriedades do serviço [30]:

Image

  • no [31-32], defina o arranque para o modo manual;
  • no [33], pare o serviço;

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

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

Vamos alterar o ficheiro [php.ini] que configura o PHP (ver parágrafo do link):

Image

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

14.3. Gerir o PostgreSQL com a ferramenta [pgAdmin]

Inicie o serviço do Windows do SGBD PostgreSQL (ver parágrafo «ligação»). Em seguida, da mesma forma que iniciou a ferramenta [services], inicie a ferramenta [pgadmin], que permite administrar o SGBD, o PostgreSQL e o [1-3]:

Image

É possível que, a dada altura, lhe seja solicitada a palavra-passe do superutilizador. Esta é [postgres]. Definiste essa palavra-passe durante a instalação do SGBD. Neste documento, atribuímos a palavra-passe [root] ao superutilizador durante a instalação.

  • em [4], [pgAdmin] é uma aplicação web;
  • em [5], a lista de servidores PostgreSQL detetados pelo [pgAdmin], neste caso 1;
  • em [6], o servidor PostgreSQL que lançámos;
  • em [7], as bases de dados do SGBD, neste caso 1;
  • em [8], a base de dados [postgresql] é gerida pelo superutilizador [postgres];

Vamos, em primeiro lugar, criar um utilizador [admimpots] com a palavra-passe [mdpimpots]:

Image

Image

  • em [17], colocámos [mdpimpots];

Image

  • em [21], o código SQL que a ferramenta [pgAdmin] irá emitir para o SGBD PostgreSQL. Esta é uma forma de aprender a linguagem SQL, propriedade da PostgreSQL;
  • No [22], após a validação do assistente [Save], foi criado o utilizador [admimpots];

Agora, criamos a base de dados [dbimpots-2019]:

Image

Clicamos com o botão direito do rato em [23] e, em seguida, em [24-25] para criar uma nova base de dados. No separador [26], definimos o nome da base de dados [27] e o seu proprietário [admimpots] [28].

Image

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

Agora, vamos criar a tabela [tbtranches] com as colunas [id, limites, coeffr, coeffn]. Uma particularidade da tabela PostgreSQL é que os nomes das colunas distinguem maiúsculas de minúsculas, o que normalmente não acontece com as 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á. Poderíamos então escrever [select LIMITES, COEFFR, COEFFN from tbtranches], mas isso continuará a não funcionar, pois PostgreSQL irá executar o comando [select limites, coeffr, coeffn from tbtranches]: por predefinição, converte os nomes das colunas para minúsculas. Para que isso não aconteça, é necessário escrever: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], ou seja, é preciso colocar os nomes das colunas entre aspas. Por estas razões, vamos atribuir nomes em minúsculas às colunas. Os nomes dos objetos de uma base de dados podem ser uma fonte de incompatibilidade entre o SGBD, uma vez que certos nomes são palavras reservadas em alguns SGBD e não noutros.

Criamos a tabela [tbtranches]:

Image

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

Image

Image

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

Temos de indicar ao SGBD que deve gerar ele próprio a chave primária [id] ao inserir uma linha na tabela:

Image

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

Image

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

A tabela [tbtranches] está agora pronta.

Repetimos as mesmas operações para criar a tabela [tbconstantes]. Indicamos o resultado a obter:

Image

Image

Image

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

Tal como fizemos com a MySQL, é possível exportar a base de dados [dbimpots-2019] para um ficheiro SQL. Posso, em seguida, importar este ficheiro SQL para recriar a base de dados, caso a tenha perdido ou se esta se tenha danificado. Aqui, irei exportar apenas a estrutura da base de dados e não os seus dados:

Image

Image

O ficheiro gerado é o seguinte:


--
-- Dump da base de dados PostgreSQL
--

-- Exportado da versão 11.2 da base 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: público; 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: público; 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 de 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: público; Proprietário: postgres
--

GRANT ALL ON TABLE public.tbconstantes TO admimpots;


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

--
-- Dump da base de dados PostgreSQL concluído
--

14.4. Preenchimento da tabela [tbtranches]

Já realizámos este trabalho com os ficheiros SGBD e MySQL no parágrafo «ligação». Basta alterarmos o ficheiro [database.json], que descreve a base de dados:

Image

O ficheiro [database.json] fica assim:


{
    "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; o [pgsql] indica que se trata do SGBD Postgres;
  • linhas 5 e 9: os nomes das tabelas foram precedidos pelo nome do esquema a que pertencem, [public]. Isto não era indispensável, uma vez que [public] é o esquema utilizado por predefiniçã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 preencher a base de dados [dbimpots-2019] é o seguinte:


<?php

// respeito rigoroso 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 a base de dados
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // é apresentado o 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 ficheiros necessários à execução da aplicação, sofrem alterações. Estas alterações devem-se à mudança do valor [__DIR__]: este valor passa a designar a pasta [version-07/Main].

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

Image

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

Repete-se o mesmo procedimento para a tabela de constantes [tbconstantes]:

Image

Image

Image

Note-se que, para a execução do script, a aplicação Laragon não precisa de estar ativa: não é necessário nem o servidor Apache, nem o SGBD MySQL. Basta 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-nos retomar o script [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] e adaptá-lo ao SGBD e ao PostgreSQL. Passa agora a chamar-se [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Image

O script [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] é o seguinte:


<?php

// respeito rigoroso 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/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../../version-05/Métier/InterfaceMetier.php";
require_once __DIR__ . "/../../version-05/Métier/Metier.php";
//
// definição das 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 dos 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 ficheiros necessários à execução da aplicação, sofrem alterações. Estas alterações devem-se à mudança do valor [__DIR__]: este valor passa a referir-se agora à pasta [version-07/Main].

Resultados da execução

Os mesmos que os obtidos nas versões anteriores.

14.6. Testes [Codeception]

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

Image

14.6.1. Teste da camada [dao]

O teste [DaoTest.php] é o seguinte:


<?php

// respeito rigoroso dos 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 com o link;
  • linhas 34-39: construção da camada [dao];
  • linha 38: o atributo [$this→taxAdminData] contém os dados a testar;
  • linhas 42-44: o método [testTaxAdminData] é o descrito no parágrafo «ligação»;

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 rigoroso dos 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ócio
  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 que o utilizado 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 «ligação»;

Os resultados do teste são os seguintes:

Image