14. Esercizio pratico – versione 6

Abbiamo appena implementato la seguente struttura a livelli:

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:

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:


- in [1-4], si scarica il programma di installazione da SGBD;
Si avvia il programma di installazione scaricato:

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

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

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

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


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]:

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

- 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):

- 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]:

È 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]:


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

- 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]:

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].

- 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]:

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


- 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:

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

- 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:



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:


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:

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]:

- 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]:



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

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]:

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]:

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:

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:
