Skip to content

14. Exercício de Aplicação – Versão 6

Image

Acabámos de implementar a seguinte estrutura em camadas:

Image

O SGBD utilizado nos exemplos foi o MySQL. Na secção «link», observámos que nada na classe que implementa a camada [dao] sugeria que estivesse a ser utilizado um SGBD específico. Vamos agora verificar isto utilizando outro SGBD, o PostgreSQL. A arquitetura em camadas fica da seguinte forma:

Image

14.1. Instalação do SGBD PostgreSQL

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

Image

Image

  • Em [1-4], descarregue o instalador do SGBD;

Execute o instalador descarregado:

Image

  • Em [6], especifique um diretório de instalação;

Image

  • em [8], a opção [Stack Builder] não é necessária para o que estamos a fazer aqui;
  • em [10], mantenha o valor padrão;

Image

  • Nos passos [12-13], introduzimos aqui a palavra-passe [root]. Esta será a palavra-passe do administrador do SGBD, cujo nome é [postgres]. O PostgreSQL também se refere a este utilizador como superutilizador;
  • Em [15], mantenha o valor padrão: esta é 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 que inicia automaticamente. Na maioria das vezes, isso não é desejável. Vamos modificar esta configuração. Digite [serviços] na barra de pesquisa do Windows [24-26]:

Image

  • em [29], pode ver que o serviço do SGBD PostgreSQL está definido como automático. Altere isto acedendo às propriedades do serviço [30]:

Image

  • Em [31-32], defina o tipo de arranque para Manual;
  • em [33], pare o serviço;

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

14.2. Ativar a extensão PDO para o SGBD PostgreSQL

Iremos modificar o ficheiro [php.ini] que configura o PHP (ver secção em link):

Image

  • Em [2], verifique se a extensão PDO do PostgreSQL está ativada. Depois de fazer isso, guarde a alteração e reinicie o Laragon para garantir que a alteração tenha efeito. Em seguida, verifique a configuração do PHP diretamente no Laragon [3-5].

14.3. Administrar o PostgreSQL com a ferramenta [pgAdmin]

Inicie o serviço do SGBD PostgreSQL no Windows (consulte a secção em destaque). Em seguida, tal como fez ao iniciar a ferramenta [services], inicie a ferramenta [pgadmin], que permite administrar o SGBD PostgreSQL [1-3]:

Image

Poderá ser-lhe solicitada a palavra-passe de superutilizador em algum momento. O superutilizador chama-se [postgres]. Esta palavra-passe é definida durante a instalação do SGBD. Neste documento, atribuímos a palavra-passe [root] ao superutilizador durante a instalação.

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

Primeiro, vamos criar um utilizador [admimpots] com a palavra-passe [mdpimpots]:

Image

Image

  • em [17], introduzimos [mdpimpots];

Image

  • em [21], o código SQL que a ferramenta [pgAdmin] enviará ao SGBD PostgreSQL. Esta é uma forma de aprender a linguagem SQL proprietária do PostgreSQL;
  • Em [22], após confirmar com o assistente [Save], o utilizador [admimpots] foi criado;

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

Image

Clique 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], defina o nome da base de dados [27] e o seu proprietário [admimpots] [28].

Image

  • Em [30], o código SQL para criar a base de dados;
  • Em [31], após confirmar com o assistente [Guardar], a base de dados [dbimpots-2019] é criada;

Agora, vamos criar a tabela [tbtranches] com as colunas [id, limites, coeffr, coeffn]. Uma característica distintiva do PostgreSQL é que os nomes das colunas diferenciam maiúsculas de minúsculas, o que normalmente não acontece com outros SGBDs. Assim, com o MySQL, a instrução SQL [select limites, coeffR, coeffN from tbtranches] funcionará mesmo que as colunas reais na tabela [tbtranches] sejam [LIMITES, COEFFR, COEFFN]. Com o PostgreSQL, esta instrução SQL não funcionará. Poder-se-ia então escrever [select LIMITES, COEFFR, COEFFN from tbtranches], mas mesmo assim não funcionaria, porque o PostgreSQL executaria a consulta [select limites, coeffr, coeffn from tbtranches]: por predefinição, converte os nomes das colunas para minúsculas. Para evitar isto, deve escrever: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], ou seja, deve colocar os nomes das colunas entre aspas. Por estas razões, daremos aos nomes das colunas nomes em minúsculas. Os nomes dos objetos da base de dados podem ser uma fonte de incompatibilidade entre SGBDs, uma vez que certos nomes são palavras reservadas em alguns SGBDs, mas 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 clicando em [Guardar], a tabela [tbtranches] é criada [52-53];

Precisamos de indicar ao SGBD para gerar a chave primária [id] automaticamente ao inserir uma linha na tabela:

Image

  • em [56] acedemos às propriedades da chave primária [id];
  • em [59], especificamos 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 os mesmos passos para criar a tabela [tbconstantes]. Aqui está o resultado esperado:

Image

Image

Image

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

Tal como fizemos com o MySQL, é possível exportar a base de dados [dbimpots-2019] para um ficheiro SQL. Podemos então importar este ficheiro SQL para recriar a base de dados caso esta se perca ou fique corrompida. Aqui, iremos exportar apenas a estrutura da base de dados e não os seus dados:

Image

Image

O ficheiro gerado é o seguinte:


--
-- PostgreSQL database dump
--
 
-- Dumped from database version 11.2
-- Dumped by pg_dump version 11.2
 
-- Started on 2019-07-04 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;
 
--
-- TOC entry 198 (class 1259 OID 16408)
-- Name: tbconstantes; Type: TABLE; Schema: public; Owner: 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;
 
--
-- TOC entry 199 (class 1259 OID 16411)
-- Name: tbconstantes_id_seq; Type: SEQUENCE; Schema: public; Owner: 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
);
 
 
--
-- TOC entry 196 (class 1259 OID 16399)
-- Name: tbtranches; Type: TABLE; Schema: public; Owner: 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;
 
--
-- TOC entry 197 (class 1259 OID 16404)
-- Name: tbimpots_id_seq; Type: SEQUENCE; Schema: public; Owner: 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
);
 
 
--
-- TOC entry 2694 (class 2606 OID 16429)
-- Name: tbconstantes tbconstantes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
 
ALTER TABLE ONLY public.tbconstantes
    ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);
 
 
--
-- TOC entry 2692 (class 2606 OID 16403)
-- Name: tbtranches tbimpots_pkey; Type: CONSTRAINT; Schema: public; Owner: admimpots
--
 
ALTER TABLE ONLY public.tbtranches
    ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);
 
 
--
-- TOC entry 2821 (class 0 OID 0)
-- Dependencies: 198
-- Name: TABLE tbconstantes; Type: ACL; Schema: public; Owner: postgres
--
 
GRANT ALL ON TABLE public.tbconstantes TO admimpots;
 
 
-- Completed on 2019-07-04 08:20:32
 
--
-- PostgreSQL database dump complete
--

14.4. A preencher a tabela [tbtranches]

Já fizemos isto com o SGBD MySQL na secção indicada. Basta modificarmos 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; [pgsql] indica que estamos a lidar com o SGBD Postgres;
  • linhas 5 e 9: os nomes das tabelas receberam o prefixo do nome do esquema ao qual pertencem [public]. Isto não era estritamente necessário, uma vez que [public] é o esquema 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 preencher a base de dados [dbimpots-2019] é o seguinte:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// error handling by PHP
// ini_set("display_errors", "0");
// interface and class inclusion
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";
//
// definition of constants
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
 
//
try {
  // creation of the [dao] layer
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // data transfer to the database
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // error is displayed
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;

Comentários

Apenas as linhas 12–21, que carregam os ficheiros necessários para executar a aplicação, são alteradas. São alteradas porque o valor de [__DIR__] muda: agora aponta para a pasta [version-07/Main].

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

Image

  • Clique com o botão direito do rato em [1] e, em seguida, em [2-3];
  • em [4], vemos os dados relativos aos escalões de imposto;

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

Image

Image

Image

Note que, para executar o script, a aplicação Laragon não precisa de estar ativa: nem o servidor Apache nem o SGBD MySQL são necessários. Precisamos apenas do SGBD PostgreSQL, para o qual já iniciámos o serviço do Windows.

14.5. Cálculo de impostos

Image

As camadas [dao] (3) e [business] (2) já foram escritas. Já escrevemos o script principal para o SGBD MySQL na secção com o link. Basta pegar no script [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] e adaptá-lo ao SGBD PostgreSQL. Agora chama-se [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Image

O script [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] é o seguinte:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// error handling by PHP
//ini_set("display_errors", "0");
// interface and class inclusion
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";
//
// definition of constants
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 {
  // creation of the [dao] layer
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // creation of the [business] layer
  $métier = new Metier($dao);
  // tax calculation in batch mode
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // error is displayed
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;

Comentários

Apenas as linhas 12–22, que carregam os ficheiros necessários para executar a aplicação, são alteradas. São alteradas porque o valor de [__DIR__] muda: agora aponta para a 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
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
 
// interface and class inclusion
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";
 
// third-party libraries
require_once VENDOR . "/autoload.php";
 
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";
 
class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;
 
  public function __construct() {
    parent::__construct();
    // creation of the [dao] layer
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }
 
  // tests
  public function testTaxAdminData() {

  }
 
}

Comentários

  • linhas 9–28: definição do ambiente de teste. Utilizamos o mesmo ambiente, sem a camada [business], que o utilizado pelo script principal [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] descrito na secção referenciada;
  • 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] é o descrito na secção com link;

Os resultados do teste são os seguintes:

Image

14.6.2. Testar a camada [business]

O teste [MetierTest.php] é o seguinte:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
 
// interface and class inclusion
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";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";
 
class MetierTest extends \Codeception\Test\Unit {
  // business layer
  private $métier;
 
  public function __construct() {
    parent::__construct();
    // creation of the [dao] layer
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // creation of the [business] layer
    $this->métier = new Metier($dao);
  }
 
  // tests
  public function test1() {

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

  }
 
}

Comentários

  • linhas 9–28: definição do ambiente de teste. Utilizamos o mesmo que o script principal [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] descrito na secção com o link;
  • linhas 34–40: construção das camadas [dao] e [business];
  • linha 39: o atributo [$this→business] faz referência à camada [business]
  • linhas 43–49: os métodos [test1, test2…, test11] são os descritos na secção com link;

Os resultados do teste são os seguintes:

Image