PHP7 学习手册(四)
原文:Learn PHP 7
七、验证
Electronic supplementary material The online version of this chapter (doi:10.1007/978-1-4842-1730-6_7) contains supplementary material, which is available to authorized users.
(对丽莎)你有头脑和天赋,想走多远就走多远,当你走远了,我会在那里借钱给你的—巴特·辛普森
章节目标/学生学习成果
完成本章后,学生将能够:
- 定义会话并解释它们如何用于身份验证
- 创建一个验证用户登录的 PHP 程序
- 创建一个注册用户的 PHP 程序
- 创建一个允许用户修改密码的 PHP 程序
- 创建一个记录无效登录尝试的 PHP 程序
- 创建一个使用当前密码加密技术的 PHP 程序
验证和会话
如果不包括用户 ID/密码认证,任何关于安全性的讨论都是不完整的。PHP 的当前版本包含了许多帮助开发人员验证用户的技术。本章着眼于一个更简单的方法。
由于立即验证登录凭证的性质,身份验证过程直接访问数据源进行验证(它不通过业务规则层)。因此,身份验证过程被视为一个独立的层,位于应用之上以提供访问。正如您将看到的,只需要在接口层程序中进行微小的更改就可以限制访问。所需的大部分编码都放在身份验证层。
除了身份验证之外,访问级别也可以在登录过程中确定。并非每个用户都需要对应用的完全访问权限。一些用户可能只需要读取权限,一些用户可能只需要对与其相关的信息进行写入,而一些用户(管理员)可能需要对整个应用进行完全访问。应用的每个部分都需要能够确定正确的访问级别,而无需向用户请求额外的信息(除了最初登录应用之外)。
一个登录过程必须允许用户验证应用的所有部分。应用的每个部分都需要访问由身份验证层设置的公共属性(如用户 ID 和密码),以验证有效的访问和有效的访问级别。PHP 通过声明一个会话提供了在服务器内存中存储应用信息的能力。一个会话被认为包括用户与应用的完整交互(例如将钱从储蓄账户转移到支票账户的完整过程)。一旦用户登录到应用,就可以建立会话。用户从系统注销后(或者应用超时,或者被关闭),可以关闭会话。当会话关闭时,存储在服务器内存中的所有属性都被垃圾收集器移除。
当会话处于活动状态时,可以在整个应用中存储和共享属性。使用此过程,用户 ID 和密码可以存储在会话属性中。然后,应用的每个部分可以通过确定userid和password属性中是否有值来验证用户已经登录。
在查看登录身份验证过程之前,我们先来看看如何确定用户是否已经登录到系统。本章中的示例不包括安全访问级别的验证。但是,确定这些级别的过程将使用类似于这些示例中所示的代码。
Programming considerations-
session_startThe method call must be the first statement at the top of the code. There can be no space or code between<?phptag andsession_start.session_startThe method generates an HTML header. If there is any code before the method is called, this header will not be formed correctly.
<?php
session_start();
if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password']))) {
echo "You must login to access the ABC Canine Shelter Reservation System";
echo "<p>";
echo "<a href='login.php'>Login</a> | <a href='register.php'>Create an account</a>";
echo "</p>";
}
else {
echo "<p>Welcome back, " . $_SESSION['username'] . "</p>";
}
?>
界面层中的每个程序(用户可以访问)都将包含类似于前面示例的代码。在这个例子中,session_start方法让操作系统知道这个程序是现有会话的一部分(在认证层声明)。系统使用唯一生成的 ID 来标识每个会话。只要用户(或系统)没有关闭会话,会话 ID 将被附加到用户调用的任何程序,包括session_start方法。这允许程序访问与当前会话相关的所有属性。
PHP isset方法(在if语句中)可以确定值是否存在于username和password属性中。如果值不存在,则表明用户尚未通过身份验证。使用$_SESSION检索(和设置)会话属性。在前面的示例中,如果没有设置任何一个属性,那么用户将获得到登录页面(login.php)或注册页面(register.php)的链接。如果设置了这两个属性,则欢迎用户进入系统。用户别无选择,只有登录才能访问该程序。此外,如前几章所述,对于更安全的程序,可以确定用户机器的 IP 地址和调用程序,以提供用户被授权的额外保证。
Example 7-1. The lab.php file with user ID/password verification
<?php
session_start();
if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password']))) {
echo "You must login to access the ABC Canine Shelter Reservation System";
echo "<p>";
echo "<a href='login.php'>Login</a> | <a href='register.php'>Create an account</a>";
echo "</p>";
}
else
{
echo "<p>Welcome back, " . $_SESSION['username'] . "</p>";
?>
<!DOCTYPE html>
<html lan="en">
<head>
<title>Dog Object</title>
<script src="get_breeds.js"></script>
<script src="validator.js"></script>
<style type="text/css">
#JS { display:none; }
</style>
<script>
function checkJS() {
document.getElementById('JS').style.display = "inline";
}
</script>
</head>
<body onload="checkJS();">
<h1>Dog Object Creater</h1>
<div id="JS">
<form method="post" action="e5adog_interface.php" onSubmit="return validate_input(this)">
<h2>Please complete ALL fields. Please note the required format of information.</h2>
Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z]*" title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />
Select Your Dog's Color:<br />
<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />
<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />
<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />
<input type="radio" name="dog_color" id="dog_color" value="White">White<br />
<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />
Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />
<script>
AjaxRequest('e5dog_interface.php');
</script>
<input type="hidden" name="dog_app" id="dog_app" value="dog" />
Select Your Dog's Breed <div id="AjaxResponse"></div><br />
<input type="submit" value="Click to create your dog" />
</form>
</div>
<noscript>
<div id="noJS">
<form method="post" action="e5adog_interface.php">
<h2>Please complete ALL fields. Please note the required format of information.</h2>
Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*"title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />
Select Your Dog's Color:<br />
<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />
<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />
<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />
<input type="radio" name="dog_color" id="dog_color" value="White">White<br />
<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />
Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />
Enter Your Dog's Breed (max 35 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*" title="Up to 15 Alphabetic Characters" maxlength="35" name="dog_breed" id="dog_breed" required /><br />
<input type="hidden" name="dog_app" id="dog_app" value="dog" />
<input type="submit" value="Click to create your dog" />
</form>
</div>
</noscript>
</body>
</html>
<?php
}
?>
在示例 7-1 中,验证码被放置在程序的顶部。else语句必须包含用户登录系统时要执行的所有代码。由于这个程序的代码是 HTML(和 CSS)代码,if语句必须包装在现有的代码中。else语句的右括号(s)被移到了代码的底部(所有 HTML 标签之后)。
PHP 允许你关闭你的 PHP 代码(通过?>)并根据需要多次重新打开你的 PHP 代码(通过<?php)。在这个例子中,PHP 代码被关闭在程序的顶部(在else语句中),就在右括号之前。然后在代码底部重新打开 PHP 代码,加入一个右括号,关闭 PHP else语句。这将else语句包装在所有现有代码周围。用户现在只有登录后才能访问这部分代码。
由于 PHP 代码现在包含在实验室程序中,所以文件结尾必须从.html改为.php。否则服务器不会执行 PHP 代码。
现在让我们看看如何通过创建登录程序来填充会话属性。首先让我们看一下向用户请求信息的 HTML。
<form method="post" action="">
Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />
Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters." name="password" id="password" required /><br />
<input type="submit" value="Login">
</form>
HTML5 不包括最小长度参数。但是,可以使用 pattern 参数(通过正则表达式)来建立最小大小。在前一个例子的username标签中,模式".{8,}"要求用户至少输入八个字符。对于密码安全性,需要一个更复杂的模式。在密码示例中,除了八个字符的最低要求之外,还需要至少一个数字(?=.*\d)、一个大写字母(?=.*[A-Z])和一个小写字母(?=.*[a-z])。
And security-HTML filtering is provided to inform users of any typos that may occur. During the login process, you did not store information; You are comparing information with stored information. You don't have to worry about any potentially harmful information being passed into the text box. Any harmful information will not match the stored valid information. The user will receive an invalid user ID/ password message.
// validate process not shown
$_SESSION['username'] = $_POST['username'];
$_SESSION['password'] = $_POST['password'];
// Redirect the user to the home page
header("Location:http://www.asite.com/lab.php
假设您已经根据有效的用户 id 和密码列表验证了信息(您将很快看到这个过程),那么您可以将有效的用户 id 和密码信息传递到会话变量中。然后可以使用 PHP header 方法将应用重定向到下一个要执行的程序(lab.php)。只要lab.php包含了session_start方法(如前所示),它就可以访问会话变量。
<?php
session_start();
if ((!isset($_POST['username'])) || (!isset($_POST['password'])))
{
?>
<form method="post" action="">
Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />
Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."
name="password" id="password" required /><br />
<input type="submit" value="Login">
</form>
<?php
} else {
// validate process not shown
$_SESSION['username'] = $_POST['username'];
$_SESSION['password'] = $_POST['password'];
// Redirect the user to the home page
header("Location:http://www.asite.com/lab.php
}
?>
将这些片段放在一起需要一个if语句来确定用户是否输入了用户 ID 和密码。如果用户没有这样做,将显示请求它们的 HTML 代码。如果信息已经输入(并且使用所示的 HTML5 模式表达式有效),语句的else部分将执行(将值存储在会话变量中并调用lab.php程序)。这为您提供了接受用户 ID 和密码、验证它们是否存在以及在接口层调用程序(如果它们确实存在)的基本外壳。当然,你需要在调用程序之前验证用户 ID 和密码。
Programming considerations-Server variables
PHP_AUTH_USERandPHP_AUTH_PWcan be used for user ID and password verification instead of session variables.header('WWW-Authenticate: Basic realm="ABC Canine"');header('HTTP/1.0 401 Unauthorized');If the user does not enter a user ID/ password or a valid user ID/ password, an unauthorized header message may be created. This will automatically cause the system to ask the user to enter the user ID/ password. This technique is very simple. However, in the past, there were some reports that browsers could not use this technology properly. In addition, creating your own technology allows you to design a login screen with the same style as your website. For more information, please visit:http://php.net/manual/en/features.http-auth.php
$valid_useridpasswords = array ("sjohnson" => "N3working");
$valid_userids = array_keys($valid_useridpasswords);
$userid = $_SESSION['username'];
$password = $_SESSION['password'];
$valid = (in_array($userid, $valid_userids)) && ($password == $valid_useridpasswords[$userid]);
If($valid) { header("Location:http://www.asite.com/lab.php
有几种方法可以验证用户 id 和密码。如果您正在创建一个不需要更改用户 id 和密码的系统,您可以使用数组。在前面的例子中,$valid_useridpasswords associate 数组包含有效用户 id 和密码的组合。PHP 方法array_keys将所有的键(在本例中是用户 id)放入一个单独的数组中($valid_userids)。在将会话变量放入$userid和$password之后,PHP in_array方法用于确定用户 ID 和密码的正确组合是否存在。in_array确定用户 ID 是否存在于数组中。然后使用用户 ID 作为下标,从valid_useridpasswords数组中提取密码,并将其与$password中的值进行比较。如果用户 ID 存在并且密码相同,那么一切都是有效的。$valid将包含TRUE。如果其中一个(或两个)无效,$valid将包含FALSE。如果$valid是TRUE,应用重定向到lab.php程序。
从技术上讲,会话中的属性受到保护,不会被会话外的任何人访问。然而,过去曾有黑客程序破坏这种安全性并访问会话信息的报道。在本例中,如果用户 ID 和密码存储在会话变量中,并通过互联网传递给另一个程序,黑客就可以访问这些信息。
如果用户 ID 和密码存储在外部的文件或数据库中,这些信息也会在程序之外传播。一旦这些项目驻留在文件或数据库中,程序将不再控制它们的安全性。这可能会让黑客获得这些信息。如上所述,安全性必须是程序员、数据管理员和网络管理员的团队工作。
通常的做法是加密密码,以减少黑客发现认证信息(或任何其他安全信息)的机会。许多 PHP 书籍演示了 MD5 散列技术的使用。但是,在过去的几年中,这种加密方式中已经发现了漏洞。
PHP 5.5 包含了方法password_ hash,它将随着时间的推移进行调整,以使用最安全的加密散列技术。
Programming considerations-Be careful when storing encrypted versions of passwords. With the emergence of the new hash version, the size of the encryption result will increase. The size of 25 characters may be large enough for many years. The number of milliseconds required for this hash increases with the size and type of encryption. Advanced programmers may want to do some time cost tests on their servers.
Visithttp://php.net/manual/en/function.password-hash.phpLearn more. Programming instructions-you can't simply compare with the hash password created by PHP'spassword_hashmethod. The generated hash of includes encryption type, salt value and hash password.
您只需要替换示例中的一行代码来验证密码。你可以替换
$valid = ((in_array($userid, $valid_userids)) && ($password == $valid_useridpasswords[$userid]));
随着
$valid =( (in_array($userid, $valid_userids)) && (password_verify($password, $valid_useridpasswords[$userid]));
如果将加密的密码放在$valid_useridpasswords数组中,验证技术就不需要任何其他的修改。PHP password_verify方法将加密用户提供的密码,并将其与现有的加密密码进行比较。如果匹配,它将返回TRUE。
当使用 XML 或 JSON 文件时,您可以使用在第六章的dogdata.php程序的构造函数中使用的相同逻辑,来检索有效的用户 id 和密码信息。唯一需要修改的是if语句,它决定了用户 ID 和密码文件的位置,还需要修改构造函数的最后一行,将生成的数组放在$valid_useridpasswords而不是dogs_array中。
<users>
<user>
<userid>Fredfred</userid>
<password>$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK</password>
</user>
<user>
<userid>Petepete</userid>
<password>$2y$10$FdbXxIVXmVOHtaBNxB8vzupRBJFCqUyOTJXrlpNdrL0HKQ/U.jFHO</password>
</user>
</users>
假设 XML 与 dog 数据 XML 文件的格式相似(如图所示),您可以使用相似的逻辑来检索信息。
$valid_useridpasswords = json_decode($json,TRUE);
$userid = $_POST['username'];
$password = $_POST['password'];
foreach($valid_useridpasswords as $users)
{
foreach($users as $user)
{
$hash = $user['password'];
if((in_array($userid, $user)) && (password_verify($password,$hash)))
{
$_SESSION['username'] = $userid;
$_SESSION['password'] = $hash;
header("Location: lab.php");
}
}
使用json_decode方法创建的数组与从dog_data XML 文件创建的数组格式相似。它需要两个foreach循环,一个循环遍历“用户”数组,另一个循环遍历“用户”数组。然后可以使用in_array方法来确定用户 ID 是否存在于用户数组中。如果是,使用 PHP 方法 password_verify 将密码与散列密码进行比较。此方法使用哈希密码的第一部分来检索关于加密技术和 salt 值的信息。salt 值是自动生成的值,用于生成哈希密码。如果密码匹配,用户 ID 和散列密码($hash)将保存为会话变量。然后调用主程序(参见示例 7-1 )。
Note
在 PHP 5.5 中,你可以调整盐值。在 PHP 7 中,这个选项被贬低了,因为它被认为是不必要的系统资源使用。
Example 7-2. The login.phpfile with XML user ID/password verification
<?php // same code as constructor from chapter``6
session_start();
try {
$user_log_file = "user.log";
if ((isset($_POST['username'])) || (isset($_POST['password'])))
{
libxml:use_internal_errors(true);
$xmlDoc = new DOMDocument();
if ( file_exists("e7dog_applications.xml") )
{
$xmlDoc->load( 'e7dog_applications.xml' );
$searchNode = $xmlDoc->getElementsByTagName( "type" );
foreach( $searchNode as $searchNode )
{
$valueID = $searchNode->getAttribute('ID');
if($valueID == "UIDPASS") // changed value to UIDPASS
{
$xmlLocation = $searchNode->getElementsByTagName( "location" );
// change $this->dog_data_xml to dog_data_xml
$dog_data_xml = $xmlLocation->item(0)->nodeValue;
break;
}
}
}
else
{
throw new Exception("Dog applications xml file missing or corrupt");
}
$xmlfile = file_get_contents($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);
// changed array name to $valid_useridpasswords
$valid_useridpasswords = json_decode($json,TRUE);
// ...... code to verify userid and password ....
$userid = $_POST['username'];
$password = $_POST['password'];
foreach($valid_useridpasswords as $users) {
foreach($users as $user) {
$hash = $user['password'];
if((in_array($userid, $user)) && (password_verify($password,$hash))) {
$_SESSION['username'] = $userid;
$_SESSION['password'] = $password;
$login_string = date('mdYhis') . " | Login | " . $userid . "\n";
error_log($login_string,3,$user_log_file);
header("Location: e7lab.php");
} } } }
}
catch(Exception $e)
{
echo $e->getMessage();
}
// code below executes if the user has not logged in or if it is an invalid login.
?>
<form method="post" action="">
Userid must contain eight or more characters.<br/>
Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />
Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />
Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."
name="password" id="password" required /><br />
<input type="submit" value="Login">
</form>
除了上面提到的代码,示例 7-2 还包括一个try catch块来捕捉抛出的异常,以及一个对用户日志文件的调用来记录成功登录到系统的情况。
JSON 数据
为了使用 JSON 数据而不是 XML 数据,示例 7-2 只需要包括第六章中所示的变更。userid和password JSON 数据也需要格式化,如图所示。
{"user":
[
{"userid":"Fredfred","password":"$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK"},
{"userid":"Petepete","password":"$2y$10$FdbXxIVXmVOHtaBNxB8vzupRBJFCqUyOTJXrlpNdrL0HKQ\/U.jFHO"}
] }
mysql 日期
将用户 ID 和密码信息存储在数据库中更常见,通常也更安全。可以使用用户 id 和密码来保护数据库,用户 id 和密码还可以包括对信息的访问级别(只读、读取和写入)。即使有了这个安全级别,密码仍然应该被加密。
第六章中的 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 Users"; // Change the table used
$result=mysqli_query($con,$sql);
If($result===null)
{
throw new Exception("No records retrieved from Database");
}
$valid_useridpasswords = mysqli_fetch_assoc($result); // change the array used
mysqli_free_result($result);
mysqli_close($con);
这个示例代码将从数据库(Users)的一个表中提取信息,并将信息放在一个关联数组中(通过mysqli_fetch_assoc方法)。如果表中的字段与 XML 文件中的标记名相同(userid和password,那么构建的关联数组将类似于使用示例 7-2 中的代码构建的数组。所有先前的代码"// ...... code to verify userid and password ...."被此处显示的代码所取代。语句下面的代码应该不需要调整。
做它
Create a conversion program to determine the encrypted version of a password using the PHP method password_hash (see http://php.net/manual/en/function.password-hash.php ). Download the example files from this section. Add XML records to the uidpass file that could be used for access permissions (read only or read/write) and levels (user or administrator). Test to verify that the new uidpass file works correctly with the existing code. Make any necessary code changes to make it compatible. Download the example files from this section. Add code to the program to limit attempts to log in with a bad password to three. Record any invalid attempt to log in (after three tries) in the user log.
登记
除了授权用户登录之外,大多数系统还允许用户创建自己的用户 id 和密码。默认情况下,自建 id 的优先级最低。一旦创建了 ID,管理员就可以进入并增加权限级别。有些网站允许非注册用户(未登录的用户)。
非注册用户只应被授予对非特权信息的只读访问权限。非注册用户的安全风险更大,因为在安全漏洞期间很难确定他们的身份。PHP 提供了使用$_SERVER['REMOTE_ADDR']检索用户 IP 地址的能力。这可能会提供一些跟踪用户的能力。然而,许多用户使用免费的公共接入点,这会产生随机的 IP 地址。如果使用了这些点中的一个,追踪它们将会困难得多。
此外,为用户提供创建用户 id 和密码的机会允许程序收集有助于安全的附加信息(如姓名和电子邮件),并且还提供了向网站用户宣传网站的简单能力。对于已经熟悉网站的客户来说,销售网站上提供的产品的成功率会高得多。为了鼓励用户创建用户 id 和密码,网站应该为他们提供一些访问者无法获得的好处(比如访问帮助台)。
注册页面在许多方面与登录页面类似。但是,任何输入的有效用户 id 或密码都将被存储(而不是比较)。因为应用将更新有效 id 的列表,所以应用必须在更新之前验证信息。在验证dog类属性时,您可以使用以前使用过的一些技术。首先,当用户输入用户 ID 和密码(必须通过前面显示的 HTML5 验证)时,它被传递给一个 PHP 程序。即使在会话中被认为是安全的,信息也会从 HTML 表单传到 PHP 代码中。应该再次验证该信息。
if ((isset($_POST['username'])) || (isset($_POST['password'])))
{
$userid = $_POST['username'];
$password = $_POST['password'];
if (!(preg_match("/^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/", $password)) || (!(strlen($userid) >= 8)))
{
throw new Exception("Invalid Userid and/or Password Format");
}
else
这个if语句使用 PHP 函数preg_match来确定密码的格式是否包含一个大写字母、一个小写字母、一个数字和至少八个字符。请注意,正则表达式的格式与 HTML5 代码中使用的格式相同(表达式的顺序发生了变化,但它仍然包含相同的信息)。PHP strlen方法还检查用户 ID 以确定它有八个或更多的字符。
如果其中一个验证没有通过,程序就会引发一个Exception。如果两者都通过,则执行语句的else部分来加密密码并存储信息。
$password = password_hash($password, PASSWORD_DEFAULT);
$input = file_get_contents($dog_data_xml);
$find = "</users>";
$newupstring = "<user>\n<userid>" . $userid . "</userid>\n<password>" . $password;
$newupstring .= "</password>\n</user>\n</users>";
$find = preg_quote($find,'/');
$output = preg_replace("/^$find(\n|\$)/m","",$input);
$output = $output . $newupstring;
file_put_contents($dog_data_xml,$output);
$login_string = date('mdYhis') . " | New Userid | " . $userid . "\n";
error_log($login_string,3,$user_log_file);
header("Location: e7login.php");
在将密码插入 XML 文件之前,必须对其进行加密(哈希处理)。PHP 方法password_hash会将密码转换成之前显示的格式。
Programming considerations-
password_hashprovides many different options and configurations for advanced developers. For more information, please visithttp://php.net/manual/en/function.password-hash.php.
file_get_contents将 XML 用户 ID/密码文件的内容转储到$input. preg_quote将在$find中的任何特殊字符旁边放置反斜杠(例如/users中包含的反斜杠),以防止 PHP 试图将这些字符解释为正则表达式的一部分。preg_replace将使用正则表达式/^$find(\n|\$)/m搜索末尾有无(\n)的</users>。由于文件中的记录是由换行符决定的,这将确保您在文件中的行尾或者作为文件中的行的一部分找到</users>。当在文件中找到</users>(文件内容在$input)时,替换为""(空字符串)。preg_replace也将尝试在第二个参数中存在的任何字符串中放置反斜杠(""当前所在的位置)。如果$newupstring放在该参数中,加密的密码将被preg_replace修改。这将导致密码无法验证,即使用户输入的密码是正确的。
因此,$newupstring的内容(新用户信息)被附加到$output。然后用$output字符串替换用户 ID/密码 XML 文件的所有内容。一旦成功保存了该信息,用户日志将更新以指示新用户 ID 的创建,并且用户将被重定向到登录屏幕,以使用新用户 ID 和密码登录到应用。
或者,可以使用前面的过程,将文件加载到关联数组中,更新关联数组,然后将关联数组加载回 XML 文件。但是,这会占用更多的代码,而且这是不必要的,因为这个过程在应用中只使用一次。该程序不会多次尝试用同一用户更新用户 ID 和密码文件。
Example 7-3. The registration.php file
<?php
session_start();
$user_log_file = "user.log";
try
{
if ((isset($_POST['username'])) || (isset($_POST['password'])))
{
$userid = $_POST['username'];
$password = $_POST['password'];
if (!(preg_match("/^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/", $password)) || (!(strlen($userid) >= 8)))
{
throw new Exception("Invalid Userid and/or Password Format");
}
else
{
libxml:use_internal_errors(true);
$xmlDoc = new DOMDocument();
if ( file_exists("e7dog_applications.xml") )
{
$xmlDoc->load( 'e7dog_applications.xml' );
$searchNode = $xmlDoc->getElementsByTagName( "type" );
foreach( $searchNode as $searchNode )
{
$valueID = $searchNode->getAttribute('ID');
if($valueID == "UIDPASS")
{
$xmlLocation = $searchNode->getElementsByTagName( "location" );
$dog_data_xml = $xmlLocation->item(0)->nodeValue;
break;
}
}
}
else
{
throw new Exception("Dog applications xml file missing or corrupt");
}
} else {
throw new Exception("Dog applications xml file missing or corrupt");
}
$password = password_hash($password, PASSWORD_DEFAULT);
$input = file_get_contents($dog_data_xml);
$find = "</users>";
$newupstring = "<user>\n<userid>" . $userid . "</userid>\n<password>" . $password . "</password>\n</user>\n</users>";
$find_q = preg_quote($find,'/');
$output = preg_replace("/^$find_q(\n|\$)/m",$newupstring,$input);
file_put_contents($dog_data_xml,$output);
$login_string = date('mdYhis') . " | New Userid | " . $userid . "\n";
error_log($login_string,3,$user_log_file);
header("Location: e7login.php");
} } }
catch(Exception $e) { echo $e->getMessage(); }
?>
<form method="post" action="">
Userid must contain eight or more characters.<br/>
Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />
Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />
Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."
name="password" id="password" required /><br />
<input type="submit" value="submit">
</form>
JSON 数据
JSON 数据只需要几处细微的改动。
{"user":
[
{"userid":"Fredfred","password":"$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK"},
{"userid":"Petepete","password":"$2y$10$FdbXxIVXmVOHtaBNxB8vzupRBJFCqUyOTJXrlpNdrL0HKQ\/U.jFHO"}
] }
数据以]}的组合结束,这种组合不会在其他地方出现。$find可以设置为这个value ($set = "]}";)。$newupstring值也可以更改为:
$newupstring = ',{"userid":"' . $userid . '","password":"' . $password . '"}\n]}';
这两个更改(以及前面的更改)将使用新的用户 ID/密码组合更新 JSON 文件。
mysql 日期
MySQL 还需要一些改变。使用password_hash加密密码后,可以打开数据库,插入记录,然后关闭数据库。
$password = password_hash($password, PASSWORD_DEFAULT);
$mysqli =mysqli_connect($server, $db_username, $db_password, $database);
if (mysqli_connect_errno())
{
throw new Exception("MySQL connection error: " . mysqli_connect_error());
}
$sql="INSERT INTO Users (userid, password) VALUES('" . $userid . "','" . $password . "');";
$result=mysqli_query($con,$sql);
If($result===null)
{
throw new Exception("Userid/Password not added to Database");
}
mysqli_close($con);
$login_string = date('mdYhis') . " | New Userid | " . $userid . "\n";
error_log($login_string,3,$user_log_file);
header("Location: e7login.php");
登录
除了为用户提供创建自己的用户 id 和密码的能力之外,应用还应该为他们提供更改密码的能力。提供密码过期日期限制的能力非常有利于提高安全性。每当用户登录时,可以对这个值进行比较。如果当前日期比保存日期早了超过xx天,用户将被要求更改密码。
由于密码嗅探器程序会尝试猜测密码,因此限制使用正确的用户 ID 和密码组合登录的尝试次数也是一个好主意。这将减少密码嗅探程序生成正确组合的机会。在达到最大尝试次数后的一段时间内不允许有效登录,这一点很重要。尽管这让用户感到沮丧,但它减少了密码嗅探程序发现正确组合的机会。如果程序不知道尝试已经超时,它将收到无效的用户 ID/密码消息,即使它在超时期间猜出了正确的组合。这些调整将需要用户 ID/密码文件(或数据库)的附加字段。
<users>
<user>
<userid>Fredfred</userid>
<password>$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK</password>
<datestamp>2015-09-03</datestamp>
<attempts>0</attempts>
<lastattempt>08052015044229</lastattempt>
<validattempt>08052015045431</validattempt>
</user>
<user>
<userid>Poppoppop</userid>
<password>$2y$10$C1jXhTl0myamuLKhZxK5m.4X4TVcdeFbeLSBIA7l4fx6tUnC8vrg6</password>
<datestamp>2015-06-04</datestamp>
<attempts>1</attempts>
<lastattempt>08062015113200</lastattempt>
<validattempt>08062015113038</validattempt>
</user>
</users>
可以在registration.php程序中调整$newupstring(例如 7-3 )来添加认证字段。
$newupstring = "<user>\n<userid>" . $userid . "</userid>\n<password>" . $hashed_password . "</password>\n";
$newupstring .= "<datestamp>" . date('Y-m-d', strtotime('+30 days')) . "</datestamp>\n";
$newupstring .= "<attempts>0</attempts>\n<lastattempt>" . date('mdYhis') . "</lastattempt>\n";
$newupstring .= "<validattempt>" . date('mdYhis') . "</validattempt>\n</user>\n</users>";
PHP 方法strtotime将解析任何标准的日期和时间格式,并尝试将其转换为 UNIX 日期和时间格式(PHP 使用的格式)。在此示例中,该方法提供了向当前日期添加 30 天的能力,这反过来将用于确定密码是否已过期。或者,当批量创建用户 id 时(例如在课程管理系统中填充学生 id),可以在该字段中放置过期日期(例如当前日期的前一天)。这将迫使用户在首次登录系统时更改密码。过期日期存储在datestamp中,供用户登录系统时使用。
在示例中,attempts标记将记录用户尝试使用错误的用户 ID 密码组合登录的次数(当有效登录发生时重置为零)。如果lastattempt中的日期和时间在五分钟内,并且尝试次数的值为3或更大,用户必须等待,直到自上次尝试登录以来超过五分钟。与大多数登录系统一样,即使用户使用有效信息登录,最后一次无效登录也必须是在五分钟或更久之前。最后有效的登录日期和时间也记录在validattempt标签中。尽管在本例中这并不用于身份验证,但跟踪所有有效的登录是很重要的。
为了使程序主体部分的代码尽可能简单,查找用户 ID 和密码文件位置的代码被移到了方法retrieve_useridpasswordfile中。将数据保存在 XML 文件中也被移到了方法saveupfile中。这些方法的代码没有发生任何变化(除了前面提到的添加了更多的 XML 标记)。
Example 7-4. The login.php file with password timeout and three tries timeout
<?php
session_start();
$user_log_file = "user.log";
$passed = FALSE;
function saveupfile($dog_data_xml,$valid_useridpasswords)
{
$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';
$xmlstring .= "\n<users>\n";
foreach($valid_useridpasswords as $users)
{
foreach($users as $user)
{
$xmlstring .="<user>\n<userid>" . $user['userid'] . "</userid>\n";
$xmlstring .="<password>" . $user['password'] . "</password>\n";
$xmlstring .="<datestamp>" . $user['datestamp'] . "</datestamp>\n";
$xmlstring .= "<attempts>" . $user['attempts'] . "</attempts>\n";
$xmlstring .= "<lastattempt>" . $user['lastattempt'] . "</lastattempt>\n";
$xmlstring .= "<validattempt>" . $user['validattempt'] . "</validattempt>\n</user>\n";
}
}
$xmlstring .= "</users>\n";
$xmlstring .= "</users>\n";
$new_valid_data_file = preg_replace('/[0-9]+/', '', $dog_data_xml);
// remove the previous date and time if it exists
$oldxmldata = date('mdYhis') . $new_valid_data_file;
if (!rename($dog_data_xml, $oldxmldata))
{
throw new Exception("Backup file $oldxmldata could not be created.");
}
file_put_contents($new_valid_data_file,$xmlstring);
}
function retrieve_useridpasswordfile()
{
$xmlDoc = new DOMDocument();
if ( file_exists("e7dog_applications.xml") )
{
$xmlDoc->load( 'e7dog_applications.xml' );
$searchNode = $xmlDoc->getElementsByTagName( "type" );
foreach( $searchNode as $searchNode )
{
$valueID = $searchNode->getAttribute('ID');
if($valueID == "UIDPASS")
{
$xmlLocation = $searchNode->getElementsByTagName( "location" );
$dog_data_xml = $xmlLocation->item(0)->nodeValue;
break;
}
}
}
else
{
throw new Exception("Dog applications xml file missing or corrupt");
}
return $dog_data_xml;
}
try {
if ((isset($_POST['username'])) && (isset($_POST['password'])))
{
libxml:use_internal_errors(true);
$dog_data_xml = retrieve_useridpasswordfile();
$xmlfile = file_get_contents($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);
$valid_useridpasswords = json_decode($json,TRUE);
$userid = $_POST['username'];
$password = $_POST['password'];
$I = 0;
$passed = FALSE;
foreach($valid_useridpasswords as $users)
{
foreach($users as $user)
{
if (in_array($userid, $user)) {
$hash = $user['password'];
$currenttime = strtotime(date('Y-m-d'));
$stamptime = strtotime($user['datestamp']);
if ($currenttime > $stamptime) {
// password expired force password change
header("Location: e7changepassword.php");
}
if (($user['attempts'] < 3) || ( date('mdYhis', strtotime('-5 minutes')) >= $user['lastattempt']))
{
$hash = $user['password'];
if(password_verify($password,$hash))
{
$passed = TRUE;
$valid_useridpasswords['user'][$I]['validattempt'] = date('mdYhis');
// shows last time successful login
$valid_useridpasswords['user'][$I]['attempts'] = 0;
// successful login resets to zero
$_SESSION['username'] = $userid;
$_SESSION['password'] = $password;
saveupfile($dog_data_xml,$valid_useridpasswords);
// save changes before header call
$login_string = date('mdYhis') . " | Login | " . $userid . "\n";
error_log($login_string,3,$user_log_file);
header("Location: e7lab.php");
}
else {
$valid_useridpasswords['user'][$I]['lastattempt'] = date('mdYhis');
// last attempted login
} } }
$I++;
} }
// drops to here if not valid password/userid or too many attempts
if (!$passed) {
$I--;
echo "Invalid Userid/Password";
$valid_useridpasswords['user'][$I]['attempts'] = $user['attempts'] + 1;
// add 1 to attempts
// if not successful must save the values
saveupfile($dog_data_xml,$valid_useridpasswords);
} } }
catch(Exception $e)
{ echo $e->getMessage(); }
?>
form method="post" action="">
Userid must contain eight or more characters.<br/>
Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />
Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />
Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."
name="password" id="password" required /><br />
<input type="submit" value="Login">
</form>
在示例 7-4 中,if语句使用datestamp中保存的日期/时间检查当前日期/时间。如果当前日期/时间超出datestamp中的值超过 30 天,则调用密码更改(changepassword)程序。下一个if语句确定用户的无效登录尝试是否少于三次,并且距离上次尝试已经超过五分钟。如果是这种情况,PHP 方法password_verify会将用户输入的密码与 XML 文件中包含的密码进行比较。如果密码匹配,validattempts值将更新为日期/时间,尝试次数将重置为 0,用户 ID 和密码将保存在会话变量中。然后保存对 XML 文件的更改。此外,用户登录记录在日志文件中。如果密码不匹配,则用当前日期/时间更新lastattempt值。
由于有效的登录或过期的密码将导致应用使用 PHP header 方法重定向到不同的程序,如果用户 id 和密码组合无效,或者如果在五分钟内尝试了太多次,程序将只执行最后一个if语句。出现这种情况时,显示"invalid /password"消息,尝试次数增加 1。然后保存对 XML 文件的更改。该程序将继续显示用户 ID 和密码框,并显示"invalid userid/password"消息。如上所述,即使在五分钟内输入了有效的用户 ID/密码组合,或者输入了三个或更多的无效条目(按顺序),也会被拒绝。
JSON 数据
JSON 数据需要一些修改来容纳额外的字段。
{"user":[
{"userid":"Fredfred","password":"$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK","datestamp":"2015-09-03","attempts":"0","lastattempt":"08052015044229","validattempt":"08052015045431"},
{"userid":"Poppoppop","password":"$2y$10$C1jXhTl0myamuLKhZxK5m.4X4TVcdeFbeLSBIA7l4fx6tUnC8vrg6","datestamp":"2015-09-04","attempts":"2","lastattempt":"08062015011347","validattempt":"08062015113038"}
]}
由于这些新字段的出现,$newupstring也需要改变。如前所述,一些代码的位置也会移动到方法上。
$newupstring = ',{"userid":"' . $user['userid'] . '","password":"' . $user['password'] . '","';
$newupstring .= 'datestamp":"' . $user['datestamp'] . '","attempts":"' . $user['attempts'] . "',"';
$newupstring .= 'lastattempt":"' . $user['lastattempt'] . '","validattempt":"' . $user['validattempt'] .'"';
$newupstring .= '"}\n]}';
mysql 日期
MySQL 登录代码要求使用UPDATE语句(而不是INSERT语句,如注册代码所示)来更新任何已更改的字段。此外,第四章中的方法应该用来从userid字段中删除任何有害的 PHP 和 SQL 语句。这将有助于减少 SQL 注入发生的可能性,它可能导致 SQL 语句更改的不仅仅是必需的字段和记录。例如,如果$user['userid']字段包含'*',那么所有记录都将被更新,而不仅仅是一个具有有效用户 id 的记录。
在执行UPDATE语句之前,用户 ID 也可以被验证为存在于数据库中。这(假设数据库中的所有数据都是有效的)也会减少有害更改的机会。
在下面的示例中,所有可能的文件都被一次更新。或者,只有那些改变的字段可以在需要时被更新。然而,这将需要更多的代码,并不一定更有效。此外,如果事先没有验证用户 ID 是否存在于数据库中,如果userid不在数据库中,SQL 语句将自动不更新字段。
$userid = clean_input($user['userid']);
$sql ="UPDATE Users SET(datestamp='" . $user['datestamp'] . "',attempts='";
$sql .=$user['attempts'] . "',lastattempt='" . $user['lastattempt'] . "',";
$sql .="validattempt='" . $user['validattempt'] . "') WHERE userid='" . $userid . "';";
注意,UPDATE代码不包括WHERE语句中的密码字段。在这个例子中,当密码无效时,一些字段将被更新,而当密码有效时,一些字段将被更新。如果 SQL 语句被分解成多个语句(至少一个用于有效用户 id/密码,一个用于无效用户/密码),那么用于有效信息的WHERE语句可以包括用户 ID 和密码,而用于无效信息的语句可以只包括密码。本书网站第七章的中包含了一个使用 MySQL 数据库的完整登录、注册和密码更改应用的例子。
修改口令
更改密码的过程需要验证当前密码,然后保存新密码。它还需要从 XML 文件中更新包含在datestamp标签中的日期。程序代码与登录程序非常相似。
$userid = $_POST['username'];
$npassword = $_POST['password'];
$newpassword = password_hash($npassword, PASSWORD_DEFAULT);
$password = $_POST['oldpassword'];
$datestamp = date('Y-m-d', strtotime('+30 days'));
$I = 0;
$passed = FALSE;
// First a few properties are set for the userid, new userid, and the new datestamp.
$hash = $user['password'];
if(password_verify($password,$hash))
{
$passed = TRUE;
$valid_useridpasswords['user'][$I]['password'] = $newpassword;
$valid_useridpasswords['user'][$I]['datestamp'] = $datestamp;
$valid_useridpasswords['user'][$I]['attempts'] = 0;
saveupfile($dog_data_xml,$valid_useridpasswords); // save changes before header call
$login_string = date('mdYhis') . " | Password Changed | " . $userid . "\n";
error_log($login_string,3,$user_log_file);
header("Location: e7logina.php");
}
else
{
$valid_useridpasswords['user'][$I]['lastattempt'] = date('mdYhis'); // last attempted login
}
如果用户 ID 和旧密码被正确验证,那么新的password、datestamp和attempts属性在 XML 文件中被更改和更新。日志文件中也会有一个条目。如果密码未通过验证,将显示一条"invalid userid/password"消息。
Example 7-5. The changepassword.php file
<?php
session_start();
$user_log_file = "user.log";
function saveupfile($dog_data_xml,$valid_useridpasswords)
{
$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';
$xmlstring .= "\n<users>\n";
foreach($valid_useridpasswords as $users)
{
foreach($users as $user)
{
$xmlstring .="<user>\n<userid>" . $user['userid'] . "</userid>\n";
$xmlstring .="<password>" . $user['password'] . "</password>\n";
$xmlstring .="<datestamp>" . $user['datestamp'] . "</datestamp>\n";
$xmlstring .= "<attempts>" . $user['attempts'] . "</attempts>\n";
$xmlstring .= "<lastattempt>" . $user['lastattempt'] . "</lastattempt>\n";
$xmlstring .= "<validattempt>" . $user['validattempt'] . "</validattempt>\n</user>\n";
}
}
$xmlstring .= "</users>\n";
new_valid_data_file = preg_replace('/[0-9]+/', '', $dog_data_xml);
// remove the previous date and time if it exists
$oldxmldata = date('mdYhis') . $new_valid_data_file;
if (!rename($dog_data_xml, $oldxmldata)) {
throw new Exception("Backup file $oldxmldata could not be created."); }
file_put_contents($new_valid_data_file,$xmlstring); }
function retrieve_useridpasswordfile() {
$xmlDoc = new DOMDocument();
if ( file_exists("e7dog_applications.xml") )
{
$xmlDoc->load( 'e7dog_applications.xml' );
$searchNode = $xmlDoc->getElementsByTagName( "type" );
foreach( $searchNode as $searchNode )
{
$valueID = $searchNode->getAttribute('ID');
if($valueID == "UIDPASS")
{
$xmlLocation = $searchNode->getElementsByTagName( "location" );
$dog_data_xml = $xmlLocation->item(0)->nodeValue;
break;
}
}
}
else {
throw new Exception("Dog applications xml file missing or corrupt");
}
return $dog_data_xml;
}
if (!(isset($_SESSION['message']))) {
// valid userid and password but password expired
echo $_SESSION['message'];
}
try {
if((isset($_POST['username'])) && (isset($_POST['oldpassword'])) && (isset($_POST['password'])) && (isset($_POST['password_confirm'])))
{
libxml:use_internal_errors(true);
$dog_data_xml = retrieve_useridpasswordfile();
$xmlfile = file_get_contents($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);
$valid_useridpasswords = json_decode($json,TRUE);
$userid = $_POST['username'];
$npassword = $_POST['password'];
$newpassword = password_hash($npassword, PASSWORD_DEFAULT);
$password = $_POST['oldpassword'];
$datestamp = date('Y-m-d', strtotime('+30 days'));
$I = 0;
$I = 0;
$passed = FALSE;
foreach($valid_useridpasswords as $users) {
foreach($users as $user) {
if (in_array($userid, $user)) {
$hash = $user['password'];
if(password_verify($password,$hash))
{
$passed = TRUE;
$valid_useridpasswords['user'][$I]['password'] = $newpassword;
$valid_useridpasswords['user'][$I]['datestamp'] = $datestamp;
$valid_useridpasswords['user'][$I]['attempts'] = 0;
saveupfile($dog_data_xml,$valid_useridpasswords);
// save changes before header call
$login_string = date('mdYhis') . " | Password Changed | " . $userid . "\n";
error_log($login_string,3,$user_log_file);
header("Location: e7login.php");
} }
$I++;
} }
// drops to here if not valid password/userid or too many attempts
if (!$passed){
echo "Invalid Userid/Password";
} } }
catch(Exception $e) {
echo $e->getMessage();
}
?>
<form method="post" action="">
Userid must contain eight or more characters.<br/>
Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />
Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />
Old Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters." name="oldpassword" id="oldpassword" required /><br />
New Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters." name="password" id="password" required /><br />
Confirm Password:<input name="password_confirm" required="required" type="password" id="password_confirm" oninput="check(this)" />
<script language='javascript' type='text/javascript'>
function check(input) {
if (input.value != document.getElementById('password').value) {
input.setCustomValidity('Password Must be Matching.');
} else {
// input is valid -- reset the error message
input.setCustomValidity('');
}
}
</script>
<input type="submit" value="submit">
</form>
除了已经解释过的 PHP 之外,示例 7-5 还包括 JavaScript 代码,用于验证新密码的正确格式以及新密码的正确输入两次。示例中没有包含对 PHP 代码中正确的用户 ID 和密码条目的验证。因为信息通过互联网从表单传输到 PHP 程序,所以用户 ID 和密码也应该在 PHP 程序中进行验证。该编码作为本节的练习。
JSON 数据
本章前面的 JSON 部分指出了修改密码程序处理 JSON 数据所需的唯一修改。
mysql 日期
UPDATE SQL 语句需要“设置”password 属性,其结构与前面 MySQL 部分中更改的其他字段的结构相同。使用 MySQL 的完整认证程序包含在本书网站的第七章中。
做它
Copy the example files for this section from the book’s web site. Adjust the change password program to include PHP code that verifies the correct format of the user ID, password, and new password. Copy the example files for this chapter from the book’s web site. Adjust the registration program to check for a duplicate user ID before attempting to insert a new user ID/password combination. Hint: This can be done using the in_array method. If the user ID exists, display a message back to the users. Copy the example files for this section from the book’s web site. Adjust the programs necessary to require the users to also enter their name, phone, and e-mail when registering. Also make sure to change the other programs to keep this information valid.
章节术语
| 证明 | 会议 | | 会话 _ 开始 | 没错 | | 会话属性 | _ 服务器['远程 _ADDR'] | 注册页面 | | 怀孕 _ 匹配 | 字符长度(stringlength) | | 文件获取内容 | 怀孕 _ 报价 | | 怀孕 _ 替换 | 密码过期 | | 密码嗅探程序 | 有限的尝试次数 | | 超时时间 | strtotime | | SQL 注入 | |
第二章问题和项目
多重选择
Authentication does which of the following? Provides security for an application. Verifies user IDs and passwords. Should use encrypted passwords. All of the above. Sessions do which of the following? Are created using the session_create method Allow the sharing of information between programs Don’t provide any security benefits All of the above Registration pages do which of the following? Allow the users to create their own user IDs and passwords Can be used to gather information about the users Should encrypt the password before storing it All of the above Verification code does which of the following? Uses session_start to attach the program to a current session Verifies that a user has logged in Must be attached only to programs in the interface tier All of the above Which of these describes SQL injection? The process of using an SQL statement to update a database The process of inserting variables in a SQL statement for flexibility Causes data to be corrupted All of these
真/假
MD5 is the most up-to-date and secure encryption technique. password_hash should be used to create an encrypted password. Users should be notified that they have exceeded the maximum number of attempts to enter a correct user ID and password. An authentication system does not need to timeout passwords and force its users to change their passwords when they time out. preg_replace can be used with an encrypted password to ensure that PHP does not interpret special characters as PHP commands.
简答/短文
Explain the techniques that can be used to reduce the chances that a password sniffing program can discover the correct user ID and password combination. Explain how sessions work. Include an explanation on how they can help secure an application with user ID and password authentication. What is SQL injection? How can it be avoided? Why should passwords be encrypted? Explore the Internet and discover the latest versions of encryption. Does the most current version of PHP use the newest version of encryption?
项目
Download log maintenance files from Chapter 6 or use your own maintenance files that you created from Chapter 6. Use the techniques shown in this chapter to secure these files with user ID and password authentication. Download the files from this chapter. Update the files and programs so users can request their passwords. A temporary password (new field in the XML file) must randomly be created (use rand) and e-mailed to the users. The password should have a quick expiration (one day or less). The user must be able to verify other information entered via the registration page (security question, or other personal info) to request the password. If the user signs in correctly with the temporary password, the system should make the user change the password.
学期项目
Update the ABC Computer Parts Inventory application to include user ID and password authentication as shown in this chapter. Be sure to secure any log maintenance programs related to the application.
八、多功能接口
Electronic supplementary material The online version of this chapter (doi:10.1007/978-1-4842-1730-6_8) contains supplementary material, which is available to authorized users.
努力工作不会导致死亡,但为什么要冒险呢—埃德加·伯根( http://coolfunnyquotes.com )
章节目标/学生学习成果
完成本章后,学生将能够:
- 创建一个完整的 PHP 应用来删除、更新和插入数据
- 使用 CSS 创建完整应用的专业外观
- 使用 JavaScript 接受和操作来自另一个程序的数据
- 保护需要用户 id/密码的应用中的所有程序
- 用 JSON 对象的值填充 HTML 对象
- 创建一个使用当前密码加密技术的 PHP 程序
完整的应用
在本章中,您将完成 ABC 犬类收容所预订系统的开发。该系统的当前版本允许用户仅插入一只狗,然后要求他们再次登录以插入额外的狗。此外,该系统不允许用户更新或删除系统中存在的狗信息。在第六章(数据对象)中,你已经完成了提供更新和删除功能所需的大部分 PHP 代码。您现在需要将数据对象的这一部分(dog_data)附加到业务规则层(dog)。此外,您需要对接口层(dog_interface和lab)进行一些更改,以调用update和delete方法并显示结果。所需的大部分编码(您很快就会看到)将发生在lab.php文件中。所以你将从这些改变开始。
使用 JavaScript 的数据处理
当前的lab.php文件只允许用户输入将被插入数据存储(XML、JSON 或 MySQL)的狗的信息。该界面需要修改,以允许用户指出他们想完成什么活动(Insert、Update或Delete)。它还需要允许用户选择一个当前的狗,如果这个过程涉及更新或删除。这应该,希望,表明你将需要一个列表框,其中填充了当前位于收容所的狗的信息。
在本章的后面几节中,你将会看到 PHP 的必要修改。现在,让我们假设dog_interface(接口层)将返回该信息,该信息是通过dog方法(业务规则层)从dog_data(数据层)检索的。你还将假设类似的编码dog_breeds程序(第四章)将被创建来产生一个狗列表框。
此外,lab.php需要访问被选中的特定狗的所有信息。代码需要将所有信息放在dog_name和dog_weight文本框、dog_breeds列表框和dog_color单选按钮中。你可以用两种方式完成这项任务。一种是允许用户从列表框中选择狗,然后调用dog_interface程序从狗类中请求特定的狗,这又会从dog_data类中请求信息。然而,这需要通过互联网进行额外的、不必要的呼叫来请求信息。相反,您可以在用户第一次调用lab.php接口时收集所有必要的信息(dog_breed列表框信息、dogs列表框信息以及当前数据存储中所有狗的完整信息)。您可以使用当前的 JavaScript AJAX 代码(第四章)进行一些修改来检索所有必要的信息。
在填充表单对象(文本框、列表框和单选按钮)之前,您必须确保用户指明请求的操作类型(至少是插入或更改/删除)。您可以通过在做出选择之前不显示表单对象来实现这一点。您可以对第四章中使用的 JavaScript 代码和 CSS 代码的组合稍作调整,要求用户在显示表单之前从狗列表框中进行选择。
Note
您将要看到的过程是 web 应用中非常常见的实践。许多 web 应用以数组、JSON 或 XML 格式返回数据。然后,接口(在本例中为lab.php)可以使用 JavaScript 来检索所需的信息。
编程注意事项——购物车使用类似的技术。当顾客选择要购买的商品时,这些商品被放在客户机上的一个数据对象(可能是一个 JSON 对象)中。当顾客开始结账时,数据被传输到服务器。这允许客户进行不会导致对服务器的额外调用的更改。由于购买信息不被视为安全风险,这通常是一个安全的过程。当然,这不是处理信用卡信息的好方法。
使用图 8-1 所示的格式,用户被迫在继续之前从列表框中选择NEW或一只狗。一旦用户做出选择,HTML 表单可以显示默认值(对于插入)或所选狗的当前值。CSS 代码最初会将按钮(和表单)显示为"none"。JavaScript 代码(您很快就会看到)会将正确按钮和表单的显示更改为"inline",以便在适当的时候显示。
图 8-1。
The lab.php file with dogs list box
图 8-2 展示了用户选择新建时的结果。如前几章所示,提供了相同的默认值。唯一的视觉变化是输入按钮的文本。当用户没有启用 JavaScript 时,这种方法就会出现弱点。对于本例,如果没有启用 JavaScript,您将需要输入所有信息。您可以从dog_data类请求单只狗的信息(通过Dog类)。虽然这种方法不如您将要使用的 JavaScript 方法高效,但它比要求输入所有信息更方便用户。
图 8-2。
The lab.php file with NEW selected
图 8-3 显示了选择现有狗的结果。列表框显示狗的名字和品种(使其尽可能独特)。此外,还可以提供狗 ID 来识别特定的狗。当狗被选中时,狗的信息(dog_name、dog_color、dog_weight和dog_breed)被填充到表格中。然后,用户可以更新或删除狗的信息。
图 8-3。
The lab.php file with a dog selected Note
如前几章所述,本例中提供的代码不限制重复条目。因此,在真实环境中,需要使用一个附加字段(狗 ID)来使狗具有唯一性。
以前,当发生insert时,系统会显示一条消息,表明更改成功。如果需要另一个更改,用户需要重新加载lab.php文件。您可以通过让dog_interface程序用要返回的消息设置一个会话属性来解决这个问题。然后,dog_interface可以调用lab.php文件,后者可以检查是否有消息要显示。
图 8-4。
The lab.php file handling message from dog_interface
您将使用 PHP 代码、JavaScript 代码和 CSS 代码的组合来创建想要的结果。我们来分解一下。
<?php
session_start();
if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password']))) {
echo "You must login to access the ABC Canine Shelter Reservation System";
echo "<p>";
echo "<a href='e8login.php'>Login</a> | <a href='e8register.php'>Create an account</a>";
echo "</p>";
}
else if(($_SERVER['HTTP_REFERER'] == 'http://127.0.0.1:8080/mysite/bgchapter8/ExampleFile8/e8login.php') || ($_SERVER['HTTP_REFERER'] == 'http://127.0.0.1:8080/mysite/bgchapter8/ExampleFile8/e8lab.php
{
if (isset($_SESSION['message'])) {
echo $_SESSION['message'];
}
else {
echo "<p>Welcome back, " . $_SESSION['username'] . "</p>";
}
?>
实验程序顶部的 PHP 代码现在将包括一个额外的检查,以确定是否有消息被返回。虽然语句的if部分不需要任何修改,但是else部分有一些额外的修改。注意,新的if语句检查实验室程序是否被自己调用。当dog_interface使用header方法调用lab.php时,$_SERVER['HTTP_REFERER']方法返回lab.php作为调用程序。此外,同一个if语句还检查login.php是否调用了lab。现在只有这两个合法的调用可以调用lab.php程序。
else块中的另一个if语句检查是否通过使用isset方法返回了一个$_SESSION['message']。如果已经返回,那么lab.php是被dog_interface调用的,因为登录不返回一个$_SESSION['message']。实验室程序现在显示消息。如果没有$_SESSION['message'],则显示欢迎消息。
现在让我们跳到lab. php中的 HTML 代码。然后,您将看一看 CSS 和 JavaScript 代码。
<body onload="checkJS();">
<h1>ABC Canine Shelter Reservation System</h1>
<div id="JS">
<script>
AjaxRequest('e8dog_interface.php');
</script>
<h3>Pick the dog name and breed to change from the dropdown box, then click the button.<br>For new dog information select 'NEW'.</h3>
Select 'NEW' or Dog's Name/Breed <div id="AjaxReturnValue"></div>
<input type="button" name="selected" id="selected" value="Click to select" onclick="process_select()" /><br><br>
<div id="input_form">
<form method="post" action="e8dog_interface.php" onSubmit="return validate_input(this)">
<h3>Please note the required format of information.</h3>
<hr>
Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z]*" title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />
Select Your Dog's Color:<br />
<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />
<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />
<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />
<input type="radio" name="dog_color" id="dog_color" value="White">White<br />
<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />
Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />
<input type="hidden" name="dog_app" id="dog_app" value="dog" />
Select Your Dog's Breed <div id="AjaxResponse"></div><br />
<input type="hidden" name="index" id="index" value="-1"/>
<input type="submit" name="insert" id="insert" value="Click to create your dog info" />
<input type="submit" name="delete" id="delete" value="Click to remove your selected dog info" />
<input type="submit" name="update" id="update" value="Click to update your selected dog info" />
<hr>
</form>
</div>
</div>
除了一些小的显示消息变化之外,还创建了一个 ID 为AjaxReturnValue的新的div标签来保存狗列表框(它将为用户提供狗的选择)。随后是一个 HTML 按钮(不是提交按钮)。当点击时,它将导致一个 JavaScript 函数(process_select)执行。添加了另一个包含 HTML 表单的div标签。该表单将被隐藏,直到用户单击该按钮。
CSS 代码(#input_form { display:none; })包含在程序的顶部,以防止表单显示。额外的 CSS 代码也阻止按钮显示。这段代码非常类似于 CSS 代码,如果用户没有在浏览器中激活 JavaScript,它会阻止非 JS 表单的显示。
在表单的底部,创建了一个隐藏属性(index)来保存所选狗的索引(来自狗数组)。初始值设定为-1。当用户选择一只狗时,这个属性将被改变。原来的 Submit 按钮被三个 Submit 按钮取代(一个用于插入,一个用于删除,一个用于更新)。无论点击哪个按钮,都会创建一个属性(insert、delete或update)并设置一个值。属性名是按钮的 ID,属性中的内容是选中按钮的value属性的内容。这将有助于dog_interface确定用户请求哪种类型的更改。这些按钮也包含在不支持 JavaScript 的表单中。请记住,在本例中,不支持 JavaScript 的浏览器将要求用户输入成功完成insert、delete或update所需的所有信息。
希望你刚才看到的变化是可以理解的。您现在将看到一些处理这些数据的 JavaScript 代码。如前所述,JavaScript 对数据的操作是 web 应用中的常见任务。虽然这不是一本 JavaScript 书,但是任何 web 应用开发人员熟悉 JavaScript 都是很重要的。我想你会看到 JavaScript 语言的结构和 PHP 语言的结构很相似。
首先你会看到对get_breeds.js文件的修改(来自第四章)。该文件被重命名为getlists.js,以反映它现在将处理getBreeds和dogs列表框。
function HandleResponse(response)
{
var responsevalues = response.split('|');
document.getElementById('AjaxResponse').innerHTML = responsevalues[0];
document.getElementById('AjaxReturnValue').innerHTML = responsevalues[1];
obj = JSON.parse(responsevalues[2]);
}
所有的代码更改都在 JavaScript 文件的HandleResponse方法中。以前,响应属性中的值(传递给方法)被直接传递给带有AjaxResponse ID 的div标记。此时,只返回品种的列表框代码。现在,该方法将接受三种类型的信息(品种列表框、狗列表框和狗数组)。为了减少对 web 服务器的调用次数,进行了一次 AJAX 调用。它将所有信息返回到response属性中。信息将使用管道符号(|)分隔。很快你就会看到这个字符串的形成会发生在dog_interface程序中。
您将需要以类似于使用 PHP explode方法破坏先前数据的方式来破坏数据。在 JavaScript 中,split方法将使用提供的参数(|)将一个字符串分解成一个数组。在示例中,这将创建数组 responsevalues。var将该数组创建为该方法的本地数组。当方法关闭时(点击}符号),它将被销毁,因为不再需要它了。该数组现在有三行。第一行([0])包含getBreeds列表框代码。第二行([1])包含dogs列表框代码。第三行(我猜您已经猜到了)包含完整的 dogs 数组。
And security-in this application, the dog information is not highly sensitive. In addition, information is displayed to the user. This allows users to view and repair any data that may be corrupted. For more sensitive information, the array should be declared with
varto keep the data accessible only by the current method (function).
下标(0、1 和 2)现在可以用来拖动数组的每个部分,并将其释放到适当的位置。responsevalues[0]放在AjaxResponse中显示getBreeds列表。responsevalues[1]放置在AjaxReturnValue中,显示狗狗列表框。dogs 数组,因为您还不知道用户选择了哪只狗,所以它将被放在一个 JSON 对象中(obj)。如您所见,JSON 数据包括命名索引和值,这与 PHP 中的关联数组非常相似。没有使用var语句,因为这个对象必须是公共的,并且对整个应用可用。
如果你还记得前面的章节,数组不能直接格式化成字符串。数组必须序列化。但是,JSON 数据也可以在字符串中传递。您将很快看到responsevalues[3]中的“数组”已经被dog_interface程序格式化为 JSON 数据。JavaScript 的JSON.parse方法能够查看数据,如果数据有效,就将其转换成 JSON 对象。这与 PHP 方法json_encode非常相似。
现在让我们看看当用户从狗列表框中选择一只狗时,如何填充表单。
JavaScript 方法process_select(在用户从狗列表中选择后由 HTML 按钮调用)被放置在lab.php文件中代码的顶部。它也可以放在自己的 JS 文件中,并以与getlists.js文件相同的方式导入。这个新方法使用包含在 OBJ(包含所有狗的 JSON dogs 对象)中的信息,用用户在列表框中选择的狗的信息填充文本框(dog_name和dog_weight)、单选按钮(dog_color)和列表框(dog_breed)。
function process_select() {
var colorbuttons = document.getElementsByName('dog_color');
首先,所有单选按钮的颜色值将从 HTML 表单中提取出来,并放入一个名为colorbuttons的数组中,使用 JavaScript 方法getElementsByName. dog_color(每个单选按钮的名称)传递到该方法中。该过程将创建一个单选按钮数组,其索引与颜色的单选按钮下标相同。例如,数组的0位置现在将包含brown,这是 HTML 表单中显示的第一个单选按钮。这将允许您通过参考其位置来设置适当的颜色单选按钮(例如colorbuttons[0]来设置brown)。
if(!(document.getElementById('dogs').value == -1))
{
index = document.getElementById('dogs').selectedIndex -1;
document.getElementById('index').value = index;
document.getElementById('dog_name').value = obj.dogs[index].dog_name;
document.getElementById('dog_weight').value = obj.dogs[index].dog_weight;
HTML 列表框包括文本和值。文本是用户看到的;价值就是它所代表的东西。这非常类似于 PHP 关联数组——键(索引)和值。if语句检查 dogs 数组以确定其当前值。如果值为-1,这表明用户没有选择任何内容,或者他们选择了新建。JavaScript !符号与 PHP !符号的工作原理相同。当'dogs'的值不是-1时,该符号改变要执行的语句的if部分。因此,如果用户从狗列表框中选择了一只狗,代码就会执行。
列表框的selectedIndex属性表示用户选择的索引。但是,HTML 列表框从 1 开始编号。JavaScript 数组和 JSON 对象索引从 0 开始。这导致selectedIndex比 JavaScript 数组或 JSON 对象中的位置多 1。该示例中的代码减去 1 以平衡该关系。该值放在索引中。它也以相同的名称(index)保存在隐藏属性中(在 HTML 表单上)。由于index是一个属性而不是一个div标签,JavaScript value属性必须用来设置 index 的值。
obj是 AJAX 调用发生时创建的 JSON 对象(包含所有的狗)。可以用与 PHP 关联数组非常相似的方式从 JSON 对象中检索信息。obj对象类似于前面章节中显示的多维 dogs 数组。最上面的阵列是狗的“阵列”。在狗数组中是每只狗的“数组”。这些没有相关的键名,但有一个数字索引。每个狗数组包含单独的元素(dog_name、dog_color、dog_breed和dog_weight)。在前面的代码行中设置的index属性包含用户选择的狗索引。它将用于从obj中提取选定的狗信息,以填充表单对象。
因为您知道要检索的数据的确切位置(在索引中),所以不需要循环。
obj.dogs[index].dog_name使用 JSON 对象名(obj)、顶层数组名(dogs)、所需 dog 数组的编号(index)和所需字段的名称(dog_name)来访问所需信息。同样,这种格式类似于从 PHP 关联数组中提取信息。dog_name和dog_weight值使用这种点符号格式从obj JSON 对象中提取信息,并将其放入 HTML 表单上适当的文本框中。
dog_color = obj.dogs[index].dog_color;
if(dog_color == "Brown")
{
colorbuttons[0].checked = true;
} else if (dog_color == "Black")
{
colorbuttons[1].checked = true;
} else if (dog_color == "Yellow")
{
colorbuttons[2].checked = true;
} else if (dog_color == "White")
{
colorbuttons[3].checked = true;
}
设置颜色需要更多的工作。从obj对象(dog_color = obj.dogs[index].dog_color;)中取出dog_color并放入属性(dog_color)中。然后使用一个if语句来确定这个属性中存在什么颜色。(是的,JavaScript 有一个case语句,您也可以使用它)。if语句将正确单选按钮的checked属性设置为true,导致单选按钮被选中。注意,默认值('mixed')不包括在if语句中。如果狗是'mixed',或者由于某种原因颜色在对象中没有值,没有理由改变默认值('mixed')。
dog_breed = obj.dogs[index].dog_breed;
document.getElementById('dog_breed').value = dog_breed;
document.getElementById('update').style.display = "inline";
document.getElementById('delete').style.display = "inline";
document.getElementById('insert').style.display = "none";
}
使用与设置文本框相同的代码样式设置breeds列表框。属性设置用户查看的列表框文本。如果需要,用户可以更改该值。"update"和"delete"按钮设置为"inline"显示。"Insert"设置为"none"。
else
{
colorbuttons[4].checked = true;
document.getElementById('dog_name').value = "";
document.getElementById('dog_weight').value = "";
document.getElementById('dog_breed').value = "Select a dog breed";
document.getElementById('insert').style.display = "inline";
document.getElementById('update').style.display = "none";
document.getElementById('delete').style.display = "none";
}
如果索引是-1,则执行if语句的else部分(选择 NEW,或者不选择任何内容)。默认设置如下,如前面其他章节所示,颜色设置为'mixed',名称和重量文本框为空,而breeds列表框设置为请求用户Select a dog breed。显示"insert"按钮(使用"inline")。其他按钮不显示(使用"none")。
document.getElementById('input_form').style.display = "inline";
}
最后,表单本身的显示(现在称为input_form')被设置为'inline',这将允许用户看到完整的表单及其值,就像前面的代码中设置的那样。让我们看看完整的代码。
Example 8-1. The getlists.js file
function AjaxRequest(value)
{
var xmlHttp = getXMLHttp();
xmlHttp.onreadystatechange = function()
{
if(xmlHttp.readyState == 4)
{
HandleResponse(xmlHttp.responseText);
}
}
xmlHttp.open("GET", value, true);
xmlHttp.send(null);
}
function HandleResponse(response)
{
var responsevalues = response.split('|');
document.getElementById('AjaxResponse').innerHTML = responsevalues[0];
document.getElementById('AjaxReturnValue').innerHTML = responsevalues[1];
obj = JSON.parse(responsevalues[2]);
}
function getXMLHttp()
{
var xmlHttp;
try {
xmlHttp = new XMLHttpRequest();
}
catch(e)
{
//Internet Explorer is different than the others
Try {
xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
}
catch(e) {
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(e)
{
alert("Old browser? Upgrade today so you can use AJAX!")
return false;
}
}
}
return xmlHttp;
}
Example 8-2. The lab.php file with update, insert, and delete
<?php
session_start();
if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password']))) {
echo "You must login to access the ABC Canine Shelter Reservation System";
echo "<p>";
echo "<a href='elogin.php'>Login</a> | <a href='eregister.php'>Create an account</a>";
echo "</p>";
}
else if(($_SERVER['HTTP_REFERER'] == 'http://127.0.0.1:8080/mysite/bgchapter8/login.php') || ($_SERVER['HTTP_REFERER'] == 'http://127.0.0.1:8080/mysite/bgchapter8/lab.php
{
if (isset($_SESSION['message']))
{
echo $_SESSION['message'];
}
else
{
echo "<p>Welcome back, " . $_SESSION['username'] . "</p>";
}
?>
<!DOCTYPE html>
<html lan="en">
<head>
<title>ABC Canine Shelter Reservation System</title>
<script src="getlists.js"></script>
<script src="validator.js"></script>
<style type="text/css">
#JS { display:none; }
#insert {display: none; }
#delete {display: none; }
#update {display: none; }
#input_form { display:none; }
</style>
<script>
function checkJS() {
document.getElementById('JS').style.display = "inline"; }
function process_select() {
var colorbuttons = document.getElementsByName('dog_color');
if(!(document.getElementById('dogs').value == -1)) {
index = document.getElementById('dogs').selectedIndex -1;
document.getElementById('index').value = index;
document.getElementById('dog_name').value = obj.dogs[index].dog_name;
document.getElementById('dog_weight').value = obj.dogs[index].dog_weight;
dog_color = obj.dogs[index].dog_color;
if(dog_color == "Brown") {
colorbuttons[0].checked = true;
} else if (dog_color == "Black") {
colorbuttons[1].checked = true;
} else if (dog_color == "Yellow") {
colorbuttons[2].checked = true;
} else if (dog_color == "White") {
colorbuttons[3].checked = true; }
dog_breed = obj.dogs[index].dog_breed;
document.getElementById('dog_breed').value = dog_breed;
document.getElementById('update').style.display = "inline";
document.getElementById('delete').style.display = "inline";
document.getElementById('insert').style.display = "none";
} else {
colorbuttons[4].checked = true;
document.getElementById('dog_name').value = "";
document.getElementById('dog_weight').value = "";
document.getElementById('dog_breed').value = "Select a dog breed";
document.getElementById('insert').style.display = "inline";
document.getElementById('update').style.display = "none";
document.getElementById('delete').style.display = "none";}
document.getElementById('input_form').style.display = "inline"; }
</script>
</head>
<body onload="checkJS();">
<h1>ABC Canine Shelter Reservation System</h1>
<div id="JS">
<script>
AjaxRequest('e8dog_interface.php');
</script>
<h3>Pick the dog name and breed to change from the dropdown box, then click the button.<br>For new dog information select 'NEW'.</h3>
Select 'NEW' or Dog's Name/Breed <div id="AjaxReturnValue"></div>
<input type="button" name="selected" id="selected" value="Click to select" onclick="process_select()" /><br><br>
<div id="input_form">
<form method="post" action="dog_interface.php" onSubmit="return validate_input(this)">
<h3>Please note the required format of information.</h3>
<hr>
Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z]*" title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />
Select Your Dog's Color:<br />
<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />
<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />
<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />
<input type="radio" name="dog_color" id="dog_color" value="White">White<br />
<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />
Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />
<input type="hidden" name="dog_app" id="dog_app" value="dog" />
Select Your Dog's Breed <div id="AjaxResponse"></div><br />
<input type="hidden" name="index" id="index" value="-1"/>
<input type="submit" name="insert" id="insert" value="Click to create your dog info" />
<input type="submit" name="delete" id="delete" value="Click to remove your selected dog info" />
<input type="submit" name="update" id="update" value="Click to update your selected dog info" />
<hr>
</form>
</div>
</div>
<noscript>
<div id="noJS">
<form method="post" action="dog_interface.php">
<h3>For Updates please enter all fields. For Deletions enter at least the dog name and breed. Then click the button.<br>For new dog information enter the requested information, Then click the button.<br> Please note the required format of information.</h3>
Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*" title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />
Select Your Dog's Color:<br />
<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />
<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />
<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />
<input type="radio" name="dog_color" id="dog_color" value="White">White<br />
<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />
Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />
Enter Your Dog's Breed (max 35 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*" title="Up to 15 Alphabetic Characters" maxlength="35" name="dog_breed" id="dog_breed" required /><br />
<input type="hidden" name="dog_app" id="dog_app" value="dog" />
<input type="submit" name="input" id="input" value="Click to create your dog info" />
<input type="submit" name="delete" id="delete" value="Click to remove your selected dog info" />
<input type="submit" name="update" id="update" value="Click to update your selected dog info" />
</form>
</div>
</noscript>
</body>
</html>
之前显示的唯一没有提到的变化是删除了代码末尾的session_destroy。因为您希望dog_interface能够召回lab.php,所以会话需要一直处于活动状态,直到用户完成更改。在本章的后面,您将创建一个注销例程来关闭会话。
做它
Explain how PHP code can be used to replace the process that the JavaScript code uses to display the list boxes, determine which dog was selected, and populate the form objects with the information from the dog selected. This process would be necessary for any browsers that do not have JavaScript enabled. Hint: The PHP code will work in a very similar way to the JavaScript code. What changes would be necessary to the dog_interface.php, dog.php, and dog_data.php programs to accomplish this task? Download the code for this section from the book’s web site. Adjust the code to handle dog information that will include the following additional fields: dog_ID (unique for each dog) and dog_gender. Assume that the dog_interface.php, dog.php, and dog_data.php file will return these fields. You won’t be able to completely test this assignment until you read about the changes to these programs.
在接口层中更新、删除和插入
是时候看看界面和业务规则层中程序的变化了。数据层几乎不需要什么改变,因为以前创建的dog_data程序是为了处理狗信息的显示、更新、插入和删除。提前这样做可以使其他层所需的全部更改变得更加容易。
正如整本书所提到的,接口层负责格式化信息以供显示,并负责从业务规则层请求或处理信息。dog_interface程序应该接受来自数据层的 dogs 数组信息,如果需要的话,将其格式化以便在lab.php程序中使用。它还应该接受狗列表框的狗信息,为其提供任何格式,并将其发送到lab.php进行显示。此外,dog_interface必须接受来自lab.php的插入、更新和删除狗信息的请求,并将这些请求传递给数据层进行处理。这听起来可能很多,但是正如您将看到的,大多数编码已经存在于这些程序中。
首先让我们看一下将对insert或update的请求传递给数据层。
$dog_color = clean_input($_POST['dog_color']);
$dog_weight = clean_input($_POST['dog_weight']);
$dog_index = clean_input($_POST['index']);
lab.php文件将发送一个属性('index')到dog_interface。该属性将以与传递的其他属性相同的方式被接受和清理。
为请求传递给insert或update的信息类型几乎完全相同(所有的狗属性加上'index')。因此,这些过程非常相似。您确实需要一种方法来指明请求的类型(update或insert)。
if ((isset($_POST['insert'])) || (isset($_POST['update'])))
{
if (isset($_POST['insert']))
{
$insert = TRUE;
}
else
{
$insert = FALSE;
}
您可以通过创建属性来实现这一点。在本例中,如果是插入请求,则$insert将被设置为TRUE,如果是update请求,则FALSE将被设置为TRUE。如果请求是一个update或insert,属性数组必须被填充。
$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml,$insert,$dog_index);
您可以像以前在许多示例中一样创建$properties_array。一旦创建,它将被传递到dog类的一个实例中。唯一真正的变化是增加了$insert和$dog_index。update程序将使用$dog_index来指示要更改哪个记录。当有插入时,它将被设置为-1(由lab.php),因为所有记录都被插入到数据的末尾。(你可以使用-1作为插入的指示器,而不是创建$Insert。)
$lab = $container->create_object($properties_array);
使用$container(它是已经在代码中创建的dog_container的一个实例),create_object方法创建一个 dog 类的实例,并将属性数组传递给它。稍后,您将对dog类稍作修改,使用属性数组来确定是否应该调用来自dog_data的insert或update方法来完成请求。
$_SESSION['message'] = "Dog $dog_name Insert/Update was successful<br />";
如果update一切顺利,程序将设置$_SESSION['message']信息,然后由lab.php显示,而不是使用echo或print显示信息。
header("Location: lab.php");
应用然后被重定向回lab.php. lab.php将验证它是从dog_interface调用的,然后在页面顶部显示"Dog $dog_name Insert/Update was successful<br />"。($dog_name替换为实际的狗名。)
如果请求是delete,则发生类似的过程。
else if($_POST['delete'])
{
$properties_array = $dog_index;
dog_data delete方法只需要数组中的位置来决定要删除什么。因此,$properties_array被设置为$dog_index中的值。尽管$properties_array现在是一个字符串而不是数组,但是dog_data中的processRecords方法使用多态性来接受数组或字符串。这使得代码非常类似于update和insert代码。
$lab = $container->create_object($properties_array);
$_SESSION['message'] = "Dog $dog_name Deletion was successful<br />";
header("Location: lab.php");
如update和delete所示,$container(已经创建的dog_container的一个实例)调用create_object方法来创建 dog 类的一个实例,并传递$properties_array(实际上是一个字符串)。如果删除成功,用delete消息设置$_SESSION['message']。然后调用lab.php程序。lab.php将验证dog_container调用了它,然后在页面顶部显示"Dog $dog_name Deletion was successful<br />"。($dog_name替换为实际的狗名。)
为了处理插入、更新或删除请求,这些是dog_interface程序中唯一需要的代码更改。dog_interface还必须从数据层接受狗列表框和完整的狗数组,以格式化并发送给lab.php。
dog_interface必须通过调用dog_data.php中的显示方法来请求狗数组信息。
$container = NULL;
在返回品种列表的请求被处理后,容器指针$container可以被重用。通过将其设置为NULL,,它将释放当前的容器(一个dog_container的实例,其属性设置为检索品种信息)。
$container = new dog_container("dog");
一个新的dog_container实例通过"dog"而不是"selectbox"。这让容器知道将创建一个dog类的实例,而不是一个breeds类的实例。
$properties = "dog";
$lab = $container->create_object($properties);
当狗类的一个实例被create_object创建时,dog再次被传入以指示需要一个dog类的实例。
$container->set_app("dog");
$dog_app = $container->get_dog_application("dog");
$method_array = get_class_methods($lab);
$last_position = count($method_array) - 1;
$method_name = $method_array[$last_position];
$returned_array = $lab->$method_name("ALL");
用于调用dog_data中的delete、insert和update方法的相同代码用于调用显示方法。但是,ALL被传入而不是属性数组。ALL告诉dog_data显示方法返回所有的狗记录。由dog_data返回的所有记录都被放入$return_array中。
现在dog_interface已经有了所有的记录(在$return_array中),它可以格式化代码来显示狗列表框。使用的代码类似于创建breeds列表框的代码。
$resultstring = "<select name='dogs' id='dogs'>";
$resultstring = $resultstring . "<option value='-1' selected>NEW</option>";
属性$resultstring将保存列表框的所有代码。首先创建一个 ID 为'dogs'的 HTML select 标记。然后,列表框的第一行被创建,以便用户选择 NEW,如果他们想要插入新的狗。请注意,NEW 的值是-1。这也是lab.php中 HTML 表单上隐藏属性'index'的默认值。你在本章前面看到的代码将确定-1是用狗信息的默认设置填充 HTML 表单对象的指示。无论用户选择新建还是不选择dogs列表框中的任何内容,都是如此。
foreach ($returned_array as $column => $column_value)
{
$resultstring = $resultstring . "<option value='$column'>" . $column_value['dog_name'];
$resultstring .= “ " . $column_value['dog_breed'] . "</option>";
}
$resultstring = $resultstring . "</select>";
一个foreach循环将遍历 dogs 数组(包含在$returned_array中),并使用数组中每个 dogs 条目的dog_name和dog_breed构建列表框的剩余行。当所有的狗都被放入列表框后,列表框使用 HTML </select>标签关闭。
print $result . "|" . $resultstring . "|" . '{ "dogs" : ' . json_encode($returned_array) . "}";
lab.php期望在第一个位置接收breeds列表框代码,在第二个位置接收dogs列表框代码,在第三个位置接收完整的 dogs 数组。$result已经包含了完整的品种列表框。$resultstring包含新的狗狗列表框。$return_array仍然包含所有的狗记录。lab.php期望 dogs 数组的外部数组被标记为'dogs'。dog_data的显示方法没有将外部数组传递回来。前面的代码将创建一个 dogs 数组,其中包含所有单个的 dogs 数组。注意,$return_array在返回时已经被转换成 JSON 代码。lab.php中的 JavaScript 代码将在收到时验证它是否是正确格式化的 JSON 代码。
我们来看完整的dog_interface程序。
Example 8-3. The dog_interface.php file with update, insert, and delete
<?php
session_start();
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("e8dog_container.php"))
{
Require_once("e8dog_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']);
$dog_index = clean_input($_POST['index']);
$breedxml = $container->get_dog_application("breeds");
if ((isset($_POST['insert'])) || (isset($_POST['update'])))
{
if (isset($_POST['insert']))
{
$insert = TRUE;
}
else
{
$insert = FALSE;
}
$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml,$insert,$dog_index);
$lab = $container->create_object($properties_array);
$_SESSION['message'] = "Dog $dog_name Insert/Update was successful<br />";
header("Location: e8lab.php");
//print "Dog $dog_name Insert/Update was successful<br />";
//get_dog_app_properties($lab);
}
else
{
$insert = FALSE;
}
$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml,$insert,$dog_index);
$lab = $container->create_object($properties_array);
$_SESSION['message'] = "Dog $dog_name Insert/Update was successful<br />";
header("Location: e8lab.php");
//print "Dog $dog_name Insert/Update was successful<br />";
}
else if($_POST['delete'])
{
$properties_array = $dog_index;
$lab = $container->create_object($properties_array);
$_SESSION['message'] = "Dog $dog_name Deletion was successful<br />";
header("Location: e8lab.php");
//print "Dog $dog_name Deletion was successful<br />";
}
}
else
{
print "<p>Missing or invalid parameters. Please go back to the dog.php page to enter valid information.<br />";
print "<a href='e8lab.php'>Dog Creation Page</a>";
}
}
else // breeds 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");
$method_array = get_class_methods($lab);
$last_position = count($method_array) - 1;
$container = NULL;
$result = $lab->$method_name($dog_app);
$container = NULL;
// read dog_data array
$container = new dog_container("dog");
$properties = "dog";
$lab = $container->create_object($properties);
$container->set_app("dog");
$dog_app = $container->get_dog_application("dog");
$method_array = get_class_methods($lab);
$last_position = count($method_array) - 1;
$method_name = $method_array[$last_position];
// return dogs from data
$returned_array = $lab->$method_name("ALL");
// format dogs list box
$resultstring = "<select name='dogs' id='dogs'>";
$resultstring = $resultstring . "<option value='-1' selected>NEW</option>";
foreach ($returned_array as $column => $column_value)
{
$resultstring = $resultstring . "<option value='$column'>" . $column_value['dog_name'] . "``&``nbsp;``&``nbsp;``&
$resultstring = $resultstring . $column_value['dog_breed'] . "</option>";
}
$resultstring = $resultstring . "</select>";
print $result . "|" . $resultstring . "|" . '{ "dogs" : ' . json_encode($returned_array) . "}";
}
}
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');
$eMessage = $date . " | System Error | " . $e->getMessage() . " | " . $e->getFile() . " | ". $e->getLine() . "\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
}
?>
做它
Download the code for this section from the book’s web site. The example code (Example 6-3) could be broken into several methods to handle the creation of the list boxes and the dogs array. Create methods and move the code in these methods to process this information. Call the methods from the previous location of the code. Download the code for this section from the book’s web site. Adjust the code to handle dog information that will include the following additional fields: dog_ID (unique for each dog) and dog_gender. Assume that the dog.php and dog_data.php files will return these fields. You won’t be able to completely test this assignment until you read about the changes to these programs. Note: If you did not previously complete the earlier Do It, complete that assignment along with this one.
在业务规则层中更新、删除和插入
您即将完成 ABC 犬类收容所预订系统。需要对Dog类进行一些更改,以确定该类被调用的原因(insert、delete或update)。然后信息可以传递给dog_data类中的正确方法。如上所述,不需要对dog_data类做任何修改,因为insert、update和delete的方法已经存在。此外,dog类必须从dog_data中检索所有的狗记录并发送给dog_interface。
<?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 = "";
private $insert = FALSE;
private $index = -1;
在Dog类属性的顶部,$insert和$index被创建来保存由dog_interface程序从properties_array传递的值。$index初始设置为-1,表示默认假设用户选择了新的或者没有从狗列表框中选择狗(在lab.php)。
if((is_bool($properties_array[5])) && ($properties_array[6] > -1))
{ // confirms true or false and valid index or takes default
$this->insert = $properties_array[5];
$this->index = $properties_array[6];
}
$this->change_dog_data("Insert/Update");
}
if(is_numeric($properties_array))
{ // confirms valid index don't delete if not valid
$this->index = $properties_array;
$this->change_dog_data("Delete");
}
在构造函数中,在验证了狗值(dog_name、dog_breed、dog_weight和dog_color)之后,还需要验证插入和索引值。所示的if语句使用 PHP 方法is_bool来确定$properties_array[5]中的值是TRUE还是FALSE. $properties_array[5]是来自insert属性的值的位置。(如果是插页,则设置为TRUE,如果是Update,则设置为FALSE)。if语句还确定$properties_array[6]中的值是否大于-1。大于-1的值表示更新或删除。值-1表示插入请求。如果有效,这些值被放入$this->insert和$this->index。然后名为change_dog_data的private方法被称为传入“插入/更新”。
下一个if语句使用 PHP 方法is_numeric来确定$properties_array实际上是否不是一个数组,以及它是否包含一个数字。如果这是真的,那么要删除的狗的索引已经通过。如果是这种情况,那么将$property_array(包含要删除的索引)放在$this->index中,调用change_dog_data的private方法在“delete”中被调用。
private function change_dog_data($type)
{
if ( file_exists("e8dog_container.php")) {
require_once("e8dog_container.php"); // use chapter``5
} 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
新的change_dog_data方法(在前面的代码中调用)将使用与前面章节中显示的display_dog_data方法非常相似的代码。首先,该方法验证dog_container是否存在。如果容器存在,则创建一个容器实例($container),传递"dogdata",告诉容器这个请求将使用 dogs XML 文件。下一个$properties_array是用一个包含"dogdata"的简单数组创建的。这个数组实际上不会被使用。然而,dog_data类要求将某些东西传递到构造函数中。现在使用容器的create_object方法创建了dog_data ( $dog_data)的一个实例。
$method_array = get_class_methods($dog_data);
$last_position = count($method_array) - 1;
$method_name = $method_array[$last_position];
如前面的代码所示,change_dog_data方法使用 PHP get_class_methods创建一个包含在dog_data对象($dog_data)中的方法数组。dog_data中的最后一个方法是processRecords方法,它使用一个case语句来调用正确的私有方法。
if (($this->index > -1) && ($type == "Delete"))
{
$record_Array = $this->index;
$dog_data->$method_name("Delete",$record_Array);
}
如果索引大于-1,类型为"delete",那么change_dog_data方法调用dog_data对象的processRecords方法,并传递"delete"和要删除的索引。
else if (($this->index == -1) && ($type == "Insert/Update"))
{
$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);
}
如果索引是-1并且类型是"insert/update",那么用户已经请求“插入”。创建一个包含所有狗属性的数组(dog_name, dog_weight, dog_color和dog_breed)。然后“插入”和数组被传递到dog_object的processRecords方法中。
else if ($type == "Insert/Update")
{
$record_Array = array($this->index => 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("Update",$record_Array);
}
$dog_data = NULL;
}
如果索引不是-1并且类型是"insert/update",则用户已经请求更新信息。创建了带有insert的相同数组,但有一个例外。数组的索引(array($this->index =>)被设置为要更新的记录的索引。然后,该数组和“更新”被传递给dog_data对象的processRecords方法。
在记录被插入、更新或删除后,dog_data对象($dog_data)被设置为NULL。如前几章所述,dog_data对象首先更新 associate 数组,该数组包含每只狗的信息。当调用dog_data对象的析构函数时,信息被更新到dog_data XML 文件中。将$dog_data设置为NULL释放dog_data对象,该对象调用其析构函数。
这就完成了对业务规则层的所有更改。让我们把它们放在一起。
Example 8-4. The dog.php file with update, insert, and delete
<?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 = "";
private $insert = FALSE;
private $index = -1;
// ---------------------------------- Constructor ------------------------------------------
function __construct($properties_array)
{
if (method_exists('dog_container', 'create_object')) {
if (is_array($properties_array)) {
$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);
}
if((is_bool($properties_array[5]))``&&
{ // confirms true or false and valid index or takes default
$this->insert = $properties_array[5];
$this->index = $properties_array[6];
}
$this->change_dog_data("Insert/Update");
}
if(is_numeric($properties_array))
{ // confirms valid index don't delete if not valid
$this->index = $properties_array;
$this->change_dog_data("Delete");
}
}
else
{
exit;
}
}
function clean_input() { }
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 Methods---------------------------------------------
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;
}
}
private function change_dog_data($type)
{
if ( file_exists("e8dog_container.php")) {
require_once("e8dog_container.php"); // use chapter``5
} 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];
if (($this->index > -1)``&&
{
$record_Array = $this->index;
$dog_data->$method_name("Delete",$record_Array);
}
else if (($this->index == -1)``&&
{
$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);
}
else if ($type == "Insert/Update")
{
$record_Array = array($this->index => 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("Update",$record_Array);
}
$dog_data = NULL;
}
function display_dog_data($record)
{
if ( file_exists("e8dog_container.php")) {
require_once("e8dog_container.php"); // use chapter``5
} 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 = $record;
return $dog_data->$method_name("Display",$record_Array);
}
}
?>
正如您所看到的,不需要对dog_data.php文件进行修改,因为它已经包含了插入、更新和删除方法。
做它
Download the code for this section from the book’s web site. Run the (almost) completed program. Try to “break” the program. Did you accomplish the task? If so, what broke? Attempt to fix any problems you might discover. Download the code for this section from the book’s web site. Adjust the code to handle dog information that will include the following additional fields: dog_ID (unique for each dog) and dog_gender. Adjust the dog.php and dog_data.php programs to return these fields. You should now be able to test the complete application. Note: If you did not previously complete the Do Its earlier in this chapter, complete these assignments along with this one.
最后润色
您将在本节中完成 ABC 犬类收容所预订系统的编码(最后!).你将使用第二章中的一些 CSS 代码来帮助应用看起来更专业。此外,您将添加一个菜单,允许用户阅读错误日志并退出应用(关闭会话)。
第二章中的 CSS 代码使用 HTML 标签(<div id='wrapper'>)来控制body部分的内容。因此,该标记直接添加在 body 标记之后(并且正好在结束 body 标记之前结束)。
<body onload="checkJS();">
<div id="wrapper">
<div id="header"><h1><img src="brody.jpg"> ABC Canine Shelter Reservation System</h1>
CSS 代码还使用 HTML 标签(<div id='header'>)来格式化页面的标题。代码也进行了调整,使用 HTML img标签添加了一张狗的图片。
<a href="e8readerrorlog.php">Manage Error Logs</a> | <a href="e75changepassword.php">Change Password</a> | <a href="e8lab.php?logoff=True">Log Off</a>
</div>
在header部分还包括一个简单的菜单,允许用户读取错误日志、更改密码或注销应用。注意,注销系统的代码将调用lab.php程序,并创建一个值为"True"的属性"logoff"。您将对lab.php代码做最后的调整来处理这个选择。
body 部分的所有剩余内容都放在 HTML 标记(<div ='contents'>)中。
<head>
<title>ABC Canine Shelter Reservation System</title>
<link href="e8dogstylesheet.css" rel="stylesheet">
第二章中的 CSS 代码将被拉入到head部分的文件中。该文件已被重命名。该文件的内容保持不变,只是在包装器中添加了一个float: top来将显示与浏览器顶部对齐,并在标题中添加了一个text-align: center来将应用的标题居中。
这些变化为应用提供了更好的外观。
在lab.php程序中,你需要查看的最后一个代码是logoff例程。
session_start();
if (isset($_GET['logoff']))
{
session_destroy();
}
if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password'])) || (isset($_GET['logoff']))) {
在lab.php代码的顶部,添加了一个if语句,以确定logoff是否已被置位(通过GET)。如果有,会话将被销毁。用于确定用户 ID 和密码是否尚未设置的if语句,也被修改为包括检查logoff是否已设置。如果已经设置,则(此时)用户不再登录系统。
echo "<html><head><title>ABC Canine Shelter Reservation System</title>";
echo "<link href= 'e8dogstylesheet.css' rel='stylesheet'><style type='text/css'>img { height: 100px; width: 140px; }</style></head><body>";
echo "<div id='wrapper'><div id='header'><h1><img src='brody.jpg'>ABC Canine Shelter Reservation System</h1></div>";
echo "<div id='content'>";
echo "You must login to access the ABC Canine Shelter Reservation System";
echo "<p>";
echo "<a href='e74login.php'>Login</a> | <a href='e73registration.php'>Create an account</a>";
echo "</p>";
echo "</div><div id='footer'>Copyright © 2015 Little Ocean Waves Publishing - Steve Prettyman</div></div>";
echo "</body></html>";
然后,用户被重定向到登录或创建帐户。还要注意,代码已经被修改,为wrapper、header、content和footer添加了相同的div标签。这将赋予页面与lab.php文件相同的外观。
此外,向用户显示的任何页面都应该具有这种外观。readerrorlog程序的displayRecords方法也被修改(如此处所示)以包含相同的div标签。
echo "<html><head><title>ABC Canine Shelter Reservation System</title>";
echo "<link href=' e8dogstylesheet.css' rel='stylesheet'>";
echo "<style> table { border: 2px solid #5c744d;}";
echo "img { height: 100px; width: 140px; } </style>";
echo "</head><body>";
echo "<div id='wrapper'><div id='header'><h1><img src='brody.jpg'> ABC Canine Shelter Reservation System</h1></div><div id='content'>";
echo "<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='e58readlogfile.php?rn=$J'>Delete</a></td>";
for($I=0; $I < 3; $I++)
{
echo "<td> " . $error_Array[$J][$I] . " </td> ";
}
echo "</tr>";
}
echo "</table>";
echo "</div><div id='footer'>Copyright © 2015 Little Ocean Waves Publishing - Steve Prettyman</div></div>";
echo "</body></html>";
让我们看看例子 8-5 中lab.php的最终版本。
Example 8-5. The complete lab.php file
<?php
session_start();
if (isset($_GET['logoff']))
{
session_destroy();
}
if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password'])) || (isset($_GET['logoff']))) {
echo "<html><head><title>ABC Canine Shelter Reservation System</title>";
echo "<link href='e8dogstylesheet.css' rel='stylesheet'><style type='text/css'>img { height: 100px; width: 140px; }</style></head><body>";
echo "<div id='wrapper'><div id='header'><h1><img src='brody.jpg'>ABC Canine Shelter Reservation System</h1></div>";
echo "<div id='content'>";
echo "You must login to access the ABC Canine Shelter Reservation System";
echo "<p>";
echo "<a href='e74login.php'>Login</a> | <a href='e73registration.php'>Create an account</a>";
echo "</p>";
echo "</div><div id='footer'>Copyright``&
echo "</body></html>";
}
else if(($_SERVER['HTTP_REFERER'] == 'http://127.0.0.1:8080/mysite/bgchapter8/ExampleFile7.4/e74login.php') || ($_SERVER['HTTP_REFERER'] == 'http://127.0.0.1:8080/mysite/bgchapter8/ExampleFile7.4/e8lab.php
{
?>
<!DOCTYPE html>
<html lan="en">
<head>
<title>ABC Canine Shelter Reservation System</title>
<link href=" e8dogstylesheet.css" rel="stylesheet">
<script src="e8getlists.js"></script>
<script src="e5validator.js"></script>
<style type="text/css">
#JS { display:none; }
#input_form { display:none; }
#insert {display: none; }
#delete {display: none; }
#update {display: none; }
img { height: 100px; width: 140px; }
</style>
<script>
function checkJS() {
document.getElementById('JS').style.display = "inline";
}
function process_select() {
var colorbuttons = document.getElementsByName('dog_color');
if(!(document.getElementById('dogs').value == -1))
{
index = document.getElementById('dogs').selectedIndex -1;
document.getElementById('index').value = index;
document.getElementById('dog_name').value = obj.dogs[index].dog_name;
document.getElementById('dog_weight').value = obj.dogs[index].dog_weight;
dog_color = obj.dogs[index].dog_color;
if(dog_color == "Brown")
{
colorbuttons[0].checked = true;
} else if (dog_color == "Black")
{
colorbuttons[1].checked = true;
} else if (dog_color == "Yellow")
{
colorbuttons[2].checked = true;
}
else if (dog_color == "White")
{
colorbuttons[3].checked = true;
}
dog_breed = obj.dogs[index].dog_breed;
document.getElementById('dog_breed').value = dog_breed;
document.getElementById('update').style.display = "inline";
document.getElementById('delete').style.display = "inline";
document.getElementById('insert').style.display = "none";
}
else
{
colorbuttons[4].checked = true;
document.getElementById('dog_name').value = "";
document.getElementById('dog_weight').value = "";
document.getElementById('dog_breed').value = "Select a dog breed";
document.getElementById('insert').style.display = "inline";
document.getElementById('update').style.display = "none";
document.getElementById('delete').style.display = "none";
}
document.getElementById('input_form').style.display = "inline";
}
</script>
</head>
<body onload="checkJS();">
<div id="wrapper">
<div id="header"><h1><img src="brody.jpg"> ABC Canine Shelter Reservation System</h1>
<a href="e8readerrorlog.php">Manage Error Logs</a> | <a href="e75changepassword.php">Change Password</a> | <a href="e8lab.php?logoff=True">Log Off</a>
</div>
<div id="content">
<?php
if (isset($_SESSION['message']))
{
echo "<p>" . $_SESSION['message'] . "</p>";
}
else
{
echo "<p> Welcome back, " . $_SESSION['username'] . "</p>";
}
?>
<div id="JS">
<script>
AjaxRequest('e8dog_interface.php');
</script>
<h3>Pick the dog name and breed to change from the dropdown box, then click the button.<br>For new dog information select 'NEW'.</h3>
Select 'NEW' or Dog's Name/Breed <div id="AjaxReturnValue"></div>
<input type="button" name="selected" id="selected" value="Click to select" onclick="process_select()" /><br><br>
<div id="input_form">
<form method="post" action="e8dog_interface.php" onSubmit="return validate_input(this)">
<h3>Please note the required format of information.</h3>
<hr>
Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z]*" title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />
Select Your Dog's Color:<br />
<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />
<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />
<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />
<input type="radio" name="dog_color" id="dog_color" value="White">White<br />
<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />
Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />
<input type="hidden" name="dog_app" id="dog_app" value="dog" />
Select Your Dog's Breed <div id="AjaxResponse"></div><br />
<input type="hidden" name="index" id="index" value="-1"/>
<input type="submit" name="insert" id="insert" value="Click to create your dog info" />
<input type="submit" name="delete" id="delete" value="Click to remove your selected dog info" />
<input type="submit" name="update" id="update" value="Click to update your selected dog info" />
<hr>
</form>
</div> </div>
<noscript>
<div id="noJS">
<form method="post" action="e8dog_interface.php">
<h3>For Updates please enter all fields. For Deletions enter at least the dog name and breed. Then click the button.<br>For new dog information enter the requested information, Then click the button.<br> Please note the required format of information.</h3>
Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*"title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />
Select Your Dog's Color:<br />
<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />
<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />
<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />
<input type="radio" name="dog_color" id="dog_color" value="White">White<br />
<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />
Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />
Enter Your Dog's Breed (max 35 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*" title="Up to 15 Alphabetic Characters" maxlength="35" name="dog_breed" id="dog_breed" required /><br />
<input type="hidden" name="dog_app" id="dog_app" value="dog" />
<input type="submit" name="input" id="input" value="Click to create your dog info" />
<input type="submit" name="delete" id="delete" value="Click to remove your selected dog info" />
<input type="submit" name="update" id="update" value="Click to update your selected dog info" />
</form>
</div>
</noscript>
</div>
<div id="footer">Copyright``&
</div>
</body> </html>
<?php } ?>
Example 8-6. The complete readerrorlog.php file
<?php
session_start();
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><title>ABC Canine Shelter Reservation System</title>";
echo "<link href= 'e8dogstylesheet.css' rel='stylesheet'>";
echo "<style> table { border: 2px solid #5c744d;}";
echo "img { height: 100px; width: 140px; } </style>";
echo "</head><body>";
echo "<div id='wrapper'><div id='header'><h1><img src='brody.jpg'> ABC Canine Shelter Reservation System</h1></div><div id='content'>";
echo "<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>";
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='e58readlogfile.php?rn=$J'>Delete</a></td>";
for($I=0; $I < 3; $I++)
{
echo "<td> " . $error_Array[$J][$I] . " </td> ";
}
echo "</tr>";
}
echo "</table>";
echo "</div><div id='footer'>Copyright``&
echo "</body></html>";
}
const ERROR_LOG = "Errors.log";
if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password']))) {
echo "<html><head><title>ABC Canine Shelter Reservation System</title>";
echo "<link href='e8dogstylesheet.css' rel='stylesheet'><style type='text/css'>img { height: 100px; width: 140px; }</style></head><body>";
echo "<div id='wrapper'><div id='header'><h1><img src='brody.jpg'>ABC Canine Shelter Reservation System</h1></div>";
echo "<div id='content'>";
echo "You must login to access the ABC Canine Shelter Reservation System";
echo "<p>";
echo "<a href='e74login.php'>Login</a> | <a href='e73registration.php'>Create an account</a>";
echo "</p>";
echo "</div><div id='footer'>Copyright``&
echo "</body></html>";
}
else
{
$logFile = fopen(ERROR_LOG, "r");
$row_Count = 0;
while(!feof($logFile))
{
$error_Array[$row_Count] = explode(' | ', fgets($logFile));
$row_Count++;
}
$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);
}
?>
做它
Download the code for this section from the book’s site. Also, download the displaychangelog.php program from Chapter 6 (in Example 6-4) along with a change log if you don’t already have one. Update the menu provided in the lab.php program to provide a link to the displaychangelog.php program. Add code to the displaychangelog.php program to require the users to log in. Redirect the users to the choice of login or registration (as shown in the examples) if they are not logged in. Also update the HTML in the displaychangelog.php file to use the e8dogstylesheet.css file to display the change log page and the login/registration selection page in the same format as shown in Figure 8-5.
图 8-5。
The complete lab.php file Download the code for this section from the book’s web site. Adjust the code to handle dog information that will include the following additional fields: dog_ID (unique for each dog) and dog_gender. Adjust all programs necessary (including the readerrorlog and displaychangelog (in 6-4)). You should now be able to test the complete application. Download the code for this section from the book’s site. Add a menu for the users to return to the shelter registration page (lab.php) or log off when they are using the readerrorlog.php file. Add the code needed to the readerrorlog.php file to log off the user. If you have not already done so, include changes need to format the display of the readerrorlog.php file as shown in Figure 8-5. Also these changes in the displaychangelog.php file (if you did #1).
ABC 犬类收容所预定系统逻辑设计
ABC 犬类收容所预订系统的最终逻辑设计展示了具有四层(身份验证、接口、业务规则和数据)的应用。额外的层也可以用于在多个服务器上拆分大型应用。
图 8-6。
The complete ABC Canine Shelter Reservation System
除了层级之外,维护程序(readerrorlog和readchangelog)提供了加强安全性、修复错误以及提供备份和恢复的能力。
限制
ABC 犬类收容所预订系统在本书中被用作教学工具。然而,该系统并不完整,并为现实世界做好准备。该系统的许多限制已经在本书中解决了,并作为练习让你获得进一步的 PHP 编程技能。应该对系统进行如下所示的更改,以改善用户体验、安全性和性能。此外,用户测试对于确保系统用户满意至关重要。对系统的设计和性能不满意的用户不太可能使用它。
lab.php
- 需要包含一个
Dog_ID或一些其他类型的独特字段来确定具有相同名称和品种的狗之间的差异。 - 不使用 JavaScript 的用户需要使用 PHP 来调用和检索更新和删除功能所需的信息。
- 需要在菜单选项中提供到
displaychangelog.php的链接。 - 需要有一个
Dog_Gender字段。如果有狗狗图片也不错。 - 该程序需要处理和响应用户试图插入一只已经存在的狗。
dog_interface.php
Dog_ID信息(更新/插入)需要从Dog类接受并传递给lab.php。- 需要代码将请求返回给
lab.php以获得完整的 dogs 数组,并为支持non_JavaScript的浏览器返回特定的 dogs。该信息将从dog.php检索。 - 需要从
dog.php中取出Dog_Gender字段,并将其传递给lab.php。 - 当用户试图输入一只已经在数据中的狗时,必须向
lab.php程序传递一条会话消息。该消息在catch块中创建。
dog.php
Dog_ID信息需要从dog_data.php中提取并传递给dog_interface.php。Dog_Gender需要从dog_data.php中取出并传递给dog_interface.php。
dog_data.php
- 需要将
Dog_ID传递给dog.php。需要包括插入和更新该字段的能力。 - 需要将
Dog_Gender传递给dog.php。插入和更新该字段的能力需要包括在内。
register.php
- 该程序需要包括一个唯一的字段(
customerID)来确定重复的用户 id。该字段将由用户生成而非输入。 - 这个程序需要检测用户试图创建一个重复的 ID。代码甚至可以根据用户试图创建的内容为用户 ID 名称提供建议(比如在用户 ID 的末尾添加数字)。
- 这个程序需要要求用户也使用一个特殊的密码符号。
- 这个程序需要安全问题,以防用户忘记密码。
- 还需要其他字段,如姓名、电子邮件和地址。
login.php
- 这个程序需要能够为忘记密码但记得安全问题答案的用户创建一个临时密码。
全部的
- 您需要考虑将更多的程序(文件)名转移到 XML 文件中,以便更容易地进行版本更改。
- 您需要考虑增加新用户 id 的访问和批准级别。
章节术语
| $ _ server[' http _ reference '] | HTML 按钮 | | 使分离 | 定义变量 | | 对象 | JSON 语法分析 | | getElementsByName | HTML 列表框 | | 项时 | 价值 | | 点符号格式 | 已检查的属性 | | is_bool | 图片 | | 浮动:顶部 | 文本对齐:居中 | | 四层应用 | |
第二章问题和项目
多重选择
Which JavaScript method accomplishes a similar task as the PHP method explode? explode slice split None of these Which JavaScript method accomplishes a similar task as the PHP method json_decode? JSON.scan JSON.parse JSON.decode None of these A JSON object is similar to which PHP object? An array A multidimensional array An associative array None of these An HTML button can be used to do what? Can be used to call a JavaScript method Can submit a form Can display a form All of the above The private word is used to create a private object in PHP. What word is used in JavaScript to accomplish the same? private No additional word is needed; they are private by default var None of these
对/错
JavaScript is commonly used to retrieve and use data passed from other program languages. The CSS code display: form is used to display a form that has been hidden from the user. The CSS code p {float: top } will cause everything in all paragraph tags to float to the top of the browser window. getElementsByName can be used to create an array from a grouping of radio buttons. is_numeric could be used to determine if an object is not an array.
简答/短文
Explain why and how initially hiding a form from the users will force them to make a selection from a list box. Explain how each program in the ABC Canine Shelter Reservation System uses -1 to indicate a request for insert. Explain each tier of a four-tier application. What changes would need to occur in the ABC Canine Shelter Reservation System to convert it to a Feline Shelter Reservation System? Give an example of three algorithms (blocks of code) that have been continuously reused in the ABC Canine Shelter Reservation System.
项目
Go back to the section entitled “Limitations” in this chapter and view the list again. Download the complete code from this chapter and fix one (or more) of these limitations. Make sure to fix the limitation in all the applicable files.
学期项目
Update the ABC Computer Parts Inventory application to provide complete delete, update, and insert capabilities (as shown in this chapter). Add CSS to each program in the application interface to provide a more professional display of the information. Add any code needed to ensure that the users are logged in to access any part of the application. List any limitations that still exist in this application.