PHP7 学习手册(三)
原文:Learn PHP 7
五、处理和记录异常
Electronic supplementary material The online version of this chapter (doi:10.1007/978-1-4842-1730-6_5) contains supplementary material, which is available to authorized users.
一个人的教育直到他死了才算完成——罗伯特·李(引自劳伦斯·J·彼得《彼得语录:我们时代的思想》(1977 年),第 175 页)
章节目标/学生学习成果
完成本章后,学生将能够:
- 解释错误和异常之间的区别
- 创建一个可以处理一般异常的 PHP 程序
- 创建一个可以创建、引发和处理用户异常的 PHP 程序
- 解释并使用
switch和/或嵌入式if/else语句 - 创建一个使用
while循环和/或for循环的 PHP 程序 - 创建一个使用二维数组读取/更新文本文件的程序
- 创建一个 PHP 程序,记录异常并向支持人员发送电子邮件
处理异常
作为一名程序员,你想尽一切可能确保你的程序不会崩溃。任何使用你的应用的人,如果他们不得不处理系统崩溃,他们的嘴里都会有不好的味道。你可能也处理过这种情况。作为用户,你可能因为差评而选择了一个应用而不是另一个。一旦应用被确定为“有问题”,就很难说服客户使用该产品,即使新版本已经纠正了部分或全部问题。必须创建一个应用来处理每一个可能的意外事件。
一个程序必须考虑每一种情况,决定它是继续运行还是必须关闭。由于意外事件,应用始终有可能无法继续运行。正确开发的程序会让用户知道有问题,而不会导致程序崩溃。当应用要求用户“稍后再试”时,用户更容易理解(假设在他们返回网站之前问题已经解决)。
错误是由系统处理的导致程序关闭的程序事件。在某些情况下,系统可以关闭程序并显示错误消息。有些错误会立即导致程序崩溃(比如服务器本身崩溃)。错误通常是超出程序控制的事件,不是由程序中的代码(或缺少代码)直接引起的。例如,内存不足会导致应用错误。
异常是不属于程序逻辑正常流程的事件。所有的异常都应该由程序来处理。当应用预见到一个问题(一个丢失的文件)或者当用户做了一些不寻常的事情(试图输入无效的信息)时,可以“引发”异常。程序应该“捕捉”所有的异常。然后,它可以检查该异常,并确定是否可以纠正、忽略它,或者是否必须关闭应用。如果程序没有捕捉到异常,系统将显示异常消息,然后关闭应用。
PHP 会根据具体情况产生错误和异常。在 PHP 5 之前,异常处理是不存在的。因此,一些旧的 PHP 命令产生错误(关闭程序)而不是异常。在 PHP 7 中,异常处理是“规则”。PHP 7 错误可以用异常处理技术来处理。如果程序代码没有处理异常,程序将会像致命错误一样暂停。
任何时候一个应用依赖于外部的东西,很可能在某个时候这个动作不会发生。例如,在Dog应用中,用户需要输入正确的信息。应用必须预见到并非所有用户都会输入正确的信息。该应用还依赖于服务器上存在的几个文件(dog_interface、dog_container、dog_applications和get_breeds)。如果缺少这些文件中的任何一个,应用将无法继续正常运行。
大多数面向对象编程语言使用标准格式来处理异常。PHP 的当前版本也使用这种方法。当您在互联网上研究 PHP 示例时,您会发现现有的 PHP 代码不使用这种标准格式。虽然这段代码仍然可以在当前版本的 PHP 中执行,但是建议使用标准技术。标准方法使用try-catch块。
try {
// code that might cause an exception
}
catch(Exception $e) {
// code that executes if there is an exception
}
catch(错误$e) {
// PHP 7+ capture and handle errors
}
任何可能导致异常的代码都应该包含在try块中。此外,您可能还想考虑在try块中放置其他条件(比如数学计算)。
try {
$result = $firstNumber / $secondNumber;
}
catch(Exception $e) {
// code that executes if there is an exception
}
catch(错误$e) {
// PHP 7+ capture and handle errors
}
如果$secondNumber包含一个零(除以零),这个例子可能会产生一个异常。如果异常发生,代码将跳转到catch块。然后将执行块中的任何代码。语句$e->getMessage();将显示任何与异常相关的系统消息(在本例中,是关于试图被零除的消息)。但是,您不必使用系统消息;您可以使用echo或print向用户显示信息。
try {
$result = $firstNumber /$secondNumber;
}
catch(Exception $e) {
echo "You entered zero for the second number. Your entry must be greater than zero";
}
然而,这些例子有一个问题。如果您试图在try块中捕获多种类型的异常,所有异常都将进入一个catch块。任何异常都会显示相同的消息。有几种不同的方法可以解决这个问题。
一种方法是抛出自己的异常,而不是让系统抛出。
try {
if ($secondNumber == 0)
{ throw new Exception("Zero Exception"); }
else { $result = $firstnumber / $secondnumber; }
// other code with exceptions }
catch(Exception $e) {
switch ($e->getMessage()) {
case "Zero Exception":
echo "The value of second number must be greater than zero";
break;
case "Some other exception":
echo "You did something else wrong";
break;
default:
echo $e->getMessage();
}
Precautions for programming-In addition to the
getMessagemethod, theExceptionandErrorobjects also include:getCode()-displaying the code causing the exceptiongetFile()-displaying the file name containing the code causing the exceptiongetLine()-displaying the line numbersgetTrace()andgetTraceAsString()causing the exception-displaying the backtracking (exception flowing through the program) information, and in some cases, displaying it to the user. However, other methods should only be used for debugging or log entries. Providing code information to users is usually unnecessary and violates security.
在这个例子中,在catch块中使用了一个switch语句来查看所有可能的异常消息。一条switch语句与一条嵌入式if语句完成相同的任务。你可以使用:
If($e->getMessage == "Zero Exception")
{ echo "The value of second number must be greater than zero"; }
else if($e->getMessage == "Some other exception")
{ echo "You did something else wrong"; }
else
{ echo $e->getMessage(); }
对于某些人来说,当查看同一个属性(变量)的多个可能值或执行方法的结果时,switch语句更容易理解(如本例所示)。switch语句的默认部分(或者嵌入式if语句中的最后一个else语句)捕捉任何您没有预料到的内容。在本例中,您只需显示其他异常的异常消息。
如前所述,处理所有异常和错误非常重要。通过包含默认代码,您能够处理您可能从未预料到的异常和错误。注意,每个case部分必须包含一个break作为最后一条语句。这阻止了代码进入下一个case语句。
Catch(Exception $e) {
switch($e->getMessage()) {
case "Zero Exception":
echo "The value of second number must be greater than zero";
case "Some other exception":
echo "You did something else wrong";
break;
default:
echo $e->getMessage():
}
在本例中,如果发生了Zero Exception,将显示消息("The value of the second number must be greater than zero"和You did something else wrong"。switch语句的使用在catch块中很常见。然而,如前所述,如果您愿意,可以使用嵌入式的if语句。
处理多个异常的另一种方法是创建自己的异常,抛出它们,然后捕获它们。您需要为自己的异常创建一个类。
class zeroException extends Exception {
public function errorMessage() {
$errorMessage = "Second Number cannot be " . $this->getMessage();
return $errorMessage;
}
}
try {
if ($secondNumber == 0)
{ throw new zeroException("Zero"); }
else
{ $result = $firstnumber / $secondnumber; }
// other code with exceptions }
catch(zeroException $e) {
echo $e->errorMessage();
}
catch(Exception $e) {
Echo $e->getMessage();
}
zeroException类扩展了Exception类。extends关键字用于继承Exception类的所有功能。继承是面向对象编程的另一个关键组件(还有封装和多态)。一个子类(比如zeroException)可以继承其父类(Exception)的所有属性和方法。然后,子类可以添加特定于该类的方法(比如函数errorMessage)。由于zeroException继承了Exception,它被视为与任何其他异常相同。zeroException可以被抛出(抛出新的zeroException("Zero"))并且可以被抓住(catch(zeroException $e))。
Program notes-exception classes created by programmers inherit from
Exception. Therefore, all functions of theExceptionclass are available in any new exception class.Class zeroException extends Exception { }The previous code created an effective newzeroExceptionclass with no new method.catch(zeroException $e) { echo $e->getMessage(); }Thiscatchblock will be called by a new exception, and the exception message generated by theExceptionclass will be displayed.
对于创建和抛出的每个异常或错误类,必须有一个catch块来捕捉异常或错误。在这个例子中,有两个 catch 块;一个捕捉zeroException,另一个捕捉任何其他可能发生的异常。就像前面使用switch默认或if else语句的例子一样,您应该总是让最后的catch块处理任何剩余的异常或错误。如果通用catch模块首先列出,所有异常将被该模块捕获,而不是该异常的特定模块。
如上所述,开发人员应该尽一切努力防止应用崩溃。然而,错误是用来显示消息和关闭带有错误代码的程序(您认为是“崩溃”程序)。在 PHP 7 之前,在某些情况下,您可以通过创建一个处理错误的方法来覆盖这个功能。
function errorHandler($severity, $message, $file, $line) {
throw new errorException($message, 0, $severity, $file, $line); }
set_error_handler('errorHandler');
// set_error_handler() doesn’t work with all fatal errors, some can’t be thrown as Exceptions.
try { trigger_error( "User Error", E_USER_ERROR);
}
catch(errorException $e)
{ echo $e->getMessage(); }
catch(Exception $e)
{ echo $e->getMessage(); }
// Code placed here would execute after an error with this handler. It would not execute if
// there was not a handler.
在这个例子中,set_error_handler方法将所有错误(可以被重定向)重定向到方法errorHandler。当方法trigger_error导致E_USER_ERROR发生时,错误的处理被重定向到errorHandler方法。然后,这个方法从错误中收集信息,抛出一个异常(errorException)。该异常由catch(errorException $e)方法捕获,这将导致显示消息"User Error"。
在 PHP 7 中,Error 对象捕获潜在的系统错误作为异常。
try {
call _ method(null);//没有这样的方法!
} catch (Error $e)
{ echo $e->getMessage; }
以前,调用不存在的函数会导致致命错误。使用EngineException对象允许程序员处理错误。前面显示的例子(在 PHP 7 之前)只能捕获一些错误;这项新技术旨在让程序员对错误有更多的控制。如果不包括这个catch块,任何“错误”都会导致程序因"Fatal error: Uncaught exception"消息而崩溃。
如果你安装了 PHP 7,使用新的Error对象和Exception对象来尽可能避免致命错误。
安全性和性能——通常使用抛出和捕获异常可以减少程序所需的代码量。然而,有一个权衡。对不同面向对象编程语言的几项研究得出结论,异常处理比使用开发人员创建的例程效率(性能)低。开发人员应该使用异常作为应用正常流程的真正“异常”。对于更频繁发生的情况,开发人员应该在应用中创建情况处理例程。
图 5-1。
Data flow for the dog application
在Dog应用中,信息在许多不同的程序之间流动。这些程序中的每一个都必须能够正确地处理异常。然而,消息处理应该全部发生在接口中。任何属于业务规则层的对象(dog_container、dog和get_breeds)都应该将任何异常消息传递给接口进行处理。起初,这听起来像是一项复杂而令人困惑的任务。然而,异常处理的层次结构将大大简化这项任务。正如您将要看到的,使用异常处理将减少必要的代码量。
当抛出异常时,环境会查看程序(或类)本身,以确定是否有一个catch块可以处理异常。如果没有一个catch块,它将在层次结构中上升一级,并检查任何调用程序(或生成了该类实例的程序)是否有一个catch块。这个过程将继续,直到发现一个catch块,或者已经确定环境本身必须处理该异常。
使用这个过程,您可以在不使用 catches 的情况下在dog_container、dog 和get_breeds模块中抛出异常。在dog_interface中,您可以围绕对这些文件的调用创建一个try块。可以在接口中创建多个catch块(或者一个带有switch语句的块)来处理来自接口和所有其他模块的异常。这满足了三层编程的要求之一。业务规则层(和数据层)将消息传递给接口层。然后,接口层决定如何处理这些消息。它可以向用户显示它们,将它们放在日志文件中(您将在本章后面看到),或者忽略它们(如果不会对应用的操作产生负面影响的话)。
在更改Dog应用代码之前,让我们看一个由层次结构处理的异常的例子。
Example 5-1. testerror.php with error and exception-producing methods
<?php
class testerror {
function produceerror() {
trigger_error( "User Error", E_USER_ERROR);
echo "This line will not display"; }
function throwexception() {
throw new userException("User Exception");
echo "This line will not display"; } }
?>
Example 5-2. The handleerror.php file captures error or exception
<?php
function errorHandler($severity, $message, $file, $line) {
throw new errorException($message, 0, $severity, $file, $line);
}
class userException extends Exception { }
Set_error_handler(‘errorHandler’);
try {
require_once(“testerror.php”);
$tester = new testerror();
$tester->produceerror();
echo “This line does not display”;
$tester->throwexception(); // will not execute if produceerror() is executed
echo “This line does not display”; }
catch (errorException $e ){
echo $e->getMessage(); }
catch (userException $e) {
echo $e->getMessage(); }
catch (Exception $e) {
echo $e->getMessage(); }
echo “This line will display”;
?>
testerror类(在示例 5-1 中)包括一个导致错误的方法(produceerror)和一个抛出异常的方法(throwexception)。然而,该类没有try或catch块。它没有能力对可能发生的任何异常或错误做出反应。
handleerror程序(例如 5-2 )包括一个处理用户错误的方法(errorHandler),以及将错误重定向到该方法的set_error_handler命令。它还包括一个类(userException,当try块中抛出userException异常时,该类可以做出反应。require_once语句包含在try块中,试图在文件丢失时捕获错误。但是,这恰好是一个系统错误(不是用户错误),无法重定向。为了捕获 PHP 7 中的系统错误,Error 类必须在前面所示的catch块中使用。
在require_once语句之后,创建了一个类testerror的实例。如果缺少该类,系统也会显示一条致命消息。这个块调用了produceerror方法,这导致了一个用户错误。这个错误被重定向到errorHandler,它抛出一个异常(errorException)。catch模块接收异常并显示错误信息。由于异常不会关闭程序(比如致命错误),所以在所有catch阻塞并执行echo语句(echo "This line will display";)后,程序流程跳转到第一行。对错误的反应将导致程序跳过try块中任何剩余的代码。在这个例子中,throwexception方法调用将被忽略。
如果注释掉了$tester->produceerror()行,就可以调用throwexception方法。方法中抛出了userException。userException类继承了Exception类。userException中未包含特殊方法。程序流程将跳转到userException的catch模块。这个块使用Exception类getMessage方法来显示消息。在catch阻塞并执行echo "This line will display"语句后,逻辑跳转到第一行代码。
Note-
try/catchcan also include afinallyblock after allcatchblocks. After the relevantcatchblock is executed,finallyblock will be executed for all captured exceptions. PHP allowsfinallyblocks without anycatchblocks to exist (buttryblocks must still exist).finallyOne of the most common uses of blocks is to close files and/or databases when an exception occurs. Programs should not be closed until files and databases are properly closed. If it is not properly closed, the data may be damaged and inaccessible.
做它
Go to the book’s web site and download the files for Examples 5-1 and 5-2. Adjust the testerror program to only create an error. Create an additional testexception program (with a testexception class) to throw an exception. Now adjust the handleerror program to create an instance of both programs. The handleerror program should now be able to handle errors or exceptions from either program (class).
异常和错误处理与 If/Else 条件
程序员总是可以选择使用第四章的中的dog应用文件所示的If/else条件语句来处理异常和错误。以这种方式处理错误并没有降低效率(甚至可能更有效率)。但是,您将会发现,如果使用异常处理,业务规则层(和数据层)中的代码的态度会发生变化。当你使用if/else语句时,程序流花费了大量时间悲观地为最坏的情况(错误和/或异常)做准备。在许多情况下,通过使用异常处理,大多数业务规则层(和数据层)的编码变得乐观,包括处理程序正常操作的代码。应用依靠接口层来处理任何问题。
Example 5-3. The dog.class with exception handling
<?php
class Dog
{
// ------------------------------------ Properties -----------------------------------------
private $dog_weight = 0;
private $dog_breed = "no breed";
private $dog_color = "no color";
private $dog_name = "no name";
private $error_message = "??";
private $breedxml = "";
// ------------------------------------ Constructor ----------------------------------------
function __construct($properties_array)
{
if (method_exists('dog_container', 'create_object')) {
$this->breedxml = $properties_array[4];
$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';
$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';
$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';
$breed_error = $this->set_dog_breed($properties_array[1]) == TRUE ? 'TRUE,' : 'FALSE,';
$this->error_message = $name_error . $breed_error . $color_error . $weight_error;
if(stristr($this->error_message, 'FALSE'))
{
throw new setException($this->error_message);
}
}
else { exit; }
}
function set_dog_name($value) {
$error_message = TRUE;
(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $this->error_message = FALSE;
return $this->error_message; }
function set_dog_weight($value) {
$error_message = TRUE;
(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $this->error_message = FALSE;
return $this->error_message; }
function set_dog_breed($value) {
$error_message = TRUE;
($this->validator_breed($value) === TRUE) ? $this->dog_breed = $value : $this->error_message = FALSE;
return $this->error_message; }
function set_dog_color($value) {
$error_message = TRUE;
(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $this->error_message = FALSE;
return $this->error_message; }
// ----------------------------------Get Methods--------------------------------------------
function get_dog_name() {
return $this->dog_name; }
function get_dog_weight() {
return $this->dog_weight; }
function get_dog_breed() {
return $this->dog_breed; }
function get_dog_color() {
return $this->dog_color; }
function get_properties() {
return "$this->dog_name,$this->dog_weight,$this->dog_breed,$this->dog_color."; }
// ----------------------------------General Method-----------------------------------------
private function validator_breed($value)
{
$breed_file = simplexml:load_file($this->breedxml);
$xmlText = $breed_file->asXML();
if(stristr($xmlText, $value) === FALSE)
{
return FALSE;
}
else
{
return TRUE;
}
}
}
?>
将示例 4-8 与示例 5-3 进行比较,您会注意到代码中仅有几处细微的变化。__toString方法已经被移除,取而代之的是一个if语句,它检查FALSE是否存在于error_message字符串中。如果确实存在,就会产生一条setException消息,将error_message字符串传递给异常处理程序。这导致了整个应用流程的逻辑变化。代替dog_interface程序(在例子 4-12 中)通过调用__toString方法检查用户输入错误,当用户错误发生时Dog类通知dog_interface(通过抛出的异常)。以前,接口必须从Dog类中提取错误。在这个例子中,Dog类将错误推送到接口类。正如您将看到的,这将从dog_interface程序中消除代码,因为它不再需要询问是否有任何错误。
And security-the
__toStringmethod "exposes" anything it returns to any program, which creates an instance of its class. Passing error messages in this way may allow hackers to determine what incorrect messages they sent to the program. In the example in Chapter 4,__toStringreturns theerror_messagestring containing'TRUE'or'FALSE'responses. This is safer than returning an error message. However, you can provide better security by throwing a special exception instead of the__toStringmethod. Hackers must now know not only whaterror_messagemeans, but also the name of this exception (setException) in order to catch it in their own programs.
Example 5-4. The getbreeds.class with exception handling
<?php
class GetBreeds {
function __construct($properties_array) {
if (!(method_exists('dog_container', 'create_object')))
{ exit; }
}
private $result = "??";
public function get_select($dog_app)
{
if (($dog_app != FALSE) && ( file_exists($dog_app)))
{
$breed_file = simplexml:load_file($dog_app);
$xmlText = $breed_file->asXML();
$this->result = "<select name='dog_breed' id='dog_breed'>";
$this->result = $this->result . "<option value='-1' selected>Select a dog breed</option>";
foreach ($breed_file->children() as $name => $value)
{
$this->result = $this->result . "<option value='$value'>$value</option>";
}
$this->result = $this->result . "</select>";
return $this->result;
}
else
{
throw new Exception("Breed xml file missing or corrupt");
}
}
}
?>
将先前的GetBreeds类(在示例 4-11 中)与示例 5-4 进行比较,仅显示一处变化。它返回'FALSE'并抛出一个一般异常,表明breed.xml文件丢失或损坏。同样,GetBreeds类将任何异常推送到接口。接口不再需要确定是否有任何异常。尽管丢失文件错误不能被重定向为异常处理,但是如果文件丢失,代码使用file_exists抛出异常。
Example 5-5. The dog_container.php file with exception handling
<?php
class dog_container
{
private $app;
private $dog_location;
function __construct($value) {
if (function_exists('clean_input')) {
$this->app = $value;
} else { exit; }
}
public function set_app($value) {
$this->app = $value; }
public function get_dog_application($search_value) {
$xmlDoc = new DOMDocument();
if ( file_exists("e5dog_applications.xml") ) {
$xmlDoc->load( 'e5dog_applications.xml' );
$searchNode = $xmlDoc->getElementsByTagName( "type" );
foreach( $searchNode as $searchNode ) {
$valueID = $searchNode->getAttribute('ID');
if($valueID == $search_value) {
$xmlLocation = $searchNode->getElementsByTagName( "location" );
return $xmlLocation->item(0)->nodeValue;
break; }
} }
throw new Exception("Dog applications xml file missing or corrupt"); }
function create_object($properties_array) {
$dog_loc = $this->get_dog_application($this->app);
if(($dog_loc == FALSE) || (!file_exists($dog_loc))) {
throw new Exception("File $dog_loc missing or corrupt."); }
else
{
require_once($dog_loc);
$class_array = get_declared_classes();
$last_position = count($class_array) - 1;
$class_name = $class_array[$last_position];
$dog_object = new $class_name($properties_array);
return $dog_object;
}
}
}
?>
当dog_application.xml文件、dog.class文件和/或get_breeds文件丢失时,实例 5-5 中的dog_container代替实例 4-10 中的返回'FALSE'。相反,会引发一个异常,指出哪个文件丢失了。
Example 5-6. The dog_interface.php file with exception handling
<?php
function clean_input($value)
{
$value = htmlentities($value);
$value = strip_tags($value);
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
$value = htmlentities($value);
$bad_chars = array( "{", "}", "(", ")", ";", ":", "<", ">", "/", "$" );
$value = str_ireplace($bad_chars,"",$value);
return $value;
}
class setException extends Exception {
public function errorMessage() {
list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $this->getMessage());
$name_error == 'TRUE' ? $eMessage = '' : $eMessage = 'Name update not successful<br/>';
$breed_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Breed update not successful<br/>';
$color_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Color update not successful<br/>';
$weight_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Weight update not successful<br/>';
return $eMessage;
}
}
function get_dog_app_properties($lab)
{
print "Your dog's name is " . $lab->get_dog_name() . "<br/>";
print "Your dog weights " . $lab->get_dog_weight() . " lbs. <br />";
print "Your dog's breed is " . $lab->get_dog_breed() . "<br />";
print "Your dog's color is " . $lab->get_dog_color() . "<br />";
}
//----------------Main Section-------------------------------------
try {
if ( file_exists("e5dog_container.php"))
{ Require_once("e5dog_container.php"); }
else
{ throw new Exception("Dog container file missing or corrupt"); }
if (isset($_POST['dog_app'])) {
if ((isset($_POST['dog_name'])) && (isset($_POST['dog_breed'])) && (isset($_POST['dog_color'])) && (isset($_POST['dog_weight'])))
{
$container = new dog_container(clean_input($_POST['dog_app']));
$dog_name = clean_input(filter_input(INPUT_POST, "dog_name"));
$dog_breed = clean_input($_POST['dog_breed']);
$dog_color = clean_input($_POST['dog_color']);
$dog_weight = clean_input($_POST['dog_weight']);
$breedxml = $container->get_dog_application("breeds");
$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml);
$lab = $container->create_object($properties_array);
print "Updates successful<br />";
get_dog_app_properties($lab); }
else {
print "<p>Missing or invalid parameters. Please go back to the lab.html page to enter valid information.<br />";
print "<a href='dog.html'>Dog Creation Page</a>";
}
} else // select box {
$container = new dog_container("selectbox");
$properties_array = array("selectbox");
$lab = $container->create_object($properties_array);
$container->set_app("breeds");
$dog_app = $container->get_dog_application("breeds");
$result = $lab->get_select($dog_app);
print $result;
}
} // try
catch(setException $e)
{
echo $e->errorMessage();
}
catch(Exception $e)
{
echo $e->getMessage();
}
catch(Error $e) // PHP 7+ only
{
echo $e->getMessage();
}
?>
当比较示例 4-12 和示例 5-6 时,处理异常所需的代码量少于使用if/else条件语句。使用很少的else语句,程序的逻辑流程更容易理解。这是因为这个应用中所有文件抛出的异常都是由dog_interface中的catch模块处理的。用户错误被抛出到一个特殊的setException异常中。系统错误由错误catch模块捕获。error_check_dog_app方法(在示例 4-12 中)已被setException类取代。类中的代码与error_check_dog_app中的代码非常相似。删除了在$eMessage字符串中显示的单个更新消息,因为该类对用户错误做出反应,而不是成功的更新。在代码主体中添加了一个普通的print行,让用户知道所有更新都已成功。该接口中的所有代码周围都添加了try块。这有助于捕捉该应用任何部分中的任何问题。注意,如果找不到dog_container文件,也会抛出异常。
该应用只需要三个catch模块。setException catch块从setException类调用errorMessage方法,该方法确定发生了什么用户错误。然后将信息显示给用户。Exception catch块处理所有其他异常。它当前向用户显示这些信息。然而,异常和错误捕捉块目前向用户提供了太多的信息。通知用户应用可能遇到的其他问题违反了安全性。你应该告诉他们,该系统目前不可用,请他们稍后再来查看。测试时显示详细的错误是可以的。然而,这对现实世界并不好。您将在下一节中解决这个安全漏洞。
For more information about exception handling, please visit the example:
http://www.w3schools.com/php/php_exception.aspVideo:https://www.thenewboston.com/videos.php?cat=11&video=17171
做它
Examine the code from this section. Are there any areas in which error checking could have been converted to exception handling? Go to the book’s web site and download the code for this section. Make the potential changes to the existing code to use additional exception handling.
记录异常
应用必须能够在出现问题时通知系统分析师。但是,不应该向应用的用户显示关于错误的特定消息。应该通知用户系统当前不可操作。应该通知系统分析师已经发生的特定问题。
提供这种能力的最简单方法是将错误消息放入日志文件中。PHP 应用可以将消息记录到默认的 PHP 错误文件或应用特定的文件中。可以编辑php.ini文件(参见第一章中的位置)来指定默认错误日志文件的位置和名称。一旦php.ini文件在编辑器中打开,搜索error_log。如果分号位于行首,则该位置已被注释掉。只需删除分号并指定一个位置,例如:
error_log = c:/temp/php_errors.log
-or-
error_log =http://www.asite.com/temp/php_errors.log
当写入默认错误日志时,PHP 将在您提交的错误消息中插入一个时间戳。您的默认时区可能没有在php.ini文件中正确设置。搜索date.timezone。美国大陆的有效时区设置为:
date.timezone = "America/New_York"
date.timezone = "America/Chicago"
date.timezone = "America/Los_Angeles"
For all other American time zones visit:http://www.php.net/manual/en/timezones.america.php
For worldwide time zones visit:http://php.net/manual/en/timezones.php
Apache httpd配置文件(位置见第一章)可以覆盖php.ini文件中的设置。你也应该打开这个文件,搜索date.timezone。用类似下面的格式替换现有行。
php_value date.timezone "America/New_York"
一旦你更新并保存了php.ini和/或apache.httpd文件,你必须重新加载你的 Apache 服务器以使更改生效(参见第一章)。
Note
时区也可以用程序代码来设置。PHP 7 不支持datefmt_set_timezone_id方法。对于 PHP 5.2+可以使用datefmt_set_timezone方法。关于用程序代码设置时区的更多信息,请访问 http://php.net/manual/en/datetime.settimezone.php 。
<?php
error_log("error message");
?>
输入此代码并将其保存在测试文件中。在您的环境中测试它。如果您的设置是正确的,PHP 将在error_log参数中指定的位置创建错误日志。不要自己创建文件。PHP 不会将信息记录到不是自己创建的日志文件中。发送到日志文件的消息格式应类似于以下内容:
[25-Jun-2015 17:01:12 America/New_York] error message
只用简单的一行代码,PHP 就在指定的位置创建了基于文本的文件,并将消息放在文件中。
如果您不能访问这些文件,您可以在 PHP 应用中指定一个特定的位置来发送您的消息。此功能还允许您设置多个应用日志文件。应用通常具有信息日志文件、身份验证(登录)日志文件、错误日志文件和安全日志文件。通过分离每种类型的消息,可以更容易地在日志文件中扫描特定类型的消息。
假设您希望在一个文件中记录用户错误,在另一个文件中记录其他错误。
<?php
const USER_ERROR_LOG = 'User_Errors.log';
const ERROR_LOG = 'Errors.log';
// sending a user error
error_log("A user error",3,USER_ERROR_LOG);
// sending all other errors
error_log("A general error",3,ERROR_LOG);
<?php
这段代码将使用常量(USER_ERROR_LOG和ERROR_LOG)将错误消息指向正确的位置。注意,3的第二个参数被用来让error_log方法知道一个不同的位置将被用来记录错误。应该使用标准格式向日志发送消息。格式应该包括时间/日期(如果环境中没有包括,如前所述)、消息类型(如果文件中有多种消息类型)、错误消息和任何其他相关信息。默认情况下,消息长度限制为 120 个字符。然而,这可以在php.ini文件中更改。
$date = date('m.d.Y h:i:s');
// For more info on data time format go to:http://php.net/manual/en/function.date.php
$errormessage = "This is the error";
$eMessage = $date . " | User Error | " . $errormessage . "\n";
error_log($eMessage,3,USER_ERROR_LOG);
The above code would produce
06.06.2015 03:00:55 | User Error | This is the error
可以使用标准的文本编辑器(Notepad++或 Notepad)或日志监控软件(您将在本章的后面创建一个日志阅读器程序)来查看文件的内容。
系统将限制日志文件的大小。但是,假设每天没有太多的日志记录,应用可以创建特定于每天的日志。
$USER_ERROR_LOG = "User_Errors" . date('mdy') . ".log";
$ERROR_LOG = "Errors" . date('mdy') . ".log";
...
error_log($eMessage,3,$USER_ERROR_LOG);
And security—The location of the log file should be in a different folder from the application. The folder needs to allow write access to the application. However, it should be prevented from being read or written outside the server itself. Only authorized personnel can access the log.
请注意,由于日期方法创建了一个可能的变量输出(不同的日期),常量(USER_ERROR_LOG和ERROR_LOG)必须更改为变量。该格式将创建一个类似于User_Errors06062015.log或Errors06062015.log的文件名。
PHP 还使得在日志文件中写入内容时发送电子邮件警报变得非常容易。web 服务器必须包括电子邮件服务器。您的本地机器可能没有此功能。然而,通常,web 主机提供商(具有 PHP 功能)包括电子邮件服务。要使用这种能力,您可以添加一个error_log语句:
error_log("Date/Time: $date - Serious System Problems with Dog Application. Check error log for details", 1, "noone@helpme.com", "Subject: Dog Application Error \nFrom: System Log <systemlog@helpme.com>" . "\r\n");
And security-although it is easy to tell the employees who receive e-mail the exact problems in the application, don't do it. By default, email is not encrypted. Sending an unencrypted e-mail containing application details will cause hackers to destroy your application. However, you should provide enough information in the message (such as date/time stamp and possibly error number) to help employees find the error message in the log file.
第一个参数指定电子邮件的消息。第二个参数通知error_log通过电子邮件发送该信息。第三个参数提供“收件人”电子邮件地址。第四个参数是一个额外的头字段。该字段通常用于包含电子邮件的主题和发送邮件的电子邮件地址。必须包含“发件人”地址,否则邮件将不会被发送。但是,“发件人”地址不必是现有的地址。
For more information about log errors, please visit the example:
http://php.net/manual/en/function.error-log.phpExample:http://www.w3schools.com/php/php_error.asp
在Dog应用中,您可以通过调整dog_interface的catch块来提供记录异常和通过电子邮件发送主要错误的能力(来自示例 5-6 )。
Example 5-7. The dog_inteface.php file with exception logging and e-mail
<?php
const USER_ERROR_LOG = "User_Errors.log";
const ERROR_LOG = "Errors.log";
function clean_input($value)
{
$value = htmlentities($value);
// Removes any html from the string and turns it into < format
$value = strip_tags($value);
if (get_magic_quotes_gpc())
{
$value = stripslashes($value); // Gets rid of unwanted slashes
}
$value = htmlentities($value); // Removes any html from the string and turns it into < format
$bad_chars = array( "{", "}", "(", ")", ";", ":", "<", ">", "/", "$" );
$value = str_ireplace($bad_chars,"",$value);
return $value;
}
class setException extends Exception {
public function errorMessage() {
list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $this->getMessage());
$name_error == 'TRUE' ? $eMessage = '' : $eMessage = 'Name update not successful<br/>';
$breed_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Breed update not successful<br/>';
$color_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Color update not successful<br/>';
$weight_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Weight update not successful<br/>';
return $eMessage;
} }
}
function get_dog_app_properties($lab)
{
print "Your dog's name is " . $lab->get_dog_name() . "<br/>";
print "Your dog weights " . $lab->get_dog_weight() . " lbs. <br />";
print "Your dog's breed is " . $lab->get_dog_breed() . "<br />";
print "Your dog's color is " . $lab->get_dog_color() . "<br />";
}
//----------------Main Section-------------------------------------
try {
if ( file_exists("e5dog_container.php"))
{
Require_once("e5dog_container.php");
}
else
{
throw new Exception("Dog container file missing or corrupt");
}
if (isset($_POST['dog_app']))
{
if ((isset($_POST['dog_name'])) && (isset($_POST['dog_breed'])) && (isset($_POST['dog_color'])) && (isset($_POST['dog_weight'])))
{
$container = new dog_container(clean_input($_POST['dog_app']));
$dog_name = clean_input(filter_input(INPUT_POST, "dog_name"));
$dog_breed = clean_input($_POST['dog_breed']);
$dog_color = clean_input($_POST['dog_color']);
$dog_weight = clean_input($_POST['dog_weight']);
$breedxml = $container->get_dog_application("breeds");
$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml);
$lab = $container->create_object($properties_array);
print "Updates successful<br />";
get_dog_app_properties($lab);
}
else
{
print "<p>Missing or invalid parameters. Please go back to the dog.html page to enter valid information.<br />";
print "<a href='dog.html'>Dog Creation Page</a>";
}
}
else // select box
{
$container = new dog_container("selectbox");
$properties_array = array("selectbox");
$lab = $container->create_object($properties_array);
$container->set_app("breeds");
$dog_app = $container->get_dog_application("breeds");
$result = $lab->get_select($dog_app);
print $result;
}
}
catch(setException $e)
{
echo $e->errorMessage(); // displays to the user
$date = date('m.d.Y h:i:s');
$errormessage = $e->errorMessage();
$eMessage = $date . " | User Error | " . $errormessage . "\n";
error_log($eMessage,3,USER_ERROR_LOG); // writes message to user error log file
}
catch(Exception $e)
{
echo "The system is currently unavailable. Please try again later."; // displays message to the user
$date = date('m.d.Y h:i:s');
$errormessage = $e->getMessage();
$eMessage = $date . " | User Error | " . $errormessage . "\n";
error_log($eMessage,3,ERROR_LOG); // writes message to error log file
error_log("Date/Time: $date - Serious System Problems with Dog Application. Check error log for details", 1, "noone@helpme.com", "Subject: Dog Application Error \nFrom: System Log <systemlog@helpme.com>" . "\r\n");
// e-mails personnel to alert them of a system problem
}
catch (Error $e)
{
echo "The system is currently unavailable. Please try again later."; // displays message to the user
$date = date('m.d.Y h:i:s');
$errormessage = $e->getMessage();
$eMessage = $date . " | Fatal System Error | " . $errormessage . "\n";
error_log($eMessage,3,ERROR_LOG); // writes message to error log file
error_log("Date/Time: $date - Serious System Problems with Dog Application. Check error log for details", 1, "noone@helpme.com", "Subject: Dog Application Error \nFrom: System Log <systemlog@helpme.com>" . "\r\n");
// e-mails personnel to alert them of a system problem
}
在示例 5-7 代码的顶部,创建了常量USER_ERROR_LOG和ERROR_LOG来精确定位日志文件的名称和位置。在代码的顶部定位可能会发生变化的常数(例如税率),为负责支持应用的程序员提供了快速更改的便利。如前所述,日志文件的位置必须位于允许应用写访问的文件夹中。建议将日志文件与其他日志文件放在同一个文件夹中,以便数据中心人员(或系统分析师)轻松访问。
其他代码更改位于catch块中。setException catch块将由setException类生成的错误消息返回给用户。该消息让用户知道哪些属性(Name、Breed、Color和Weight)没有更新。导致此异常的错误可能来自用户,也可能是由于信息从客户机传输到服务器时遭到破坏。这些消息仅提供关于属性需求的信息,而用户应该已经知道这些信息。catch模块也向用户错误日志中写入类似的消息。用户错误不是需要分析师解决的紧急错误。然而,跟踪用户问题的趋势可以提供所需的可能变化的指示,以确保用户对应用具有可能的最佳体验。
Exception and Error catch块捕获所有非用户生成的异常。这些异常导致的消息可能会泄露会破坏应用安全性的信息。因此,应该向用户显示一条通用消息(如"The system is currently unavailable. Please try again later.")。有关异常的详细信息(错误消息、文件位置、引发异常的代码行)应该放在错误日志中,以供进一步分析。这些catch块捕获的大多数异常会阻止应用运行。因此,将出现的问题告知相关人员非常重要。这个catch街区是向支持人员发送电子邮件以提醒他们任何问题的好地方。
既然您已经在程序中内置了异常处理和错误处理,那么您可以编辑php.ini文件来关闭对用户的错误报告。但是,您应该等到所有开发和测试都完成后再这样做。在php.ini文件中找到行"display_errors = On"。如果您将此设置更改为"display_errors = Off",大多数错误信息将不会显示给用户。这一改变不会影响程序通过echo或print方法(包括在任何catch块中)发回给用户的任何消息。这一改变将使开发人员能够更好地控制当系统出现问题时向用户显示的消息类型。
做它
Download the code for this section. Create or use an existing HTML page that does not check for user input errors. Run the program entering values for the name, breed, weight, and color, which should cause user errors. Stop the program and open the contents of the user error log file. Did the errors appear in the file? If not, check the security of the folder that contains the log file to make sure that it allows write access to the log file. Once you are satisfied that it has caught user errors, try to cause other system errors to occur. Hint: Change the file names to nonexistent names in the dog application XML file. Check the error log to determine if the errors have been written to the file. Were you able to cause any errors that are not captured by one of the log files? If so, is there a way to capture those errors?
读取日志和文本文件
在上一节中,您发现了error_log方法只使用一行代码就可以写入日志文件。如果日志文件不存在,它会创建日志文件。它将传递给文件内容的任何消息追加(添加到文件的末尾)。然后它关闭文件。如果您要创建自己的日志记录过程,需要几行代码。
$logFile = fopen("error_log.log", "a");
$eMessage = $e->getMessage();
fwrite($logFile, $eMessage);
fclose($logFile);
如果这个文件不存在的话,fopen方法也会创建这个文件。“a”参数表示写入文件的任何内容都应该被附加。“w”表示文件中的任何内容都将丢失(被覆盖)。然后,fwrite方法将把位于第二个参数($eMessage)中的字符串放入由第一个参数($logFile)指示的文件中。$logFile是指向文本文件位置的指针。方法关闭文本文件。
For more information about writing to a text file, please visit an example: visit w3schools at
http://www.w3schools.com/php/php_file_create.aspVideo: visit New Boston athttps://www.thenewboston.com/videos.php?cat=11&video=17063
因为日志文件是基于文本的文件,所以您可以使用类似的逻辑创建自己的应用来打开日志文件并读取其内容。
$logFile = fopen("error_log.log", "r");
echo fgets($logFile);
fclose($logFile);
这段代码将打开日志文件,读取文件中的第一行(通过一个fgets方法)并关闭文件。但是,文件中可能有多行。您必须能够遍历并显示文件中的每一行。您可以使用这里显示的while循环来完成此操作。
$logFile = fopen("error_log.log", "r");
while(!feof($logFile))
{
echo fgets($logFile) . "<br>";
}
fclose($logFile);
只要条件语句为TRUE,则while循环将继续循环。一旦语句为FALSE,代码将退出循环,并在循环结束后跳转到下一行代码。在本例中,error_log文件以只读方式打开(r)。while循环查看日志文件的文件结束指示符(feof),以确定是否已到达文件的结尾。如果feof返回TRUE,则已经到达文件的末尾。当您还没有到达文件末尾时,循环必须继续。要使条件语句产生一个TRUE,同时仍有记录要读取,您必须反转逻辑,如果有记录,让feof产生TRUE,如果没有记录,让FALSE产生。您可以使用!操作符来完成此操作。!操作符是一个NOT操作符,它反转结果。A NOT TRUE是FALSE或者 a NOT FALSE是TRUE。因此,当有更多记录时,!feof操作符将产生TRUE,当没有记录时产生FALSE。该循环结合fgets方法将显示文件中的每条记录。一旦显示了每条记录,它将使用fclose关闭文件。
For more information about reading text files, please visit w3schools for example:
http://www.w3schools.com/php/php_file_open.aspVisit New Boston for video:https://www.thenewboston.com/videos.php?cat=11&video=17064For more information aboutwhile loop:, please visit w3schools for example:http://www.w3schools.com/php/php_looping.aspVisit New Boston for video:https://www.thenewboston.com/videos.php?cat=11&video=17011
前一个例子产生的输出非常简单。
06.06.2015 03:00:55 | User Error | This is the error
这并不比在文本编辑器中打开日志文件更好。您可以使用 HTML 表格、explode方法和数组的组合来产生更好的输出。您可以使用explode方法将日志文件中的每一行放入一个二维数组中。二维数组将像 HTML 表一样有行和列。
$dogs = array
(
array("Sammy","Lab",18,"Yellow"),
array("Spot","Mixed",14,"Mixed"),
array("Princess","Chihuahua",4,"White"),
array("Max","Boxer",35,"Brown")
);
二维数组是信息行的集合。每行的每个位置(列)都有公共信息。在前面的例子中,所有狗的名字在位置 0,狗的品种在位置 1,狗的体重在位置 2,狗的颜色在位置 3。这直接与表中的位置相关联。
For more information about multidimensional arrays, please visit: Example:
http://www.w3schools.com/php/php_arrays_multi.aspVideo:https://www.thenewboston.com/videos.php?cat=11&video=17026
| 制革时将皮弄湿 | 工党 | Eighteen | 黄色 | | 地点 | 混合的 | Fourteen | 混合的 | | 公主 | 奇瓦瓦州 | four | 白色的 | | 最大 | 拳击运动员 | Thirty-five | 褐色的 |
表格和二维数组中的每个位置都由列和行引用。在这张表中,萨米的位置是(0,0)。黄色在位置(0,3)。最大值在位置(3,0)。布朗就位(3,3)。第一个位置是列。第二个位置是行。在 PHP 中,[]用来定义数组的位置(下标)。
echo $dogs[0][0] // displays Sammy
echo $dogs[0][3] // displays Yellow
echo $dogs[3][0] // displays Max
echo $dogs[3][3] // displays Brown
现在可以调整循环,将日志内容放在一个二维数组中。但是,您不知道数组的大小。所以不能使用之前显示的格式。如果开发人员没有使用 PHP,这可能会让他们头疼。然而,PHP 允许您动态创建数组,就像它允许您在需要时创建变量(属性)一样。
$logFile = fopen("error_log.log", "r");
$row_Count = 0;
while(!feof($logFile))
{
print_r ($error_Array[$row_Count] = explode(' | ', fgets($logFile)));$row_Count++;
}
fclose($logFile);
在本例的循环中,explode方法通过|字符(实际上是一个空格、|和一个空格)从文本文件中断开输入行。它将每个分隔的字符串放入$error_Array中由$row_Count中的值指示的行。第一次循环时,日志文件的第一行放在$error_Array[0](数组的第一行)。因为explode命令分隔了字符串,这导致为每个片段创建列。
如果文件的第一行包含:
A general error | stuff | more stuff
那么数组的第一行将包含:
$error_Array[0][0] = "A general error"
$error_Array[0][1] = "stuff";
$error_Array[0][2] = "more stuff";
您可以使用示例中所示的print_r命令来验证这一点。print_r以下列格式显示数组的内容。
Array ( [0] => A general error [1] => stuff [2] => more stuff )
这种格式验证字符串的每一部分都已放入数组中的正确位置。
$row_count在循环继续之前增加 1。这将文件的下一行定位到数组中的下一个位置(如果是文件的第二行,则为$error_Array[1])。你当然不希望使用print_r向用户显示结果(这不是很漂亮)。
但是,它是一个很好的工具,可以帮助您确保程序将所有东西都正确地放置在数组中。您可以在循环中添加代码来构建一个表。
$logFile = fopen("Errors.log", "r");
$row_Count = 0;
echo "<table>";
while(!feof($logFile))
{ echo "<tr>";
$error_Array[$row_Count] = explode(' | ', fgets($logFile));
$displayString = "";
for($I=0; $I < 3; $I++)
{
echo "<td> " . $error_Array[$row_Count][$I] . " </td> ";
}
echo "</tr>";
$row_Count++;
}
echo "</table>";
fclose($logFile);
一个echo语句位于打开 HTML 表格的while循环之前。一个额外的echo语句(echo "<tr>")存在于while循环中,用于创建表中的一行。
For more information about the
forloop, please visit: Example:http://www.w3schools.com/php/php_looping_for.aspVideo:https://www.thenewboston.com/videos.php?cat=11&video=17013
同样在while循环中,已经创建了一个for循环来遍历该行的每一列。既然您知道有四列,那么for循环是一个不错的选择。当您确切知道要循环多少次时,可以使用for循环。for循环的第一个参数(在;之前)初始化计数变量($I=0)。该变量($I)用于计算每个循环。第二个参数($I < 3)包括确定逻辑流是否将停留在循环中的比较。如果比较结果为TRUE,循环将继续。如果是FALSE,逻辑流程跳转到循环后的第一条语句(echo "</tr>")。第三个参数($I++)可以增加或减少计数变量。for循环通过在一行代码中要求所有信息来帮助程序员记住初始化变量、检查条件和增加变量。
for循环中的echo语句使用$row_Count和$I变量从当前行的每一列中提取信息。第一次循环时,$row_Count将为 0。echo语句将显示$errorArray[0][0]的内容。随着for循环的继续,将显示$errorArray[0][1]、$errorArray[0][2]和$errorArray[0][3]的内容。使用<td>和</td>标签将每个值放入表格的一个单元格中。一旦for循环完成,流程下降到循环以下并关闭行(回显</tr>)。然后row_Count变量递增。如果有更多行(文件中有更多记录),则while循环将继续下一行的过程,直到文件中没有更多记录。一旦流程跳出while循环,工作台关闭(echo "</table>")。然后文件被关闭。
文本(日志)文件是顺序文件。添加(追加)项目时,它们会被添加到列表的底部。您可能希望对信息进行排序,首先列出最新的信息。只需对代码稍加修改就可以实现这一点。
$logFile = fopen("Errors.log", "r");
$row_Count = 0;
while(!feof($logFile))
{
$error_Array[$row_Count] = explode(' | ', fgets($logFile));
$row_Count++;
}
$row_Count--;
fclose($logFile);
echo "<table>";
for ($J=$row_Count; $J >= 0; $J--)
{ echo "<tr>";
$displayString = "";
for($I=0; $I < 3; $I++)
{
echo "<td> " . $error_Array[$J][$I] . " </td> ";
}
echo "</tr>";
}
echo "</table>";
while循环现在用记录加载数组,并记录数组中的项数。在循环结束并且文件被关闭之后,一个for循环以相反的顺序遍历数组,回显表中的行。计数器变量$J从数组中的总行数开始($row_Count)。在循环之前,从$row_Count中减去 1,因为在检索到最后一条记录之后,它在while循环中增加,这使得计数多了 1。然后$J对于每个循环递减($J--),直到值小于零。内部的for循环(for($I=0;$I<3;$I++))没有改变,因为它仍然必须循环通过行的每一列来显示信息。
通过将记录加载到数组中,您可以根据需要修改它们。让我们假设您希望能够从日志中删除一条记录。只要知道要删除的行号,就可以从数组中删除该记录。然后,您可以用剩余的记录重新填充该文件。
首先,您将对已经完成的echo代码做一个小小的修改,在要删除的记录旁边添加一个链接。然后,您将添加一个delete方法,将显示代码移动到一个display方法(这样它可以在任何需要的时候被调用),并创建一个save changes方法来更新日志文件。
Example 5-8. The readerrorlog.php file
<?php
function deleteRecord($recordNumber, &$row_Count, &$error_Array) {
for ($J=$recordNumber; $J < $row_Count - 1; $J++) {
for($I=0; $I < 3; $I++)
{ $error_Array[$J][$I] = $error_Array[$J + 1][$I]; }
}
Unset($error_Array[$row_Count]);
$row_Count--;
}
function saveChanges($row_Count,$error_Array,$log_File) {
$logFile = fopen($log_File, "w");
for($I=0; $I < $row_Count; $I++) {
$writeString = $error_Array[$I][0] . " | " . $error_Array[$I][1] . " | " . $error_Array[$I][2];
fwrite($logFile, $writeString);
}
fclose($logFile);
}
function displayRecords($row_Count, $error_Array) {
echo "<html><head>";
echo "<style> table { border: 2px solid #5c744d;} </style>";
echo "</head><body><table>";
echo "<caption>Log File: " . ERROR_LOG . "</caption>";
echo "<tr><th></th><th>Date/Time</th><th>Error Type</th><th>Error Message</th></tr><tr>";
for ($J=$row_Count; $J >= 0; $J--) {
echo "<td><a href='readlogfilea.php?rn=$J'>Delete</a></td>";
for($I=0; $I < 3; $I++) {
echo "<td> " . $error_Array[$J][$I] . " </td> ";
}
echo "</tr>";
}
echo "</table>";
echo "</body></html>";
} // main section
const ERROR_LOG = "Errors.log";
$logFile = fopen(ERROR_LOG, "r");
$row_Count = 0;
while(!feof($logFile))
{
$error_Array[$row_Count] = explode(' | ', fgets($logFile));
$row_Count++;
}
fclose($logFile);
if(isset($_GET['rn']))
{
deleteRecord($_GET['rn'], $row_Count, $error_Array);
saveChanges($row_Count,$error_Array,ERROR_LOG);
}
displayRecords($row_Count,$error_Array);
?>
图 5-2。
The readerrorlog.php file with user errors
在示例 5-8 中,displayRecords方法包含了之前显示的大部分相同的代码。添加了额外的 CSS 代码,使显示更加专业。此外,显示的每条记录都包含一个 HTML href链接。该链接调用程序,传递用户想要删除的记录号。
“主要部分”中的代码集(执行的第一行代码)创建一个常量ERROR_LOG来定义日志文件的位置和名称。文件以与前面所示相同的方式打开并加载到数组中。
一旦加载了数组,程序就会检查它是否被每个记录的删除链接调用过。如果已经通过 HTTP GET传递了一个值,程序就会调用deleteRecord方法。一旦deleteRecord方法完成,程序就调用saveChanges方法。不管有没有值被传入程序,它都会执行最后一条语句,这条语句调用displayRecords方法。
Note-The title line of
deleteRecordsmethod (functiondeleteRecord($recordNumber, &$row_Count, &$error_Array)) allows$row_Count and $error_Arrayto be updated in the method by reference rather than by value. By default, the parameters passed to the method cannot be changed by the method (by value).&indicates that the parameter is passed by reference. This allowed value is changed.deleteRecordsYou can adjust the number of rows in the array ($row_Count) and the information of the array itself ($error_Array). Pass the content (data) contained in the parameter to the method by value. Pass the memory address of the parameter to the method by reference, which allows the method to change the value of the parameter in memory.
deleteRecords方法接受要删除的记录号作为其参数之一。数组中该记录号以上的任何位置都保持不变。低于该记录的任何位置都必须上移一个位置。例如,如果一个数组有十个位置(0-9 ),第五个位置(4)将被删除,那么位置 5-9 现在必须变成位置 4-8。
在下面的例子中,对于要删除的$recordNumber之后的任何记录,位置$J+1被放入位置 J:
不再需要数组中的最后一个位置。释放数组的最后一个位置(使用unset)。这将通知操作系统不再需要内存空间。操作系统将调用其垃圾收集器来释放内存位置。当我们在后面的章节中讨论关联数组时,我们会发现一个更简单的方法来完成这个任务。一旦数组被重新配置,就会调用saveChanges方法来替换日志文件中的记录。显示的代码与本章前面的例子非常相似,只有一个例外。fopen方法使用参数“w”。“w”参数将删除文件中已经存在的任何内容,并用当前正在写入文件的内容替换它。在本例中,文件将被新的记录集更新(替换),该记录集不包括已删除的记录。在程序被调用的任何时候都会调用displayRecords方法(删除或不删除记录)。此方法显示日志文件的内容。
Programming instructions-A program that retrieves data to be used in the whole program usually retrieves information at the initial stage of the code and places it in a data structure (array, list or data set). When the data in the program is updated, the information in the data structure is also updated. After the data processing is completed, the information will be returned to the original location (text file or database). Updating a text file or database is usually completed as the last stage of the program. The process of the user exiting the program will provide an event to indicate that the updated data should be saved.
做它
Download the code for Example 5-8 from the book’s web site. Add a try catch block to handle any unexpected problems (such as a nonexistent log file). Test and save your changes. Adjust the Example 5-8 code from either #1 or the book’s web site to allow the users to select which log file to read. The program should allow the users to select either the user log file or the general system log file. Test and save your changes.
章节术语
| 错误 | 例外 | | try/catch 块 | $e->getMessage(): | | 交换语句 | 嵌入式 if 语句 | | 默认代码 | 延伸 | | 异常类 | 遗产 | | 子类别 | 父类 | | 触发器 _ 错误 | 异常处理的层次结构 | | 引发(抛出)异常 | 捕捉异常 | | 代码的态度 | 推错误 | | 找出错误 | 日志文件 | | 默认 PHP 错误文件 | 错误日志 | | 时间戳 | 日期.时区 | | 应用日志文件 | 常数 | | 电子邮件提醒 | 显示 _ 错误 | | 文本文件 | 顺序文件 | | 打开文件 | fwrite | | 指针 | fclose(美国联邦航空局) | | 从文件指针中读取一行 | while 循环 | | 文件结尾判断 | !操作员 | | 引擎异常 | 激增 | | 二维数组 | 排 | | 圆柱 | 下标 | | 动态数组 | 数组的第一行 | | 打印 _f | 增量 | | 减量 | for 循环 | | HTML HREF | 复原 |
第二章问题和项目
多重选择
Which of the folowing is true about PHP program errors? They are used for all exceptional situations before PHP 5.0. They are handled by the operating system. They might be displayed to the users. All of these. Which of the folowing is true about PHP program exceptions? They are used for all exceptional situations after PHP 5.0. They can be handled by the operating system. They might be displayed to the users. All of these. The try/catch block is used to do which of the following? Capture all errors. Capture only known errors. Capture all exceptions. Capture only known exceptions. Inheritance does which of the following? Is part of object-oriented programming. Allows the child class to use all the attributes of its parent class. Allows the child to create its own methods or properties. All of these. Text files do which of the following? Are easy to use in PHP. Are more secure than using databases. Are non-sequential. None of these. PHP log files do which of the following? Are created by the error_log method. Are updated by the error_log method. Should be located in a different directory than the application. All of these Which of the following describes two-dimensional arrays? They are similar to HTML tables. They contain rows and columns. They should be avoided at all costs. They are inefficient and difficult to use. They require all columns to be loaded with data before rows. None of these. Which of the following describes the first position of a two-dimensional array? It has a subscript of 0. It has a subscript of 1. It has a default value. None of these. Application log files should include which of the folllowing?. User log files. Error log files. Informational log files. All of these. E-mails generated because of program exceptions should do what? Include all error information, including the error descriptions. Include the code lines that caused the error to occur. Include the date and time the error occurred. All of these.
对/错
All exceptions should be displayed to the users. The for loop should be used when you know exactly how many loops will occur. The while loop will continue to loop until the conditional statement becomes true. unset can be used to release a position in an array. All PHP arrays must be declared before being used. A pointer points to the location in memory that an object resides. The ! operator reverses a TRUE result to a FALSE result. print_f can be used to display the contents of an array. Try/Catch blocks should reside in the business rules and data tiers but not the interface tier. Only Exceptions intentionally thrown by program code should be caught.
简答/短文
Explain how hierarchy of exception handling works with three-tier applications. What is the difference between an error and an exception? How do you correct the time zone if PHP is providing an incorrect timestamp? How can PHP programmers try to capture errors so they can be treated as if they are exceptions? Why is it important to have multiple log files produced by an application?
项目
Adjust the code from project #1 (or #2) from Chapter 4 to include exception handling and logging. Create an application that will register contestants for your favorite game show. Include verification of the data entered using HTML5 and JavaScript. Also validate the data when it is passed to the application. The application should include an interface php program (interface tier) and a registration class (business rules tier). The registration class should include exception handling (both user exceptions and program exceptions). The interface program should handle the exceptions using try/catch blocks as shown in this chapter.
学期项目
Update the ABC Computer Parts Warehouse Inventory application to include exception handling. The application should attempt to handle all exceptions, and errors, when possible. User exceptions should be logged to a user log. All other exceptions should be logged to a system log. If the exception is considered to be extreme (will cause the program to otherwise crash), an e-mail should be sent to the system administrator. Hint: The Try/Catch block should only exist in the interface tier.
六、数据对象
Electronic supplementary material The online version of this chapter (doi:10.1007/978-1-4842-1730-6_6) contains supplementary material, which is available to authorized users.
“我是一个理想主义者。我不知道我要去哪里,但我在路上。”——卡尔·桑德堡,杂项(1904)
章节目标/学生学习成果
完成本章后,学生将能够:
- 创建插入、更新和删除 XML 或 JSON 数据的数据类
- 解释如何创建一个使用 SQL 脚本更新 MySQL 数据的数据类
- 创建一个 PHP 程序来创建一个变更备份日志
- 创建一个 PHP 程序,可以从以前的备份中恢复数据
- 应用更改以创建最新的有效信息
- 使用依赖注入将数据类附加到 BR 层中的另一个类
- 创建一个三层 PHP 应用
数据类
接口和业务规则层不应该存储应用信息。这些层甚至不应该知道信息是如何存储的(文本文件、XML 或数据库)或者存储信息的位置。存储的任何信息都必须从业务规则层传递到数据层。数据层还负责响应来自业务规则层的信息请求。
这使得接口层和业务规则层不知道存储方法类型(文本文件、XML 或数据库)和存储项位置的任何变化。签名(接受的参数)和从数据层返回的项应该在应用的生命周期中保持不变。只要这些没有改变,当数据层发生变化时,其他层就不需要改变。
安全性和性能—使用数据库时,在业务规则层构建一个 SQL 字符串并将该字符串传递给数据层似乎是合乎逻辑的。这将在应用中造成一个主要的安全漏洞。黑客可以传递任何 SQL 字符串(包括一个delete字符串)。将 SQL 更新命令(DELETE、UPDATE和INSERT)传递到数据层似乎也是合乎逻辑的。这又提供了一个大洞。为一个WHERE SQL 命令传递数据也是一个坏主意,因为它可能允许黑客删除或更改数据库中的任何数据组合。
数据类应该为操作信息提供完整的功能。这包括读取、插入、更新和删除信息的能力。即使当前应用不需要所有这些命令,从逻辑上讲,它们也应该存在于数据类中以备将来使用。
应该在性能和存储信息的需求之间取得平衡。虽然非常重要的信息可能需要立即存储,但其他信息可以保存在应用的数据结构(列表、数组和数据集)中,直到用户完成任何更新。在服务器的内存中保存信息并对其进行更改,比在存储位置更有效。仅在完成所有更改后存储信息会将对存储位置的几次调用减少到两次(初始信息检索和更新信息的保存)。对内存中的信息进行更改总是比在存储设备(如硬盘)上进行更改更有效。
使用数据类提供了自动填充数据结构并将信息保存在存储位置的逻辑能力。假设只有在需要更新信息时才会创建数据类的实例,则可以使用该类的构造函数从存储中检索信息,并将其放入服务器的内存中。当不再需要数据对象时,从逻辑上讲,不再需要对信息进行更改。类的析构函数可用于将信息从内存返回到存储区。
class dog_data
{
function __construct()
{
$xmlfile = file_get_contents(get_dog_application("datastorage"));
$xmlstring = simplexml:load_string($xmlfile);
$array = (array)$xmlstring;
print_r($array);
}
}
这个示例构造函数非常接近于从 XML 文件中提供有用的信息。PHP file_get_contents方法打开一个文本文件,将内容放入一个字符串中,然后关闭文件。构造函数调用这个方法和get_dog_application方法(与示例 5-5 中的dog_container使用的方法相同)来确定 XML 数据文件的文件名和位置。然后文件的内容被放在$xmlfile中。PHP simplexml:load_string方法然后格式化数据,以允许 SimpleXML 数据模型遍历信息。此时,可以使用 SimpleXML 方法来显示和操作数据。但是,下一行试图将 XML 数据转换成数组。(array)语句尝试使用类型转换。print_r语句显示结果。
<?xml version="1.0" encoding="UTF-8"?>
<dogs>
<dog>
<dog_name>Woff</dog_name>
<dog_weight>12</dog_weight>
<dog_color>Yellow</dog_color>
<dog_breed>Lab</dog_breed>
</dog>
<dog>
<dog_name>Sam</dog_name>
<dog_weight>10</dog_weight>
<dog_color>Brown</dog_color>
<dog_breed>Lab</dog_breed>
</dog>
</dogs>
假设 XML 文件的格式如下所示,输出包括:
Array ( [dog] => Array ( [0] => SimpleXMLElement Object ( [dog_name] => Woff [dog_weight] => 12
[dog_color] => Yellow [dog_breed] => Lab ) [1] => SimpleXMLElement Object ( [dog_name] => Sam
[dog_weight] => 10 [dog_color] => Brown [dog_breed] => Lab ) ) )
已经创建了多维数组和 SimpleXML 对象的组合。这不能提供易于操作的有用数据。但是,您可以使用 JSON 方法来欺骗 PHP 创建一个多维关联数组。
class dog_data
{
function __construct()
{
$xmlfile = file_get_contents(get_dog_application("datastorage"));
$xmlstring = simplexml:load_string($xmlfile);
$json = json_encode($xmlstring);
print_r($json);
}
}
{"dog":[{"dog_name":"Woff","dog_weight":"12","dog_color":"Yellow","dog_breed":"Lab"},{"dog_name"
:"Sam","dog_weight":"10","dog_color":"Brown","dog_breed":"Lab"}]}
使用 PHP json_encode方法将数据转换成结构良好的 JSON 数据。您可以使用几种 PHP 技术中的一种来操作 JSON 数据,或者通过一个额外的语句(json_decode),您可以创建一个结构良好的多维关联数组。
class dog_data
{
function __construct()
{
$xmlfile = file_get_contents(get_dog_application("datastorage"));
$xmlstring = simplexml:load_string($xmlfile);
$json = json_encode($xmlstring);
$dogs_array = json_decode($json,TRUE);
print_r($dogs_array);
}
}
Array ( [dog] =>
Array (
[0] => Array ( [dog_name] => Woff [dog_weight] => 12 [dog_color] => Yellow [dog_breed] => Lab )
[1] => Array ( [dog_name] => Sam [dog_weight] => 10 [dog_color] => Brown [dog_breed] => Lab ) ) )
如您所见,不再有数组和 SimpleXML 对象的混合。创建了一个关联数组,它使用关键字代替下标(索引)的数值。在前面的例子中,一个名为"dog"的数组已经创建了两行(每行由一个数组表示)。在每一行中,列(单元格)由列名(dog_name、dog_weight、dog_color和dog_breed)而不是索引(0、1、2、3)引用。这些行和列可以使用您在前面章节中看到的一些技术来操作。
一旦您完成了对数组的所有更改(按照业务规则层的请求),您将把信息返回到析构函数中的存储位置。
private $dogs_array = array(); // defined as an empty array initially
function __construct()
{
$xmlfile = file_get_contents(get_dog_application("datastorage"));
$xmlstring = simplexml:load_string($xmlfile);
$json = json_encode($xmlstring);
$this->dogs_array = json_decode($json,TRUE);
}
function __destruct()
{
$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';
$xmlstring .= "\n<dogs>\n";
foreach ($this->dogs_array as $dogs=>$dogs_value) {
foreach ($dogs_value as $dog => $dog_value)
{
$xmlstring .="<$dogs>\n";
foreach ($dog_value as $column => $column_value)
{
$xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";
}
$xmlstring .= "</$dogs>\n";
}
}
$xmlstring .= "</dogs>\n"; file_put_contents(get_dog_application("datastorage"),$xmlstring);
}
有许多方法可以在 PHP 中创建 XML 数据。前面的例子采用了一种简单的方法,从数组中提供 XML 标记。如结构所示,这个多维数组中有三组数组。第一个foreach循环用于流经第一个数组(dogs)。第二个foreach循环处理狗数组(行)。一旦进入这个循环,第三个foreach循环控制每个 dog 数组中的列(每行)。
第三个循环检索列名(从$column)并将它们放在 XML 标签中。$column也用于拉动($dog_value[$column]列中的值。$xmlstring提供了与原始 XML 文件相同的标签和结构。请注意,每一行都包含一个换行符(\n)来显示文件中的不同行。没有这种添加,该结构也能工作。但是,它使文件在文本编辑器中更具可读性。
一旦创建了$xmlstring,代码使用 PHP file_put_contents方法和get_dog_application方法的组合(来自第四章)来打开 XML 文件,用$xmlstring中包含的字符串替换内容,并关闭文件。
您需要对构造函数进行最后一次调整,以允许它处理 XML 解析错误。当 XML 结构有问题时,就会出现解析错误。之前的dog_breed和dog_application XML 文件没有被应用更新,相当稳定。然而,狗的信息的 XML 文件将被频繁地更新。你需要处理任何可能出现的问题。您将提出一个一般性错误,该错误将被dog_interface视为一个重要错误,并记录下来,通过电子邮件发送给支持人员。它还会向用户显示一条"System currently not available please try again later"消息。
private $dogs_array = array(); defined as an empty array initially
libxml:use_internal_errors(true);
function __construct() {
$xmlfile = file_get_contents(get_dog_application("datastorage"));
$xmlstring = simplexml:load_string($xmlfile);
if ($xmlstring === false) {
$errorString = "Failed loading XML: ";
foreach(libxml:get_errors() as $error) {
$errorString .= $error->message . " "; }
throw new Exception($errorString); }
$json = json_encode($xmlstring);
$this->dogs_array = json_decode($json,TRUE);
}
默认情况下,XML 解析错误将导致系统向用户显示错误并关闭程序。libxml:user_internal_errors(true)方法将抑制错误。当字符串通过simplexml:load_string方法被转换成 XML 格式时,XML 被解析以确定它是否有效。如果无效,该方法将返回FALSE而不是 XML 信息。所示的if语句将创建一个$errorString,并使用foreach语句遍历由libxml:get_errors方法返回的每个错误(返回一个包含错误的数组)。一旦收集了所有错误,它将通过$errorString引发一个异常。dog_interface程序将捕捉这个错误并处理它,如第五章所示。
这个例子做了一个不好的假设(这简化了这个例子)。它假设$errorString没有超过日志文件 120 个字符的最大容量。一个格式非常糟糕的文件可能会很快导致$errorString超过这个大小。这个限制可以在 PHP 配置文件中调整。
当数据对象从内存中删除时,数据会自动保存,插入、更新和删除方法只需要调整多维关联数组的内容。让我们先来看看如何创建一个delete方法,因为你已经在第五章中看到了一个例子。
在readerrorlog程序中(在示例 5-8 中)你创建了一个deleterecord方法。该方法用于常规多维数组。我们可以对这个例程做一些调整,为dog_data类创建deleteRecord方法。
function deleteRecord($recordNumber) {
foreach ($this->dogs_array as $dogs=>&$dogs_value) {
for($J=$recordNumber; $J < count($dogs_value) -1; $J++)
{
foreach ($dogs_value[$J] as $column => $column_value)
{
$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];
}
}
unset ($dogs_value[count($dogs_value) -1]);
}
}
在前面的deleterecord方法中,数组中的行数和数组本身被传递到方法中。dog_data类中的数组由包含狗信息的 XML 文件填充。没有设置记录数的属性。这不是问题。PHP 方法 count 将返回数组的大小。您可以使用$this指针访问和更新dogs_array(这是一个受保护的私有财产)。类中的方法可以使用this指针来访问和更新受保护的属性;没有必要将它们传递到方法中。您需要传递给deleteRecord方法的惟一属性是要删除的记录号($recordNumber)。
相关阵列具有三维。外部维度与 XML 结构中的 dogs 标记相关。虽然狗只有一个“行”,但是仍然需要一个循环来移动到下一个数组(dog)。foreach循环穿透 dogs 数组并提供对dog数组的访问(该数组是从 XML 文件中的 dogs 标记创建的)。$dogs将包含当前使用的狗排的数量。$dogs_value将包含该行的内容(包含dog_name、dog_weight、dog_color和dog_breed中的值的数组)。
为了遍历 dog 数组中包含的每一行(数组),该方法使用了一个for循环。条件语句($J < count($dogs_value) -1)使用count方法来确定dog数组的大小。count方法返回数组的大小,而不是数组的最后一个位置。因此循环计数必须小于(count)。从这个值中减去一。如第五章中的所述,被删除行之后的任何一行都必须从当前位置上移一行。将不再需要数组的最后一个位置,这将所需的循环数减少了一个。
在第五章的例子中,使用了一个for循环从行中取出每一列,并将其放入上面的行中。对于关联数组,使用一个foreach循环。$column参数包含列名($J包含行号),用于将列中的值放入适当的位置。移动行中的值后,使用 PHP unset 方法移除数组的最后一个位置。
类似的逻辑几乎可以用在任何编程语言中。然而,PHP 关联数组允许跳过索引号。不像其他语言会在丢失的索引中放置一个空值,PHP 关联数组只是跳过实际的索引。因此,任何数组的索引都可以是 0、1、2、4、5,foreach 循环可以正确地遍历数组。考虑到这一点,我们可以大大简化前面的 delete 示例,使其只包含一行代码,只包含所示的 unset 命令。unset 命令将从dog_array中删除传递给该方法的索引。任何使用dog_array的 for 循环仍然可以正确地遍历数组。演示网站中包含的示例代码提供了这种能力。
您还可以对第五章中的displayRecords方法做一些调整,以返回调用程序(比如Dog类)请求的任何记录。
function readRecords($recordNumber) {
if($recordNumber === "ALL") {
return $this->dogs_array["dog"];
}
else {
return $this->dogs_array["dog"][$recordNumber];
} }
如你所见,readRecords方法比displayRecords方法更简单。该方法结果的所有格式都留给调用程序处理(如果需要)。请记住,输出的显示和格式化发生在接口层,而不是数据层(或业务规则层)。
该方法允许调用程序请求所有记录或特定记录。无论哪种情况,它都返回一个包含一行(所请求的特定记录)或所有行的数组。当返回所有行时,移除顶部数组(表示 rows XML 标记),以保持两个选择的维数(两个)相同。
insertRecords方法接受带有前面提到的下标名的关联数组。然而,为了在 XML 文件的标记名中允许依赖注入和灵活性,调用程序不需要知道标记名,直到创建了一个类的实例。这可以通过使用readRecords方法提取第一条记录,然后让调用程序检查从该记录返回的下标名来实现。
function insertRecords($records_array)
{
$dogs_array_size = count($this->dogs_array["dog"]);
for($I=0;$I< count($records_array);$I++)
{
$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];
}
}
Note
使用前面显示的 JSON 函数创建dog_array的过程将在创建dog_array时产生一个不一致。如果dog_data.xml文件只包含一条记录,JSON 函数将不会创建一个数字索引(比如‘0’)。当 xml 文件中包含多条记录时,将创建数字索引(如“0”、“1”)。教科书网站上的演示文件中提供了处理这些差异的替代解决方案。
在insertRecords方法中,所有记录都被添加到数组的末尾(如果需要,调用程序可以对它们进行排序)。dogs_array的当前大小由count方法确定并存储到$dogs_array_size中。在for结构中也使用了count方法来确定$records_array的大小和循环的数量。因为count方法的结果产生了数组的大小,它比上一个下标位置大 1,所以count的结果也给出了可用于插入记录的下一个位置。
在第一次循环中,$I为 0。$records_array的第一条记录放入$dogs_array_size加 0,或$dogs_array_size(放置记录的第一个开放行)。下一次循环时,$records_array ( $I被循环递增)的第二条记录被放入位置$dogs_array_size加 1。这是插入第一条记录后的下一个可用位置。循环将继续,直到$records_array中不再有记录。顺便说一下,这个方法也适用于只插入一条记录的情况(只要它是作为关联数组传递的)。该循环将只执行一次。
您需要检查的最后一个方法是update方法。这个方法是析构函数方法的一种非常简单的形式。
function updateRecords($records_array)
{
foreach ($records_array as $records=>$records_value) {
foreach ($records_value as $record => $record_value) {
$this->dogs_array["dog"][$records] = $records_array[$records];
}
}
}
这个小小的方法将接受任意大小的关联数组并更新dogs数组。它基于 PHP 动态构建数组的能力。
$records_array = Array (
0 => Array ( "dog_name" => "Jeffrey", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ),
2 => Array ( "dog_name" => "James", "dog_weight" => "21", "dog_color" => "Black", "dog_breed" => "Mixed" ));
动态构建的数组不需要数组中每个位置都有值。如果前面显示的动态数组被传递到updateRecords方法中,记录 0 和 2 将被更新为新的信息。在dogs数组中位置 1 的值将保持不变。
花点时间看看这些方法。在方法中只有两个 XML 标记被编码(dogs和dog)。甚至这两个都可以从 XML 文件中检索到。然而,假设这些标签将一直存在于一个有效的 dog XML 文件中是合乎逻辑的。通过从 XML 文件中动态提取所有其他标签(dog_name、dog_weight、dog_color和dog_breed),可以对文件进行更改,而不会导致任何代码更改。可以添加、移除和/或改变附加标签。
让我们把它们放在一起。
Example 6-1. The dog_data.php file
<?php
class dog_data
{
private $dogs_array = array(); //defined as an empty array initially
private $dog_data_xml = "";
function __construct() {
libxml:use_internal_errors(true);
$xmlDoc = new DOMDocument();
if ( file_exists("e5dog_applications.xml") ) {
$xmlDoc->load( 'e5dog_applications.xml' );
$searchNode = $xmlDoc->getElementsByTagName( "type" );
foreach( $searchNode as $searchNode )
{
$valueID = $searchNode->getAttribute('ID');
if($valueID == "datastorage")
{
$xmlLocation = $searchNode->getElementsByTagName( "location" );
$this->dog_data_xml = $xmlLocation->item(0)->nodeValue;
break;
}
} }
else { throw new Exception("Dog applications xml file missing or corrupt"); }
$xmlfile = file_get_contents($this->dog_data_xml);
$xmlstring = simplexml:load_string($xmlfile);
if ($xmlstring === false) {
$errorString = "Failed loading XML: ";
foreach(libxml:get_errors() as $error) {
$errorString .= $error->message . " " ; }
throw new Exception($errorString); }
$json = json_encode($xmlstring);
$this->dogs_array = json_decode($json,TRUE);
}
function __destruct()
{
$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';
$xmlstring .= "\n<dogs>\n";
foreach ($this->dogs_array as $dogs=>$dogs_value) {
foreach ($dogs_value as $dog => $dog_value)
{
$xmlstring .="<$dogs>\n";
foreach ($dog_value as $column => $column_value)
{
$xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";
}
$xmlstring .= "</$dogs>\n";
}
}
$xmlstring .= "</dogs>\n";
file_put_contents($this->dog_data_xml,$xmlstring);
}
function deleteRecord($recordNumber)
{
foreach ($this->dogs_array as $dogs=>&$dogs_value) {
for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {
foreach ($dogs_value[$J] as $column => $column_value)
{
$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];
}
}
unset ($dogs_value[count($dogs_value) -1]);
}
}
function readRecords($recordNumber)
{
if($recordNumber === "ALL") {
return $this->dogs_array["dog"];
}
else
{
return $this->dogs_array["dog"][$recordNumber];
}
}
function insertRecords($records_array)
{
$dogs_array_size = count($this->dogs_array["dog"]);
for($I=0;$I< count($records_array);$I++)
{
$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];
}
}
function updateRecords($records_array)
{
foreach ($records_array as $records=>$records_value)
{
foreach ($records_value as $record => $record_value)
{
$this->dogs_array["dog"][$records] = $records_array[$records];
}
}
}
}
?>
Note
教科书网站上提供了另一种解决方案,它处理带有缺失索引的关联数组,以及dog_data.xml文件可能包含一个或零个记录的可能性。
在这个最终版本的dog_data类中,唯一的变化是在构造函数中包含了get_dog_application方法代码,以检索保存狗数据的 XML 文件的位置和名称。
Example 6-2. The testdata.php file
<?php
include("dog_data.php");
$tester = new dog_data();
$records_array = Array (
0 => Array ( "dog_name" => "Sally", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ));
$tester->insertRecords($records_array);
print_r ($tester->readRecords("ALL"));
print("<br>");
$records_array = Array (
1 => Array ( "dog_name" => "Spot", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ));
$tester->updateRecords($records_array);
print_r ($tester->readRecords("ALL"));
print("<br>");
$tester->deleteRecord(1);
print_r ($tester->readRecords("ALL"));
$tester = NULL; // calls the destructor and saves the xml records in the file
?>
示例 6-2 测试了一些使用dog_data类的可能场景。注意最后一行代码调用了析构函数(保存数据)。这是通过将指向对象的指针($tester)设置为NULL来实现的,这样就释放了对象。这将通知操作系统的垃圾收集器应该从内存中移除该对象。这将导致析构函数执行,从而更新 XML 文件并从服务器内存中移除对象。
JSON 数据
让我们花点时间回顾一下,看看读取和写入 JSON 数据的能力。使用本章中显示的示例代码,当您使用除 XML 之外的其他形式的数据时,只需要调整构造函数和析构函数。访问和使用 JSON 数据甚至比使用 XML 数据更容易。
...
$json = file_get_contents($this->dog_data_JSON);
$this->dogs_array = json_decode($json,TRUE);
if ($this->dogs_array === null && json_last_error() !== JSON_ERROR_NONE)
{
throw new Exception("JSON error: " . json_last_error_msg());
}
...
在构造函数中,在从dog_application.xml文件中检索数据位置的if else结构之后,访问和格式化 XML 数据的多行可以替换为前面显示的行。json_decode方法(如前所示)试图将文本文件中的数据格式化成相关的数组格式。如果数据不是有效的 JSON 格式,就会抛出一个异常,传递错误消息。由于使用了Exception类,dog_interface程序会将此信息记录在错误日志中,通过电子邮件发送给支持人员,并向用户显示一般消息。
$json = json_encode($this->dogs_array);
file_put_contents($this->dog_data_JSON,$json);
析构函数的完整代码只需要两行。json_encode方法将关联数组数据转换成 JSON 格式。然后,file_put_contents方法会将信息保存到 JSON 文件($this->dog_data_JSON的适当位置。不需要对dog_data中的任何其他方法进行修改。注意:使用 JSON 数据的示例应用可以在本书网站的第六章中找到。
mysql 日期
这本书旨在介绍 PHP 语言。因此,您不会花太多时间来学习数据库的用法。不过,现在是给出一个简单示例的好时机,您可以对构造函数和析构函数方法进行调整,以访问和更新数据库信息。
Note
mysql从 PHP5.5 开始已经被移除,建议大家使用mysqli或者pdo_mysql。
$mysqli =mysqli_connect($server, $db_username, $db_password, $database);
if (mysqli_connect_errno())
{
throw new Exception("MySQL connection error: " . mysqli_connect_error());
}
$sql="SELECT * FROM Dogs";
$result=mysqli_query($con,$sql);
If($result===null)
{
throw new Exception("No records retrieved from Database");
}
$this->dogs_array = mysqli_fetch_assoc($result);
mysqli_free_result($result);
mysqli_close($con);
构造函数方法所需的大部分代码都与数据库的连接、检索和断开连接相关。mysqli_connect方法使用服务器位置($server)、数据库用户 ID ( $db_username)、数据库密码($db_password)和数据库名称($database)来连接数据库。如果mysqli_connect_errno包含任何错误,则抛出Exception描述错误。如果没有错误,使用 SQL SELECT 语句($sql)从数据库的Dogs表中检索所有记录。如果没有检索到记录,则会引发另一个异常。如果检索到记录,mysqli_fetch_assoc方法会将数据转换成一个关联数组。mysqli_free_result语句从$result中释放数据。mysqli_close方法关闭对数据库的访问。
析构函数需要更多的编码。然而,循环类似于保存 XML 数据。
$mysqli = new mysqli($server, $db_username, $db_password, $database);
if ($mysqli->connect_errno)
{
throw new Exception("MySQL connection error:" . $mysqli->connect_error);
}
If( (!$mysqli->query("DROP TABLE IF EXISTS Dogs") ||
(!$mysqli->query("CREATE TABLE IF NOT EXISTS Dogs (dog_id CHAR(4), dog_name CHAR(20), dog_weight CHAR(3), dog_color CHAR(15), dog_breed CHAR(35)") )
{
throw new Exception("Dog table can't be created or deleted. Error: " . $mysqli->error);
}
foreach ($this->dogs_array as $dogs=>$dogs_value) {
foreach ($dogs_value as $dog => $dog_value)
{
$dog_id = $dog_value["dog_id"];
$dog_name = $dog_value["dog_name"];
$dog_weight = $dog_value["dog_weight"];
$dog_color = $dog_value["dog_color"];
$dog_breed = $dog_value["dog_breed"];
If(!$mysqli->query("INSERT INTO Dogs(dog_id, dog_name, dog_weight, dog_color,
dog_breed) VALUES ('$dog_id', '$dog_name', '$dog_weight', '$dog_color',
'$dog_breed')"))
{
throw new Exception("Dog Table Insert Error: " . $mysqli->error);
}
}
}
...
析构函数方法试图连接到数据库。如果连接成功,该方法将删除任何预先存在的Dogs表,并创建一个包含必需字段的新表。(注意:重命名旧的并创建一个新的可能会更好。).如果可以移除旧表并创建新表,则该方法会尝试向表中插入行。SQL INSERT语句将来自$dog_name、$dog_weight、$dog_color和$dog_breed的值放入表中的一行。foreach循环从关联数组中检索每一行,并放入表中。如果任何插入不成功,就会引发异常。在本书网站的第六章下有一个示例程序。
Programming shows that Apache server must be configured correctly and MySQL must be installed correctly to run this (or similar) database example.
$servermust be set to URL, "localhost" or "127.0.0.1".$db_usernamemust be set to the user ID name to access the database ('root'if the user ID has not been configured).$db_passwordmust be set to the database password (or''if there is no password).$databasemust be set to the database name. There are many ways to access and operate databases in PHP.
做它
Download the example files for this section from the book’s web site. Adjust the deleteRecords method to allow the ability to delete multiple records. However, also include a check to limit the amount of records that can be deleted. It would not be very secure to allow all records to be deleted. If an attempt is made to delete all records (or too many records), an exception should be raised. The exception should cause the calling program (eventually dog_interface) to write an error message to the main log file, e-mail the support personnel, and display the general message to the users (shown in Chapter 5). Adjust the testdata program to test the ability to delete multiple records and catch the exceptions. Download the example files for this section from the book’s web site. Adjust the testdata program to test all remaining scenarios that have not already been tested. These are related to inserting, updating (more than one), reading, and deleting records. Be sure to test improperly formatted information. Create a try catch block in the testdata program to capture any exceptions. You can use the try catch block from dog_interface in Chapter 5 as an example.
备份和恢复
对存储的信息进行更改时,总有可能出错。而一个开发良好的应用必须在保存数据之前过滤和清理数据;它还必须准备好处理坏数据仍可能流过并破坏信息的可能性。除了蓄意破坏之外,还可能出现无法预料的问题(如系统崩溃)。应用必须能够在不丢失数据的情况下进行恢复。这可以通过记录变更请求和备份有效信息来实现。通过使用有效的备份并对备份文件重新应用有效的更改以产生最新的信息,可以完成恢复。
您可以对dogdata文件(例如 6-1 )进行一些小的修改,以创建一个变更日志并提供备份和恢复功能。首先,您将创建一个 main 方法(processRecords),它将解释传递给该类的任何数据。该函数允许恢复程序将所有更改日志信息传递到一个方法中,从而简化了恢复过程。这也将使依赖注入更容易完成。
function processRecords($change_Type, $records_array)
{
switch($change_Type)
{
case "Delete":
$this->deleteRecord($records_array);
break;
case "Insert":
$this->insertRecords($records_array);
break;
case "Update":
$this->updateRecords($records_array);
break;
case "Display":
$this->readRecords($records_array);
Break;
default:
throw new Exception("Invalid XML file change type: $change_Type");
}
}
所有变更请求现在都将通过此方法传递。该方法接受变更类型(Insert、Delete、Update或Display)和数组(对于Insert或Update)或记录号(对于Delete或Update)。传入$record_array. $record_array的值被动态创建为数组或字符串。这允许processRecords方法提供多态性(同一方法调用接受不同参数的能力),这是面向对象语言的需求之一(以及封装和继承)。switch语句查看$change_Type以确定调用哪个方法。然后它调用相关的方法。如果传递了无效类型,则会引发异常。
And security-in a "real-time" environment, it is safer to pass "code" to this type of method than to use a value indicating the action that will happen. For example, 101 can be used to indicate an update. You can easily adjust the
switchstatement to check the code to determine which method to call.
您之前检查过的每个方法(除了构造函数和析构函数)现在都被设置为'private'。这使得该过程更加安全;只有使用processRecords方法才能进行更改。在这三种方法的末尾还添加了三行代码,以提供备份和恢复功能。
...
$change_string = date('mdYhis') . " | Delete | " . $recordNumber . "\n";
$chge_log_file = date('mdYhis') . $this->change_log_file;
error_log($change_string,3,$chge_log_file); // might exceed 120 chars
...
第一行格式化更改日志文件的字符串。使用的格式类似于你在第五章中看到的格式。在前面的例子中,记录号是按照delete方法的要求传递的。
$change_string = date('mdYhis') . " | Update | " . serialize($records_array) . "\n";
对于update和insert,数组通过。但是,数组不能放在字符串中。serialize方法将一个数组转换成如下所示的字符串格式。
a:1:{i:0;a:4:{s:8:"dog_name";s:7:"Spot";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}
可以使用unserialize方法将序列化字符串中的数据返回到数组格式(或另一种格式)。第二行创建一个字符串($chge_log_file,它使用date方法和位于dog_applications XML 文件中的日志文件名来创建一个备份文件名(和位置)。然后使用error_log方法将创建的字符串传递给这个日志。日志文件的内容将类似于以下内容。
07142015042510 | Insert | a:1:{i:0;a:4:{s:8:"dog_name";s:7:"tester1";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}
07142015042510 | Update | a:1:{i:1;a:4:{s:8:"dog_name";s:7:"tester2";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}
07142015042510 | Delete | 1
此格式提供了帮助恢复过程所需的所有信息。如果 dog 数据文件的当前版本被破坏,则可以使用更改日志文件将更改应用到文件的良好版本,以开发新的当前版本。
对data类唯一需要的其他改变是析构函数中的一些额外的代码行。
$new_valid_data_file = preg_replace('/[0-9]+/', '', $this->dog_data_xml);
// remove the previous date and time if it exists
$oldxmldata = date('mdYhis') . $new_valid_data_file;
if (!rename($this->dog_data_xml, $oldxmldata))
{
throw new Exception("Backup file $oldxmldata could not be created.");
}
file_put_contents($new_valid_data_file,$xmlstring);
在析构函数使用file_put_contents方法将更改应用到 XML 文件之前,应该创建一个备份,以防更改导致当前数据损坏。恢复过程将允许支持人员选择哪个数据文件包含好的数据,以及哪个(些)改变文件将被应用于数据以产生数据的正确的当前版本。
因为该过程可能使用数据的备份文件,其中包括带有日期和时间的文件名,所以使用preg_replace方法从数据文件名中删除任何数字信息。第一个参数中的正则表达式(/[0-9]+/)指示该方法在$this->dog_data_xml中搜索所有出现的数字。如果找到任何匹配项,它将被替换为第二个参数中的值('')。在这种情况下,什么都没有。然后新文件名被放入$new_valid_data_file。这不会导致“正常”非备份文件名的任何改变,因为它不包含任何数字信息。使用带有日期和时间信息的$new_valid_data_file中的文件名创建一个新的备份文件名。新的备份文件名保存在$oldxmldata中。
现在可以使用rename方法将最后的有效数据移动到新的备份文件中。$this->dog_data_xml中的数据(没有变化的好数据的位置)被复制到新的备份文件位置($oldxmldata)。如果文件无法重命名,将引发异常。
最后,有效的已更改数据(位于$xmlstring中)可以被放置到包含在$new_valid_data_file属性中的有效数据(没有任何日期信息的相同文件名)的新位置。
例如,如果07142015042510dog_data.xml包含最后可用的有效数据,则在应用任何更改之前,07152015001510dog_data.xml可能是该数据的新位置。dog_data.xml是应用更改后有效数据的位置。对dog数据类的最后一个编码更改是包含了一个set方法。
function setChangeLogFile($value)
{
$this->dog_data_xml = $value;
}
要允许恢复应用使用最后的有效数据,该应用必须能够更改有效数据的位置。setChangeLogFile方法改变$this->dog_data_xml中的值。该属性最初是根据 dog 应用 XML 文件中的定位信息设置的。但是,这可能不是当前有效数据的位置。添加到析构函数中的代码将使用有效数据的新位置,应用所需的更改,并将有效数据放回原始数据文件中。没有必要对data_application XML 文件做任何修改。析构函数完成后,数据文件现在将包含最新的有效数据。
Example 6-3. The dogdata.php file with logging as well as backup and recovery processes
<?php
class dog_data {
private $dogs_array = array(); //defined as an empty array initially
private $dog_data_xml = "";
private $change_log_file = "change.log";
function __construct() {
libxml:use_internal_errors(true);
$xmlDoc = new DOMDocument();
if ( file_exists("e5dog_applications.xml") ) {
$xmlDoc->load( 'e5dog_applications.xml' );
$searchNode = $xmlDoc->getElementsByTagName( "type" );
foreach( $searchNode as $searchNode ) {
$valueID = $searchNode->getAttribute('ID');
if($valueID == "datastorage") {
$xmlLocation = $searchNode->getElementsByTagName( "location" );
$this->dog_data_xml = $xmlLocation->item(0)->nodeValue;
break;
}
}
}
else { throw new Exception("Dog applications xml file missing or corrupt"); }
$xmlfile = file_get_contents($this->dog_data_xml);
$xmlstring = simplexml:load_string($xmlfile);
if ($xmlstring === false) {
$errorString = "Failed loading XML: ";
foreach(libxml:get_errors() as $error) {
$errorString .= $error->message . " " ; }
throw new Exception($errorString); }
$json = json_encode($xmlstring);
$this->dogs_array = json_decode($json,TRUE);
}
function __destruct() {
$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';
$xmlstring .= "\n<dogs>\n";
foreach ($this->dogs_array as $dogs=>$dogs_value) {
foreach ($dogs_value as $dog => $dog_value) {
$xmlstring .="<$dogs>\n";
foreach ($dog_value as $column => $column_value)
{
$xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";
}
$xmlstring .= "</$dogs>\n";
} }
$xmlstring .= "</dogs>\n";
$new_valid_data_file = preg_replace('/[0-9]+/', '', $this->dog_data_xml); // remove the previous date and time if it exists
$oldxmldata = date('mdYhis') . $new_valid_data_file;
if (!rename($this->dog_data_xml, $oldxmldata)) { throw new Exception("Backup file $oldxmldata could not be created."); }
file_put_contents($new_valid_data_file,$xmlstring);
}
private function deleteRecord($recordNumber)
{
foreach ($this->dogs_array as $dogs=>&$dogs_value) {
for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {
foreach ($dogs_value[$J] as $column => $column_value)
{
$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];
}
}
unset ($dogs_value[count($dogs_value) -1]);
}
$change_string = date('mdYhis') . " | Delete | " . $recordNumber . "\n";
$chge_log_file = date('mdYhis') . $this->change_log_file;
error_log($change_string,3,$chge_log_file); // might exceed 120 chars
}
private function readRecords($recordNumber)
{
if($recordNumber === "ALL") {
return $this->dogs_array["dog"];
} else {
return $this->dogs_array["dog"][$recordNumber];
}
}
private function insertRecords($records_array)
{
$dogs_array_size = count($this->dogs_array["dog"]);
for($I=0;$I< count($records_array);$I++) {
$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];
}
$change_string = date('mdYhis') . " | Insert | " . serialize($records_array) . "\n";
$chge_log_file = date('mdYhis') . $this->change_log_file;
error_log($change_string,3,$chge_log_file); // might exceed 120 chars
}
private function updateRecords($records_array)
{
foreach ($records_array as $records=>$records_value)
{
foreach ($records_value as $record => $record_value)
{
$this->dogs_array["dog"][$records] = $records_array[$records];
}
}
$change_string = date('mdYhis') . " | Update | " . serialize($records_array) . "\n";
$chge_log_file = date('mdYhis') . $this->change_log_file;
error_log($change_string,3,$chge_log_file); // might exceed 120 chars
}
function setChangeLogFile($value)
{
$this->dog_data_xml = $value;
}
function processRecords($change_Type, $records_array)
{
switch($change_Type)
{
case "Delete":
$this->deleteRecord($records_array);
break;
case "Insert":
$this->insertRecords($records_array);
break;
case "Update":
$this->updateRecords($records_array);
break;
default:
throw new Exception("Invalid XML file change type: $change_Type");
} } }
Note
教科书网站上提供了一个替代解决方案,它允许数组与丢失的索引相关联,并且在dog_data.xml文件中有一个或没有记录。
既然您已经有能力提供备份和恢复,那么让我们对readerrorlog文件做一些调整(在示例 5-8 )。新的应用将需要允许支持人员选择(和修改)任何有效的变更日志文件,选择最有效的可用数据文件,并应用来自变更日志文件的变更以产生新的有效数据 XML 文件。
if(isset($_POST['data_File']))
{
update_XML_File_Process();
}
else if(isset($_GET['rn']))
{
delete_Process();
}
else if(isset($_POST['change_file']))
{
display_Process();
}
else
{
select_File_Process();
}
由于代码的长度增加了,如果大部分工作是在方法中完成的,那么遵循逻辑流程(并在必要时修改代码)会容易得多。与许多应用一样,程序的主要流程变成了一个嵌入式的if else语句。
图 6-1。
The readchangelog.php file requesting selection of a change log file
打开应用时,列表框将允许用户选择使用哪个有效的更改日志文件(以及可能的更新)。语句的else部分将把程序引导到select_File_Process,后者将处理这个请求。
图 6-2。
The readchangelog file displaying the selected log and requesting the valid data file
一旦用户选择了一个更改文件,文件的内容将以与第五章相同的方式显示。display_Process方法将提供这些信息。用户可以决定删除所选更改文件中的一些条目。如果需要删除的话,delete_Process方法将使用第五章中显示的相同技术完成这个过程。此外,相同的方法将允许用户选择最近的有效数据文件来应用更改。
一旦选择了数据文件,update_XML_File_Process将使用dogdata程序将更改应用到文件中(例如 6-3 )。该流程将向用户显示“更改已完成”消息。
select_File_Process方法使用类似于getbreeds.php程序中的逻辑(例如 4-5 )。
function select_File_Process()
{
$directory = "";
$files = glob($directory . "*change.log");
echo "<form id='file_select' name='file_select' method='post' action='readchangelog.php'>";
echo "<h3>Select a file to display</h3>";
echo "<select name='change_file' id='change_file'>";
foreach($files as $file)
{
echo "<option value='$file'>$file</option>";
}
echo "</select>";
echo "<input type='submit' id='submit' name='submit' value='select'>";
echo "</form>";
}
PHP glob方法将给定目录($directory)中的所有文件名放入一个数组($files)中。将$directory设置为""表示将搜索当前目录。第二个参数提供了过滤检索到的文件类型的能力。*change.log指示该方法提取所有以change.log结尾的文件。*(星号)是接受任何字符的通配符。这种组合将提取由dog_data类产生的所有变更日志文件。剩余的行创建一个 HTML 下拉列表,显示检索到的文件名。一个submit将导致程序使用驻留在change_file属性中的选定文件再次调用自身。
function display_Process()
{
$change_Array = load_Array();
$row_Count = count($change_Array) -1;
displayRecords($row_Count, $change_Array, $_POST['change_file']);
}
display_Process is called when a change file has been selected. This method calls the``load_Array method
function load_Array()
{
$change_File = $_POST['change_file'];
$logFile = fopen($change_File, "r");
$row_Count = 0;
while(!feof($logFile))
{
$change_Array[$row_Count] = explode(' | ', fgets($logFile));
$row_Count++;
}
$row_Count--;
fclose($logFile);
return $change_Array;
}
load_Array方法非常类似于dog_data类中的构造函数。该方法检索change_file中的值,并将其放在$change_File中。然后打开该文件,文件中的所有条目都被放入$change_Array。explode方法将产生三列(日期/时间、变更类型、用于变更的数组或字符串)。它将这个数组返回给调用程序(display_Process)。
数组返回到display_Process中的$change_Array。count方法决定了这个数组的大小。它的值放在$row_co unt 中。displayRecords被调用,通过row_count、change_Array、change_file进入displayRecords。
function displayRecords($row_Count, $change_Array, $change_File)
{
echo "<html><head>";
echo "<style> table { border: 2px solid #5c744d;} </style>";
echo "</head><body>";
echo "<table>";
echo "<caption>Log File: " . $change_File . "</caption>";
echo "<tr><th></th><th>Date/Time</th><th>Change Type</th><th>Change Data</th></tr><tr>";
for ($J=$row_Count -1; $J >= 0; $J--)
{
echo "<td><a href='readchangelog.php?rn=$J&change_File=$change_File'>Delete</a></td>";
for($I=0; $I < 3; $I++)
{
echo "<td> " . $change_Array[$J][$I] . " </td> ";
}
echo "</tr>";
}
echo "</table>";
echo "</body></html>";
$directory = "";
$files = glob($directory . "*dog_data.xml");
echo "<form id='data_select' name='data_select' method='post' action='readchangelog.php'>";
echo "<h3>Delete entries above or select a file to update with change log $change_File</h3>";
echo "<select name='data_File' id='data_File'>";
foreach($files as $file)
{
echo "<option value='$file'>$file</option>";
}
echo "</select>";
echo "<input type='hidden' id='change_file' name='change_file' value='$change_File'>";
echo "<input type='submit' id='submit' name='submit' value='select'>";
echo "</form>";
}
displayRecords使用与readerrorlog程序的displayRecords方法几乎完全相同的逻辑显示变更日志文件的内容(例如 5-8 )。它还使用与selectFileProcess(前面解释过)几乎相同的逻辑来显示数据文件,以便用户选择最后一个未损坏的文件。
如果用户决定从变更日志中删除一些记录,就会调用delete_Process。
function delete_Process()
{
$change_Array = load_Array();
deleteRecord($_GET['rn'], $row_Count, $change_Array);
saveChanges($row_Count,$change_Array,$change_File);
displayRecords($row_Count,$change_Array,$change_File);
}
如前所示,delete_Process方法将使用相同的change_Array将变更文件记录放入$change_Array中。它将把要删除的记录号($_GET['rn'])、数组中的行数($row_Count)和数组($change_Array)传递给deleteRecord方法。deleteRecords方法将使用与readerrorlog(例如 5-8 )程序中的deleteRecord方法相同的逻辑。然后delete_Process将调用saveChanges方法,传入row_count、change_Array和change_File信息。
function saveChanges($row_Count,$change_Array,$change_File)
{
$changeFile = fopen($change_File, "w");
for($I=0; $I < $row_Count; $I++)
{
$writeString = $change_Array[$I][0] . " | " . $change_Array[$I][1] . " | " . $change_Array[$I][2];
fwrite($changeFile, $writeString);
}
fclose($changeFile);
}
saveChanges方法从change_Arr ay 构建了date/time-changetype-changedata格式,如前所述。该信息保存在$writeString中,用于替换更新版本的变更日志文件(减去被删除的记录)。
然后,delete_Process方法调用displayRecords方法(前面描述过)来显示更新的变更日志(减去删除的记录)和数据文件下拉列表。
一旦用户选择了要更改的数据文件,就会调用update_XML_File_Process方法。
function update_XML_File_Process()
{
$change_Array = load_Array();
require_once("dog_data.php");
$data_Changer = new dog_data();
$row_Count = count($change_Array) -1;
for($I=0;$I < $row_Count; $I++)
{
if($change_Array[$I][1] != "Delete")
{
$temp = unserialize($change_Array[$I][2]);
}
else
{
$temp = (integer)$change_Array[$I][2];
}
$data_Changer->processRecords($change_Array[$I][1], $temp);
}
该方法调用load_Array方法将更改返回到$change_Array中。dog_data文件被导入到方法中,为用户选择的数据文件的更改做准备。创建了一个data_data类的实例($data_Changer。
使用一个for循环来仔细检查变化数组,并将每个变化传递给数据类的processRecords方法。但是,在传递记录之前,必须使用unserialize方法将序列化数据返回到关联的数组格式。如果更改请求是Delete,则必须进行类型转换,将数据(记录号)更改为整数。这是 PHP 要求类型转换的少数几次之一。序列化数据不被视为数据类型。必须通过取消序列化或类型转换来转换数据。变更类型(Update、Delete或Insert)被传递到processRecords方法的第一个参数中。更改数组或记录号被传递到该方法的第二个参数中。对数据进行所有更改,备份文件,并创建新的更改日志,以防出现更多损坏问题。
Example 6-4. The displaychangelog.php file
<?php
function displayRecords($row_Count, $change_Array, $change_File) {
echo "<html><head>";
echo "<style> table { border: 2px solid #5c744d;} </style>";
echo "</head><body><table><caption>Log File: " . $change_File . "</caption>";
echo "<tr><th></th><th>Date/Time</th><th>Change Type</th><th>Change Data</th></tr><tr>";
for ($J=$row_Count -1; $J >= 0; $J--) {
echo "<td><a href='readchangelog.php?rn=$J&change_File=$change_File'>Delete</a></td>";
for($I=0; $I < 3; $I++) {
echo "<td> " . $change_Array[$J][$I] . " </td> ";
}
echo "</tr>";
}
echo "</table>";
echo "</body></html>";
echo "</table>";
echo "</body></html>";
$directory = "";
$files = glob($directory . "*dog_data.xml");
echo "<form id='data_select' name='data_select' method='post' action='readchangelog.php'>";
echo "<h3>Delete entries above or select a file to update with change log $change_File</h3>";
echo "<select name='data_File' id='data_File'>";
foreach($files as $file) {
echo "<option value='$file'>$file</option>";
}
echo "</select>";
echo "<input type='hidden' id='change_file' name='change_file' value='$change_File'>";
echo "<input type='submit' id='submit' name='submit' value='select'>";
echo "</form>";
}
function deleteRecord($recordNumber, &$row_Count, &$change_Array) {
for ($J=$recordNumber; $J < $row_Count - 1; $J++) {
for($I=0; $I < 3; $I++) {
$change_Array[$J][$I] = $change_Array[$J + 1][$I];
}
}
unset($change_Array[$row_Count]);
$row_Count--;
}
function saveChanges($row_Count,$change_Array,$change_File)
{
$changeFile = fopen($change_File, "w");
for($I=0; $I < $row_Count; $I++)
{
$writeString = $change_Array[$I][0] . " | " . $change_Array[$I][1] . " | " . $change_Array[$I][2];
fwrite($changeFile, $writeString);
}
fclose($changeFile);
}
function delete_Process() {
$change_Array = load_Array();
deleteRecord($_GET['rn'], $row_Count, $change_Array);
saveChanges($row_Count,$change_Array,$change_File);
displayRecords($row_Count,$change_Array,$change_File);
}
function load_Array() {
$change_File = $_POST['change_file'];
$logFile = fopen($change_File, "r");
$row_Count = 0;
while(!feof($logFile)) {
$change_Array[$row_Count] = explode(' | ', fgets($logFile));
$row_Count++; }
$row_Count--;
fclose($logFile);
return $change_Array;
}
function display_Process() {
$change_Array = load_Array();
$row_Count = count($change_Array) -1;
displayRecords($row_Count, $change_Array, $_POST['change_file']);
}
function select_File_Process() {
$directory = "";
$files = glob($directory . "*change.log");
echo "<form id='file_select' name='file_select' method='post' action='readchangelog.php'>";
echo "<h3>Select a file to display</h3>";
echo "<select name='change_file' id='change_file'>";
foreach($files as $file) {
echo "<option value='$file'>$file</option>";
}
echo "</select>";
echo "<input type='submit' id='submit' name='submit' value='select'>";
echo "</form>";
}
function update_XML_File_Process() {
$change_Array = load_Array();
require_once("dog_datad.php");
$data_Changer = new dog_data();
$row_Count = count($change_Array) -1;
for($I=0;$I < $row_Count; $I++) {
if($change_Array[$I][1] != "Delete") {
$temp = unserialize($change_Array[$I][2]);
} else {
$temp = (integer)$change_Array[$I][2];
}
$data_Changer->processRecords($change_Array[$I][1], $temp);
}
$data_Changer->setChangeLogFile($_POST['data_File']);
$data_Changer = NULL;
echo "Changes completed";
}
// main section
if(isset($_POST['data_File'])) {
update_XML_File_Process();
} else if(isset($_GET['rn'])) {
delete_Process();
} else if(isset($_POST['change_file'])) {
display_Process();
} else {
select_File_Process();
}
?>
JSON 备份和恢复
为 JSON 数据而不是 XML 数据提供备份和恢复需要做哪些更改?实际上,一点都没变。只要实现了本章第一节中的更改,displaychangelog程序和对dog_data类的更改将以与 XML 数据相同的方式处理 JSON。
MySQL 备份和恢复
正如您所猜测的,只要实现了本章第二部分的更改,MySQL 数据的备份和恢复就不需要额外的更改。但是,您可以花点时间看看处理 MySQL 数据的另一种方法。
创建针对数据库执行的 SQL 脚本文件是一种常见的做法。脚本文件包含更新数据库所需的所有 SQL 代码。使用这种类型的文件将允许您执行正确的INSERT、UPDATE和DELETE SQL 命令,而不仅仅是前面显示的INSERT。前面的例子需要为关联数组中的每条记录创建一个INSERT命令。这包括未更改的记录。这对于大中型数据库来说是低效的。您只需要更新已更改的记录。
您可以从关联数组中已更改的记录开发脚本文件。您可以将更改日志用作脚本文件,因为 SQL 脚本列出了所有已请求的更改。可以重新运行它来修复任何损坏的数据。
例如,在updateRecords方法中,您可以创建任何需要的 SQL UPDATE命令。
private function updateRecords($records_array)
{
$chge_log_file = date('mdYhis') . $this->change_log_file;
$chge_string = "";
foreach ($records_array as $records=>$records_value)
{
$this->dogs_array["dog"][$records] = $records_array[$records];
$chge_string .= "UPDATE Dogs ";
$chge_string .= "SET dog_name='" . $records_array[$records]['dog_name'] ."', ";
$chge_string .= "dog_weight='" . $records_array[$records]['dog_weight'] ."',
$chge_string .= "dog_color='" . $records_array[$records]['dog_color'] ."', ";
$chge_string .= "dog_breed='" . $records_array[$records]['dog_breed'] ."' ";
$chge_string .= "WHERE dog_id='" . $records_array[$records]['dog_id'] . "';\n";
}
$chge_log_file = date('mdYhis') . $this->change_log_file;
error_log($chge_string,3,$chge_log_file); // might exceed 120 chars
}
这些更改将从关联阵列构建所有更新要求。对插入和删除方法也可以进行类似的更改。
private function deleteRecord($recordNumber)
{
foreach ($this->dogs_array as $dogs=>&$dogs_value) {
for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {
foreach ($dogs_value[$J] as $column => $column_value)
{
$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];
}
}
unset ($dogs_value[count($dogs_value) -1]);
}
$dog_id = $this->dogs_array['dog'][$recordNumber]['dog_id'];
$chge_string = "DELETE FROM Dogs WHERE dog_id='" . $dog_id . "';\n";
$chge_log_file = date('mdYhis') . $this->change_log_file;
error_log($chge_string,3,$chge_log_file); // might exceed 120 chars
}
这个示例delete方法一次删除一条记录。因此,delete字符串是在循环之外构建的。update方法允许更新多条记录,因此update字符串构建在循环内部。insert方法也需要你在循环中构建字符串。
private function insertRecords($records_array)
{
$chge_string = "";
$dogs_array_size = count($this->dogs_array["dog"]);
for($I=0;$I< count($records_array);$I++)
{
$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];
$dog_id = rand(0,9999); // get a number between 0 and 9999
while (in_array($dog_id, $this->dogs_array, true)) // in array?
{ $dog_id = rand(0,9999); // if it is get another number
}
$chge_string .="INSERT INTO Dogs VALUES('";
$chge_string .= $dog_id . "', '" . $records_array[$I]['dog_name'] . "', '";
$chge_string .= $records_array[$I]['dog_weight'] . "', '";
$chge_string .= $records_array[$I]['dog_color'] . "', '";
$chge_string .= $records_array[$I]['dog_breed'] . "');";
}
$chge_log_file = date('mdYhis') . $this->change_log_file;
error_log($chge_string,3,$chge_log_file); // might exceed 120 chars
}
如果您回顾一下changeRecords方法,就会发现一个 SQL WHERE子句是使用名为dog_id的属性构建的。在 XML 和 JSON 示例中,您没有这个字段。然而,SQL UPDATE需要一个where子句来决定更新哪些记录。使用的属性必须是唯一的,以标识准确的记录。代码必须生成这个dog_id的唯一地方是在数据库中创建新记录的时候(在insertRecords方法中)。这可以使用 PHP rand方法来完成。
PHP rand方法产生随机数。第一个参数是起始数字(0),第二个参数是最后一个数字(9999)。该字段的大小在数据库中设置为char(4),最多允许四个字符。这将允许你多达 10,000 只狗。我相信这已经足够了!
insertRecords方法中的while循环使用 PHP in_array方法来确定该数字是否已经在dogs_array中(包含数据库中所有的当前记录)。第三个参数决定是否应该进行strict搜索(比较数据类型),必须设置这个参数来产生多维关联数组的可靠结果。如果该数字确实存在,则逻辑继续生成新的随机数,直到找到唯一的一个为止。然后将该值放入$dog_id,它将与其他字段(dog_name、dog_weight、dog_color和dog_breed)一起插入数据库。注意:这段代码假设数据库中的Dogs表是用显示的顺序(dog_id、dog_name、dog_weight、dog_color和dog_breed)的字段创建的。
更改日志(现在也是一个 SQL 脚本文件)现在将包含类似如下的语句:
INSERT INTO Dogs VALUES('2288', 'tester1', '19', 'Green', 'Lab');
UPDATE Dogs SET dog_name='tester1', dog_weight='19', dog_color='Green', dog_breed='Lab' WHERE dog_id='0111';
UPDATE Dogs SET dog_name='tester2', dog_weight='19', dog_color='Green', dog_breed='Lab' WHERE dog_id='1211';
DELETE FROM Dogs WHERE dog_id='1111';
当记录了所有更改后,可以对数据库运行该文件。析构函数现在可以执行这个文件(而不是删除表并将所有记录重新插入到一个新表中)。
$mysqli = new mysqli($server, $db_username, $db_password, $database);
if ($mysqli->connect_errno)
{
throw new Exception("MySQL connection error:" . $mysqli->connect_error);
}
$chge_log_file = date('mdYhis') . $this->change_log_file;
$sql = explode(";",file_get_contents($chge_log_file));
foreach($sql as $query) {
If(!$mysqli->query($query))
{
throw new Exception("Dog Table Change Error: " . $mysqli->error);
}
}
析构函数的代码变得比最初的 MySQL 示例更简单。析构函数不需要格式化任何 SQL 语句。它只需要执行它们。该方法从变更日志中读取变更记录,通过每个 SQL 命令行末尾的;分割每个记录。每一行都放在数组$sql中。然后,逻辑循环遍历数组,并通过query命令执行每条语句。如果任何 SQL 语句有问题,就会抛出一个异常(它还会通过dog_interface程序向支持人员发送一封电子邮件)。该书的网站上有一个示例程序。
注意:如上所述,展示 MySQL 示例是为了帮助读者了解dog_data类的整体逻辑适用于所有数据类型。完整的书籍都是关于使用 PHP 与数据库交互的。这本书的目的不是训练用户完全掌握数据库操作的知识。
做它
The dog_data class creates a new log file every time it is run. This could cause a lot of log files to be created in a very short period of time. Your mission is to either update the readchangelog file (download it from the book’s web site) or to create your own maintenance program. The code will ask the users for the number of log files (and data files) to keep. The program will then keep the most recent number of files requested. The glob method, as shown previously, can be used to retrieve all the file names. The unlink method can be used to delete a file. unlink($file); The MySQL examples shown now produce different contents in the change log file. Download the readchangelog program from the book’s web site and make any adjustments needed to the code to properly view and delete the change log. Assuming that the database administrator has reversed the contents of the database to the last valid set of data, adjust the program to execute the change log selected against the database. Hint: Your completed program will have less code than the example from the book’s web site.
连接数据层
既然已经创建了一个可靠的、经过良好测试的数据类,是时候将它连接到业务规则层了。Dog类将使用dog_data类在 XML 文件中存储狗的信息。
if (method_exists('dog_container', 'create_object')) {
$this->breedxml = $properties_array[4];
$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';
$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';
$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';
$breed_error = $this->set_dog_breed($properties_array[1]) == TRUE ? 'TRUE,' : 'FALSE,';
$this->error_message = $name_error . $breed_error . $color_error . $weight_error;
$this->save_dog_data();
if(stristr($this->error_message, 'FALSE'))
{
throw new setException($this->error_message);
}
} else
{
exit;
}
dog类的构造函数设置所有属性,如果有问题就抛出异常。如果没有问题,信息被保存(通过save_dog_data,程序关闭(exit)。
为了保持数据层独立于业务规则层,将使用依赖注入来发现dog_data类的位置和名称,并从该类调用processRecords方法。你将借用第四章中的逻辑。实际上,您可以使用示例 4-10 中的dog_container而无需任何更改。如果你不记得这堂课的细节,重温第四章。
dog_container类包含了get_dog_application方法,该方法使用多次讨论过的逻辑在 dog 应用 XML 文件中搜索所需的文件名(dog_data.php)。set_app方法允许您将应用类型(dogdata)传递到get_dog_application中进行搜索。它还包括create_object类,该类将确定类名(dog_data),创建该类的一个实例,并将该类(该类在内存中的地址)传递回调用程序。该类要求调用程序中存在一个clean_input函数。您目前在Dog类中还没有。但是,您可以在类中创建一个 shell(一个空函数)来满足这一要求。
要使用容器,您可以使用dog_interface程序中的逻辑来创建容器的实例,找到dog_data的位置,并创建dog_data的实例(不知道类名)。
function clean_input() { }
private function save_dog_data()
{
if ( file_exists("e5dog_container.php")) {
require_once("e5dog_container.php"); // use 第五章 container w exception handling
} else {
throw new Exception("Dog container file missing or corrupt");
}
$container = new dog_container("dogdata"); // sets the tag name to look for in XML file
$properties_array = array("dogdata"); // not used but must be passed into create_object
$dog_data = $container->create_object($properties_array); // creates dog_data object
$method_array = get_class_methods($dog_data);
$last_position = count($method_array) - 1;
$method_name = $method_array[$last_position];
$record_Array = array(array('dog_name'=>"$this->dog_name", 'dog_weight'=>"$this->dog_weight", 'dog_color'=>"$this->dog_color", 'dog_breed'=>"$this->dog_breed"));
$dog_data->$method_name("Insert",$record_Array);
$dog_data = NULL;
}
完成此操作的代码行与之前在dog_interface中看到的相同;您创建了dog_container,找到了dogdata文件的位置,并创建了dog_data类的一个实例。唯一的区别是“dogdata”是为搜索传递的。PHP 函数get_class_methods用于在dog_data类中创建一个方法列表。类中最后一个方法是processRecords。这个方法的名字被拉出来放入$method_name。然后构建record_Array以传递给processRecords。方法被调用,传递"Insert"和record_Array。最后,dog_data对象被设置为NULL,这导致析构函数保存数据。
这允许完全的依赖注入。在被这段代码确定之前,dog对象不知道dog_data类的名称、dog_data类的位置或者要调用的方法的名称。这在数据层和业务规则层之间建立了一个完整的分界,这是三层设计所需要的。
Example 6-5. The dog.php file using dog_data.php to save data
<?php
class Dog
{
// ---------------------------------- Properties ------------------------------------------
private $dog_weight = 0;
private $dog_breed = "no breed";
private $dog_color = "no color";
private $dog_name = "no name";
private $error_message = "??";
private $breedxml = "";
// ---------------------------------- Constructor -----------------------------------------
function __construct($properties_array)
{
if (method_exists('dog_container', 'create_object')) {
$this->breedxml = $properties_array[4];
$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';
$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';
$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';
$breed_error = $this->set_dog_breed($properties_array[1]) == TRUE ? 'TRUE,' : 'FALSE,';
$this->error_message = $name_error . $breed_error . $color_error . $weight_error;
$this->save_dog_data();
if(stristr($this->error_message, 'FALSE'))
{
throw new setException($this->error_message);
} }
else
{ exit; }
}
function clean_input() { }
private function save_dog_data()
{
if ( file_exists("e5dog_container.php")) {
require_once("e5dog_container.php"); // use 第五章 container w exception handling
} else {
throw new Exception("Dog container file missing or corrupt");
}
$container = new dog_container("dogdata"); // sets the tag name to look for in XML file
$properties_array = array("dogdata"); // not used but must be passed into create_object
$dog_data = $container->create_object($properties_array); // creates dog_data object
$method_array = get_class_methods($dog_data);
$last_position = count($method_array) - 1;
$method_name = $method_array[$last_position];
$record_Array = array(array('dog_name'=>"$this->dog_name", 'dog_weight'=>"$this->dog_weight", 'dog_color'=>"$this->dog_color", 'dog_breed'=>"$this->dog_breed"));
$dog_data->$method_name("Insert",$record_Array);
$dog_data = NULL;
}
function set_dog_name($value)
{
$error_message = TRUE;
(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $this->error_message = FALSE;
return $this->error_message;
}
function set_dog_weight($value)
{
$error_message = TRUE;
(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $this->error_message = FALSE;
return $this->error_message; }
function set_dog_breed($value)
{
$error_message = TRUE;
($this->validator_breed($value) === TRUE) ? $this->dog_breed = $value : $this->error_message = FALSE;
return $this->error_message; }
function set_dog_color($value)
{
$error_message = TRUE;
(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $this->error_message = FALSE;
return $this->error_message;
}
// ----------------------------- Get Methods -----------------------------------------------
function get_dog_name()
{
return $this->dog_name;
}
function get_dog_weight()
{
return $this->dog_weight;
}
function get_dog_breed()
{
return $this->dog_breed;
}
function get_dog_color()
{
return $this->dog_color;
}
function get_properties()
{
return "$this->dog_name,$this->dog_weight,$this->dog_breed,$this->dog_color.";
}
// ------------------------------General Method---------------------------------------------
private function validator_breed($value)
{
$breed_file = simplexml:load_file($this->breedxml);
$xmlText = $breed_file->asXML();
if(stristr($xmlText, $value) === FALSE)
{
return FALSE;
}
else
{
return TRUE;
}
}
}
?>
Dog应用现在有三个完整的层。
图 6-3。
Three-tier dog application
接口层包含lab.html文件和dog_interface.php程序。业务规则层包括dog.php类和get_breeds类。dog_data类位于数据层。来自dog_interface程序的任何与业务规则层通信的请求都由dog_container类处理。任何与数据层通信的请求(来自业务规则层)也由dog_container类处理。只能从业务规则层或通过业务规则层访问数据层。对接口层的访问只能通过业务规则层进行。
接口层不知道业务规则层中任何类或方法的位置/名称。这些信息是通过使用dog_container发现的。业务规则层不知道数据层中任何类或方法的位置/名称。该信息也是通过使用dog_container发现的。这允许每一层完全独立,允许在一层中发生变化,而不需要在其他两层中进行变化。
做它
Download all the files for the dog application from this chapter. Adjust file names in the dog_application XML file to discover how missing files are handled in the application. Does the application handle these problems properly? Adjust the dog_data XML file to include badly formed XML data. Run the application. Does it handle these problems properly? Empty the dog_data XML file (except for the dogs and dog tags). Run the application. Does it handle this situation properly? For any instance that causes the application to error instead of raising an exception, attempt to adjust the code to anticipate the problem and raise an exception.
章节术语
| 数据层 | 文件获取内容 | | SimpleXML 数据模型 | simplexml:load_string | | 铅字铸造 | JSON 数据 | | JSON 方法 | 关联数组 | | json_encode | json_decode | | 关键词 | 新行字符 | | 文件内容 | libxml:user _ internal _ errors(true) | | libxml:get_errors | 数数 | | $这个 | 复原 | | 动态数组 | 释放对象 | | mysqli_connect | mysqli_connect_errno | | mysqli_fetch_assoc | mysqli _ free _ result | | mysqli_close | 标准选取 | | SQL 插入 | 多态性 | | 更改日志文件 | 连载 | | 不系列化 | 备份和恢复 | | 怀孕 _ 替换 | 正则表达式 | | 一团 | 嵌入 if then else | | SQL 更新 | SQL 脚本文件/日志 | | SQL WHERE | 边缘 | | 严格搜索 | 使分开 | | 壳 | 依赖注入 |
第二章问题和项目
多重选择
Which of the following describes type casting? It’s rarely needed in PHP It exists in most languages It’s needed for serialized data All of the above Which of the following describes an associative array? It has a key and value relationship (key->value) It uses numerical subscripts It does not have an index A and B Which of the following is the newline character? & . ; None of these Which of the following describes JSON data? It has a similar format to arrays It cannot only be used with JavaScript It requires much more coding than when using XML data It is more secure than XML data Which of the following describes unlink? It can be used to release a parameter of an array It can be used to delete a file or directory It can be used to split apart an array None of these Which of the following describes polymorphism? The container that holds and secures an application The ability to pass multiple data types into the same signature of a method The ability to use the methods and properties of a parent class None of these Which of the following describes rand? It can be used to produce a random number The first parameter is the starting number The second parameter is the last number All of the above When using in_array, strict search does which of the following? It compares data types It is the third parameter (set to true) It should be used when searching associative arrays All of these Which of the following describes a shell method? It contains no data It has no signature It includes JSON data All of these The data tier does which of the following? Updates data Returns data Filters data All of the above
真/假
Log files are important for successful recovery of information. Data is serialized to convert the data type to string. After updates have been completed, all backup copies of data can be destroyed. Dependency injection is necessary to keep the tiers (interface, business rules, and data) independent of each other. SQL script files update all records including those that have not changed.
简答/短文
Explain the process used to correct data files that have been corrupted. Why is data stored in a database usually more secure than data stored in a text file? Compare and contrast the methods used to update XML data to the methods used to update MySQL data. Which is more efficient? Why? When should an e-mail be sent to the system administrator when data is being updated? What should this e-mail contain? What should it not contain? Why? How can a system administrator determine which data file is the last non-corrupted version?
项目
Adjust the code from Chapter 4 project #1 or #2 to include backup and recovery ability. Create a complete storage (XML or JSON format) and a backup and recovery system for one of the previous projects you have completed. The system should include the ability for the users to limit the number of recovery files, the ability to adjust contents of a selected file (update, insert, and delete), and the ability to execute the file against the most recent valid data. When the process is complete, any corrupted files should automatically be removed. The system should also keep its own log file to indicate when changes have occurred.
学期项目
Update the ABC Computer Parts Inventory program to include storage of the data (XML or JSON format) and complete backup and recovery capabilities. The application should include a change log to indicate any data changes. Additional support programs should be included to allow for easy recovery of any corrupted data. Your complete application should use logic similar to the examples shown in this chapter.