Skip to content

14. Esercizio pratico – versione 6

Image

Abbiamo appena implementato la seguente struttura a livelli:

Image

Il SGBD utilizzato negli esempi era MySQL. Nel paragrafo dedicato ai collegamenti avevamo notato che nulla nella classe che implementa il livello [dao] lasciava supporre che si utilizzasse un SGBD specifico. È proprio questo che verificheremo ora utilizzando un altro SGBD, ovvero il SGBD PostgreSQL. L’architettura a livelli diventa la seguente:

Image

14.1. Installazione di SGBD e PostgreSQL

Le distribuzioni di SGBD e PostgreSQL sono disponibili su URL e [https://www.postgresql.org/download/] (maggio 2019). Di seguito illustriamo l’installazione della versione per Windows a 64 bit:

Image

Image

  • in [1-4], si scarica il programma di installazione da SGBD;

Si avvia il programma di installazione scaricato:

Image

  • in [6], specificare una cartella di installazione;

Image

  • in [8], l'opzione [Stack Builder] non è necessaria per l'operazione che vogliamo eseguire qui;
  • in [10], lasciare il valore che verrà visualizzato;

Image

  • in [12-13], qui è stata inserita la password [root]. Questa sarà la password dell’amministratore di SGBD, denominato [postgres]. PostgreSQL lo chiama anche «superutente»;
  • in [15], lasciate il valore predefinito: è la porta di ascolto di SGBD;

Image

  • in [17], lasciare il valore predefinito;
  • in [19], il riepilogo della configurazione dell’installazione;

Image

Image

Su Windows, il processo SGBD PostgreSQL è installato come servizio di Windows avviato automaticamente. Nella maggior parte dei casi ciò non è auspicabile. Modificheremo questa configurazione. Digitare [services] nella barra di ricerca di Windows [24-26]:

Image

  • in [29]; si nota che il servizio SGBD PostgreSQL è in modalità automatica. Modifichiamo questa impostazione accedendo alle proprietà del servizio [30]:

Image

  • in [31-32], impostare l'avvio in modalità manuale;
  • per [33], arrestare il servizio;

Quando si desidera avviare manualmente il servizio SGBD, tornare all’applicazione [services], fare clic con il tasto destro del mouse sul servizio [postgresql] (34) e avviarlo (35).

14.2. Attivazione dell’estensione PDO di SGBD PostgreSQL

Modificheremo il file [php.ini] che configura PHP (vedi paragrafo sul collegamento):

Image

  • in [2], verificare che l’estensione PDO di PostgreSQL sia attivata. Fatto ciò, salvare la modifica e riavviare Laragon per assicurarsi che la modifica venga applicata. Successivamente, verificare la configurazione di PHP direttamente da Laragon [3-5].

14.3. Gestire PostgreSQL con lo strumento [pgAdmin]

Avviate il servizio Windows di SGBD PostgreSQL (cfr. paragrafo "link"). Quindi, allo stesso modo in cui avete avviato lo strumento [services], avviare lo strumento [pgadmin] che consente di gestire SGBD, PostgreSQL e [1-3]:

Image

È possibile che a un certo punto vi venga richiesta la password del superutente. Questa è [postgres]. Avete impostato la password durante l’installazione di SGBD. In questo documento, durante l’installazione abbiamo assegnato al superutente la password [root].

  • in [4], [pgAdmin] è un’applicazione web;
  • in [5], l’elenco dei server PostgreSQL rilevati da [pgAdmin], in questo caso 1;
  • in [6], il server PostgreSQL che abbiamo avviato;
  • in [7], i database di SGBD, in questo caso 1;
  • in [8], il database [postgresql] è gestito dal superutente [postgres];

Creiamo innanzitutto un utente [admimpots] con la password [mdpimpots]:

Image

Image

  • in [17], abbiamo inserito [mdpimpots];

Image

  • in [21], il codice SQL che lo strumento [pgAdmin] invierà a SGBD PostgreSQL. È un modo per imparare il linguaggio proprietario SQL di PostgreSQL;
  • in [22], dopo la convalida della procedura guidata [Save], è stato creato l'utente [admimpots];

Ora creiamo il database [dbimpots-2019]:

Image

Si fa clic con il tasto destro su [23], quindi su [24-25] per creare un nuovo database. Nella scheda [26], si definisce il nome del database [27] e il suo proprietario [admimpots] [28].

Image

  • in [30], il codice SQL di creazione del database;
  • in [31], dopo la convalida da parte della procedura guidata [Save], viene creato il database [dbimpots-2019];

Ora creeremo la tabella [tbtranches] con le colonne [id, limites, coeffr, coeffn]. Una particolarità di PostgreSQL è che i nomi delle colonne distinguono tra maiuscole e minuscole, cosa che di solito non avviene con gli altri SGBD. Pertanto, con MySQL, l’ordine [select limites, coeffR, coeffN from tbtranches] funzionerà anche se le colonne effettive della tabella [tbtranches] sono [LIMITES, COEFFR, COEFFN]. Con PostgreSQL, l’ordine SQL non funzionerà. Si potrebbe quindi scrivere [select LIMITES, COEFFR, COEFFN from tbtranches], ma non funzionerebbe comunque, poiché PostgreSQL eseguirà il comando [select limites, coeffr, coeffn from tbtranches]: per impostazione predefinita, converte i nomi delle colonne in minuscolo. Per evitare che ciò accada, occorre scrivere: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], ovvero è necessario racchiudere i nomi delle colonne tra virgolette. Per questi motivi, assegneremo alle colonne nomi in minuscolo. I nomi degli oggetti di un database possono essere fonte di incompatibilità tra SGBD, poiché alcuni nomi sono parole riservate in alcuni SGBD e non in altri.

Creiamo la tabella [tbtranches]:

Image

  • utilizzare il pulsante [40] per creare le colonne;

Image

Image

  • dopo aver completato la procedura guidata di creazione tramite [Save], viene creata la tabella [tbtranches] [52-53];

Dobbiamo indicare a SGBD che deve generare autonomamente la chiave primaria [id] al momento dell’inserimento di una riga nella tabella:

Image

  • in [56] si accede alle proprietà della chiave primaria [id];
  • in [59], si indica che la colonna è di tipo [Identity]. Ciò comporterà che SGBD generi i valori della chiave primaria;

Image

  • in [62], il codice SQL generato per questa operazione;

La tabella [tbtranches] è ora pronta.

Ripetiamo le stesse operazioni per creare la tabella [tbconstantes]. Indichiamo il risultato da ottenere:

Image

Image

Image

Il database [dbimpots-2019] è ora pronto. Lo riempiremo con i dati.

Come abbiamo fatto con MySQL, è possibile esportare il database [dbimpots-2019] in un file SQL. È quindi possibile importare questo file SQL per ricreare il database in caso di perdita o danneggiamento. In questo caso esporteremo solo la struttura del database e non i suoi dati:

Image

Image

Il file generato è il seguente:


--
-- dump del database PostgreSQL
--

-- Dump dal database versione 11.2
-- Esportato con pg_dump versione 11.2

-- Avviato il 04/07/2019 alle 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;

--
-- Voce 198 di TOC (classe 1259 OID 16408)
-- Nome: tbconstantes; Tipo: TABLE; Schema: public; Proprietario: 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 voce 199 (classe 1259 OID 16411)
-- Nome: tbconstantes_id_seq; Tipo: SEQUENCE; Schema: public; Proprietario: 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
);


--
-- voce TOC n. 196 (classe 1259 OID 16399)
-- Nome: tbtranches; Tipo: TABLE; Schema: public; Proprietario: 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 voce 197 (classe 1259 OID 16404)
-- Nome: tbimpots_id_seq; Tipo: SEQUENCE; Schema: pubblico; Proprietario: 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
);


--
-- voce TOC 2694 (classe 2606 OID 16429)
-- Nome: tbconstantes tbconstantes_pkey; Tipo: CONSTRAINT; Schema: pubblico; Proprietario: postgres
--

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


--
-- TOC voce 2692 (classe 2606 OID 16403)
-- Nome: tbtranches tbimpots_pkey; Tipo: CONSTRAINT; Schema: public; Proprietario: admimpots
--

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


--
-- TOC voce 2821 (classe 0 OID 0)
-- Dipendenze: 198
-- Nome: TABLE tbconstantes; Tipo: ACL; Schema: pubblico; Proprietario: postgres
--

GRANT ALL ON TABLE public.tbconstantes TO admimpots;


-- Completato il 04/07/2019 alle 08:20:32

--
-- Dump del database PostgreSQL completato
--

14.4. Compilazione della tabella [tbtranches]

Abbiamo già svolto questo lavoro con i file SGBD e MySQL nel paragrafo relativo ai link. È sufficiente modificare il file [database.json] che descrive il database:

Image

Il file [database.json] diventa il seguente:


{
    "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"
}
  • riga 2: il DSN è cambiato; [pgsql] indica che si tratta del SGBD Postgres;
  • righe 5 e 9: i nomi delle tabelle sono stati preceduti dal nome dello schema a cui appartengono, [public]. Ciò non era indispensabile, poiché [public] è lo schema utilizzato per impostazione predefinita quando nel nome della tabella non è specificato alcuno schema;
  • righe 6-8, 10-19: i nomi delle colonne sono stati modificati;

Lo script [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] per il popolamento del database [dbimpots-2019] è il seguente:


<?php

// Rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// gestione degli errori tramite PHP
// ini_set("display_errors", "0");
// inclusione di interfaccia e classi
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";
//
// definizione delle costanti
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // creazione del livello [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // trasferimento dei dati nel database
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // viene visualizzato l'errore
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fine
print "Terminé\n";
exit;

Commenti

Cambiano solo le righe 12-21 che caricano i file necessari all’esecuzione dell’applicazione. Cambiano perché cambia il valore [__DIR__]: ora indica la cartella [version-07/Main].

Quando si esegue questo script, si ottiene il seguente risultato nella tabella [tbtranches]:

Image

  • si fa clic con il tasto destro su [1], quindi su [2-3];
  • in [4] sono presenti i dati relativi alle fasce di imposta;

Si ripete la stessa operazione per la tabella delle costanti [tbconstantes]:

Image

Image

Image

Si noti che per l’esecuzione dello script non è necessario che l’applicazione Laragon sia attiva: non servono né il server Apache né i file SGBD e MySQL. È sufficiente il SGBD PostgreSQL, di cui è stato avviato il servizio Windows.

14.5. Calcolo dell’imposta

Image

I livelli [dao] (3) e [métier] (2) sono già stati scritti. Abbiamo già scritto lo script principale per SGBD e MySQL nel paragrafo «link». Basta riprendere lo script [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] e adattarlo a SGBD e PostgreSQL. Ora si chiama [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Image

Lo script [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] è il seguente:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// gestione degli errori tramite PHP
//ini_set("display_errors", "0");
// inclusione di interfacce e classi
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";
//
// definizione delle costanti
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 {
  // creazione del livello [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // creazione del livello [métier]
  $métier = new Metier($dao);
  // calcolo delle imposte in modalità batch
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // viene visualizzato l'errore
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fine
print "Terminé\n";
exit;

Commenti

Cambiano solo le righe da 12 a 22, che caricano i file necessari all’esecuzione dell’applicazione. Cambiano perché cambia il valore [__DIR__]: ora indica la cartella [version-07/Main].

Risultati dell’esecuzione

Gli stessi ottenuti nelle versioni precedenti.

14.6. Test [Codeception]

Come per le versioni precedenti, convalidiamo questa versione con i test [Codeception]:

Image

14.6.1. Test dello strato [dao]

Il test [DaoTest.php] è il seguente:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// directory radice
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusione di interfacce e classi
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";

// librerie di terze parti
require_once VENDOR . "/autoload.php";

// definizione delle costanti
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";

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

  public function __construct() {
    parent::__construct();
    // creazione del livello [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // test
  public function testTaxAdminData() {

  }

}

Commenti

  • righe 9-28: definizione dell'ambiente di test. Utilizziamo lo stesso ambiente, senza il livello [métier], di quello utilizzato dallo script principale [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] descritto nel paragrafo link;
  • righe 34-39: creazione del livello [dao];
  • riga 38: l’attributo [$this→taxAdminData] contiene i dati da testare;
  • righe 42-44: il metodo [testTaxAdminData] è quello descritto nel paragrafo link;

I risultati del test sono i seguenti:

Image

14.6.2. Test del livello [métier]

Il test [MetierTest.php] è il seguente:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// directory radice
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusione di interfacce e classi
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";
// librerie di terze parti
require_once VENDOR . "/autoload.php";
// definizione delle costanti
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";

class MetierTest extends \Codeception\Test\Unit {
  // livello business
  private $métier;

  public function __construct() {
    parent::__construct();
    // creazione del livello [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // creazione del livello [métier]
    $this->métier = new Metier($dao);
  }

  // test
  public function test1() {

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

  }

}

Commenti

  • righe 9-28: definizione dell'ambiente di test. Utilizziamo lo stesso ambiente utilizzato dallo script principale [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] descritto nel paragrafo "link";
  • righe 34-40: creazione dei livelli [dao] e [métier];
  • riga 39: l’attributo [$this→métier] fa riferimento al livello [métier]
  • righe 43-49: i metodi [test1, test2…, test11] sono quelli descritti nel paragrafo "link";

I risultati del test sono i seguenti:

Image