Skip to content

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

Image

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

Image

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

Image

14.1. Встановлення SGBD та PostgreSQL

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

Image

Image

  • у [1-4] завантажується інсталятор з SGBD;

Запускаємо завантажений інсталятор:

Image

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

Image

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

Image

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

Image

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

Image

Image

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

Image

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

Image

  • у [31-32] встановіть запуск у ручному режимі;
  • у [33] зупиніть службу;

Коли ви захочете запустити SGBD вручну, поверніться до програми [services], клацніть правою кнопкою миші на службі [postgresql] (34) і запустіть її (35).

14.2. Активація розширення PDO для SGBD та PostgreSQL

Ми змінимо файл [php.ini], який налаштовує PHP (див. параграф «посилання»):

Image

  • на [2], переконайтеся, що розширення PDO для PostgreSQL увімкнено. Після цього збережіть зміни та перезапустіть Laragon, щоб переконатися, що зміни будуть застосовані. Потім перевірте налаштування PHP безпосередньо в Laragon [3-5].

14.3. Управління PostgreSQL за допомогою інструменту [pgAdmin]

Запустіть службу Windows для SGBD PostgreSQL (див. параграф «Посилання»). Потім, так само, як ви запускали інструмент [services], запустіть утиліту [pgadmin], яка дозволяє адмініструвати SGBD, PostgreSQL та [1-3]:

Image

Можливо, в якийсь момент вас попросять ввести пароль суперкористувача. Він називається [postgres]. Ви встановили цей пароль під час інсталяції SGBD. У цьому документі ми вказали пароль [root] для суперкористувача під час інсталяції.

  • [4], [pgAdmin] — це веб-додаток;
  • у [5] — список серверів PostgreSQL, виявлених [pgAdmin], тут — 1;
  • у [6] — сервер PostgreSQL, який ми запустили;
  • у [7] — бази даних SGBD, тут 1;
  • у [8] база [postgresql] управляється суперкористувачем [postgres];

Спочатку створимо користувача [admimpots] з паролем [mdpimpots]:

Image

Image

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

Image

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

Тепер створюємо базу даних [dbimpots-2019]:

Image

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

Image

  • у [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]:

Image

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

Image

Image

  • після завершення роботи майстра створення за допомогою [Save] створюється таблиця [tbtranches] [52-53];

Нам потрібно вказати SGBD, що він повинен самостійно генерувати первинний ключ [id] під час вставки рядка в таблицю:

Image

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

Image

  • у [62] — код SQL, згенерований для цієї операції;

Таблиця [tbtranches] тепер готова.

Повторюємо ті самі операції, щоб створити таблицю [tbconstantes]. Вказуємо результат, який потрібно отримати:

Image

Image

Image

База даних [dbimpots-2019] тепер готова. Зараз ми заповнимо її даними.

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

Image

Image

Створений файл має такий вигляд:


--
-- злиття бази даних 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], який описує базу даних:

Image

Файл [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] отримуємо такий результат:

Image

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

Повторіть те саме для таблиці констант [tbconstantes]:

Image

Image

Image

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

14.5. Розрахунок податку

Image

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

Image

Скрипт [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]:

Image

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] — це той, що описаний у розділі за посиланням;

Результати тестування такі:

Image

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] — це ті, що описані в розділі «посилання»;

Результати тесту такі:

Image