14. Практичне завдання — версія 6

Ми щойно реалізували таку багаторівневу структуру:

SGBD, що використовувався в прикладах, був MySQL. У розділі «Посилання» ми зауважили, що ніщо в класі, який реалізує рівень [dao], не вказувало на те, що використовується саме SGBD. Саме це ми й перевіримо зараз, використовуючи інший SGBD, а саме SGBD та PostgreSQL. Архітектура шарів стає такою:

14.1. Встановлення SGBD та PostgreSQL
Дистрибутиви SGBD та PostgreSQL доступні на URL та [https://www.postgresql.org/download/] (травень 2019 року). Ми покажемо встановлення версії для 64-бітної Windows:


- у [1-4] завантажується інсталятор з SGBD;
Запускаємо завантажений інсталятор:

- у [6] вкажіть папку для встановлення;

- у [8] опція [Stack Builder] для наших цілей не потрібна;
- у [10] залиште значення, яке вам буде запропоновано;

- у [12-13] ми вказали тут пароль [root]. Це буде пароль адміністратора SGBD, який називається [postgres]. PostgreSQL також називають суперкористувачем;
- у полі [15] залиште значення за замовчуванням: це порт прослуховування SGBD;

- у полі [17] залиште значення за замовчуванням;
- у [19] — підсумок конфігурації інсталяції;


У Windows служба SGBD PostgreSQL встановлюється як служба Windows, що запускається автоматично. У більшості випадків це небажано. Ми змінимо цю конфігурацію. Введіть [services] у рядку пошуку Windows [24-26]:

- на [29]. Ми бачимо, що служба SGBD PostgreSQL працює в автоматичному режимі. Змінимо це, відкривши властивості служби [30]:

- у [31-32] встановіть запуск у ручному режимі;
- у [33] зупиніть службу;
Коли ви захочете запустити SGBD вручну, поверніться до програми [services], клацніть правою кнопкою миші на службі [postgresql] (34) і запустіть її (35).
14.2. Активація розширення PDO для SGBD та PostgreSQL
Ми змінимо файл [php.ini], який налаштовує PHP (див. параграф «посилання»):

- на [2], переконайтеся, що розширення PDO для PostgreSQL увімкнено. Після цього збережіть зміни та перезапустіть Laragon, щоб переконатися, що зміни будуть застосовані. Потім перевірте налаштування PHP безпосередньо в Laragon [3-5].
14.3. Управління PostgreSQL за допомогою інструменту [pgAdmin]
Запустіть службу Windows для SGBD PostgreSQL (див. параграф «Посилання»). Потім, так само, як ви запускали інструмент [services], запустіть утиліту [pgadmin], яка дозволяє адмініструвати SGBD, PostgreSQL та [1-3]:

Можливо, в якийсь момент вас попросять ввести пароль суперкористувача. Він називається [postgres]. Ви встановили цей пароль під час інсталяції SGBD. У цьому документі ми вказали пароль [root] для суперкористувача під час інсталяції.
- [4], [pgAdmin] — це веб-додаток;
- у [5] — список серверів PostgreSQL, виявлених [pgAdmin], тут — 1;
- у [6] — сервер PostgreSQL, який ми запустили;
- у [7] — бази даних SGBD, тут 1;
- у [8] база [postgresql] управляється суперкористувачем [postgres];
Спочатку створимо користувача [admimpots] з паролем [mdpimpots]:


- у [17] ми вказали [mdpimpots];

- у [21] — код SQL, який інструмент [pgAdmin] передасть до SGBD PostgreSQL. Це один із способів вивчити мову SQL, що належить PostgreSQL;
- у [22], після підтвердження майстром [Save], було створено користувача [admimpots];
Тепер створюємо базу даних [dbimpots-2019]:

Клацаємо правою кнопкою миші на [23], а потім на [24-25], щоб створити нову базу даних. На вкладці [26] задаємо ім’я бази даних [27] та її власника [admimpots] [28].

- у [30] — код створення бази SQL;
- в [31], після перевірки майстром [Save] створюється база даних [dbimpots-2019];
Тепер ми створимо таблицю [tbtranches] зі стовпцями [id, limites, coeffr, coeffn]. Особливістю PostgreSQL є те, що імена стовпців чутливі до регістру (великі/малі літери), чого зазвичай не спостерігається в інших SGBD. Так, у випадку з MySQL команда [select limites, coeffR, coeffN from tbtranches] працюватиме, навіть якщо фактичні стовпці таблиці [tbtranches] мають назви [LIMITES, COEFFR, COEFFN]. У випадку з PostgreSQL команда SQL не працюватиме. Тоді можна було б написати [select LIMITES, COEFFR, COEFFN from tbtranches], але це все одно не спрацює, оскільки PostgreSQL виконає команду [select limites, coeffr, coeffn from tbtranches]: за замовчуванням він перетворює імена стовпців на малі літери. Щоб цього уникнути, потрібно написати: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], тобто імена стовпців слід взяти в лапки. З цих причин ми будемо надавати стовпцям імена малими літерами. Імена об’єктів бази даних можуть бути джерелом несумісності між SGBD, оскільки деякі імена є зарезервованими словами в одних SGBD, а в інших — ні.
Створюємо таблицю [tbtranches]:

- використовуйте кнопку [40] для створення стовпців;


- після завершення роботи майстра створення за допомогою [Save] створюється таблиця [tbtranches] [52-53];
Нам потрібно вказати SGBD, що він повинен самостійно генерувати первинний ключ [id] під час вставки рядка в таблицю:

- у [56] ми отримуємо доступ до властивостей первинного ключа [id];
- у [59] вказується, що стовпець має тип [Identity]. Це призведе до того, що SGBD буде генерувати значення первинного ключа;

- у [62] — код SQL, згенерований для цієї операції;
Таблиця [tbtranches] тепер готова.
Повторюємо ті самі операції, щоб створити таблицю [tbconstantes]. Вказуємо результат, який потрібно отримати:



База даних [dbimpots-2019] тепер готова. Зараз ми заповнимо її даними.
Як ми вже робили з MySQL, можна експортувати базу даних [dbimpots-2019] у файл SQL. Потім цей файл SQL можна імпортувати, щоб відновити базу даних у разі її втрати або пошкодження. Тут ми експортуємо лише структуру бази даних, а не її дані:


Створений файл має такий вигляд:
--
-- злиття бази даних PostgreSQL
--
-- Злито з версії бази даних 11.2
-- Злито за допомогою pg_dump версії 11.2
-- Запущено 04.07.2019 о 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 № 198 (клас 1259 OID 16408)
-- Ім'я: tbconstantes; Тип: TABLE; Схема: public; Власник: 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 запис 199 (клас 1259 OID 16411)
-- Ім'я: tbconstantes_id_seq; Тип: SEQUENCE; Схема: public; Власник: 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 № 196 (клас 1259 OID 16399)
-- Назва: tbtranches; Тип: TABLE; Схема: public; Власник: 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 запис 197 (клас 1259 OID 16404)
-- Назва: tbimpots_id_seq; Тип: SEQUENCE; Схема: public; Власник: 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 № 2694 (клас 2606 OID 16429)
-- Ім'я: tbconstantes tbconstantes_pkey; Тип: CONSTRAINT; Схема: public; Власник: postgres
--
ALTER TABLE ONLY public.tbconstantes
ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);
--
-- запис TOC № 2692 (клас 2606 OID 16403)
-- Ім'я: tbtranches tbimpots_pkey; Тип: CONSTRAINT; Схема: public; Власник: admimpots
--
ALTER TABLE ONLY public.tbtranches
ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);
--
-- Запис TOC № 2821 (клас 0 OID 0)
-- Залежності: 198
-- Назва: TABLE tbconstantes; Тип: ACL; Схема: public; Власник: postgres
--
GRANT ALL ON TABLE public.tbconstantes TO admimpots;
-- Завершено 04.07.2019 о 08:20:32
--
-- Зливання бази даних PostgreSQL завершено
--
14.4. Заповнення таблиці [tbtranches]
Ми вже виконували цю роботу з файлами SGBD та MySQL у розділі «Посилання». Нам залишається лише змінити файл [database.json], який описує базу даних:

Файл [database.json] набуває такого вигляду:
{
"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"
}
- рядок 2: DSN змінився, [pgsql] вказує, що ми маємо справу з SGBD Postgres;
- рядки 5 та 9: перед назвами таблиць додано назву схеми, до якої вони належать — [public]. Це не було обов’язковим, оскільки [public] є схемою, що використовується за замовчуванням, коли в назві таблиці не вказано жодної схеми;
- рядки 6–8, 10–19: назви стовпців змінено;
Скрипт [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] для заповнення бази даних [dbimpots-2019] має такий вигляд:
<?php
// суворе дотримання оголошених типів параметрів функцій
declare (strict_types=1);
// простір імен
namespace Application;
// обробка помилок за допомогою PHP
// ini_set("display_errors", "0");
// включення інтерфейсу та класів
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";
//
// визначення констант
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
//
try {
// створення шару [dao]
$dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
// перенесення даних у базу
$dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
// виводиться повідомлення про помилку
print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// кінець
print "Terminé\n";
exit;
Коментарі
Змінюються лише рядки 12–21, які завантажують файли, необхідні для виконання програми. Вони змінюються, оскільки змінюється значення [__DIR__]: тепер воно вказує на папку [version-07/Main].
При виконанні цього скрипта в таблиці [tbtranches] отримуємо такий результат:

- клацніть правою кнопкою миші на [1], а потім на [2-3];
- у [4] дійсно містяться дані про податкові шкали;
Повторіть те саме для таблиці констант [tbconstantes]:



Слід зазначити, що для виконання скрипта додаток Laragon не повинен бути активним: не потрібні ані сервер Apache, ані SGBD та MySQL. Потрібен лише SGBD PostgreSQL, для якого запущено службу Windows.
14.5. Розрахунок податку

Шари [dao] (3) та [métier] (2) вже написано. Ми вже написали головний скрипт для SGBD та MySQL у розділі «Посилання». Нам залишається лише взяти скрипт [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] і адаптувати його до SGBD та PostgreSQL. Тепер він називається [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Скрипт [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] має такий вигляд:
<?php
// суворе дотримання оголошених типів параметрів функцій
declare (strict_types=1);
// простір імен
namespace Application;
// обробка помилок за допомогою PHP
//ini_set("display_errors", "0");
// включення інтерфейсів та класів
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";
//
// визначення констант
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 {
// створення шару [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// створення шару [métier]
$métier = new Metier($dao);
// розрахунок податків у пакетному режимі
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// виведення повідомлення про помилку
print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// кінець
print "Terminé\n";
exit;
Коментарі
Змінюються лише рядки 12–22, які завантажують файли, необхідні для виконання програми. Вони змінюються, оскільки змінюється значення [__DIR__]: тепер воно вказує на папку [version-07/Main].
Результати виконання
Такі самі, як і в попередніх версіях.
14.6. Тестування [Codeception]
Як і у попередніх версіях, ми перевіряємо цю версію за допомогою тестів [Codeception]:

14.6.1. Тест шару [dao]
Тест [DaoTest.php] виглядає наступним чином:
<?php
// суворе дотримання оголошених типів параметрів функцій
declare (strict_types=1);
// простір імен
namespace Application;
// кореневі каталоги
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// включення інтерфейсів та класів
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 VENDOR . "/autoload.php";
// визначення констант
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// створення шару [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// тестування
public function testTaxAdminData() {
…
}
}
Коментарі
- рядки 9–28: визначення середовища тесту. Ми використовуємо те саме середовище, що й у головному скрипті [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase], описаному в розділі за посиланням, але без шару [métier];
- рядки 34–39: побудова шару [dao];
- рядок 38: атрибут [$this→taxAdminData] містить дані, що підлягають тестуванню;
- рядки 42–44: метод [testTaxAdminData] — це той, що описаний у розділі за посиланням;
Результати тестування такі:

14.6.2. Тестування шару [métier]
Тест [MetierTest.php] виглядає так:
<?php
// суворе дотримання оголошених типів параметрів функцій
declare (strict_types=1);
// простір імен
namespace Application;
// кореневі каталоги
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// включення інтерфейсів та класів
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";
// сторонні бібліотеки
require_once VENDOR . "/autoload.php";
// визначення констант
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";
class MetierTest extends \Codeception\Test\Unit {
// бізнес-шар
private $métier;
public function __construct() {
parent::__construct();
// створення шару [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// створення шару [métier]
$this->métier = new Metier($dao);
}
// тестування
public function test1() {
…
}
--------------------------------------------------------------------
public function test11() {
…
}
}
Коментарі
- рядки 9–28: визначення середовища тестування. Ми використовуємо те саме середовище, що й у головному скрипті [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase], описаному в розділі «Посилання»;
- рядки 34–40: побудова шарів [dao] та [métier];
- рядок 39: атрибут [$this→métier] посилається на шар [métier]
- рядки 43–49: методи [test1, test2…, test11] — це ті, що описані в розділі «посилання»;
Результати тесту такі:
