Skip to content

14. Ćwiczenie praktyczne – wersja 6

Image

Właśnie zaimplementowaliśmy następującą strukturę warstwową:

Image

SGBD używany w przykładach to MySQL. W akapicie dotyczącym linków zauważyliśmy, że nic w klasie implementującej warstwę [dao] nie sugerowało, że używamy konkretnego obiektu SGBD. Teraz to sprawdzimy, wykorzystując inny obiekt SGBD, a mianowicie SGBD i PostgreSQL. Architektura warstwowa wygląda następująco:

Image

14.1. Instalacja plików SGBD i PostgreSQL

Dystrybucje SGBD i PostgreSQL są dostępne w URL i [https://www.postgresql.org/download/] (maj 2019 r.). Poniżej przedstawiamy instalację wersji dla systemu Windows 64-bitowego:

Image

Image

  • w [1-4] należy pobrać instalator z pliku SGBD;

Uruchamiamy pobrany instalator:

Image

  • w [6] należy wskazać folder instalacyjny;

Image

  • w [8] opcja [Stack Builder] nie jest potrzebna do tego, co chcemy tutaj zrobić;
  • w [10] pozostaw domyślną wartość;

Image

  • w polu [12-13] wprowadzono hasło [root]. Będzie to hasło administratora SGBD, który nazywa się [postgres]. PostgreSQL nazywa go również superużytkownikiem;
  • w polu [15] należy pozostawić wartość domyślną: jest to port nasłuchowy serwera SGBD;

Image

  • w polu [17] pozostaw wartość domyślną;
  • w [19] – podsumowanie konfiguracji instalacji;

Image

Image

W systemie Windows usługa SGBD PostgreSQL jest zainstalowana jako uruchamiana automatycznie usługa systemu Windows. W większości przypadków nie jest to pożądane. Zmienimy tę konfigurację. Wpisz [services] w pasku wyszukiwania systemu Windows [24-26]:

Image

  • na [29]. Widzimy, że usługa SGBD PostgreSQL działa w trybie automatycznym. Zmieniamy to, przechodząc do właściwości usługi [30]:

Image

  • w przypadku usługi [31-32] należy ustawić uruchamianie w trybie ręcznym;
  • w przypadku usługi [33] należy zatrzymać usługę;

Gdy zechcesz ręcznie uruchomić usługę SGBD, wróć do aplikacji [services], kliknij prawym przyciskiem myszy usługę [postgresql] (34) i uruchom ją (35).

14.2. Aktywacja rozszerzenia PDO dla SGBD i PostgreSQL

Zmodyfikujemy plik [php.ini], który konfiguruje PHP (patrz akapit dotyczący linku):

Image

  • na [2], upewnij się, że rozszerzenie PDO dla pliku PostgreSQL jest włączone. Po wykonaniu tej czynności zapisz zmianę, a następnie uruchom ponownie Laragon, aby mieć pewność, że zmiana zostanie uwzględniona. Następnie sprawdź konfigurację PHP bezpośrednio z poziomu Laragon [3-5].

14.3. Zarządzanie PostgreSQL za pomocą narzędzia [pgAdmin]

Uruchom usługę systemu Windows dla SGBD PostgreSQL (patrz akapit „link”). Następnie, w taki sam sposób, w jaki uruchomiłeś narzędzie [services], uruchom narzędzie [pgadmin], które umożliwia zarządzanie narzędziami SGBD, PostgreSQL i [1-3]:

Image

Możliwe, że w pewnym momencie pojawi się prośba o podanie hasła superużytkownika. Nazywa się ono [postgres]. Hasło to zostało przez Państwa ustawione podczas instalacji programu SGBD. W niniejszym dokumencie podczas instalacji nadaliśmy superużytkownikowi hasło [root].

  • w przypadku [4], [pgAdmin] jest aplikacją internetową;
  • w [5] znajduje się lista serwerów PostgreSQL wykrytych przez [pgAdmin], w tym przypadku 1;
  • w [6] – serwer PostgreSQL, który uruchomiliśmy;
  • w [7] – bazy danych serwera SGBD, w tym przypadku 1;
  • w [8] baza [postgresql] jest zarządzana przez superużytkownika [postgres];

Najpierw utwórzmy użytkownika o nazwie [admimpots] z hasłem [mdpimpots]:

Image

Image

  • w [17] wpisaliśmy [mdpimpots];

Image

  • w [21] kod SQL, który narzędzie [pgAdmin] wygeneruje do SGBD PostgreSQL. Jest to sposób na naukę języka SQL, będącego własnością PostgreSQL;
  • w [22], po zatwierdzeniu przez kreatora [Save], utworzono użytkownika [admimpots];

Teraz tworzymy bazę danych o nazwie [dbimpots-2019]:

Image

Klikamy prawym przyciskiem myszy na [23], a następnie na [24-25], aby utworzyć nową bazę danych. W zakładce [26] definiujemy nazwę bazy danych [27] oraz jej właściciela [admimpots] [28].

Image

  • w [30] kod SQL tworzenia bazy;
  • na [31], po zatwierdzeniu przez kreatora [Save], baza [dbimpots-2019] zostaje utworzona;

Teraz utworzymy tabelę [tbtranches] z kolumnami [id, limites, coeffr, coeffn]. Cechą charakterystyczną tabeli PostgreSQL jest to, że nazwy kolumn rozróżniają wielkość liter (wielkie/małe), co zazwyczaj nie ma miejsca w przypadku innych tabel typu SGBD. Tak więc w przypadku pliku MySQL polecenie [select limites, coeffR, coeffN from tbtranches] zadziała, nawet jeśli rzeczywiste kolumny tabeli [tbtranches] to [LIMITES, COEFFR, COEFFN]. W przypadku PostgreSQL polecenie SQL nie zadziała. Można by wtedy wpisać [select LIMITES, COEFFR, COEFFN from tbtranches], ale to nadal nie zadziała, ponieważ PostgreSQL wykona polecenie [select limites, coeffr, coeffn from tbtranches]: domyślnie przekazuje nazwy kolumn pisane małymi literami. Aby tego uniknąć, należy wpisać: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], tzn. nazwy kolumn należy ująć w cudzysłowy. Z tych powodów nadamy kolumnom nazwy pisane małymi literami. Nazwy obiektów w bazie danych mogą być źródłem niezgodności między SGBD, ponieważ niektóre nazwy są słowami zastrzeżonymi w niektórych SGBD, a w innych nie.

Tworzymy tabelę [tbtranches]:

Image

  • użyj przycisku [40], aby utworzyć kolumny;

Image

Image

  • po zakończeniu pracy kreatora za pomocą [Save] tworzona jest tabela [tbtranches] [52-53];

Musimy poinformować tabelę SGBD, że ma ona sama generować klucz główny [id] podczas wstawiania wiersza do tabeli:

Image

  • w [56] uzyskujemy dostęp do właściwości klucza głównego [id];
  • w polu [59] określa się, że kolumna ma typ [Identity]. Spowoduje to, że SGBD wygeneruje wartości klucza głównego;

Image

  • w [62] – kod SQL wygenerowany dla tej operacji;

Tabela [tbtranches] jest teraz gotowa.

Powtarzamy te same operacje, aby utworzyć tabelę [tbconstantes]. Podajemy wynik, który ma zostać uzyskany:

Image

Image

Image

Baza danych [dbimpots-2019] jest już gotowa. Teraz wypełnimy ją danymi.

Podobnie jak w przypadku bazy MySQL, można wyeksportować bazę danych [dbimpots-2019] do pliku SQL. Następnie można zaimportować ten plik SQL, aby odtworzyć bazę danych w przypadku jej utraty lub uszkodzenia. W tym przypadku wyeksportujemy jedynie strukturę bazy danych, a nie jej dane:

Image

Image

Wygenerowany plik wygląda następująco:


--
-- zrzut bazy danych PostgreSQL
--

-- Zrzut z bazy danych w wersji 11.2
-- Zrzut wykonany przez pg_dump w wersji 11.2

-- Rozpoczęto 2019-07-04 o godz. 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;

--
-- Wpis TOC nr 198 (klasa 1259 OID 16408)
-- Nazwa: tbconstantes; Typ: TABLE; Schemat: public; Właściciel: 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 wpis 199 (klasa 1259 OID 16411)
-- Nazwa: tbconstantes_id_seq; Typ: SEQUENCE; Schemat: public; Właściciel: 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 wpis 196 (klasa 1259 OID 16399)
-- Nazwa: tbtranches; Typ: TABLE; Schemat: public; Właściciel: 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 wpis 197 (klasa 1259 OID 16404)
-- Nazwa: tbimpots_id_seq; Typ: SEQUENCE; Schemat: public; Właściciel: 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 wpis 2694 (klasa 2606 OID 16429)
-- Nazwa: tbconstantes tbconstantes_pkey; Typ: CONSTRAINT; Schemat: public; Właściciel: postgres
--

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


--
-- TOC wpis 2692 (klasa 2606 OID 16403)
-- Nazwa: tbtranches tbimpots_pkey; Typ: CONSTRAINT; Schemat: public; Właściciel: admimpots
--

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


--
-- TOC wpis 2821 (klasa 0 OID 0)
-- Zależności: 198
-- Nazwa: TABLE tbconstantes; Typ: ACL; Schemat: public; Właściciel: postgres
--

GRANT ALL ON TABLE public.tbconstantes TO admimpots;


-- Zakończono 2019-07-04 o godz. 08:20:32

--
-- Zrzut bazy danych PostgreSQL zakończony
--

14.4. Wypełnianie tabeli [tbtranches]

Wykonaliśmy już tę pracę w przypadku plików SGBD i MySQL w sekcji „link”. Wystarczy, że zmodyfikujemy plik [database.json], który opisuje bazę danych:

Image

Plik [database.json] przyjmuje następującą postać:


{
    "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"
}
  • wiersz 2: plik DSN uległ zmianie, a [pgsql] wskazuje, że mamy do czynienia z plikiem SGBD dla Postgres;
  • wiersze 5 i 9: przed nazwami tabel umieszczono nazwę schematu, do którego należą, tj. [public]. Nie było to konieczne, ponieważ [public] jest schematem domyślnym, gdy w nazwie tabeli nie podano żadnego schematu;
  • wiersze 6–8, 10–19: zmieniono nazwy kolumn;

Skrypt [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] służący do wypełnienia bazy danych [dbimpots-2019] ma następującą postać:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

// obsługa błędów przez PHP
// ini_set („display_errors”, „0”);
// dołączenie interfejsu i klas
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";
//
// definicja stałych
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // utworzenie warstwy [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // przeniesienie danych do bazy
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // wyświetlanie błędu
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// koniec
print "Terminé\n";
exit;

Komentarze

Zmieniają się jedynie wiersze 12–21, które ładują pliki niezbędne do działania aplikacji. Zmieniają się one, ponieważ zmienia się wartość [__DIR__]: obecnie wskazuje ona na folder [version-07/Main].

Po uruchomieniu tego skryptu w tabeli [tbtranches] otrzymujemy następujący wynik:

Image

  • klikamy prawym przyciskiem myszy na [1], a następnie na [2-3];
  • w tabeli [4] widoczne są dane dotyczące przedziałów podatkowych;

Powtarzamy tę samą procedurę dla tabeli stałych [tbconstantes]:

Image

Image

Image

Należy zauważyć, że do wykonania skryptu aplikacja Laragon nie musi być uruchomiona: nie potrzebujemy ani serwera Apache, ani SGBD ani MySQL. Wymagane są jedynie pliki SGBD i PostgreSQL, dla których uruchomiono usługę systemu Windows.

14.5. Obliczanie podatku

Image

Warstwy [dao] (3) i [métier] (2) zostały już napisane. Główny skrypt dla SGBD i MySQL napisaliśmy już w akapicie „link”. Wystarczy, że wykorzystamy skrypt [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] i dostosujemy go do plików SGBD oraz PostgreSQL. Nazywa się on teraz [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Image

Skrypt [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] wygląda następująco:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

// obsługa błędów przez PHP
//ini_set("display_errors", "0");
// dołączanie interfejsów i klas
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";
//
// definicja stałych
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 {
  // utworzenie warstwy [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // utworzenie warstwy [métier]
  $métier = new Metier($dao);
  // obliczanie podatku w trybie wsadowym
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // wyświetlenie błędu
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// koniec
print "Terminé\n";
exit;

Komentarze

Zmieniają się jedynie wiersze 12–22, które ładują pliki niezbędne do działania aplikacji. Zmieniają się one, ponieważ zmienia się wartość [__DIR__]: wskazuje ona teraz na folder [version-07/Main].

Wyniki wykonania

Takie same jak w poprzednich wersjach.

14.6. Testy [Codeception]

Podobnie jak w przypadku poprzednich wersji, weryfikujemy tę wersję za pomocą testów [Codeception]:

Image

14.6.1. Test warstwy [dao]

Test [DaoTest.php] wygląda następująco:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

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

// dołączanie interfejsów i klas
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";

// biblioteki zewnętrzne
require_once VENDOR . "/autoload.php";

// definicja stałych
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";

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

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

  // testy
  public function testTaxAdminData() {

  }

}

Komentarze

  • wiersze 9–28: definicja środowiska testowego. Używamy tego samego środowiska, bez warstwy [métier], co w głównym skrypcie [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] opisanym w akapicie „link”;
  • wiersze 34–39: tworzenie warstwy [dao];
  • wiersz 38: atrybut [$this→taxAdminData] zawiera dane do przetestowania;
  • wiersze 42–44: metoda [testTaxAdminData] jest tą opisaną w akapicie „link”;

Wyniki testu są następujące:

Image

14.6.2. Test warstwy [métier]

Test [MetierTest.php] wygląda następująco:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

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

// dołączanie interfejsów i klas
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";
// biblioteki zewnętrzne
require_once VENDOR . "/autoload.php";
// definicja stałych
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";

class MetierTest extends \Codeception\Test\Unit {
  // warstwa biznesowa
  private $métier;

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

  // testy
  public function test1() {

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

  }

}

Komentarze

  • wiersze 9–28: definicja środowiska testowego. Wykorzystujemy to samo środowisko, co w głównym skrypcie [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] opisanym w akapicie „link”;
  • wiersze 34–40: tworzenie warstw [dao] i [métier];
  • wiersz 39: atrybut [$this→métier] odwołuje się do warstwy [métier]
  • wiersze 43–49: metody [test1, test2…, test11] są opisane w akapicie „link”;

Wyniki testu są następujące:

Image