Skip to content

14. 应用练习 – 第 6 版

Image

我们刚刚实现了以下分层结构:

Image

示例中使用的数据库管理系统(DBMS)是 MySQL。“链接”部分,我们曾指出,实现 [dao] 层的类中没有任何内容表明正在使用特定的数据库管理系统。现在,我们将通过使用另一种数据库管理系统 PostgreSQL 来验证这一点。分层架构如下所示:

Image

14.1. 安装 PostgreSQL 数据库管理系统

PostgreSQL 数据库管理系统(DBMS)的发行版可从 [https://www.postgresql.org/download/] 获取(2019年5月)。此处演示 64 位 Windows 版本的安装过程:

Image

Image

  • 请访问 [1-4] 下载数据库管理系统安装程序;

运行下载的安装程序:

Image

  • [6] 中,指定安装目录;

Image

  • [8] 中,对于我们当前的操作,无需选择 [Stack Builder] 选项;
  • [10] 中,保留默认值;

Image

  • [12-13] 中,我们在此处输入了密码 [root]。这将是 DBMS 管理员(用户名为 [postgres])的密码。PostgreSQL 也将此用户称为超级用户;
  • [15] 中,保留默认值:这是 DBMS 的监听端口;

Image

  • [17] 中,保留默认值;
  • [19] 中,显示安装配置的摘要;

Image

Image

在 Windows 系统中,PostgreSQL 数据库管理系统会作为 Windows 服务安装并自动启动。大多数情况下,这并非理想状态。我们将修改此配置。在 Windows 搜索栏中输入 [services] [24-26]

Image

  • [29] 中,您可以看到 PostgreSQL 数据库管理系统服务被设置为“自动”启动。通过访问服务属性 [30] 来更改此设置:

Image

  • [31-32] 中,将启动类型设置为“手动”;
  • [33] 中,停止该服务;

当您需要手动启动数据库管理系统时,请返回 [服务] 应用程序,右键单击 [postgresql] 服务 (34),然后启动它 (35)。

14.2. 为 PostgreSQL DBMS 启用 PDO 扩展

我们将修改用于配置 PHP 的 [php.ini] 文件(参见相关章节):

Image

  • [2] 中,确认 PostgreSQL PDO 扩展已启用。完成后,保存更改并重启 Laragon 以确保更改生效。随后直接通过 Laragon 验证 PHP 配置 [3-5]

14.3. 使用 [pgAdmin] 工具管理 PostgreSQL

启动 PostgreSQL DBMS 的 Windows 服务(参见相关章节)。接着,就像您启动 [services] 工具那样,启动 [pgadmin] 工具,该工具可用于管理 PostgreSQL DBMS [1-3]

Image

系统可能会在某个时候提示您输入超级用户密码。超级用户的名称为 [postgres]。您在安装数据库管理系统时设置了此密码。在本文档中,我们在安装过程中为超级用户设置了密码 [root]

  • [4] 中,[pgAdmin] 是一个 Web 应用程序;
  • [5] 中,表示 [pgAdmin] 检测到的 PostgreSQL 服务器列表,此处为 1;
  • [6] 中,指我们启动的 PostgreSQL 服务器;
  • [7] 中,表示 DBMS 数据库,此处为 1;
  • [8] 中,[postgresql] 数据库由超级用户 [postgres] 管理;

首先,让我们创建一个用户 [admimpots],密码为 [mdpimpots]

Image

Image

  • [17] 中,我们输入了 [mdpimpots]

Image

  • [21] 处,显示了 [pgAdmin] 工具将发送给 PostgreSQL 数据库管理系统 (DBMS) 的 SQL 代码。这是学习 PostgreSQL 专有 SQL 语言的一种方式;
  • [22] 中,通过 [Save] 向导确认后,用户 [admimpots] 已创建;

现在我们创建数据库 [dbimpots-2019]

Image

右键单击 [23],然后依次选择 [24-25] 创建新数据库。在 [26] 选项卡中,定义数据库名称 [27] 及其所有者 [admimpots] [28]

Image

  • [30] 中,显示用于创建数据库的 SQL 代码;
  • [31] 中,通过 [保存] 向导确认后,数据库 [dbimpots-2019] 即创建完成;

现在,我们将创建表 [tbtranches],其列包含 [id, limites, coeffr, coeffn]。PostgreSQL 的一个显著特点是列名区分大小写(大写/小写),而其他数据库管理系统通常并不区分大小写。 因此,在 MySQL 中,即使 [tbtranches] 表中的实际列名为 [LIMITES, COEFFR, COEFFN],SQL 语句 [select limites, coeffR, coeffN from tbtranches] 依然有效。但在 PostgreSQL 中,该 SQL 语句将无法执行。 此时,有人可能会写成 [select LIMITES, COEFFR, COEFFN from tbtranches],但这仍然无法运行,因为 PostgreSQL 会执行查询 [select limites, coeffr, coeffn from tbtranches]:默认情况下,它会将列名转换为小写。 为避免此问题,必须写成:[select "LIMITES", "COEFFR", "COEFFN" from tbtranches],即必须将列名用引号括起来。基于这些原因,我们将为这些列赋予小写名称。 数据库对象的名称可能导致不同 DBMS 之间的不兼容,因为某些名称在某些 DBMS 中是保留字,而在其他 DBMS 中则不是。

我们创建表 [tbtranches]

Image

  • 使用按钮 [40] 创建列;

Image

Image

  • 点击 [保存] 完成创建向导后,表 [tbtranches] 即创建完成 [52-53]

我们需要告知数据库管理系统,在向表中插入行时,由其自动生成主键 [id]

Image

  • [56] 处,我们访问主键 [id] 的属性;
  • [59] 处,我们指定该列的类型为 [Identity]。这将导致 DBMS 自动生成主键值;

Image

  • [62]中,为该操作生成的SQL代码;

[tbtranches] 表现已准备就绪。

我们重复相同的步骤来创建 [tbconstantes] 表。以下是预期结果:

Image

Image

Image

[dbimpots-2019] 数据库现已准备就绪。接下来我们将向其中导入数据。

与处理 MySQL 时一样,可以将 [dbimpots-2019] 数据库导出为 SQL 文件。如果数据库丢失或损坏,我们可以导入该 SQL 文件来重建数据库。在此,我们将仅导出数据库结构,而不导出其中的数据:

Image

Image

生成的文件如下:


--
-- PostgreSQL database dump
--
 
-- Dumped from database version 11.2
-- Dumped by pg_dump version 11.2
 
-- Started on 2019-07-04 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 entry 198 (class 1259 OID 16408)
-- Name: tbconstantes; Type: TABLE; Schema: public; Owner: 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 entry 199 (class 1259 OID 16411)
-- Name: tbconstantes_id_seq; Type: SEQUENCE; Schema: public; Owner: 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 entry 196 (class 1259 OID 16399)
-- Name: tbtranches; Type: TABLE; Schema: public; Owner: 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 entry 197 (class 1259 OID 16404)
-- Name: tbimpots_id_seq; Type: SEQUENCE; Schema: public; Owner: 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 entry 2694 (class 2606 OID 16429)
-- Name: tbconstantes tbconstantes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
 
ALTER TABLE ONLY public.tbconstantes
    ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);
 
 
--
-- TOC entry 2692 (class 2606 OID 16403)
-- Name: tbtranches tbimpots_pkey; Type: CONSTRAINT; Schema: public; Owner: admimpots
--
 
ALTER TABLE ONLY public.tbtranches
    ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);
 
 
--
-- TOC entry 2821 (class 0 OID 0)
-- Dependencies: 198
-- Name: TABLE tbconstantes; Type: ACL; Schema: public; Owner: postgres
--
 
GRANT ALL ON TABLE public.tbconstantes TO admimpots;
 
 
-- Completed on 2019-07-04 08:20:32
 
--
-- PostgreSQL database dump complete
--

14.4. 正在填充 [tbtranches] 表

我们在链接部分中已经使用 MySQL 数据库管理系统(DBMS)完成了这一步。我们只需修改描述该数据库的 [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] 表示我们正在处理 Postgres 数据库管理系统;
  • 第 5 行和第 9 行:表名前已添加所属模式的名称 [public]。这并非绝对必要,因为当表名中未指定模式时,[public] 是默认模式;
  • 第 6–8 行、第 10–19 行:列名已更改;

用于向 [dbimpots-2019] 数据库导入数据的脚本 [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] 如下:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// error handling by PHP
// ini_set("display_errors", "0");
// interface and class inclusion
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";
//
// definition of constants
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
 
//
try {
  // creation of the [dao] layer
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // data transfer to the database
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // error is displayed
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;

注释

只有第 12–21 行发生了变化,这些行负责加载运行应用程序所需的文件。之所以发生变化,是因为 [__DIR__] 的值发生了变化:它现在指向 [version-07/Main] 文件夹。

运行此脚本后,[tbtranches] 表中将得到以下结果:

Image

  • 右键单击 [1],然后单击 [2-3]
  • [4]中,我们可以看到税率区间数据;

我们对常量表 [tbconstantes] 重复相同的操作:

Image

Image

Image

请注意,运行该脚本时,Laragon 应用程序无需处于活动状态:既不需要 Apache 服务器,也不需要 MySQL 数据库管理系统。我们仅需 PostgreSQL 数据库管理系统,为此我们已启动了 Windows 服务。

14.5. 税费计算

Image

[DAO] (3) 和 [业务] (2) 层已经编写完成。 我们在前文链接的章节中已编写了针对 MySQL 数据库管理系统的主脚本。我们只需将脚本 [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] 稍作调整,使其适配 PostgreSQL 数据库管理系统。该脚本现更名为 [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]

Image

脚本 [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] 内容如下:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// error handling by PHP
//ini_set("display_errors", "0");
// interface and class inclusion
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";
//
// definition of constants
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 {
  // creation of the [dao] layer
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // creation of the [business] layer
  $métier = new Metier($dao);
  // tax calculation in batch mode
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // error is displayed
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;

注释

只有第 12–22 行发生了变化,这些行负责加载运行应用程序所需的文件。之所以发生变化,是因为 [__DIR__] 的值发生了变化:它现在指向 [version-07/Main] 文件夹。

执行结果

与之前版本中获得的结果相同。

14.6. [Codeception] 测试

与之前版本一样,我们使用 [Codeception] 测试对本版本进行验证:

Image

14.6.1. [DAO] 层测试

[DaoTest.php] 测试内容如下:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
 
// interface and class inclusion
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";
 
// third-party libraries
require_once VENDOR . "/autoload.php";
 
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";
 
class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;
 
  public function __construct() {
    parent::__construct();
    // creation of the [dao] layer
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }
 
  // tests
  public function testTaxAdminData() {

  }
 
}

评论

  • 第 9–28 行:测试环境的定义。我们使用与链接部分中描述的主脚本 [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] 相同的环境,但不包含 [business] 层;
  • 第 34–39 行:构建 [dao] 层;
  • 第 38 行:[$this→taxAdminData] 属性包含待测试的数据;
  • 第 42–44 行:[testTaxAdminData] 方法即链接部分中所述的方法;

测试结果如下:

Image

14.6.2. 测试 [business] 层

[MetierTest.php] 的测试内容如下:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
 
// interface and class inclusion
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";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";
 
class MetierTest extends \Codeception\Test\Unit {
  // business layer
  private $métier;
 
  public function __construct() {
    parent::__construct();
    // creation of the [dao] layer
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // creation of the [business] layer
    $this->métier = new Metier($dao);
  }
 
  // tests
  public function test1() {

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

  }
 
}

评论

  • 第9–28行:测试环境的定义。我们使用与链接部分中描述的主脚本[MainCalculateImpotsWithTaxAdminDataInPostgresDatabase]相同的环境;
  • 第34–40行:构建[dao][business]层;
  • 第 39 行:属性 [$this→business] 引用 [business]
  • 第 43–49 行:方法 [test1, test2…, test11] 即链接部分中所述的方法;

测试结果如下:

Image