8. 应用练习 – 第3版
我们将重新审视之前讲过的练习(第4.3节和第4.4节),并使用采用类的PHP代码来解决它。
8.1. 脚本目录结构

8.2. [ExceptionImpots] 异常
在第 03 版中,当构造函数或类方法遇到错误时,将抛出以下 [ExceptionImpots] 异常:
评论
- 第 4 行:[ExceptionImports] 类位于 [Application] 命名空间中;
- 第 6 行:[ExceptionImpots] 类继承自 PHP 的预定义类 [RuntimeException];
- 第 8 行:构造函数期望两个参数:
- $message:是与该异常相关的错误消息;
- $code:是与该异常关联的错误代码。如果未提供,则使用代码 0;
8.3. [TaxAdminData] 类
在 02 版本中,税务管理数据首先被收集:
- 首先存储在 JSON 文件中;
- 随后从该 JSON 文件导入关联数组;
在 03 版本中,税务管理数据仍保存在 [taxadmindata.json] 文件中,但属性名称有所不同:
{
"limites": [
9964,
27519,
73779,
156244,
0
],
"coeffR": [
0,
0.14,
0.3,
0.41,
0.45
],
"coeffN": [
0,
1394.96,
5798,
13913.69,
20163.45
],
"plafondQfDemiPart": 1551,
"plafondRevenusCelibatairePourReduction": 21037,
"plafondRevenusCouplePourReduction": 42074,
"valeurReducDemiPart": 3797,
"plafondDecoteCelibataire": 1196,
"plafondDecoteCouple": 1970,
"plafondImpotCouplePourDecote": 2627,
"plafondImpotCelibatairePourDecote": 1595,
"abattementDixPourcentMax": 12502,
"abattementDixPourcentMin": 437
}
在 02 版本中,此文件用于初始化一个关联数组。在 03 版本中,该文件将初始化以下 [TaxAdminData] 类:
<?php
namespace Application;
class TaxAdminData {
// tax brackets
private $limites;
private $coeffR;
private $coeffN;
// tax calculation constants
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// initialization
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// retrieve the contents of the tax data file
$fileContents = \file_get_contents($taxAdminDataFilename);
$erreur = FALSE;
// mistake?
if (!$fileContents) {
// we note the error
$erreur = TRUE;
$message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
}
if (!$erreur) {
// retrieve the jSON code from the configuration file in an associative array
$arrayTaxAdminData = \json_decode($fileContents, true);
// mistake?
if ($arrayTaxAdminData === FALSE) {
// we note the error
$erreur = TRUE;
$message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
}
}
// mistake?
if ($erreur) {
// throw an exception
throw new ExceptionImpots($message);
}
// initialization of class attributes
foreach ($arrayTaxAdminData as $key => $value) {
$this->$key = $value;
}
// check that all keys have been initialized
$arrayOfAttributes = \get_object_vars($this);
foreach ($arrayOfAttributes as $key => $value) {
if (!isset($this->$key)) {
throw new ExceptionImpots("L'attribut [$key] de [TaxAdminData] n'a pas été initialisé");
}
}
// we check that we only have real values
foreach ($this as $key => $value) {
// $value must be a real number >=0 or an array of reals >=0
$result = $this->check($value);
// mistake?
if ($result->erreur) {
// throw an exception
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// we note the value
$this->$key = $result->value;
}
}
// we return the object
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// object's Json string
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// getters and setters
public function getLimites() {
return $this->limites;
}
public function getCoeffR() {
return $this->coeffR;
}
…
}
public function setLimites($limites) {
$this->limites = $limites;
return $this;
}
public function setCoeffR($coeffR) {
$this->coeffR = $coeffR;
return $this;
}
…
}
评论
- 第 6–20 行:这些属性将存储来自 JSON 文件 [taxadmindata.json] 中同名的属性。这一点非常重要:[TaxAdminData] 类的属性与 JSON 文件 [taxadmindata.json] 中的属性完全一致。这一特性使得编写代码变得更加简单;
- [TaxAdminData] 类没有构造函数。在 PHP 中,无法拥有多个构造函数。因此,定义一个构造函数会阻止通过其他任何方式初始化该对象。今后,我们的类将不再拥有构造函数,而是拥有若干 [setFromSomething] 类型的方法,这些方法将允许通过不同方式进行初始化。随后,使用以下表达式构建 [TaxAdminData] 类型的对象:
- 第 23 行:[setFromJsonFile] 方法使用 [$jsonFilename] 文件中同名字段的值初始化类属性;
- 第 24–42 行:解析 JSON 文件以构建关联数组 [$arrayTaxAdminData]。我们在版本 02 的 [main.php] 脚本中已经见过这段代码;
- 第 44–47 行:若在处理 JSON 文件时发生错误,将抛出异常。该异常将向上传播至主脚本 [main.php];
- 第 48–51 行:初始化类属性。此处利用了关联数组 [$arrayTaxAdminData] 与类 [TaxAdminData] 的属性名称与 JSON 文件中的值名称完全一致这一特性;
- 第 53–57 行:我们验证 [TaxAdminData] 类的所有属性是否已初始化;
- 第 53 行:表达式 [get_object_vars($this)] 返回一个关联数组,其属性即对象 [$this] 的属性,也就是 [TaxAdminData] 类的属性。这里需要理解的是,第 48–51 行的初始化过程可能已向对象 [$this] 添加了属性。因此,如果我们写:
那么属性 [x] 就会被添加到对象 [$this] 中,即使该属性并未在 [TaxAdminData] 类中声明。 可以确定的是,第 6–20 行中的属性确实属于对象 [$this],但它们可能尚未被初始化。这是一个很容易犯的错误;只需在 [taxadmindata.json] 文件中的属性名称中出现一个拼写错误即可;
- 第 54–57 行:我们遍历 [$this] 的所有属性,如果其中任何一个未被初始化,则抛出异常;
- 属性可能被初始化为错误的值。在 PHP 中,无法为属性指定类型。因此,以下操作:
是可行的,尽管 [$plafondQfDemiPart] 属性应为实数;
- 第 59–71 行:我们验证该类的每个属性值均为正实数或零。第 76 行的 [check] 函数负责执行此任务。其参数 [$value] 可以是单个值,也可以是值的数组;
- 第 62 行:[check] 函数返回一个类型为 [\stdClass] 的对象,该对象具有两个属性:
- [error]:若发生错误则为 TRUE,否则为 FALSE;
- [value]:与第62行传入的[$value]参数对应的实际数值;
- 第 64 行:检查验证是否成功;
- 第 66 行:如果某个属性不是正实数或零,则抛出异常;
- 第 69 行:否则,记录其数值;
- 第 73 行:将对象 [$this] 作为结果返回;
[check] 函数如下:
private function check($value): \stdClass {
// $value est soit un tableau d'éléments soit un unique élément
// on crée un tableau
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// on transforme le tableau d'éléments de type non connu en tableau de réels
$newTableau = [];
$result = new \stdClass();
// les éléments du tableau doivent être des nombres décimaux positifs ou nuls
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// on met le float dans newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// on note l'erreur
$result->erreur = TRUE;
// on quitte
return $result;
}
}
// on rend le résultat
$result->erreur = FALSE;
if (!\is_array($value)) {
// une seule valeur
$result->value = $newTableau[0];
} else {
// une liste de valeurs
$result->value = $newTableau;
}
return $result;
}
注释
- 第 1 行:参数 [$value] 可以是数组,也可以是单个元素。此外,其类型未知。该值来自文件 [taxadmindata.json]。根据该文件中输入的值,读取到的值可以是整数、实数、字符串或布尔值。例如:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
在情况 1 中,该值为 [整数] 类型;在情况 2 中为 [浮点数] 类型;在情况 3 中为可转换为数字的 [字符串] 类型;在情况 4 中为无法转换为数字的 [字符串] 类型;
- 第 4–8 行:我们根据第 1 行接收到的 [$value] 参数创建一个数组;
- 第 10 行:该数组将填充实数;
- 第 11 行:结果将是一个类型为 [\stdClass] 的对象;
- 第 13 行:用于判断是否为正数或零的实数的关系表达式;
- 第 14–24 行:我们验证数组 [$tableau] 的所有元素是否均为正数或零,并将这些元素转换为 [float] 类型后填入数组 [$newTableau](第 17 行);
- 第 18–23 行:一旦检测到某个元素不是正数或零,就会在结果中记录该错误并返回结果;
- 第 25–34 行:数组 [$tableau] 的所有元素均被判定为有效的特例;
- 第 32 行:返回值 [$result→value] 是一个浮点数数组或单个浮点数;
第 82–85 行中的 [__toString] 函数返回对象 [$this] 的属性和值的 JSON 字符串。
第 87–110 行:类的 getter 和 setter 方法;
注意:为类编写所有 get/set 方法有时会比较繁琐,尤其是当属性较多时。NetBeans 可以自动生成这些方法以及构造函数。要实现这一点,只需选择属性 [1]:

- 在 [2] 中,右键单击您希望插入代码的位置,然后选择 [插入代码] 选项;

- 在 [4] 中,指定要生成构造函数;
- 在 [5] 中,勾选所有属性:这意味着您希望构造函数为每个属性都提供一个参数;
- 在 [6] 中,选择 Java 构造函数样式;
- 在 [7] 中,指定要在构造函数前显式添加 [public] 关键字;
- 在 [8] 中,点击“确定”;

- 在 [9] 中,NetBeans 已生成构造函数。但由于它不知道参数类型,因此无法指定。请自行添加 [10];
要生成 getter 和 setter 方法,请重复步骤 2–4,并在步骤 4 中选择 [Getter 和 Setter]:

- 在 [5] 中,指定希望为每个属性提供 getter 和 setter 方法;
- 在 [6] 中,指定希望采用 Java 风格的 getter 和 setter:setAttribute、getAttribute;
- 在 [7] 中,指定这些 getter 和 setter 应为 public;
- 在 [8] 中,点击“确定”;

- 在 [9] 中,NetBeans 生成的 getter 和 setter 方法;
删除这些 getter 和 setter 方法,然后重复步骤 2–7。
- 在 [8] 中,勾选之前未勾选的 [Fluent Setter] 选项;
结果如下:

每个设置器都以 [return $this] 操作结尾。这使得属性能够按如下方式初始化:
实际上,[$data→setLimites($limites)](代码第 32 行)的返回值是 [$this],因此这里是 [$data]。 因此,我们可以调用该对象的 [setCoeffR($coeffR)] 方法,以此类推,因为该方法同样返回 [$this](代码第 37 行)。这种编写类方法的风格——即本应返回空值的方法改为返回对象 [$this]——被称为流畅式编程。这使得这些方法更易于使用。
8.4. [InterfaceImpots] 接口
现在,我们定义以下 [InterfaceImpots] 接口 [InterfaceImpots.php]:
<?php
// namespace
namespace Application;
interface InterfaceImpots {
// retrieve tax bracket data for tax calculations
// can throw the ExceptionImpots exception
public function getTaxAdminData(): TaxAdminData;
// the interface knows how to calculate a tax
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// the interface can process data in text files
// $usersFilename: user data file in the form of marital status, number of children, annual salary
// $resultsFilename: results file with marital status, number of children, annual salary, tax amount
// $errorsFilename: file of errors encountered
// can throw the ExceptionImpots exception
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
注释
- 第 4 行:该接口位于 [Application] 命名空间中;
- 第 6 行:用于计算税款的接口;
- 第 10 行:[getTaxAdminData] 方法将从税务管理部门检索数据,并将其装入我们刚刚引入的 [TaxAdminData] 类型的对象中。由于这些数据可能位于文件、数据库甚至网络中,因此 [getTaxAdminData] 方法可能会检索失败。 此时,它将抛出 [ExceptionImpots] 异常。这是面向对象编程中用于指示方法或构造函数中遇到错误的标准方法;
- 第 13 行:[calculateTax] 方法将计算用户的税款;
- 第 20 行:[executeBatchImpots] 方法将计算多个纳税人的税款:
- [$usersFileName] 是包含纳税人数据的文本文件名;
- [$resultsFileName] 是包含这些纳税人税额的文本文件名;
- [$errorsFileName] 是包含处理这些文件时遇到的错误信息的文本文件名;
文本文件 [$usersFileName] 的内容可能如下所示:
oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000
请注意,第 5 行和第 7 行包含错误的条目。
文本文件 [$resultsFileName] 的内容将如下所示:
而文本文件 [$errorsFileName] 中的内容如下:
8.5. [Utilities] 类
我们还在名为 [Utilities.php] 的文件中定义了一个 [Utilities] 类:
<?php
// namespace
namespace Application;
// a class of utility functions
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// delete the end-of-line mark from $ligne if it exists
$longueur = strlen($ligne); // line length
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// end - return the line
return($ligne);
}
}
评论
- 第 4 行:[Utilities] 类也被放置在 [Examples] 命名空间中;
- 第 9 行:[cutNewLineChar] 方法会从作为参数传递的文本中移除任何换行符,并返回由此形成的新行。请注意,这是一个静态方法,这意味着它将以 [Utilities::cutNewLineChar] 的形式被调用;
8.6. 抽象类 [AbstractBaseImpots]
接口 [InterfaceImpots] 将由以下抽象类 [AbstractBaseImpots] 实现 [AbstractBaseImpots.php]:
<?php
// namespace
namespace Application;
// definition of an abstract class AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
// tax administration data
private $taxAdminData = NULL;
// data required for tax calculation
abstract function getTaxAdminData(): TaxAdminData;
// tAX CALCULATION
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié : yes, no
// $enfants : number of children
// $salaire: annual salary
// $this->taxAdminData: tax administration data
//
// we check that we have the correct data from the tax authorities
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// tax calculation with children
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// tax calculation without children
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// application of the family allowance ceiling
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euros for the first 2 children
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// we take the highest tax
if ($impot1 > $impot2) {
$impot = $impot1;
$taux = $result1["taux"];
$surcôte = $result1["surcôte"];
} else {
$surcôte = $impot2 - $impot1 + $result2["surcôte"];
$impot = $impot2;
$taux = $result2["taux"];
}
// calculation of any discount
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// calculation of any tax reduction
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// result
return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
// --------------------------------------------------------------------------
private function calculerImpot2(string $marié, int $enfants, float $salaire): array {
…
// result
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=annualwage-discount
// the allowance has a minimum and a maximum
private function getRevenuImposable(float $salaire): float {
…
// result
return floor($revenuImposable);
}
// calculates any discount
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// result
return ceil($décôte);
}
// calculates any reduction
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// result
return ceil($réduction);
}
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
…
}
}
评论
- 第 4 行:[AbstractBaseImpots] 类将位于 [Application] 命名空间中,与当前正在编写的应用中的其他元素一样;
- 第 7 行:[AbstractBaseImpots] 类实现了 [InterfaceImpots] 接口;
- 第 9 行:税务管理数据将存放在 [$taxAdminData] 属性中;
- 第 12 行:实现接口的 [getTaxAdminData] 方法。我们目前尚不清楚如何定义此方法:在前一节中,我们看到过一个示例,其中税务管理数据是从 JSON 文件中获取的。 接下来我们将探讨另一种情况,即从数据库中检索数据。具体如何定义 [getTaxAdminData] 方法的内容将由派生类决定。前两种情况将分别产生两个派生类。因此,[getTaxAdminData] 方法被声明为抽象方法,这会自动使该类本身成为抽象类(第 7 行);
- 第 15–64 行:此前在 link 和 link 章节中已见过的税款计算函数;
- 版本 02 将税务管理数据存储在关联数组 [$taxAdminData] 中。版本 03 将其存储在属性 [$this→taxAdminData] 中。这两种方法之间的第一个区别在于税务数据的可见性:
- 在 02 版本中,关联数组 [$taxAdminData] 没有全局可见性。因此,它被作为参数传递给所有税费计算函数;
- 在版本 03 中,属性 [$this→taxAdminData] 对类的所有方法都具有全局可见性。因此,它不再作为参数传递给所有税费计算函数;
- 第二个区别源于版本 03 将函数替换为类方法。现在,每次方法调用都使用表达式 [$this→getMethod(…)] 进行(第 27、31、57、60 行);
- 第三个区别在于,当 [calculateTax] 方法开始工作时,它并不知道其所需的 [private $taxAdminData] 属性是否已被初始化。这是因为类构造函数并未对其进行初始化。 因此,[calculateTax] 方法需要通过第 12 行的 [getTaxAdminData] 方法自行完成初始化。第 23–25 行正是为此而设计的;
- 除上述差异外,税费计算方法与之前版本保持一致;
[executeBatchImpots] 函数如下:
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
// pas mal d'erreurs peuvent se produire dès qu'on gère des fichiers
try {
// ouverture fichier des erreurs
$errors = fopen($errorsFileName, "w");
if (!$errors) {
throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
}
// ouverture fichier des résultats
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// lecture des données utilisateur
// chaque ligne a la forme statut marital, nombre d'enfants, salaire annuel
$data = fopen($usersFileName, "r");
if (!$data) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
}
// on exploite la ligne courante du fichier des données utilisateur
// qui a la forme statut marital, nombre d'enfants, salaire annuel
$num = 1; // n° ligne courante
$nbErreurs = 0; // nbre d'erreurs rencontrées
while ($ligne = fgets($data, 100)) {
// debug
// print "ligne n° " . ($i + 1) . " : " . $ligne;
// on enlève l'éventuelle marque de fin de ligne
$ligne = Utilitaires::cutNewLineChar($ligne);
// on récupère les 3 champs marié:enfants:salaire qui forment $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// on les vérifie
// le statut marital doit être oui ou non
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// le nombre d'enfants doit être un entier
$enfants = trim($enfants);
if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// le salaire est un entier sans les centimes d'euros
$salaire = trim($salaire);
if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// erreur ?
if ($erreur) {
fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
$nbErreurs++;
} else {
// on calcule l'impôt
$result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
// on inscrit le résultat dans le fichier des résultats
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
}
// ligne suivante
$num++;
}
// des erreurs ?
if ($nbErreurs > 0) {
throw new ExceptionImpots("Il y a eu des erreurs", 15);
}
} catch (ExceptionImpots $ex) {
// on relance l'exception
throw $ex;
} finally {
// on ferme tous les fichiers
fclose($data);
fclose($results);
fclose($errors);
}
}
代码注释
- 第 1 行:该函数接受三个参数:
- [$usersFileName]:包含纳税人数据的文本文件名称。文本中的每一行都以以下格式包含一名纳税人的数据:婚姻状况(是/否)、子女数量、年薪:
- (续)
- [$resultsFileName]:将包含结果的文本文件名称。每行文本将采用以下格式:
- (续)
- [$errorsFileName]:包含错误信息的文本文件名称:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- 第 3 行:由于某些操作可能会抛出异常,因此整个方法代码被 try / catch / finally 代码块包围;
- 第 3–19 行:打开这三个文件。一旦打开操作失败,就会抛出异常;
- 第 24 行:以最多 100 个字符为单位逐行读取 [$data] 文件(所有行长度均小于 100 个字符);
- 第 28 行:使用静态方法 [Utilities::cutNewLineChar] 移除所有换行符;
- 第 30 行:获取读取行的三个元素;
- 第 33–52 行:检查这三个元素的有效性。此处若发生错误,不会抛出异常,而是将错误信息写入文本文件 [$errors](第 55 行);
- 第 59 行:如果读取的行有效,则计算税额。结果以关联数组的形式返回:["tax" => floor($tax), "surcharge" => $surcharge, "discount" => $discount, "reduction" => $reduction, "rate" => $rate];
- 第 61 行:将键 [married, children, salary] 添加到结果中;
- 第 61 行:将结果以 JSON 字符串的形式写入文本文件 [$results];
- 第 68–70 行:在处理 [$data] 文件结束时,我们检查遇到的错误行数。如果至少有一行错误,则抛出异常;
- 第 71–74 行:捕获代码可能抛出的异常,并立即重新抛出(第 73 行)。此技巧的目的是包含第 74–79 行的 [finally] 代码块:无论方法的代码执行如何结束,该代码可能打开的三个文件都会被关闭。关闭未打开的文件不会引发错误;
8.7. [ImpotsWithTaxAdminDataInJsonFile] 类
抽象类 [AbstractBaseImpots] 未实现 [InterfaceImpots] 接口中的 [getTaxAdminData] 方法。因此,我们必须在派生类中定义该方法。我们在以下派生类 [ImpotsWithTaxAdminDataInJsonFile] 中进行了此定义:
<?php
// namespace
namespace Application;
// definition of a ImpotsWithDataInArrays class
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
// a Data attribute
private $taxAdminData;
// the manufacturer
public function __construct(string $jsonFileName) {
// initialize $this->taxAdminData from file jSON
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
}
// returns data for tax calculation
public function getTaxAdminData(): TaxAdminData {
// we make the attribute [$this->taxAdminData]
return $this->taxAdminData;
}
}
评论
- 第 7 行:类 [ImpotsWithTaxAdminDataInJsonFile] 继承自抽象类 [AbstractBaseImpots]。它需要定义父类未定义的方法 [getTaxAdminData];
- 第 9 行:属性 [$taxAdminData] 将包含税务管理数据;
- 第 12–15 行:构造函数接收的唯一参数是包含税务数据的 JSON 文件名;
- 第 14 行:创建并初始化一个 [TaxAdminData] 类型的对象。此操作可能会抛出 [ExceptionImpots] 类型的异常。该异常将向上传播至主脚本 [main.php];
- 第 18–20 行:我们为 [getTaxAdminData] 方法提供了实现,该方法在父类中未被定义。此处,我们仅返回由构造函数初始化的 [$this->taxAdminData] 属性;
8.8. [main.php] 脚本
以下 [main.php] 脚本使用了这些类和接口:
<?php
// strict adherence to declared types of function parameters
declare(strict_types = 1);
// namespace
namespace Application;
// interface and class inclusion
require_once __DIR__ . '/InterfaceImpots.php';
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . '/ExceptionImpots.php';
require_once __DIR__ . '/Utilitaires.php';
require_once __DIR__ . '/AbstractBaseImpots.php';
require_once __DIR__ . "/ImpotsWithTaxAdminDataInJsonFile.php";
// test -----------------------------------------------------
// definition of constants
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// create a ImpotsWithTaxAdminDataInJsonFile object
$impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// we execute the tax batch
$impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// error is displayed
print $ex->getMessage() . "\n";
}
// end
print "Terminé\n";
exit();
注释
- 第 4 行:强制严格遵守函数参数类型;
- 第 7 行:[main.php] 脚本也被置于 [Application] 命名空间中;
- 第 10–15 行:我们告知 PHP 解释器脚本所使用的类和接口位于何处。请注意,此处我们未使用 `use` 语句来声明脚本所用类的全名。这是因为脚本和类位于同一个命名空间 [Application] 中,因此无需如此;
- 第 18–22 行:脚本中使用的文本文件名称;
- 第 24–29 行:创建一个 [ImpotsWithTaxAdminDataInJsonFile] 对象,并处理可能出现的异常;
- 第 28 行:执行 [executeBatchImpots] 方法,该方法为 [TAXPAYERSDATA_FILENAME] 文件中的所有纳税人计算税款。结果将保存到 [RESULTS_FILENAME] 文件中,任何错误将保存到 [ERRORS_FILENAME] 文件中;
- 第 29–32 行:若发生致命错误,将显示错误信息;
结果
假设有一个如下所示的纳税人文件 [taxpayersdata.txt]:
oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000
我们得到以下错误文件 [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
以及以下结果文件 [resultats.txt]: