PHP7-学习手册-全-

PHP7 学习手册(全)

原文:Learn PHP 7

协议:CC BY-NC-SA 4.0

一、PHP 7 简介

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1730-6_​1) contains supplementary material, which is available to authorized users.

"PHP is a popular universal scripting language, especially suitable for web development. Fast, flexible and practical PHP provides power for everything from your blog to the most popular websites in the world. — www . php . net

章节目标/学生学习成果

完成本章后,学生将能够:

  • 理解灯,WAMP 和 MAMP 之间的区别
  • 成功安装 LAMP、WAMP 或 MAMP 的版本
  • 搜索互联网以解决问题
  • 解释编程语言和脚本语言的区别
  • 创建一个没有错误的简单 PHP 程序

PHP 5.5+,PHP 7+和 PHP.NET

今天,PHP(超文本预处理器)是 web 应用开发中最流行的语言之一。该语言已经发展到允许程序员使用过程化和面向对象的编程技术快速开发结构良好的无错误程序。它提供了使用许多预先存在的代码库的能力,这些代码库要么是基本安装自带的,要么可以安装在 PHP 环境中。这为您提供了多种完成特定任务的方法。它比许多其他语言提供了更多的灵活性。额外的代码库可以很容易地添加到环境中,这是它受欢迎的众多驱动力之一。过程语言——过程编程语言包括可以从程序的主流程中调用的函数/方法。程序流程跳转到函数/方法,执行模块内的代码,然后返回到程序主流程中的下一条语句。一些过程语言包括一个在程序执行时自动调用的主函数/方法。面向对象的语言—面向对象的语言使用类和对象。类类似于蓝图。一个类描述了一个对象可以包含什么,包括属性/变量和函数/方法。一个对象是一个类的实例(就像一个从蓝图中创建的建筑)。面向对象的语言提供了多态性、封装和继承。通过在对象本身中包含所有相关的函数/方法和属性/变量,对象被自然地封装。多态性允许在面向对象的对象中使用重复的方法/函数名。但是,“签名”必须是不同的。“签名”是传入方法/函数的变量类型(数字和字符)和从方法/函数传出的信息类型的组合。例如,可以创建几个 add 方法—一个只接受整数(整数),一个只接受浮点数(带小数的数字),一个接受组合。程序将根据传递给方法/函数的内容来决定调用哪个方法/函数。面向对象编程中的继承允许一个对象从另一个对象继承属性/变量和函数/方法。该对象还可以重写那些继承的项。这类似于孩子从父母那里继承特征。面向对象的语言也可以是事件驱动的。事件驱动的程序将“休眠”直到事件发生。这类似于 ATM 机程序等待用户输入 ATM 卡。

PHP 是一种开源语言。因此,这种语言的每个版本都是使用使用它的个人——程序员自己——的输入创建的。随着时间的推移,这允许语言向用户驱动的方向发展。自 1995 年拉斯马斯·勒德尔夫作为个人主页工具(PHP)首次发布以来,这些版本已经在互联网上发布,并提供论坛,让用户能够提出建议,甚至提供代码更改和添加。今天 www.php.net 是 PHP 官方网站。

Open source language-open source programming language is developed by related groups. The community accepts input from programmers' partners to suggest upgrades and corrections. Several community members work together to make suggestions and make language changes. Open source languages are "free". Non-open source languages (such as Microsoft C#) are created and updated by companies or major organizations. Non-open source languages are usually not "free".

A978-1-4842-1730-6_1_Fig1_HTML.jpg

图 1-1。

PHP.NET (09/24/15)

www.php.net 主页提供了该语言每个最新版本的信息。它还提供了有关未来版本的信息、为这些版本计划的功能以及计划的发布日期。此外,还可以找到其他相关的 PHP 信息,包括主要 PHP 会议的链接和信息。

A978-1-4842-1730-6_1_Fig2_HTML.jpg

图 1-2。

Get involved (09/24/15)

如前所述,这个网站为用户提供了帮助语言未来发展的能力。用户可以参与测试 beta 版本,并报告错误或程序错误。访问者还可以查看与未来可能的版本开发相关的文档。这是在向公众发布重大公告之前发现未来增强功能或安全修复的好方法。

A978-1-4842-1730-6_1_Fig3_HTML.jpg

图 1-3。

Download page (09/24/15)

正如您可能已经猜到的,下载页面提供了轻松访问该语言最新版本的能力。然而,正如您将注意到的,只提供了语言本身。更常见的是,建议初级用户使用 WAMP (Windows、Apache、MySQL、PHP);LAMP (Linux,Apache,MySQL,PHP);或者 MAMP (Mac,Apache,MySQL,PHP)包进行初始安装。这些包(我们将在后面看到)允许同时安装多个产品。否则,您必须运行许多单独的安装,如果安装了不兼容的版本,这会变得复杂并且容易出错。WAMP/LAMP/MAMP——开源(免费)组合,包括 Apache Web Server、MySQL 和针对特定操作系统(Windows、Linux 和 Mac)的 PHP。这些包是开源的。该软件组合用于创建动态网站和网络应用。

A978-1-4842-1730-6_1_Fig4_HTML.jpg

图 1-4。

Documentation pages (09/24/15)

PHP 网站的一个更重要的页面是文档页面。该页面允许用户搜索语言本身的描述和功能。您也可以下载完整的文档。但是,由于这是一个“实时”网站,可能会发生变化,因此最好直接从网站上获取最新信息。

A978-1-4842-1730-6_1_Fig5_HTML.jpg

图 1-5。

The Manual (11/11/14)

通过从头开始点击每个链接,您可以像使用教科书一样使用手册。手册的每一部分所提供的有限的解释可能会导致初学者想要放弃编程,而将兴趣转向像网络这样可怕的东西!该手册确实为有经验的程序员提供了很好的指导,因为该语言的语法类似于其他语言,如 JavaScript、Perl 和 Java。

A978-1-4842-1730-6_1_Fig6_HTML.jpg

图 1-6。

Search (11/11/14)

在网站的任何页面上,用户都可以输入一个术语、一个表达式,甚至一个函数名来查找更多信息。当在搜索框中输入信息时,网页将在搜索框下方向用户提供一个或多个选项供用户选择。

一旦用户选择了一个选项(例如图 1-7 中所示的 echo),搜索结果将为用户提供所请求项目的一般描述、功能的任何输入或输出(参数)以及示例代码。

A978-1-4842-1730-6_1_Fig7_HTML.jpg

图 1-7。

Echo (11/11/14)

示例代码通过使用注释(在图 1-8 中用//和金色表示)在代码本身中解释了函数的用法。注释不是可执行代码。可执行代码用颜色编码,突出显示字符串(红色)、变量(蓝色)、关键字(绿色)和 PHP 开始和结束标记(蓝色)。颜色编码有助于提高代码的可读性。它还可以使创建程序时更容易发现语法错误。许多 PHP 编辑器都提供了类似的配色方案。

A978-1-4842-1730-6_1_Fig8_HTML.jpg

图 1-8。

Echo code (11/11/14)

PHP 5.6+和 PHP 7+

随着 PHP 7 环境的发布,发生了很大的改进。PHP 5.5+极大地提高了安全性。在本书中,我们将使用最新的 PHP 加密工具“password hash ”,而不是目前许多书使用的 MD5。在过去的几年中,MD5 已经被证明易受黑客攻击。

"PHP 7 is based on the PHPNG project (PHP Next-Gen), which is led by Zend and aims to speed up PHP applications. The performance improvement realized by PHP 7 is enormous! Their differences in real applications are between 25% and 70%, all of which are realized by upgrading PHP without changing any line of code! " — www . zend . com

PHP 7 还取代了致命错误,以前致命错误会导致程序崩溃,异常可以在程序内部处理。

如果您要从以前的 PHP 版本迁移到 PHP 7,请查看以下链接:

http://php.net/manual/en/migration70.php

本书示例中使用的代码与 PHP 7 兼容。大多数例子也兼容 PHP 5.5 和 PHP 5.6。

做它

Go to www.​php.​net. Search for information on the print and printf functions. How are these functions similar? How are they different?   How do you “join the team” and help with the creation of the next version of PHP? Hint: Go to the “Get Involved” section of www.php.net , select “Guide for Contributors,” and then find the “Join the team” link. Of course, the web site changes, so you may need to find a different route to the information.   Which ways can the www.php.net web site be useful for a beginning PHP programmer?   What language is used to create PHP? Hint: The answer is somewhere on the www.php.net web site.   Go to www.php.net . Search for the list of improvements and changes with PHP 7. List those improvements and changes. Which of these do you think will affect a beginning level programmer?

PHP、JavaScript、CSS、HTML 和 Apache Web 服务器

PHP 是一种脚本语言。脚本语言不同于实际的编程语言。编程语言(如 Java)是由程序员用类似英语的语法编写的。程序被编译,这意味着它从英语语法转换成机器代码(0 和 1)。该代码然后在兼容的操作系统和硬件中执行(运行)。脚本语言不使用编译器。第一次访问代码时,会在程序执行时逐行解释。

您可能想知道这是否会导致代码比编译后的代码慢。答案是否定的。一旦代码被执行了一次,被解释的代码就留在计算机或服务器的内存中,供其他执行使用。如果程序员修改了这段代码,新的版本将替换内存中的前一个版本。

JavaScript 也是一种脚本语言。您可能知道,通过查看源代码,可以在 web 浏览器中看到 JavaScript 代码,如图 1-9 所示。

A978-1-4842-1730-6_1_Fig9_HTML.jpg

图 1-9。

JavaScript, HTML, and CSS code from yahoo.com (11/11/14)

图 1-9 中显示的源代码来自 www.yahoo.com ,它显示了几种语言的组合,包括 HTML、CSS 和 JavaScript。JavaScript 代码(以黑色显示)位于脚本标签(<script type="text/JavaScript"></script>之间。如果您的浏览器允许 cookie,这段 JavaScript 代码将尝试在您的机器上创建一个 cookie。

然而,当我们查看 www.php.net 源代码时(图 1-10 ,我们看不到任何 PHP 脚本代码。这里有一些 PHP 文件的链接,但是没有显示实际的 PHP 代码。为什么?

A978-1-4842-1730-6_1_Fig10_HTML.jpg

图 1-10。

The www.php.net source code (11/11/14)

JavaScript 代码驻留在用户的计算机上。它在浏览器中被解释和执行。PHP 代码驻留在 web 服务器上。代码也可以被解释和执行,但是是由 web 服务器,而不是浏览器。执行 PHP 代码的结果被返回给浏览器,而不是实际的代码本身。

<?php

Print "Hello";

?>

Note

您可能会注意到使用 PHP 的其他格式(例如:<%<%=%><script language="php">);在 PHP 7 中,这些样式不再有效。以前实际上已经折旧了,但是仍然可以使用。

您可能会猜测这段代码会显示Hello。虽然这是正确的,但问题是,产生这种结果的过程是什么?

如果这段代码放在 web 服务器上的一个文件中(如hello.php),我们将使用我们的 web 浏览器通过在 URL(地址)框中输入它的名称和位置来请求这个文件(如 http://servera.com/hello.php )。输入的地址指示浏览器向 web 服务器(servera.com)发送 HTTP Get请求,以返回网页(hello.php)。

A978-1-4842-1730-6_1_Fig11_HTML.jpg

图 1-11。

Requesting an HTML/JavaScript web page

接收请求的 web 服务器将确定 PHP 代码必须首先被解释和执行。它只是通过查看所请求文件的文件扩展名(.php)来确定这一点。文件中的任何 PHP 代码都会被发送到 PHP 处理器进行解释和执行。代码的执行结果被返回给 web 服务器,web 服务器又将它(以及任何其他 HTML 和/或 JavaScript 代码)发送回浏览器。在本例中,浏览器将返回并显示Hello。如果我们接着查看源代码,如上所述,我们只会看到真正的单词Hello。我们不会看到任何 HTML 或 PHP。为什么呢?因为我们没有向浏览器发回任何 HTML。

A978-1-4842-1730-6_1_Fig12_HTML.jpg

图 1-12。

Requesting a web page with PHP code

您可能想知道是否可以使用这个过程发送回实际的 HTML(和/或 JavaScript)代码来创建动态 web 页面。答案是肯定的。PHP print函数将返回放置在""之间的任何 HTML(或 JavaScript)代码。浏览器将解释 web 服务器返回的任何代码。

Print function. Print function is not actually a function. It is a linguistic structure. The function string is required to be enclosed in quotation marks when passing. Structure does not need to enclose strings in quotation marks. Still recommended. Print will pass the content passed to it to the browser. It will try to convert any item that is not a string into a string (text) format, because all items displayed in the web page are in text format. For more information, visit: http :// php . net / manual / en / function . . php Please visit the free "New Boston" (thenewboston . com) video (s) at https :// www . thenewboston . com / videos . .

Note

本书提供的所有链接均可从 http://www.littleoceanwaves.com/securephp 进入。

<?php

Print "<h1>Hello</h1>";

?>

如果我们将代码更改为上面的清单,浏览器会将Hello显示为 HTML 标题(h1)。使用print函数的缺点是程序无法控制语句在网页上的显示位置。该语句实际上将显示为代码的第一行,甚至在任何其他现有的 HTML 标记之前。如果我们只是向用户返回一个语句,比如“您的过程已经完成”,这可能没问题。但是,如果您的目标是在页面上的精确位置格式化输出,这可能是不可接受的。我们可以选择其他技术和功能来消除这个问题。但是,这超出了我们目前的讨论范围。

现在我们知道我们必须在 web 服务器的帮助下解释和执行 PHP 代码,那么我们应该使用什么服务器呢?

Apache web 服务器是最常用于托管和处理 PHP 网页请求的服务器。像其他 web 服务器一样,Apache 也可以接受和返回对其他类型文件的请求,包括 HTML、JavaScript、PERL、图像和 RSS 提要。如前所述,Apache 通过首先查看所请求文件的文件扩展名来确定 HTTP 请求需要完成哪些进程。

A978-1-4842-1730-6_1_Fig13_HTML.jpg

图 1-13。

Apache.org web site (09/24/15)

Apache 和 PHP 一样,都是开源产品。Apache web 服务器的所有更改都由 Apache 软件基金会协调。ASP 维护着apache.org网站,为用户和开发人员提供发现当前正在开发的项目的能力,以及下载最新版本的 Apache 的能力。然而,如前所述,下载 PHP、Apache 和 MySQL 的不同版本会导致版本不兼容的问题。除非你知道自己在做什么,否则下载完整的 WAMP、LAMP 或 MAMP 版本要明智得多。

A978-1-4842-1730-6_1_Fig14_HTML.jpg

图 1-14。

Apache’s Get Involved (09/24/15)

Apache 软件基金会还鼓励他们产品的所有用户保持更新,并参与未来产品的开发。我们鼓励用户加入讨论和邮件组,测试新版本,甚至帮助修复 bug 或为他们的产品添加新功能。

做它

What are the differences in executing PHP code compared to executing Java code?   What is the difference between a scripting language and a programming language? What type of language is PHP?   How does the Apache web server handle requests for a PHP web page?   Why can we see JavaScript code within a web browser but we can’t see PHP code?   Go to www.apache.org . What are some of the ways that you can become involved with the development of Apache projects, even though you have limited experience?

PHP、Apache 和 MySQL

当网页向数据库请求信息时会发生什么?

通常,数据库存储在独立于 web 服务器本身的服务器上。

对数据的请求是来自 web 服务器还是来自 PHP 处理器?

由于 SQL 语句包含在 PHP 代码本身中,PHP 处理器将 SQL 语句发送到数据库管理系统(MySQL)进行处理。SQL—结构化查询语言是一种特殊的语言,用于更新、插入或删除 DBMS(数据库管理系统)中的数据。DBMS 是一种应用,它与程序语言和数据库进行交互,以更新、插入或删除数据。DBMS 使用 SQL 来解释数据库中数据所需的更改。有关 SQL 的更多信息,请访问http``://``en``.``wikipedia``.``org``/``wiki``/``SQL.

A978-1-4842-1730-6_1_Fig15_HTML.jpg

图 1-15。

Requesting a PHP web page that retrieves information from a MySQL database

Apache 服务器将首先发现 PHP 代码必须被解释。PHP 代码将被发送到 PHP 处理器。PHP 处理器解释代码(一行一行)。在这样做的时候,它会发现 SQL 语句必须针对数据库执行。然后,SQL 语句被传输到适当的数据库管理系统(DBMS)进行处理。DBMS 将把 SQL 语句的执行结果返回给 PHP 处理器。然后,PHP 处理器将使用这些结果来格式化输出,以提供给 Apache 服务器。然后,Apache 服务器将把 PHP 处理器返回的结果与可能驻留在原始请求页面上的任何其他 HTML(和/或 JavaScript)代码结合起来,并将所有输出返回给用户机器上的浏览器。然后,浏览器将解释 HTML 和 JavaScript 来显示所请求页面的结果。

你全都明白了吗?

让我们看一个“真实世界”的例子,如图 1-16 所示。

A978-1-4842-1730-6_1_Fig16_HTML.jpg

图 1-16。

Google.com search for “green cats” (11/12/14)

出于一些非常奇怪的原因,我们决定在互联网上查找“绿猫”。当我们将字符串输入到我们最喜欢的搜索引擎(本例中是 Google)中,然后单击 search 按钮,信息就会被传递到某个 Google 服务器群。哪里?

谁知道;它可能在地球上的任何地方。但互联网的强大之处在于,只要能快速拿回结果,我们就不在乎。

A978-1-4842-1730-6_1_Fig17_HTML.jpg

图 1-17。

Green cats (11/12/14)

好吧,我必须说我很惊讶有 1.04 亿个可能和绿猫有关的链接。哇哦。也许我们应该过滤掉一些。然而,关键是 Google 返回一个网页,上面有链接列表和对这些链接的描述(外加广告)。

Google 是否返回了一个已经存在的静态页面?

没有。服务器根据用户的请求创建了一个动态页面。谷歌算法(软件)搜索巨大的谷歌数据库农场。该请求首先从用户的浏览器发送到 Google web 服务器。然后,web 服务器向 Google 数据库发送请求(实际上使用了一种类似于 SQL 的 Google 查询语言)以返回关于“绿猫”的信息。web 服务器上的软件然后编译结果,添加 HTML 和 JavaScript(还有一些 Google Script 语言代码)来格式化结果网页,并将信息返回给用户。

Static and dynamic web pages-static web pages will not change due to user requests or inputs. This page is created by a web developer and resides on a web server. When a user requests a page through a browser, a copy of the page is sent to the browser for display. There is no dynamic page in the web server. Use input from users to create pages. The program residing on the web server will create and format the page. The page created by the program is then downloaded to the user's browser. A copy of the web page (usually) is not saved on the web server. For more information about static web pages, please visit http :// en . wikipedia . org / wiki / Static _ _ page For more dynamic webpage information, please visit: [http :// en . wikipedia . org / wiki / Dynamic _ web _ en.wikipedia.org/wiki/Dynamic_web_page

每个页面都被下载到用户的网络浏览器了吗?

不,只是第一页。第一个结果页面底部的页面链接向 web 服务器返回信息,请求下一组信息(以动态创建第二个页面或另一个请求的页面)。正如您现在可能开始发现的,我们刚刚讨论的与处理互联网上的 PHP 文件相关的相同过程是创建动态页面的一个非常常见的过程。

您可能已经注意到显示的结果页面 URL 地址有一些有趣的地方。现在的地址是 https://www.google.com/?gws_rd=ssl#q=Green+Cats ,而不是 www.google.com 。Google 算法在向服务器发送搜索请求时使用 GET HTTP 请求。

HTTP—-Hypertext Transfer Protocol is a protocol (standard) for transmitting messages (text and web pages) between nodes (computers and servers) on the Internet. This is a request-response protocol. For example, the user "requests" a web page through the browser. The web server "responds" to the request and returns the page to the browser. Convert the browser request into an HTTP Get request (such as GET / pages / mypage . html HTTP / . 1) and send it to the web server. The web server responds with the requested information and status code (such as http / 1 . 1 200 OK). For more information about HTTP, please visit http :// en . wikipedia . org / wiki / Hypertext _ Transfer _ Protocol Protocol

<form name="orders" method="get" id="orders" action="searchprocess.php">

Name: <input type="text" name="customername" id="customername"><br />

<input type="submit" value="Submit your name">

</form>

让我们看一个简单的例子来发现发生了什么。假设上面的代码保存在本地主机网站上的projects文件夹下的index.html文件中。

A978-1-4842-1730-6_1_Fig18_HTML.jpg

图 1-18。

Index.html example

如果用户在浏览器显示的文本框中输入Fred,结果页面(通过在 web 服务器上解释并执行searchprocess.php文件并将结果发送回浏览器而创建)将显示 URL 行: http://127.0.0.1/projects/searchprocess.php?customername=Fred

A978-1-4842-1730-6_1_Fig19_HTML.jpg

图 1-19。

Execution of searchprocess.php

文本框的名称(customername)和文本框中输入的值(Fred)现在在 URL 行上可见。实际上,customername现在是一个参数,Fred现在是参数的值。这是使用 GET 进程的结果。

当我们单击 Google Search 按钮或简单示例中的 Search 按钮时,请求的信息通过 GET HTTP 进程发送。接收程序所需的所有信息(和变量)都在实际的 URL 行上发送,由处理初始请求的 web 服务器上的程序接收。

为什么谷歌搜索引擎通过 GET 而不是 POST 发送信息(这样会隐藏信息)?

主要原因是为了节省服务器内存。想想谷歌对信息的数百万次请求。如果所有这些请求都驻留在内存中,服务器很快就会崩溃。此外,由于用户是在“公开”搜索信息,因此没有理由隐藏信息。在后面的章节中,我们将发现如何在 PHP 程序中读取 GET 和 POST 参数。

然而,现在让我们回到我们对 Apache、PHP 和 DBMS 的讨论。

PHP 可以从许多类型的 DBMS 系统中访问信息,包括 Oracle 和 SQL Server。然而,最流行的组合(如前所述)是将 PHP 与 MySQL 配对。我敢打赌,在这一点上,你能猜到为什么?是的,它是开源和免费的。MySQL 也是比较容易使用的 DBMS 系统之一。

A978-1-4842-1730-6_1_Fig20_HTML.jpg

图 1-20。

www.​mysql.​com (09/24/15)

就像我们讨论过的所有开源产品一样,用户可以去官方网站( www.mysql.com )下载最新版本。此外,正如您可能猜到的,您可以加入帮助开发 MySQL 新版本和相关产品的乐趣。可以下载当前版本和几个以前版本的文档。文档内容相当丰富,不适合智力低下的人或初学者。另外,由于你可能已经厌倦了阅读,所以不建议你单独下载 MySQL 的新版本,直到你有了更多的经验。坚持 WAMP,灯,MAMP 套餐,至少现在是这样。

做它

Why does the PHP processor send SQL to the DBMS instead of the Apache web server sending it directly to the DBMS?   What might Apache do with the information received from the DBMS before sending it back to the user’s browser?   Go to www.mysql.com . What is the latest version of MySQL? Which versions of Apache and PHP are compatible?   Why do search engines pass information via GET HTTP requests instead of POST HTTP requests? When would you use a POST request?

把它们放在一起——PHP、Apache 和 MySQL

到目前为止,您可能已经发现,PHP、Apache 和 MySQL 需要无缝地一起运行,才能成功地创建动态网页。有许多开发包提供了这些产品的组合,以及其他工具,如 PhpMyAdmin(这对设置 web 服务器和数据库有很大的帮助)。通过安装这些产品中的一个,你将大大降低你的沮丧程度,甚至可能设法保留你的大部分头发。我们将简要地看两个最流行的;EasyPHP 和 XAMPP。出于两个原因,我们不会深入探讨或提供分步安装指导。一旦这本书出版,方向可能会改变,使用默认设置是没问题的。大多数时候,接受开发人员的建议是可行的。

EasyPHP

A978-1-4842-1730-6_1_Fig21_HTML.jpg

图 1-21。

www.​easyphp.​org (09/24/15)

你可以从下面的链接下载 EasyPHP 的开发者版本。没有必要下载主机版本,除非您计划将“实时”网页直接从您的计算机托管到互联网上。

http://www.easyphp.org/easyphp-devserver.php

开发者版本的 EasyPHP 是一个 WAMP (Windows,Apache,MySQL,PHP)包,用于微软的 Windows 环境中。您还可以选择安装其他工具来帮助开发。然而,对于我们的目的,您只需要基本安装。安装后,这些文件将位于 EasyPHP 目录下的 program files 目录中。

Warning

下载时请注意您点击了网站上的哪些按钮。如果您在单击按钮之前没有阅读您正在下载的内容,您可能会下载您不感兴趣的额外项目。

单击网站上的下载箭头下载安装程序后,按照软件提供的说明进行操作。第一次尝试时,保留所有默认设置。希望一切都能正确安装。如果没有,请阅读下一节“常见安装问题”。

常见安装问题

缺少 C#库

PHP 7(以及 PHP 的早期版本)需要 Microsoft Visual Studio C#库。如果您使用的是 Windows 8 或更高版本,可能已经安装了该库。此外,如果您有最新版本的 Microsoft Visual Studio,它也可能已经安装。如果您收到一条错误消息,指出 C#丢失或版本错误,请将该消息粘贴到 Internet 上的搜索引擎中。搜索 Microsoft 的响应以获得修复错误的指导。响应应包括下载缺失文件和安装说明的链接。

端口冲突

如果您已经有一个使用端口 80 的服务,端口 80 是您的 PC 和外部世界之间的 HTML 流量的默认端口,当它试图运行时,您将收到来自 Apache 的错误消息。您可以通过多种方式解决这个问题。

Wikipedia defines ports as: in computer networks, ports are application-specific or process-specific software structures that are used as communication endpoints in computer host operating systems. The purpose of port is to uniquely identify different applications or processes running on a single computer, so that they can share a single physical connection to a packet-switched network like the Internet. In the context of Internet protocol, a port is associated with the IP address of the host and the type of protocol used for communication. For more information about ports, please visit: [http :// en . wikipedia . org / wiki / Port _( _ networking](http://en.wikipedia.org/wiki/Port_(computer_networking)

A.如果您不介意在开发时关闭使用该端口的其他服务,您可以遵循下面的说明。一旦你使用完 Apache 和 PHP,你可以重新打开服务或者重启你的电脑,服务就会重新打开。

Go to the Microsoft Windows 7/8/10 Task Manager (press Ctrl+Alt+Delete at the same time).   Select the Services tab.   Look for any of the following services in Windows 7/8/10. If you find one running, right-click it and turn it off. Then try restarting Apache again. If that does not work, turn that one back on and try another one. (The names may be slightly different depending on the version of Windows.)

SQL Server Reporter、Web 部署代理、BranchCache、同步共享服务、WAS (IIS 管理员)和 W3SVC

B.如果您需要运行其他服务,或者您没有关闭端口 80 上的服务的管理权限,您可以更改 Apache 的默认监听端口位置。

转到系统托盘(屏幕右下角)。通过滚动图标找到 EasyPHP 图标。应该会出现每个的描述。如果看不到图标,请单击系统托盘中的向上箭头以查看更多图标。右键单击 EasyPHP 图标。选择配置,然后选择 Apache。这将在记事本(或您的默认文本编辑器)中打开 Apache 配置文件(httpd.conf)。首先在某个地方保存这个文件的副本,以防出错。这将让你从任何重大错误中恢复过来。然后在文件中搜索Listen 127.0.0.1:80。仅在该行上将80的出现改为808081;。这将允许 Apache 服务器监听一个不常用的端口。重新保存文件(确保将原始文件重新保存到原始位置)。

Note

确保在使用记事本或任何其他文本编辑器时使用“另存为”,然后选择“所有文件”作为文件类型。还要确保包含文件扩展名.conf。如果您不将文件类型更改为所有文件,您的文件将被保存为httpd.conf.txt。如果发生这种情况,服务器将看不到该文件。您可以通过重新打开文件并以正确的方法保存它来轻松解决这个问题。

然后,您可以通过返回系统托盘找到 EasyPHP 图标来重新启动 Apache。双击图标;将出现一个消息框,显示 Apache 和 MySQL 的状态。对于 Apache 状态,您可能会看到红色。单击 Apache 按钮。几分钟后,它应该会变成绿色。这将表明服务器现在正在运行。对 MySQL 做同样的事情。

丢失的文件

如果您收到一条与此相关的错误消息,不知何故,您的文件在安装前已经损坏。返回 EasyPHP 网站,再次下载文件。此外,如果您不知何故弄乱了 Apache 配置文件,请返回并重新安装产品。

无法在程序文件目录中安装文件

这表明您或其他人对该目录有很高的安全性限制。重新运行安装,并将安装位置更改为另一个目录。请记住,当我们在本书后面引用程序文件目录时,您应该查看文件的安装目录。

Apache 延迟和挂起

在 Windows 8/10 中,您可能会遇到 Apache 运行缓慢或挂起的问题。要纠正这个问题,请转到系统托盘(屏幕右下角)。通过滚动图标找到 EasyPHP 图标。应该会出现每个的描述。如果看不到图标,请单击系统托盘中的向上箭头以查看更多图标。右键单击 EasyPHP 图标。选择配置,然后选择 Apache。这将在记事本(或您的默认文本编辑器)中打开 Apache 配置文件(httpd.conf)。首先在某个地方保存这个文件的副本,以防出错。这将让你从任何重大错误中恢复过来。然后将下面两行添加到文件的底部。

接受过滤器 http 无

AcceptFilter https none 重新保存文件(确保将原始文件重新保存到原始位置)。

其他错误

对于这里没有讨论的错误,将错误复制并粘贴到搜索引擎中。找到一个回答专栏或博客,提供修复错误的建议。网上有很多免费资源。不要花钱请网站(或其他人)来解决你的问题。

配置

您需要确定希望 Apache 何时运行。Apache 可以设置为在您启动 PC 时、应用需要时运行,或者手动运行。要更改设置,您可以右键单击系统托盘(屏幕右下角)中的 EasyPHP 图标,然后选择配置,然后选择 EasyPHP。如果看不到图标,请点按系统托盘中的向上箭头。应该会出现一个小屏幕,允许您选中(或取消选中)两个选项—在会话启动时启动和在应用启动时启动服务器。

有许多可选的库,您可以根据需要链接或取消链接到 PHP。在许多情况下,库已经被加载,只需要被链接。您可以通过转到 PHP 配置文件(php.ini)并删除行首的注释(;)字符来添加这些库。右键单击系统托盘中的 EasyPHP 图标可以很容易地找到 PHP 配置文件。然后选择配置和 PHP。此时没有必要进行任何更改。还建议您仅在需要时进行这些更改。可以使用 Curl 和本书后面章节讨论的其他方法添加其他库。

要更深入地了解php.ini文件,请在https://www.thenewboston.com/videos.php?cat=11&video=16993观看免费的“新波士顿”(thenewboston.com)视频。

XAMPP(洗发精)

A978-1-4842-1730-6_1_Fig22_HTML.jpg

图 1-22。

XAMPP at www.​apachefriends.​org (09/24/15)

虽然 XAMPP 与 EasyPHP 相似,但 XAMPP 更受欢迎,因为它有免费的 Windows、Linux 和 OS X 版本。它还包括许多附加组件,包括一些最流行的内容管理系统——Drupal、Joomla 和 WordPress。可以在 XAMPP 官方网站或许多其他下载位置直接获得最新下载。

https://www.apachefriends.org/

Warning

注意你在网站上点击的按钮。如果您在单击按钮之前没有阅读您正在下载的内容,您可能会下载您不感兴趣的额外项目。

在您第一次尝试安装时,请使用开发人员在安装软件中建议的默认设置。你将大大减少出现问题或头痛的可能性。如果确实有错误,请阅读下一节“常见安装问题”以获得帮助。

常见安装问题

端口冲突

如果您已经有一个使用端口 80 的服务,端口 80 是您的 PC 和外部世界之间的 HTML 流量的默认端口,当它试图运行时,您将收到来自 Apache 的错误消息。您可以通过多种方式解决这个问题。

A.如果您不介意在开发时关闭使用该端口的其他服务,您可以按照视频链接上的说明进行操作。一旦你使用完 Apache 和 PHP,你可以重新打开服务或者重启你的电脑,服务就会重新打开。

Go to the Windows 7/8/10 Task Manager (press Ctrl+Alt+Delete at the same time).   Select the Services tab.   Look for any of the following services in Windows 7/8/10. If you find one running, right-click it and turn it off. Then try restarting Apache again. If that does not work, turn that one back on and try another one. (The names may be slightly different depending on the version of Windows.) SQL Server Reporter, Web Deployment Agent, BranchCache, Sync Share Service, WAS (IIS Administrator), and W3SVC

B.如果您需要运行其他服务,或者您没有关闭端口 80 上的服务的管理权限,您可以更改 Apache 的默认监听端口位置。

转到系统托盘(在 Microsoft Windows 中,它位于屏幕的右下角)。通过在图标上滚动鼠标找到 XAMPP 图标。应该会出现每个的描述。如果看不到图标,请单击系统托盘中的向上箭头以查看更多图标。双击该图标。应该会出现控制面板。您应该会在控制台上看到红色的启动错误消息。如果是端口冲突,单击 Apache 右侧的 Config 按钮。从提供的列表中选择httpd.conf。这将在记事本(或您的默认文本编辑器)中打开 Apache 配置文件(httpd.conf)。首先在某个地方保存这个文件的副本,以防你出错。这将允许您恢复原始文件。在文件中搜索 Listen 80。将80的发生更改为808081。这将允许 Apache 服务器监听其中一个不常用的端口。重新保存文件(确保将原始文件重新保存到原始位置)。

Note

确保在使用记事本或任何其他文本编辑器时使用另存为,然后选择所有文件作为文件类型。还要确保包含文件扩展名.conf。如果您不将文件类型更改为所有文件,您的文件将被保存为httpd.conf.txt。如果发生这种情况,服务器将看不到该文件。您可以通过重新打开文件并以正确的方法保存它来轻松解决这个问题。

然后,您可以通过在 XAMPP 控制台中单击 Apache 旁边的 start 按钮来重新启动 Apache。如果 Apache 的状态为绿色,那么您还需要通过单击 MySQL 右侧的 start 按钮来启动 MySQL。

丢失的文件

如果您收到一条与此相关的错误消息,不知何故,您的文件在安装前已经损坏。返回 XAMPP 网站,再次下载文件。如果您不知何故弄乱了 Apache 配置文件,您还需要再次下载这些文件。

无法在程序文件目录中安装文件

这表明您或其他人对该目录有很高的安全性限制。重新运行安装,并将安装位置更改为另一个目录。请记住,当您在本书后面引用程序文件目录时,您应该查看文件的安装目录。

Apache 延迟和挂起

在 Windows 8/10 中,您可能会遇到 Apache 运行缓慢或挂起的问题。要纠正这个问题,请转到系统托盘(屏幕右下角)。通过滚动图标找到 XAMPP 图标。应该会出现每个的描述。如果看不到图标,请单击系统托盘中的向上箭头以查看更多图标。右键单击 XAMPP 图标。选择配置,然后选择 Apache。这将在记事本(或您的默认文本编辑器)中打开 Apache 配置文件(httpd.conf)。首先在某个地方保存这个文件的副本,以防出错。这将让你从任何重大错误中恢复过来。然后将下面两行添加到文件的底部。

接受过滤器 http 无

AcceptFilter https none

重新保存文件(确保将原始文件重新保存到原始位置)。

其他错误

对于这里没有讨论的错误,将错误复制并粘贴到搜索引擎中。找到一个回答专栏或博客,提供修复错误的建议。网上有很多免费资源。不要花钱请网站(或其他人)来解决你的问题。

配置

您可以转到控制面板来更改 XAMPP 的配置(双击屏幕右下角系统托盘上的 XAMPP)。然后单击屏幕右上角的配置按钮(不是应用右侧的配置按钮)。然后,您可以选中(或取消选中)那些希望在下次控制面板启动时自动启动的应用。你应该在 Apache 和 MySQL 中查找这本书的内容。当然,你可以在需要的时候从控制面板启动它们。

有许多可选的库,您可以根据需要链接或取消链接到 PHP。在许多情况下,库已经被加载,只需要被链接。您可以添加这些库,方法是转到 PHP 配置文件(php.ini)并删除行首的注释(;)字符。双击系统托盘中的 XAMPP 图标可以很容易地找到 PHP 配置文件。然后单击 Apache 右侧的 Config 按钮。将显示一个列表;选择php.ini。此时没有必要进行任何更改。还建议您仅在需要时进行这些更改。可以使用 Curl 和本书后面章节讨论的其他方法添加其他库。

微软互联网信息服务器

或者,如果您无法让 Apache 在 Windows(尤其是 Windows 8 或 Windows 10)中正常运行,或者您喜欢微软的 IIS 服务器,您可以安装 PHP 来使用微软 IIS(互联网信息服务器)而不是 Apache。有关更多信息,请访问:

http://www.microsoft.com/web/platform/phponwindows.aspx

做它

Use a search engine to answer this question: You receive the following error either while installing or as soon as you try to start XAMMP or EasyPHP. How can you find the solution to your problem? What might be causing this error? Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request. Please contact the server administrator, you@example.com and inform them of the time the error occurred, and anything you might have done that may have caused the error. More information about this error may be available in the server error log.   Use a search engine to answer this question: What is XAMPP error #1130? How can you fix this error?   Use a search engine to answer this question: When trying to run a PHP program using EasyPHP (and Apache) you receive the error below. What is causing this error? How can you fix it? Cannot load mcrypt extension. Please check your PHP configuration.   If you have not already attempted to do so, install either EasyPHP or XAMPP on your personal machine. Did you have any problems with your installation? If so, what problems did you have? How did you solve those problems?

测试您的环境

你现在有绿灯了,对吧?一切正常吗?

希望如此。然而,你需要确保。最好的方法是测试您的环境。

测试您的管理环境

首先,我们需要测试服务器,看看我们的管理页面是否会显示。在 EasyPHP 中,您可以执行以下任一操作:

1.右键单击 EasyPHP 图标并选择管理。

或者

2.打开您最喜欢的浏览器,输入以下内容:

http://127.0.0.1/home/

如果由于冲突而不得不更改您的端口,您可能需要输入端口号,例如:

http://127.0.0.1:8080/home/

您应该会看到一个类似于图 1-23 所示的屏幕。

A978-1-4842-1730-6_1_Fig23_HTML.jpg

图 1-23。

EasyPHP administration screen (PHP 5.6)

对于 XAMPP,打开您最喜欢的浏览器,输入以下地址:

http://127.0.0.1/dashboard/

如果由于冲突而不得不更改您的端口,那么您还必须包括此端口:

http://127.0.0.1:8080/dashboard/

如果 XAMPP 安装正确,您应该会看到类似于图 1-24 所示的屏幕。

A978-1-4842-1730-6_1_Fig24_HTML.jpg

图 1-24。

XAMPP dashboard screen (09/24/15)

如果此时你看不到这个页面(或显示 XAMPP 的某个页面),那一定是出了问题。检查以下内容:

是 EasyPHP 还是 XAMPP 在运行(启动)?你在 XAMPP 控制面板或 EasyPHP 中看到 Apache 的绿灯或绿色阴影了吗?如果没有,请尝试单击开始按钮或链接。

如果 Apache 无法启动,您会看到错误信息吗?如果没有,请检查错误日志文件。对于 EasyPHP,右键单击系统托盘上的图标,并选择 Apache 的错误日志文件。对于 XAMPP,单击 Apache 旁边的控制面板上的日志按钮,并选择错误日志。

如果您看到绿色并且页面似乎被锁定,请尝试停止并重新启动 Apache。可能要试几次才能唤醒它。如果它继续挂起,请检查您的计算机设置。您是否最大限度地利用了 CPU?

你能确定问题吗?如果你有一个错误信息,把它粘贴到你最喜欢的搜索引擎中,看看专家对这个问题怎么说。

做它

1.如果您还没有这样做,请按照上面的说明测试您的环境。你有什么问题吗?如果是,发生了什么问题?你是如何解决这些问题的?

测试您的 PHP 环境

希望此时一切都好。要么你运气一直很好,要么你设法解决了你遇到的所有问题。然而,你仍然需要看看你是否能在 Apache 中运行你自己的 PHP 程序。

打开一个文本编辑器(不是 Word,但是 Notepad 或 Notepad++都可以),按照显示的内容输入下面的代码。

<?php

print "Hello World";

?>

A978-1-4842-1730-6_1_Fig25_HTML.jpg

图 1-25。

Using Save As with All Files to save PHP programs

使用文件菜单上的另存为选项,将文件类型更改为所有文件或php。输入文件名myfirstprogram.php并将其保存在以下位置之一。

如果您使用的是 EasyPHP,请将其保存到:

C:\Program Files (x86)\EasyPHP-DevServer-14.1VC11\data\localweb\projects

当然,您应该将版本名称(或程序文件名)更改为您在计算机上使用的正确版本(位置)。如果您的 EasyPHP 版本没有 localweb\projects 文件夹,请在 EasyPHP 文件夹下找到www文件夹的位置,并创建一个名为projects的文件夹。然后将文件保存在该文件夹下。

如果你正在使用 XAMPP,首先转到C:\xampp\htdocs并创建一个名为projects的文件夹。然后回到你的文本编辑器,选择另存为(不要忘记将文件类型改为所有文件或php),将文件命名为myfirstprogram.php并保存到下面的位置。

C:\xampp\htdocs\projects

如果你在 EasyPHP 或 XAMPP 位置正确地保存了你的文件,你可以通过在你的浏览器 URL 框中输入以下内容来运行你的程序。

http://127.0.0.1/projects/myfirstprogram.php

如果您更改了端口,则将第一部分更改为 http://127.0.0.1:8080/ (输入您正在使用的正确端口来代替8080)。

你的程序应该显示如图 1-26 所示的信息。

A978-1-4842-1730-6_1_Fig26_HTML.jpg

图 1-26。

Hello World

常见问题

不显示任何内容,错误 404:

Make sure you typed the address exactly as shown.   Your server might be hung up. Stop and restart it.   Make sure you placed your file in the correct location.   Make sure you saved your file as a .php file and not as .txt. Try Save As again and renaming the file (make sure file type is either All Files or php).   Check for typos in your actual program code. Did you remember the semicolon (;)? Fix any and resave. You might need to stop and start the server if it does not see the changes for some reason. You can go to the log files and look at the PHP log files to see any errors that might exist in your code.   Go to the Apache log files (see directions in previous common problems) to look for errors. If you cannot correct them, copy the errors and paste them in a search engine to see what others have found as solutions.

显示的是实际的程序代码,而不是执行代码的结果:

Make sure you saved your file as a .php file and not as .txt. Try Save As again and renaming the file (make sure file type is either All Files or php).   Your Apache server or PHP might not be started or is hung. Stop and start Apache again.   Did you forget or have a typo in the <?php or ?> lines?   Go to the Apache log files (see directions in previous common problems) to find the errors. If you cannot correct them, copy the errors and paste them in a search engine to see what others have found as solutions.

对于任何其他错误,将它们复制并粘贴到网络搜索引擎中,看看其他人发现了什么解决方案。

EasyPHP 的代码教室

如果你在电脑上安装 LAMP、MAMP 或 WAMP 软件时仍然有问题,一切都还没完。

EasyPHP ( www.easyphp.org )现在为学生和教师提供了一个在线编码环境。这个环境(见图 1-27 )允许你输入代码(下面的黑色窗口),点击提交按钮(红色按钮),并在右边看到你的结果(白色窗口)。

A978-1-4842-1730-6_1_Fig27_HTML.jpg

图 1-27。

www.​codeclassroom.​net

做它

1.如果您尚未测试您的环境,请进行测试。测试成功了吗?你遇到过什么问题,如果有的话?你是如何解决那些问题的?

别名目录

在现实世界中,在 web 服务器中创建别名目录是常见的做法。别名目录是文件的“虚假”位置,它欺骗网站的用户,使他们相信文件在一个位置,而它在另一个位置。

为什么要使用别名目录?随着网站的增长,文件在服务器上的位置可能必须改变。通过使用别名,网站的用户不会知道文件的实际位置已经改变。别名使您能够将文件存储在计算机(服务器)上的任何位置。如果不使用别名,所有文件都必须存储在默认位置。默认位置是:

easy PHP:

xampp:

您可能需要考虑创建一个别名目录,尤其是当您希望将文件放在一个跳转驱动器上时。在本书中,我们假设文件位于默认位置下的一个projects文件夹中。

easy PHP:

xampp

这将允许我们使用相同的 URL 测试程序,不管我们是使用 EasyPHP 还是 XAMPP。

http://127.0.0.1/projects/myfirstprogram.php

如果您确实创建了一个别名目录,请记住用您正在使用的别名替换projects

A978-1-4842-1730-6_1_Fig28_HTML.jpg

图 1-28。

EasyPHP alias screen

在 EasyPHP 中,可以从管理屏幕创建别名目录。转到系统托盘(屏幕右下角),右键单击 EasyPHP 图标,然后选择 Administration。在屏幕中间,找到本地文件,然后找到右侧的添加别名按钮。点击并按照指示操作。添加别名之前,该文件夹必须已经存在。

A978-1-4842-1730-6_1_Fig29_HTML.jpg

图 1-29。

The httpd-xampp file

在 XAMPP,这需要更多的工作。双击 XAMPP 图标打开系统面板。然后单击 Apache 右侧的 Config 按钮。从列表中选择httpd-xampp配置文件。选择编辑。从文本编辑器菜单中查找。搜索字符串'<IfModule alias_module>'。不要更改本节中已经列出的任何内容。但是,您可以为您的可执行文件添加位置(使用下面的代码)。一旦输入了所需的行,停止并启动 Apache 来帮助它找到新的变化。

Alias /myfiles "C:/Temp"

<Directory "C:/Temp">

Options Indexes FollowSymLinks MultiViews ExecCGI

AllowOverride All

Require all granted

</Directory>

有关创建别名目录的更多信息,请访问:

https://www.youtube.com/watch?v=XX6t3zJRXF8

Note

此目录设置允许对目录的完全读写能力。我们将在后面的章节中讨论保护“实时”网站目录的选项。

A978-1-4842-1730-6_1_Fig30_HTML.jpg

图 1-30。

Hello World running from the alias directory called myfiles as an index.php file

当用户在 URL 行中输入目录名myfiles时,这个清单将允许在 Apache 中执行C:/Temp目录中的任何文件。所提供的目录设置不太安全。然而,这只是为了在测试机上进行测试。如果你在一个真实的环境中,你需要加强directory标签下的安全设置。要执行这个目录中的文件,您可以在浏览器中输入 URL http://127.0.0.1/myfiles 。如果不包含文件名,Apache 将尝试查找一个index.htmlindex.php文件。如果两者都不存在,Apache 将使用当前设置列出目录中的文件。这允许容易地访问用于测试的文件。然而,在真实环境中,这并不是一个好主意。您可以通过添加文件名( http://127.0.0.1/myfiles/myfirstprogram.php )来请求不是索引文件的文件。如果必须更改端口号,请记住包括端口号( http://127.0.0.1:8080/myfiles/myfirstprogram.php )。当然,在浏览器中测试 URL 之前,目录必须存在并且文件必须在目录中。

做它

1.创建一个名为myfiles的别名文件夹,该文件夹指向硬盘或跳转驱动器上的现有位置。将您用来测试您的环境的hello world程序放在文件夹中。尝试使用前面的说明运行文件夹中的程序。你成功了吗?如果不是,你有什么问题?你是如何解决这些问题的?

记事本++编辑器和代码测试器

创建 PHP 代码时,您不需要使用特定的编辑器或购买编辑器。您可以在文本编辑器中(甚至在记事本中)创建所有代码。然而,拥有一个突出显示(着色)代码的编辑器确实有助于更容易地发现编码(语法)错误。

记事本++

A978-1-4842-1730-6_1_Fig31_HTML.jpg

图 1-31。

Hello World inside Notepad++

记事本++可在 www.notepad-plus-plus.org 下载。这是一个简单易用的编辑器。它可以创建超过 20 种不同形式的文件(包括 PHP)。本书中提供的大多数编码示例都是用 Notepad++创建的。当你在 Notepad++中创建新的 PHP 程序时,你必须告诉应用文件的类型。您可以通过选择语言菜单项,然后选择 PHP 来完成此操作。这将允许应用帮助您进行颜色编码和其他功能。

其他编辑

还有数百个其他可用的编辑器。我建议你挑一个你用起来最舒服的; www.download.com 是一个寻找 PHP 免费编辑的好网站。选择一个有很多用户和一个强大的评级。

如前所述,您可能还想试试 EasyPHP 的代码教室,网址是:

www.codeclassroom.net

做它

1.在使用这本书的时候,决定一个你将用来完成你的编码的文本编辑器。将编辑器下载到您的 PC 上。在编辑器中重新键入您的hello world程序,并保存该程序。从浏览器运行程序。你为什么选择你下载的编辑器?你喜欢编辑的什么?你不喜欢编辑的什么?

章节术语

| 服务器端编程语言(Professional Hypertext Preprocessor 的缩写) | 过程语言 |
| 面向对象语言 | www.php.net |
| 服务器 | 灯 |
| 美国。 | 脚本语言 |
| 程序设计语言 | Java Script 语言 |
| 网络浏览器 | 源代码 |
| 超文本标记语言 | 半铸钢ˌ钢性铸铁(Cast Semi-Steel) |
| 脚本标签 | 网络服务器 |
| print功能 | 街头流氓 |
| 阿帕奇软件基金会 | 结构化查询语言 |
| 数据库管理系统 | 静态页面 |
| 动态页面 | 获取 HTTP 请求 |
| 关系型数据库 | EasyPHP(开发者版本) |
| 端口 80 | httpd.conf |
| php.ini | XAMPP(洗发精) |
| XAMPP 控制面板 | 别名目录 |
| 开放源码 | 记事本++ |

第二章问题和项目

多重选择

MAMP stands for Mobile Application Motor Processor   My Awesome Mobile Program   Apache-MySQL-PHP   Modern Application Modular Programing   None of these     The goals of the Apache Software Foundation (ASP) include Coordination of all changes to the Apache web server   Overseeing the selling of all Apache software   Licensing all web servers   Maintaining membership to open source programming through the Apache web server     What scripting languages can be viewed within a web browser? Java   XML   JavaScript   PHP     Select the statements that are true about static web pages: They are sometimes called flat page/stationary pages   They are generated by a web application   Static web pages display the same information for all users   All of the above   A and C     ___________ is an object-oriented computer programming language commonly used to create interactive effects within web browsers. HTML   XML   JavaScript   PHP     What is the correct way to end a PHP statement? ;   New line   <!php>   .     The configurations button on the XAMPP Control Panel allow the user to do what? Install XAMPP   Select applications to automatically start   Manage add-ons   Configure MYSQL     php.net provides all of the following except Information of each latest release   PHP language documentation   LAMP, MAMP, and WAMP downloads   Contributions to the PHP documentation     Which of the following best describes a scripting language? The language is compiled.   The program must be compatible with operating system and hardware.   The first time the code is accessed is when the program is first executed.   The code is slower than compiled code

真/假

The alias directory allows you to save your web page files in a directory (folder) of your choice rather than a www directory. It allows a URL address to point directly to that folder.   A static page is a page added to the dryer when drying clothes to prevent static cling.   A client browser submits a GET HTTP request to the server, then the server returns a response to the client browser.   The (Developer’s Version) of EasyPHP can be purchased only for one year at a time.   A dynamic web page never changes but a static web page is always changing.   WAMP stands for Windows, Apache, MySQL, and PHP.   Apache is an example of a web server.   HTML stands for Hypertext Markup Language.   PHP originally stood for Personal Home Page.   A procedural language is a programming language that uses classes and objects.   While viewing a web site in a browser, you may view the source code, which can show you HTML, JavaScript, and CSS. However, PHP code will not be visible.   Java is a procedural programming language.

简答/短文

Explain the process that occurs when a browser requests a static web page.   Explain the process that occurs when a browser requests a dynamic web page.   Explain the difference between a procedural language and a scripting language.   How do you add access to PHP library code to your program?   Why should you use WAMP, MAMP, or LAMP instead of installing each application individually?

项目

Create a PHP program to display a summary of your work history and educational history.   Create a PHP program to display your major and the courses required to complete your major.   Create a PHP program to display the goals and/or objectives of your college/university.

学期项目

Your supervisor has requested that you design a secure application that will keep track of inventory in the warehouse of the ABC Computer Parts Corporation. This application will be accessible both within the warehouse itself and outside (via the Internet and/or smart phones). What data fields are necessary to keep track of this information? What size and data types (string, integer, floating point) should define these fields? What other restrictions on input (no negative values for item number) should be defined for these fields? For example, if a Social Security Number (SSN) was a required field: SSN: SIZE: Min: 9 characters (string) Max: 9 characters (string) Restrictions: Valid SSN format Notice that it uses characters, not integers, because no calculations will be done on the SSN number.

二、接口、平台、容器和三层编程

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1730-6_​2) contains supplementary material, which is available to authorized users.

"As of January 2013, more than 240 million websites (accounting for 39% of the sampled websites) and 2.1 million web servers have installed PHP."

——Ide,安迪(2013-01-31)。“PHP 只是增长&增长

章节目标/学生学习成果

完成本章后,学生将能够:

  • 给出可以托管 PHP 程序的平台或容器的例子
  • 使用 PHP 创建一个简单的动态 web 应用
  • 解释三层设计并确定每层包含的内容
  • 设计三层应用
  • 解释程序开发生命周期的每一步(PDLC)
  • 定义并解释 MVC 和依赖注入

PHP 平台和容器

PHP 是一种强大的语言,因为它可以适用于几乎任何硬件或软件平台。它与 HTML 和 JavaScript 接口的便利性为 PHP 应用提供了在任何可以托管浏览器的系统上运行的能力。越来越多的应用是使用浏览器作为应用的主要界面工具来创建的。这使得应用可以在 PC、互联网甚至智能手机上运行,而无需在设备上安装实际的应用(或其他软件)。它还允许用户在设备之间切换时体验相同的应用“感觉”。

在接下来的几节中,我们将简要介绍一些可以托管 PHP 应用的平台和容器。我们将互换使用平台和容器这两个词。在这个讨论中,我们认为平台或容器是一个软件工具,它“托管”PHP 程序和执行程序返回的任何输出。

PHP PC 应用

PHP 程序可以开发成运行在操作系统中的 PC 应用。然而,创建这些应用所需的技能要求深入理解操作系统和事件驱动的 PHP 程序之间的通信。这些高级技能超出了这本入门书所展示的概念。然而,有些人可能会认为完成这项工作所需的技能是不必要的,因为 PHP 程序可以在任何操作系统中的任何浏览器上运行。

PHP PC 应用确实具有“创建一次”和“在任何地方运行”的能力,就像其他语言(比如 Java)一样。PHP PC 应用可以被创建成不需要(或很少)在 PC 操作系统和硬件平台之间改变代码。

GTK SDK(软件开发工具包)是可用于 PC 应用开发的几种工具之一。如果你访问网站( http://gtk.php.net/ ),你可以找到最新版本和未来发展的信息。

If you are interested in PHP PC application development, try the link below or search for "PHP GTK Tutorial" in your browser. http://www.developertutorials.com/building-desktop-applications-in-php-8-02-01/

PHP 智能手机应用

PHP 也可以用来创建智能手机应用。通常,这是为了尝试将当前的 web 应用更新到智能手机环境中。智能手机应用开发需要在程序和智能手机操作系统之间开发通信链接的高级技能。

使用智能手机操作系统内置的一些功能(如 GPS)可能需要智能手机应用。所有智能手机操作系统都提供 API(应用接口)调用,允许手机上的应用使用这些功能。尽管 web 应用(将在本章后面讨论)可以在智能手机的浏览器中运行,但它们不能对智能手机操作系统进行 API 调用(因为它们不在手机本身中)。如果应用需要与智能手机操作系统交互的能力,它必须驻留在智能手机上。

开发智能手机应用需要高级编程技能和对智能手机操作系统的深入了解。使用目前可用的免费智能手机 SDK(软件开发工具包)可以减少一些麻烦。一些 SDK 现在提供了转换应用以在多种操作系统上使用的能力。

在尝试创建智能手机应用之前,您应该具备良好的基本 PHP 技能。

PHP 脸书和其他社交应用

PHP 可以用来创建托管在脸书画布平台和其他社交应用(如 Twitter)中的应用(和游戏)。这些应用中使用的技术是相似的。我们将简单地以脸书的油画为例。

应用可以使用工具在脸书本身(如脸书登录)使用 API(应用接口)调用。例如,应用可以使用脸书工具来监控应用的使用。当用户使用脸书登录功能登录到应用(或游戏)时,用户必须承认并接受运行该程序所需的安全级别。通过接受这些设置,用户授予应用进行 API 调用的权限,以便从用户的脸书环境中检索信息(可能是更改信息)。

您可以创建仅由脸书托管的 PHP 应用,而不需要 API 编程知识。许多包含用于更多信息的点进能力的广告被托管在画布中,但是实际上驻留在位于互联网上其他地方的 web 服务器上。脸书画布确实有能力解释 HTML 和 JavaScript 代码。这使得开发人员能够创建既可以在画布中显示又可以在外部浏览器中显示的应用。

让我们简单看看如何在脸书画布中托管应用。

您将保持“开发人员”模式的开发。这意味着当你完成演示后,脸书的每个人都将无法访问该应用。要使您的应用“活跃”,您还必须经历额外的步骤(详情见developers.facebook.com)。

Warning

脸书经常改变它的安全设置和 API(应用员接口)。随着时间的推移,下面的一些步骤可能会略有变化。如果你发现这些步骤不起作用,在互联网上搜索“一个简单的 PHP 脸书应用”,并检查教程或视频的创建日期,以确保你有最新的信息。

做它

完成以下工作:

For this example, you need to use your browser to search for an existing secure web page (one that uses https instead of http). Facebook no longer allows non-secured web pages to be hosted in the Canvas. Since this is a PHP book, it would be preferable to find a web application that includes PHP code (one that has a file ending of php instead of html). However, you can use an HTML site for this example. Remember, this is just for demonstration and educational purposes. You should always request permission to host a web site in another site (such as Facebook Canvas). All web sites are assumed to be protected by copyright laws (even when there is no indication on the site that they are copyrighted). Note: Some sites are secured to not allow their site to be hosted in another site. If you follow these directions and the site does not display, that may be the cause. If that happens, search for another secured web page. You could elect to use your own web page (such as the hello world example). However, the web page will need a hosting environment (besides your PC). Facebook cannot interface with your localhost development environment. There are many low-price hosting sites now available; some for as little as $1 a month. Most hosting companies enable you to have secured sites (using https). However, there is usually an additional fee for this service. When picking a host site, make sure to read the fine print to determine if it will host PHP applications. It is a good idea to have a hosting site for you to upload your test programs. Programs should be tested in multiple environments. Sometimes programs react differently in different environments. If you have your own secure URL, or one that your school allows you to use, upload your Hello World program onto the host. Then test it to make sure it still works in the host and make sure that you know the actual URL address that will run the program.   Sign in to Facebook ( www.facebook.com ). If you do not have a Facebook user ID/password, you can create one for free. This demo does not require you to completely build a Facebook page. If you have an existing account, you might consider creating an extra account (with a different e-mail address) just for your development needs.   Once you are signed in to Facebook, go to the developer’s page (developers.facebook.com). Find the Menu on the page. Select Apps, and then select Add New App. A screen similar to the one shown in Figure 2-1 will appear. Locate and select the Facebook Canvas option.

A978-1-4842-1730-6_2_Fig1_HTML.jpg

图 2-1。

Selecting Canvas from Facebook developer’s page (11/17/14) (Remember that Facebook does change their screens. However, the sign-in process should be similar to this demo.) The next screen requires you to enter a name for your application. Any unique name is fine since this application will not become live. You might also be prompted with some other questions related to whether this app is part of another app (no) and you may be requested to select a category (Apps for Pages would be fine). Once you have filled (and selected) the required information click the Create App ID button.   You should now see a Quick Start page similar to Figure 2-2. Ignore the code at the top of the Quick Start page (it’s beyond what we want to accomplish). Scroll down the page to find the textbox that says, “Where Is Your App Hosted?”. You may see a textbox (not required) that requests you to enter a unique URL for your app. If you do not enter one, Facebook will generate one. For our demo, we will leave it blank.

A978-1-4842-1730-6_2_Fig2_HTML.jpg

图 2-2。

The Facebook Quick Start page Another textbox requires you to enter a secure URL to link your app into Facebook. This is the actual location of your application on the Internet. Enter the complete address of the secure web page you have discovered or enter the location of your “Hello World” program if it is hosted on a secure site (https). Note: Facebook requires (at least) an ending slash for your link, something similar to: www.mysita.com/projects/myfirstprogram.php/ Click the Next button.   The next page displayed will show more example code. This code can be used to log in to your application using the Facebook login and has the ability to request Facebook permissions for your application. However, our demo does not need login ability or any Facebook permissions. Just click the Next button at the bottom of the page.

A978-1-4842-1730-6_2_Fig3_HTML.jpg

图 2-3。

The Facebook Open App page   Click the Open Your App button. Once you click the button, Facebook will try to load your page in the Canvas. However, if you have picked an existing secure web page and see a message similar to Figure 2-4, the page you selected might not allow hosting in another site. If this happens, go to Steps 7-10. If you were successful, skip to Step 11. However, copy and paste the URL (from the browser) that Facebook uses to display your page, so you can test it again, if needed.

A978-1-4842-1730-6_2_Fig4_HTML.jpg

图 2-4。

Facebook open app failure   If your page did not display, do the following. After you have copied the URL, close the test page. You now want to find the “Dashboard” for your app. If it is not visible on your Developers Facebook page, go to the menu at the top of the page and select Apps. A drop-down list should display showing the name of your app(s). Select your app.

A978-1-4842-1730-6_2_Fig5_HTML.jpg

图 2-5。

The Facebook dashboard   Once you have displayed your dashboard, select Settings on the left menu.

A978-1-4842-1730-6_2_Fig6_HTML.jpg

图 2-6。

The Facebook Settings page   Scroll down to the bottom of the page. Locate Secured Canvas URL and erase the address that resides in this textbox. Type the location of another secured web page (https) in the box. Remember, this must be a URL on the web, not your localhost. Click the Save Changes button.   Now paste the URL of your app (if you lost the URL address, it is under Canvas Page on the Settings Page) in your browser.

A978-1-4842-1730-6_2_Fig7_HTML.jpg

图 2-7。

Facebook working app   You should now see your app display on the canvas within your Facebook page.   Were you successful? What problems did you encounter following this example? How did you solve those problems?

恭喜你,你已经创建了你的第一个脸书应用(虽然它不是实时的)。如果你想让你的一些朋友看到你的应用,你可以回到仪表盘,从菜单中选择滚动。然后,你可以使用你的朋友(是的,他们必须是脸书的朋友),并在你的应用开发中给他们一个“角色”(如“开发人员”或“测试人员”)。然后他们就可以访问你的应用,而不用它上线。

作为一名脸书游戏开发者,你正在走向赚大钱的道路!嗯,也许还不是时候。我们省略了许多代码来应对脸书的 API。但是希望您明白,脸书可以很容易地用作 PHP 应用的容器。

PHP、AJAX 和 CSS——网络应用

PHP 和 AJAX(异步 JavaScript 和 XML)配合得很好。AJAX 提供了动态改变部分网页而无需重新加载整个页面的能力。大多数网页都有静态区域(菜单、页眉和页脚),它们不会随着用户交互而改变。当用户与页面交互(点击按钮)时,网页的这些区域不必改变。AJAX 可以让您开发一个容器(在下面的例子中,容器位于div标记之间)来显示 web 服务器上托管的程序的输出;而不会扰乱整个网页。然后,当用户交互(单击按钮)的结果仍在处理中时,用户可以查看网页上的内容(菜单、页眉和页脚)。如果由于某种原因程序运行缓慢、挂起或丢失,页面的其余部分仍然可以运行。当您显示一个页面时,可能会遇到这种情况,该页面由于试图一次加载大量信息(大量广告)而挂起,并且该页面将无法运行,因为它必须在可用之前完全加载。

AJAX 还允许您在不干扰网页的情况下改变 web 服务器上 PHP 应用的内容。然后,您可以在用户不知情的情况下更新应用中的代码(只要您保持应用的名称不变)。

我们来看一个例子。

Note

代码示例文件包含在该书的网站上。您可以复制并使用这些示例,无需任何更改。我们已尽一切努力确保本书中显示的代码是正确的。打印错误可能会影响显示的代码(例如大写和小写被调整,括号被尖括号替换)。网站上的所有代码都是有效的。

Example 2-1. AJAX_Example_JavaScript.js

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;

}

function AjaxRequest()

{

var xmlHttp = getXMLHttp();

xmlHttp.onreadystatechange = function()

{

if(xmlHttp.readyState == 4)

{

HandleResponse(xmlHttp.responseText);

}

}

xmlHttp.open("GET", "myfirstprogram.php", true);

xmlHttp.send(null);

}

function HandleResponse(response)

{

document.getElementById('AjaxResponse').innerHTML = response;

}

JavaScript, a scripting language, provides interactive capabilities for web pages. With JavaScript, the web page can respond to the user's input of information in the text box and/or clicking the button. If you need to review JavaScript or need more examples, please watch the free "New Boston" (D thenewboston.com) video: https://www.youtube.com/watch?v=yQaAGmHNn9s&list=PL46F0A159EC02DF82 To learn more about AJAX, please watch the free "New Boston" (thenewboston.com) video: https://www.youtube.com/playlist?list=PL6gx4Cwl9DGDiJSXfsJTASx9eMq_HlenQ .

如果你不懂 JavaScript,不要太在意这个程序的细节。我们只看几个关键点。AJAX 使用 HTTP GET 请求程序,类似于浏览器请求页面。在这个例子中,类XMLHttpRequest(存在于 JavaScript 库中)的一个实例被命名为XmlHttp。然后这个对象被用来打开对myfirstprogram.php的请求(就像打开一个管道)。然后对象的send方法发送请求(将水推下管道)。如果文件被正确返回,文件的输出将放在 HTML 网页上 ID 为AjaxResponsediv标签之间。如果浏览器不能处理 AJAX 通信,将显示一个警告框,建议用户升级浏览器。尽管不太可能有人使用不能解释 AJAX 代码的浏览器,但是您仍然应该处理所有的可能性。

Example 2-2. ajaxdemo.html

<head> <title>PHP Ajax Demo</title>

<meta charset="utf-8">

<link href="ajaxdemo.css" rel="stylesheet">

<script type='text/javascript' src='Ajax_Example_JavaScript.js'></script></head>

<body>

<div id="wrapper">

<div id="header"> <h1>PHP Ajax Demo</h1>  </div>

<div id="content">  <h2>"Watch it!!"</h2>

<p>The words below will be replaced by "Hello World" which is pulled from the 'myfirstprogram.php' file via AJAX.</p>

<h2>AJAX DEMO</h2>

<input type='button' onclick='AjaxRequest();' value='Find Hello World!'/><br /><br />

<div id='AjaxResponse'>

Pay attention… Notice when you click the button that only this section changes.

</div>  </div> <!-- end of content -->

<div id="footer">Copyright © 2015 Little Ocean Waves – Steve Prettyman

</div><!-- end of footer -->

</div> <!-- end of wrapper -->

</body></html>

要使用这个脚本,更改xmlHttp.open语句来选择您想要执行的文件(而不是myfirstprogram.php)。更改document.getElementById行,在 HTML 文件中包含您想要用来存放输出的div标记的 ID(而不是AjaxResponse)(参见下一个例子中的 HTML 代码)。HTML—超文本标记语言是一种用于格式化网页布局的标记语言。浏览器解释 HTML,然后向用户显示结果。要更深入地了解 HTML,请访问免费的“新波士顿”(thenewboston.com)视频:g https://www.youtube.com/playlist?list=PL081AC329706B2953

如果你对 HTML 不是很了解,不要担心。在这个例子中,你只需要看几行。首先,在代码顶部附近,一个链接标签拉入了ajaxdemo.css文件。这个 CSS 文件允许你看到一个有一些图形细节的页面。它允许您演示页面更新,而不需要 CSS 文件中的图形重新粘贴或闪烁。在这条线的正下方,脚本类型标签从示例 2-1 的 JavaScript 文件中加载。如果您将文件命名为其他名称,这一行需要用新文件名进行调整。如果您的文件不在同一个文件夹中,您应该在文件名中包含文件夹名称。

在 HTML body 部分的中间,input type 标记创建了一个按钮,单击该按钮将调用AjaxRequest函数(包含在 JavaScript 文件中)。这将导致示例 2-1 中的所有 JavaScript 代码执行。我们需要注意的最后一行是<div id='AjaxResponse'>标签。id ( AjaxResponse)中的值必须与 JavaScript getElementById代码中使用的对象名完全匹配。假设它们匹配正确,一旦点击按钮,JavaScript 代码将请求myfirstprogram.php文件并显示带有AjaxResponse ID 的div标签之间的结果。

Example 2-3. ajaxdemo.css

body { background-color: #000000;

font-family: Arial, Verdana, sans-serif; }

#wrapper { margin: 0 auto;

width: 85%;

min-width: 800px;

background-color: #cc0000;

color: #000066; }

#header { background-color: #ff0000;

color: #00005D; }

h1 { margin-bottom: 10px; }

#content { background-color: #ffffff;

color: #000000;

padding: 10px 20px;

overflow: auto; }

#footer { font-size: 80%;

text-align: center;

padding: 5px;

background-color: #0000FF;

color: #ffffff;

clear: both;}

h2 { color: #000000;

font-family: Arial, sans-serif; }

#floatright { float: right;

margin: 10px; }

为了完整起见,这里显示了 CSS 文件。不懂 CSS 也不用担心。这个文件只是让网站看起来更适合演示。如果您要使用该文件,请确保将其保存在扩展名为.css的文件中。此外,如果您要更改文件的名称,请确保也更改链接标记(在 HTML 文件中)中的文件名以完全匹配。如有必要,请确保包括所有文件夹名称。

CSS-Cascading Style Sheets (CSS) together with HTML display graphics on web pages. CSS describes the layout, color, text font, background image and other characteristics of the webpage. To learn more about CSS, please visit the free "New Boston" (thenewboston.com) video: https://www.thenewboston.com/videos.php?cat=40&video=18754g.

仔细检查所有三个文件(.js.css.html),确保文件名与调用它们的链接完全匹配。如果所有文件链接正确,首先显示ajaxdemo.html文件,如图 2-8 所示。

A978-1-4842-1730-6_2_Fig8_HTML.jpg

图 2-8。

ajaxdemo.html

如果您的代码不起作用,请仔细检查文件名(确保它们没有以.txt结尾)。如果你看到一个空白页,那就有问题了。在你的代码中寻找错别字。是不是忘了;{(或者其他编码?如果您收到一条错误消息,请将其粘贴到您的浏览器中,以发现可能的解决方案。如果你没有看到错误信息,请查看 Apache 和 PHP 的日志(见第一章)来确定其他可能的问题。

A978-1-4842-1730-6_2_Fig9_HTML.jpg

图 2-9。

The ajaxdemo.html file after the AJAX request

如果没有语法错误(或不正确的文件名或位置),当用户单击按钮时,页面将使用 AJAX 请求myfirstprogram.php文件,并将在div标记之间显示程序的执行结果。在这个例子中,Hello World将被显示。

做它

Copy the three files shown previously from the book’s web site into your projects folder (the location that you are running your projects in Apache). Run the HTML program. Was your test successful? If not, why not?   Change the file name of your .js file and change the link tag in your HTML file to reflect the new file name. Test your HTML program. Was your test successful? If not, why not?   Change the name of your PHP program. Make changes to the .js file to reflect the new name of your PHP program. Test your HTML program. Was your testing successful? If not, why not?

PHP、AJAX 和 CSS——智能手机网络应用

至此,您应该开始发现创建 PHP 应用的灵活性和强大功能。您可能会有点失望,因为我们没有详细介绍智能手机的应用开发。如前所述,智能手机上的应用通常对操作系统进行 API 调用,请求使用手机上的应用(如 GPS)。那种程度的编码已经超越了入门级。因此,超出了这本书的内容。然而,有时我们希望为智能手机提供从手机浏览器访问 web 应用的能力。我们只需对之前的演示做一些修改就可以提供这种能力。这为您提供了一个适用于任何大小的包含浏览器的设备的应用。

您将在不调整 HTML(除了添加链接)、JavaScript 或 PHP 代码的情况下做出这一更改。您可以使用 CSS 在智能手机或其他移动设备中调整ajaxdemo网页的格式。在这个例子中,你只需要改变 HTML 文件来发现显示(界面)的大小。然后,CSS 可以用来改变图形,以适应适当的屏幕尺寸。

如果 PHP 文件包含大量 HTML(和/或其他 CSS 代码),我们可能也需要调整 PHP 文件。然而,我们应该总是考虑让 CSS 格式化整个输出(而不是 PHP 文件)。这将允许您在多个容器(主机)中使用相同的代码,正如您在本章中看到的演示。

您可以在ajaxdemo.html文件中原始 CSS 文件的链接下方添加以下行。

<link href="ajaxdemomobile.css" rel="stylesheet" media="only screen and (max-device-width:480px)">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Note

新的完整 HTML 文件位于该书的网站上。

这两行代码将尝试确定显示屏的大小,并将新的 CSS 文件用于 480px 或更小的屏幕。这个演示程序可以在大多数移动设备上运行。但是,让我们调整一下,去掉一些间距、填充和边距。

Note

您还可以将 CSS3 Flexbox 属性用于多种设备尺寸。由于它的使用需要对 CSS 有更多的了解,我们将坚持使用“旧风格”的方法。你可以在下面的 w3schools 链接找到更多关于 CSS3 Flexbox 的信息。

http://www.w3schools.com/css/css3_flexbox.asp

Example 2-4. The ajaxdemomobile.css file

body { background-color: #000000;

font-family: Arial, Verdana, sans-serif;

margin: 0;       }

#wrapper { margin: 0 auto;

width: 100%;

margin: 0;

min-width: 0px;

background-color: #cc0000;

color: #000066; }

#header { background-color: #ff0000;

color: #00005D;

font-size: 100%;

padding: 0.5px 0.5px 0.5px 0.5px; }

h1 { margin: 0px; }

#content { background-color: #ffffff;

color: #000000;

padding: 0.5px 0.5px;

overflow: auto; }

#footer { font-size: 80%;

text-align: left;

padding: 0px;

background-color: #0000FF;

color: #ffffff;

clear: both;}

h2 { color: #000000;

font-family: Arial, sans-serif;

margin: 0px;          }

#floatright { float: none;

margin: 0px; }

对比示例 2-4 和示例 2-3 。示例 2-4 中的代码并不完美,但它确实给了你一个在移动设备上显示的原始 CSS 文件中可以调整的概念。本示例将边距和填充减少到零,或几乎为零。这极大地减少了较小移动设备的显示器上浪费的空间。

做它

Locate the new HTML file and CSS file on the book’s web site. If you have a web host provider, upload these files (and the .js file) to the host. Try to access the HTML page from your smart phone. You can also try to display the page by using the URL location of the file on the book’s web site. Was your test successful? If not, why not? Did the page format properly on your phone? If not, what do you think needs to be adjusted?   Note

如果你在互联网上没有一个主机网站,你也可以通过下载一个免费的移动模拟器来测试这个 CSS 文件(试试 www.download.com )。你也可以改变 HTML 文件中的链接标签指向ajaxdemomobile.css文件而不是ajaxdemo.css文件。然后试着缩小你的浏览器来模拟智能手机屏幕。

PHP、HTML、JavaScript、CSS 和动态网页

作为 PHP 程序容器(主机)的最后一个例子,让我们考虑一下浏览器。正如你在本章中看到的,你可以使用一个 HTML 文件来调用我们的 PHP 程序(点击前面演示中的按钮)。如第一章中所详述的,这将导致 PHP 程序执行并将结果返回给浏览器。你也可以让你的 PHP 程序返回 HTML 代码。

Example 2-5. Dynamic HTML page created by a PHP program

<?php

print "<html>";

print "<head><title>My Program</title></head>";

print "<body>";

print "<h1>Hello World</h1>";

print "</body>";

print "</html>";

?>

您可以向请求执行 PHP 程序的设备(浏览器)返回一个完整的 HTML 页面(甚至包含 JavaScript 和链接)。示例 2-5 将从 PHP 代码中创建一个完整的动态 HTML 页面。创建的输出甚至可以包含指向 CSS 文件的链接和用于格式化的嵌入式 CSS 标签。然而,我强烈建议在嵌入依赖于容器大小的 CSS 标签时要小心。作为开发人员,您不知道哪种类型的设备会显示结果(PC、智能手机、平板电脑或 pad)。最好提供多个 CSS 文件(如前所示)来格式化不同设备的输出。

在前面的例子中,我们已经将初始界面(HTML 和 JavaScript)从 PHP 程序中分离出来。您实际上可以将 HTML(显示初始网页)和 PHP 代码放在同一个文件中。

您可以创建一个 PHP 文件来确定用户以前是否请求过该页面。如果没有,或者浏览器超时了,那么 PHP 程序会显示一个初始的 HTML 页面供用户交互(比如点击一个按钮)。然后,同一个程序可以再次调用自身来确定按钮是否被单击并返回响应。

让我们看一个不同版本的Hello World程序来完成这个任务。

Example 2-6. The callmyself.php file

<?php

if (isset($_POST['submitbutton']))

{

print "<h1> Hello World </h1>";

}

else

{

print "<html><head><title>PHP Example</title></head>";

print "<form method='post' action='callmyself.php'>";

print "<input type='submit' id='submitbutton' name='submitbutton' value='Find Hello World!'/>";

print "</form>";

print "</body></html>";

}

?>

为了更深入地演示 PHP If 语句,请访问以下来自“新波士顿”(The New Boston)的免费视频:https://www.thenewboston.com/videos.php?cat=11&video = 17004。

这个程序包括一个简单的if条件语句来确定用户是否点击了提交按钮。

if语句的格式如下:

if (conditional statement)

{

// code to execute if the conditional statement is true

}

else

{

// code to execute if the conditional statement is false

}

条件语句通常比较两个值来确定它们是相同还是不同,或者调用返回truefalse值的方法。我们一会儿将看看返回值的函数。让我们看一个第一种类型的简单例子。

条件语句使用比较运算符(==, <, >, <=>=来确定语句是true还是false

If ( a > b)

{

print "it's A!";

}

else

{

print "it's B!";

}

在这个例子中,比较属性ab中的值。如果a大于b,那么“是 A!”显示。否则“是 B!”显示。当 PHP 认为有必要时,它会做一些类型转换。例如,假设a = "5",和b = 6。PHP 会将a中的值从字符串 5 转换为数字 5,这样它就可以进行比较。许多语言不这样做,如果您尝试比较字符串和数字,会显示一个错误。如果不希望发生这种转换,可以使用一些特殊的比较运算符。例如,您可以使用三个=符号而不是两个符号来查看值是否完全相同(a === b)。

And security-use = = instead of = = whenever possible. This will ensure that you get what you expect.

在示例 2-6 中,if语句调用一个方法(isset)。$_GET尝试检索属性('submitbutton')及其值(Find Hello World!")来自 HTML 表单,它使用 HTTP GET 来传递信息(您本来可以使用$_POST和 HTTP POST)。isset将向if语句返回一个falsetrue,这取决于$_GET是否可以检索属性(及其内容)。truefalse将导致if语句决定执行哪个代码块。

正如在第一章中提到的,HTML 表单中的一个对象(比如一个按钮)将根据对象的名称和包含在该对象的值语句中的内容产生一个属性和值的组合。对于已经给定了名称(id)和值的提交按钮也是如此(如示例 2-6 )。

第一次从浏览器调用程序时,按钮没有被单击。所以没有创建submitbutton变量。通过isset方法返回一个false。代码跳转到else部分并执行显示 HTML 表单和提交按钮的print语句(如图 2-10 )。

A978-1-4842-1730-6_2_Fig11_HTML.jpg

图 2-11。

callmyself.php after the button is clicked

A978-1-4842-1730-6_2_Fig10_HTML.jpg

图 2-10。

callmyself.php before the button is clicked

当用户点击按钮时,程序调用自己(看一下form标签的action参数)。这一次,因为用户已经单击了按钮,所以有一个 submitbutton 属性和一个变量的值('Find Hello World!')。程序确定变量是“set”(里面有一个值)并返回一个true。然后,if语句执行ifelse语句之间的一行代码。然后显示Hello World。PHP 程序处理应用的所有功能,而不使用任何现有的静态 HTML 页面。

这种技术的优点是所有代码都可以包含在一个文件中。因此,所有的变化都发生在一个地方。这种技术的缺点是所有代码都在一个文件中。代码越复杂,就可能变得越“混乱”。清理代码的一种方法是将代码转移到包含在其他 PHP 库中的函数中(我们将在后面讨论)。另一个缺点是,在不影响用户的情况下,不能更改文件名。如果您将文件名更改为mynewprogram.php,您将需要通知您的所有用户新的名称(可能还有位置)。前面使用 AJAX 的例子允许您在 HTML 代码页中更改文件名,但是不要求您更改用户请求的 HTML 页面的实际名称。

做它

Find the callmyself.php file on the book’s web site. Download the file to your Apache projects folder. Change and add print statements to display your complete name, the term, and your major. Test your program. Did your program run successfully? If not, why not?

PHP 三层架构

这一章的大部分都在探索可以“托管”PHP 应用的不同平台(或容器)。我们发现 PHP 可以在几乎任何容器(PC、脸书、智能手机/移动设备或浏览器)中显示其输出。PHP 可以轻松地与 JavaScript、HTML 和 CSS 交互,这提供了这种灵活性。今天,几乎任何平台都有与互联网交互的能力(那些不具备这种能力的平台将在未来某个时候具备)。任何可以与互联网交互的平台也可以与 PHP 应用交互。

接口的这种独立性(或灵活性)展示了平台或接口主机与应用的其他“层”(部分)的逻辑分离。这就引出了对 PHP 应用的三层架构和逻辑设计的讨论。应用越大,就越有可能需要将应用分成模块。此外,这些模块更有可能驻留在不同的服务器(或 web 服务器)上。更大的应用可能需要多个程序员同时编写代码。这些程序员甚至可以使用不同的语言来创建程序模块。

构建一个大型应用与组装一辆汽车没有太大区别。汽车的各个部件(车身、车轮、电子设备和发动机)首先被单独组装。然后,每个完成的组件被放入汽车底盘内。然后将组件连接(软管、电线和皮带)到其他组件。完成后,汽车的所有部件一起工作。如果一个部件坏了,它可以被替换,而不会导致汽车中任何其他部件的替换或改变。

模块化(或组件)编程的思想是基于代码块的方法,这些代码块可以单独创建,然后与其他模块组装在一起,生成一个工作应用。这些模块可以被修改或替换,而不需要改变其他模块。这种方法已经存在一段时间了。即使在今天,许多程序也不是模块化的,因为较小的程序不需要分成模块也能有效地工作。然而,随着这些应用扩展成更大的应用,它们变得更加难以更新或维护。更改需要更新整个应用,而不仅仅是一个模块。在某种程度上,一个没有用模块创建的扩展应用必须从头开始重新设计成模块,以便更好地维护和提高可靠性。

正如在第一章中所讨论的,搜索引擎可以在用户电脑的浏览器中显示网页界面。一旦用户输入搜索请求,信息可以通过互联网传输到远程服务器(我们不知道在哪里),远程服务器执行搜索应用(我们不知道它是用什么程序语言创建的)。然后,应用在数据库(我们不知道是什么 DBMS,也不知道它在哪里)中搜索所请求的信息。结果被发送回应用,应用又将信息发送回用户 PC 上的浏览器(通过 web 服务器)。

A978-1-4842-1730-6_2_Fig12_HTML.jpg

图 2-12。

Three-tier modular application design

这个过程中的信息流使得这种类型的 web 应用的设计自然地分为(至少)三层(模块);接口层、业务规则层和数据层。将代码分成不同层的一个优点是能够在多个应用中重用层。例如,我们的搜索引擎可以为多种设备使用相同的业务规则层和数据层,同时使用不同的界面(PC 应用或智能手机应用)。不同的层也可以更新,而不会影响其他层。智能手机应用界面可以更新,以使用最新操作系统的最新功能,而无需更改业务规则或数据层。可以更新业务规则层中的代码来修复逻辑错误,而不需要更改接口或数据层。让我们看看每一层通常会发生什么。模块化的三层应用、设计和编程——三层设计提供了创建程序的能力,这些程序可以分为界面层、业务规则层和数据层。界面层包含与向用户显示信息相关的所有图形和程序代码。业务规则层不包含接口。但是,它处理从接口层提交的任何信息,然后可以将信息提交给数据层进行存储。数据层是应用的主要存储位置,可能包括数据库的使用。每一层都可以独立地更改和构建(编译),而不会影响其他层。

做它

What are the name of the three tiers of modular design?   How is modular design similar to designing a building?   How does modular programming make coding more efficient?

界面层

A978-1-4842-1730-6_2_Fig13_HTML.jpg

图 2-13。

Interface tier

界面层(IT)显示信息,并为用户提供与应用交互的能力。大多数界面都提供了图形用户界面(GUI ),允许用户以一种吸引人的方式查看信息并与应用交互。GUI 界面提供通用对象,包括文本框和按钮,帮助用户快速适应新的应用。此外,图片、图像、图标、视频和声音也经常被包含进来,以保持用户的兴趣。通常还包括菜单和其他导航对象,以帮助用户成功地在应用中移动。对象——对象是已经被编译用于应用的代码块。通过创建对象的实例,可以将对象放入程序中。对象包含方法和属性。方法(或函数)是完成一项任务的代码块(例如将项目放入列表框中)。属性(或变量)是可以改变的对象的特征(如背景颜色)。对象通常经过良好的测试,没有错误。通过重用现有对象,程序员可以快速创建更可靠的程序。

这一层将使用对象(如标签和图片框)或脚本代码(如本章中的ajaxdemo.html示例)来显示信息。该层还将通过交互式对象(如文本框和按钮)接受来自用户的信息。静态信息可以在层内提供(通过菜单、徽标或页脚)。动态信息通常从业务规则层提供给业务规则层(例如本章中显示的myfirstprogram.php的输出)。

接口层中可能会出现一些编码(在后面的章节中介绍),用于准备发送到其他层的信息。例如,验证用户输入了所有必需信息或正确信息(年龄文本框中的数字字符)的 JavaScript 代码是可以接受的。附加代码还可以准备从业务规则层接收的信息,以便在界面中显示(例如将数字转换为文本格式)。

Verification/verification code-verification code verification information. The code compares the received information with the expected standard format. For example, the code can verify that the e-mail address contains both @ and. (period). If the information contains these two symbols, it can be considered as "valid" (although we are still not sure whether the email address really exists). Without these two symbols, the code is invalid. Invalid information usually causes the program to display an error message to the user, asking to re-enter valid information.

界面必须提供能够对用户交互(单击提交按钮)作出反应的代码,这通常称为用户事件。还可以提供代码来准备由用户提供的信息以供其他层使用(例如将用户输入的文本转换成数字格式以用于商业规则层中的计算)。事件——事件驱动语言(如 PHP)可以在事件发生时执行代码块。事件可以是用户已经完成的事情(比如点击按钮)。操作系统也可以触发事件。程序提供“听到”事件的监听器代码。当事件发生时,代码提供一个事件方法,然后执行该方法。程序通过侦听器代码的存在与否来选择要侦听的事件。

接口层不应该直接与数据库管理系统或数据库本身交互。通过这样做,这将把层锁定到数据库位置和数据库的实际设计中。该层不应操作数据(除了用于显示目的)。任何与应用本身相关的会计、数学计算或数据处理都应该在业务规则层完成。数据库管理系统(DBMS)——数据库管理系统是一种允许用户或应用创建和定义数据库的软件。它还提供了在数据库中插入、更新或删除信息的能力。

格式化数据以便显示 从数据库访问数据
验证用户提供的信息是否正确 计算结果
响应用户事件 过程信息
处理意外情况(异常) 验证用户 id 和密码
格式化业务规则层的数据

做它

Give three examples of items that would be included in the interface tier.   Give three examples of items that would not be included in the interface tier.   Can some program code exist in the interface tier? If so, what tasks does this code provide?

业务规则层

A978-1-4842-1730-6_2_Fig14_HTML.jpg

图 2-14。

Business rules tier

业务规则层处理从接口层和数据层收到的所有信息和数据。这个层还将返回接口层请求的信息(比如返回"Hello World"),并将信息提交给数据层进行存储。大多数实际的编程代码都包含在这一层中。

业务规则层代码通常使一个应用真正不同于另一个应用。如果一个应用是受版权保护的,那么独一无二的创造性算法可能就隐藏在这一层。例如,web 搜索引擎中的许多差异以及信息最终将如何显示都嵌入到业务规则层中。

与可能在浏览器中包含代码的界面层不同,业务层中的所有编码都是使用服务器上的程序(如 PHP)完成的。驻留在服务器上的脚本和编程代码由服务器本身保护,用户不能访问。业务层代码也可以驻留在应用服务器(作为服务)或 web 服务器(作为 web 服务)上。服务器通过不允许用户直接访问来确保这一层中的代码是安全的。

Server/application server /Web server-the server is connected to the network and provides services for any node (machine) on the network (or the Internet). Servers can provide multiple services (communication, security and/or storage) or specific services. The application server stores applications that users can access on the network (or Internet). The Web server hosts web pages and web applications. They are usually exposed to access outside the company. Service—A service is an application that resides in the memory of a computer or server and is used to respond to requests from other applications. Services can be automatically loaded into memory when the computer or server starts, or started and stopped manually when needed. Service has no GUI interface. The business rule layer can reside as a service on a computer or server.

如果在应用各层之间传递的信息可能会受到黑客的操纵,那么这一层可能会包含类似于接口层的代码,以验证所接收数据的格式是否有效和正确。

从这一层传递到其他层的信息可以被格式化以便于接受。例如,数据集(类似于表格或电子表格)可以返回到界面层,以便显示在网页上现有的表格或列表框中。数据集也可以被发送到数据层以插入到数据库中。数据集—数据集是一种可以容纳多个数据表的结构。数据表类似于数据库(包含行、列和数据)或电子表格中的表。数据集通常用于在层之间和方法之间传递信息。

业务规则层将值返回给请求它的层。该层不提供任何 GUI 界面或任何形式的表单。不需要界面,因为该层在任何时候都不与用户直接联系。所有与业务规则层的通信都是通过接口层或数据层来处理的。与接口层一样,业务规则层不直接更新数据库中存储的信息。所有存储更新都发生在数据层。

操纵数据 显示信息
格式化数据 在辅助设备上保存数据
将数据存储在内存中 显示错误消息
引发异常
验证数据

做它

What tasks can business rule tier code accomplish that is similar to a task accomplished by code in the interface tier? Why is this code possibility duplicated in both tiers?   How does the business rules tier pass and receive data (information)?   Why must the business rules tier talk indirectly through the interface tier to provide information to the users?

数据层

A978-1-4842-1730-6_2_Fig15_HTML.jpg

图 2-15。

Data tier

数据层的主要功能是在辅助设备上存储信息或将数据返回给业务规则层。数据可以(通常是)使用数据库管理系统(如 MySQL)存储在数据库中。该层与业务规则层相连接,因为在显示数据之前,可以对数据进行操作,就像创建报告一样。数据以业务规则层(和程序语言)可以接受的格式从该层返回。常见的格式包括 JSON、XML、SOAP 和数据集。JSON——JavaScript 对象符号是一种类似于 XML 的格式,用于存储和交换数据。JSON 可以在编辑器或浏览器中查看。它最常用于在层间传递数据。XML—可扩展标记语言是一种标记语言,类似于 HTML,用于存储和描述数据。XML 也可以用来在层之间传输数据。我们将在第三章中提供一个 XML 的例子。SOAP—简单对象访问协议用于与 web 服务交换数据。它与 HTTP(超文本传输协议)和 SMTP(简单邮件传输协议)一起提供应用和 web 服务之间的通信。Web 服务—没有接口的应用,可以被调用来处理信息。web 服务可以在 web 服务器上远程提供业务层和/或数据层的功能。web 服务描述语言(WSDL)用于描述对 web 服务的调用以及 Web 服务接受和返回的信息格式。WSDL 类似于 XML。

数据层的验证是在数据库管理系统中和/或通过程序代码完成的。这一层的验证是在数据库更新之前确保数据可靠和准确的最后机会。在存储验证问题之前发现它们比在记录无效信息之后发现它们要容易得多。

数据存储可以是本地和/或远程的。移动设备可以将存储限制在本地数据库(智能手机),但也可以使用 WSDL (web 服务)在服务器或云中(如微软 Azure)远程存储和检索信息。此外,许多应用使用 cookies 在本地保存少量信息,或者在远程数据库中保存大量信息。微软 Azure——微软 Azure 是一个云平台,提供数据服务、应用服务和网络服务。Visual Studio 应用可以在 Microsoft Azure cloud 中上传和保护。

数据层使用 SQL 语句(INSERT、DELETE、UPDATE 和 SELECT)检索和更新数据库中的信息。然而,对信息进行更改的实际请求来自业务规则层。例如,在 web 应用中请求地址更改的用户必须在接口层输入所请求的更改,在业务规则层格式化更新请求,并在数据层更改数据库中的信息。

在辅助设备上保存数据 操纵数据
更新辅助设备上的数据 显示错误消息
引发异常 显示信息
验证数据

做它

Why must the data tier validate information again, before it is placed in a database?   What type of code, if any, can exist in the data tier?   What is SQL used for?

把这一切放在一起

程序开发生命周期(PDLC)引导我们完成应用开发的整个设计和创建过程。应用开发成功的关键是在开始编码和开发之前正确规划系统的布局。这样做可以减少可能出现的错误和问题。许多项目可能会陷入一个“角落”,由于事先计划不周,开发人员无法绕过这个角落。不同的作者和教师对计划过程中花费的时间的估计会有所不同,因为这是基于个人的。一个更有技能或更有经验的人可能比不自信的人做得更少。然而,每个人都至少计划好了所需的模块和模块之间流动的数据类型;即使只是在草稿纸上完成。同样,关键是在编码和开发过程中修改你的计划。当你开始编码时,你会发现“我没有想到”的情况。回去,然后调整你的计划。你这样做得越多,你就越不可能碰到一个你找不到办法绕过的“角落”。

在你的 PDLC 过程中,一定要让用户参与进来,同时要有一个强大的专家团队。请记住,您开发这个项目是为了让您的客户满意。你可能相信你有最终的设计和解决方案。然而,如果你的用户不同意你的观点,那也没关系。有时候,你确实需要调整自己的思维,以便更符合客户的需求。在这个行业工作时,我记得我的工作团队正在创建一个重大的变革管理系统(我很高兴没有参与这个项目)。设计者和编码者创建了一个非常花哨和昂贵的系统,一旦在数据中心实施,就很难使用。为什么?数据中心人员不想要花哨的系统;他们想要一个能够快速完成所需任务的简单系统。尽管最初的项目需要六个月才能完成,但一名数据中心员工利用自己的几个周末时间创建了另一个系统,实现了他们的目标。该系统在此后的许多年里一直被实施和使用。

通常,PDLC 由五个步骤定义;规划和信息收集、分析、设计、实施和评估。在计划和信息收集步骤中,集合了项目和项目团队的所有需求。还创建了初始文档来回答问题“我们试图完成什么?”“我们将如何完成它?”,以及“我们将使用哪个团队?”在分析步骤中,确定项目的可行性。文档回答了这样的问题“我们能用可用的团队和资源完成项目吗?”、“需要收集哪些额外资源?”,以及“项目能否在规定的时间和预算内完成?”如果项目通过了分析步骤(许多项目没有),那么实际的设计就开始了。在设计阶段,自顶向下的方法从首先查看整体所需的模块以及模块之间的数据流开始。随着所需方法的确定、平台的确定、通信工具的组合以及信息存储的设计,更多的细节将逐渐增加。

详细设计完成并获得批准后,项目将进入开发和测试阶段。单元测试(单个模块)和完整的应用测试都会发生。测试成功后,项目就可以进入实施阶段了。在实现过程中,必须决定如何安装项目以及何时安装。实现后,应用就“活”了。但是仍然应该进行持续的评估,以有效地确定需求、安全问题、逻辑问题以及项目可能的整体增强。最终,大多数项目会回到开发新版本过程的第一步。

本节假定规划、信息收集和分析步骤已经成功完成,这将使您进入设计步骤。总的来说,我们将关注决定什么类型的活动应该发生在应用的哪个层的过程。我们还将了解可能在各层之间流动的数据或信息的类型。在后面的章节中,我们将通过确定所需方法的类型以及必须流入和流出这些方法的信息和数据来完善这种分析。我们还将看看为这些方法创建的实际活动和代码。

最好的学习方法是通过实践,所以让我们看一个案例问题,然后从那里开始。

个案研究

公司:原子鱼孵化场有限公司。

项目:现场销售订单和佣金申请

范围:该应用将可从多种设备(移动设备和 PC)访问,以允许现场销售代理、经理和工资单人员轻松访问必要的信息。系统将接受来自销售代理的信息,这些信息将用于确定客户的购买成本、销售经理的销售量以及工资部门的佣金。项目的第一阶段是应用的开发,以接受来自销售代理的信息,显示购买成本,确定佣金,并将信息存储到 MySQL 数据库中。在成功测试平台后,它将被移动并安全保存在公司的云平台中。

输入(来自销售代理):销售代理编号、客户编号、订单编号、项目编号、数量和特殊需求

产出:(可能为未来阶段确定更多信息)

到数据库:除了来自销售代理的输入:佣金,销售总额

目标是确定每一层中将要出现的信息和流程的类型,以及各层之间的数据流。一旦确定下来,这些信息就可以用来开发一个通用的空层结构,这个结构最终将包含整个项目。

界面层

该公司已请求从多个设备进行访问。因此,我们必须记住,除了笔记本电脑和个人电脑之外,移动设备(平板电脑和智能手机)也将用于输入和输出信息。我们可以决定为每种类型的设备创建多个接口。使用三层设计,您可以设计一个共享业务规则层和数据层内容的系统,同时允许这种灵活性。

作为一名设计师和/或程序员,你必须确定将要输入的信息类型;几个字段(销售代理编号、客户编号、订单编号、项目编号和数量)都表示将输入数字。您需要确定这些字段的大小,并验证是否只输入了数字信息。“金额”字段是唯一可能在未来计算中使用的字段。因此,所有其他字段都可以保持文本格式。在将信息传递给业务规则层之前,需要将 Amount 字段转换为整数格式。特殊需求字段将是一个更大的字段,可以接受字母数字字符。该数据库目前不存在,因此您需要根据公司政策和/或当前使用的纸质表单来确定字段大小。审查后,确定客户和销售代理编号为八个字符。订单编号和项目编号目前是六个字符。

收集完这些信息后,您现在可以设计接口层的俯视图,如图 2-16 所示。

A978-1-4842-1730-6_2_Fig16_HTML.jpg

图 2-16。

Atomic Fish Hatchery Inc. sales and order application interface

销售代理可以多次使用带+的字段来输入所有信息。当创建实际的数据集和数据表时,必须有多个列可用于这些条目。

还会有一个额外的表单(未显示)来显示订单的结果。该表单将显示所购物品和总费用的摘要。所有信息都可以由销售代理输入、提交并保存在接口层(cookie?),直到销售代理表示订单已完成。然后,可以将信息转移到业务规则层进行处理。然后,业务规则层可以返回要在第二个表单中显示的信息。第二个表单可以包含一个“批准”(或取消)按钮。一旦获得“批准”,信息就可以传递到数据层进行存储。

业务规则层

业务规则层将包括两个流程。一个流程将接受完整的订单(数据集/数据表),并在销售代理单击“完成订单”按钮后确定总销售成本。然后,它会将该信息的摘要返回给第二个表单。当代理单击“批准”按钮时,将执行另一个流程。然后,该流程将确定佣金并创建一个数据集(现在有了额外的字段)传递给数据层。然后,信息将被传递存储。

此外,为了确保订单数据有效,需要一些方法来验证从接口接收的字段。业务规则层的俯视图如图 2-17 所示。

A978-1-4842-1730-6_2_Fig17_HTML.jpg

图 2-17。

Atomic sales and order application business rules tier

数据层

在这种情况下,数据层仅用于存储业务规则层提供的信息。该层将验证收到的信息,并将其存储到数据库中,如图 2-18 所示。

A978-1-4842-1730-6_2_Fig18_HTML.jpg

图 2-18。

Atomic sales and order application data tier

有许多工具可以在设计过程中提供帮助。重要的因素是设计者在过程中包括所有重要的信息。作为示例的替代,您可以从层中删除数据集的描述,并将它们放在层之间的数据流中,如图 2-19 所示。

A978-1-4842-1730-6_2_Fig19_HTML.jpg

图 2-19。

Alternate three-tier model

A978-1-4842-1730-6_2_Fig20_HTML.jpg

图 2-20。

Atomic Sales and Order Application Three-Tier Model

做它

When must you convert numbers to numeric form (such as Integer) for storage within a database?   Why use datasets to pass information between tiers?   What items have been missed or ignored in the case example?

MVC 和依赖注入

MVC(模型-视图-控制器)是软件工程师(包括 PHP 应用设计人员)使用的一种设计模式,通过控制器在视图和模型之间进行通信。控制器是将任何用户输入传输到模型的软件。MVC 设计可以被认为是循环的,因为模型、控制器和视图能够相互通信。标准的三层模型是线性的;对于接收信息或向数据层传递信息的接口,它必须通过业务规则层传递信息。市场上有很多工具(比如 Ruby on Rails)可以帮助软件工程师设计 MVC 应用。

Visit thenewboston.com for a general example of MVC application: https://www.thenewboston.com/videos.php?cat=88

MVC 和基于组件的设计可以使用依赖注入。依赖注入允许程序(客户端)在不知道代码块的实际实现的情况下使用代码块(比如类)。这允许模块的独立开发、更新、测试和重用。这类似于汽车的点火装置与起动器通信的能力。点火装置对起动机部件及其工作原理知之甚少。它甚至不知道启动器的品牌。点火装置仅仅知道发送一个信号(电)给启动器,告诉它运行。如果更换了起动机,只要起动机在接收到信号时仍然工作,点火装置就不会察觉到这种变化,也不会受到这种变化的影响。虽然 MVC 和依赖注入是高级主题,但我们将在第四章的中探索依赖注入的例子。

章节术语

| 平台或集装箱 | GTK 软件开发工具包 |
| 智能手机 API | 脸书帆布平台 |
| 应用编程接口 | 脸书开发者页面 |
| 创建交互式、快速动态网页应用的网页开发技术 | 字符串 |
| XMLHttpRequest | document.getElementById |
| 半铸钢ˌ钢性铸铁(Cast Semi-Steel) | 条件语句 |
| 如果语句 | 三层架构 |
| 界面层 | 业务规则层 |
| 数据层 | 图形用户界面(GUI) |
| 目标 | 验证码 |
| 事件 | 数据库管理系统 |
| 服务器 | 应用服务器 |
| 网络服务器 | 服务 |
| 资料组 | 数据 |
| 可扩展置标语言 | 肥皂 |
| 服务描述语言 | 结构化查询语言 |
| Microsoft Azure | 聚合物分散液晶 |
| 规划和信息收集 | 分析步骤 |
| 设计步骤 | 实施步骤 |
| 评估步骤 | 模型-视图-控制器 |
| 依赖注入 |   |

第二章问题和项目

多重选择

The interface tier Is the primary storage location for the application, which may include the use of a database   Provides common objects, including textboxes and buttons, that help the user quickly adapt to new applications   Should be used for any accounting, mathematical calculations, or processing of data related to the application   Displays information and provides the user the ability to interact with the application     A service is an application that Can be manually started and stopped when required   Does not have an interface   Resides in the memory of a computer   All of the above     SOAP stands for Security Object Access Protocol   Simple Object Access Process   Simple Object Access Protocol   None of the above     The five steps of PDLC are Planning and Information Storing, Analysis, Design, Implementation, and Evaluation   Planning and Information Gathering, Analysis, Design, Implementation, and Evaluation   Planning and Information Gathering, Analysis, Data, Implementation, and Evaluation   Planning and Information Gathering, Analysis, Design, Observing, and Evaluation     The Web Service Description Language is used to Describe calls to the web server and the format of information sent and returned by the web service   Describe calls to the web service and the format of information accepted by the web service   Describe the code being used in HTML documents   Describe calls to the web service and discard the information received     CSS stands for Cascading Static Styles   Cascading Style Sheets   Cascading Script Sheets   Cache Style Scripts     What type of code belongs between the <script>……</script> tags? Java   HTML   Servlets   Javascript     What is the three-tier architecture composed of? GUI/tags/validation   interface/business/data access   Objects/variables/SQL   JSPs/servlets/sockets     The best description for the Model View Controller (MVC) is An architectural pattern that divides a given software application into three interconnected parts.   A programming language used to validate and secure information in any browser.   A type of validator mainly used to check if the user entered the information on the correct format   A type of method that is used to convert a harmful code so it cannot be executed by the compiler.     What is performed in the Analysis step? Determination of the feasibility of the project   Creation the code and error correction   Determination of the logical data flow of the project   Re-evaluation of the implemented application for possible improvements     Why must you validate your code? To determine the capabilities of the browser you are using   To make sure your information is correct and secure   To make sure that you have only JavaScript and HTML5 code   To check if your browser can run your code     What is the correct HTML syntax for referring to an external cascading style sheet? <stylesheet>my_style_sheet.css</stylesheet>   <link rel="stylesheet" type="text/css" href="my_style_sheet.css">   <style src="my_style_sheet.css">   None of these     Which tier is used to store and retrieve data? Presentation tier   Data tier   Application tier   None of these     What does SQL stand for? Structured Question Language   Structured Query Language   Strong Question Language     Which is true about smart phone APIs? The smart phone APIs allow procedural (method) calls to be made to the phones operating system.   The smart phone APIs will allow web applications to make procedural (method) calls to the phone’s operating system.   The smart phone APIs re not needed for applications that use GPS.   Not all smart phones come equipped with an API.     A dataset is similar to A paragraph of data   Sets of two words together   A table or spreadsheet   None of these

对/错

Facebook Canvas Platform can accept apps written in any code, not just in PHP.   WSDL stands for Web Service Definition Language.   AJAX is used to create more interactive applications.   AJAX provides the ability to dynamically change portions of a web page with reloading the complete page.   When using the HTTP GET function, the URL must be in quotation marks.   A service is a function provided by a web server.   The design step comes after the analysis steps have been completed successfully.   MVC (Model-View-Controller) is a design pattern used by software engineers (including PHP application designers) to communicate between the view and model using a controller.   Planning and information gathering is part of the PDLC.   Dependency Injection allows the program client to enter a block of code to know the implementation of the block of code it will be using.   Objects are blocks of code that have already been compiled for use within an application.   HTML stands for Hypertext Markup Language.   Method signature includes the method name, and the number, types, and order of its parameter.   Datasets are not one of the many formats used when sending data between tiers in a three-tier architecture.

简答/短文

In your own words, briefly describe each of the steps in the PDLC.   Why do you think development of PC applications using PHP has decreased?   Why do you think some web sites do not allow themselves to be inserted inside other sites (such as the Facebook Canvas)?   What are the advantages of creating web pages that use AJAX?

项目

Using the steps of the PDLC demonstrated in this chapter, design the logic for a smart phone and mobile device application that allows your water meter reader to enter the full address and meter readings. The application should validate the information and send valid information to a MySQL database. If the information is not valid, a message should be displayed back to the user indicating the problem.   Adjust the AJAX example in this chapter (the code is available on the book’s web site) to display a mini version of your resume.   Adjust the Hello World (Example 2-6, callmyself.php) program to display your college/university name, address, and main phone number.

学期项目

Using the information you have determined from the Chapter 1 Term Project assignment and the design techniques shown in this chapter, develop a logical design of the ABC Computer Parts Inventory application. This design should include all possible programs, interfaces, and data storage. The application design must be three-tier (interface, business rules, and data). Your final design should look similar to the examples shown in this chapter.

三、模块化程序设计

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1730-6_​3) contains supplementary material, which is available to authorized users.

“是的,我是一个糟糕的程序员,但我可能还是比你强:)”——拉斯马斯·勒德尔夫

章节目标/学生学习成果

完成本章后,学生将能够:

  • 创建一个没有错误的简单面向对象(OO)的模块化 PHP 程序
  • 创建一个 PHP 类并创建该类的一个实例(object)
  • 创建一个 OO PHP 封装程序,包括 GET 和 SET 方法
  • 创建接受参数并返回信息的 PHP 方法(函数)
  • 创建 PHP 公共和私有属性(变量)
  • 将现有的 PHP 代码从另一个文件或库中导入到程序中
  • 使用三元(条件)运算符验证收到的信息

PHP 库、扩展、类和对象

PHP 的优势之一是能够轻松地在库中存储代码模块。一旦代码被安装到一个库中,它就可以很容易地在其他程序中重用。对已经在“真实”环境中测试和使用过的代码的重用大大减少了程序错误并提高了生产率,因为您不必重新发明轮子。不需要重新创建已经成功运行的代码。这是浪费时间和精力,并且可能导致不必要的程序错误。程序员实际上可能不知道在一个代码模块(类)中实际存在什么代码。但是,程序员知道什么参数(比如数字)可以传入“黑盒”,从黑盒返回什么(数字的总和)。

您可能会担心程序员盲目地将信息传递到黑盒中,并盲目地接收信息。但是,这是优点,不是缺点。这允许模块的创建者在不影响模块使用方式的情况下更新代码。只要模块接受相同的输入并返回相同的输出,使用该模块的程序员就不会注意到任何变化。可以对模块进行更新,以便更有效、更安全,或者纠正任何程序代码问题,而不会导致用户改变他们在代码中处理模块的方式。

PHP 扩展

Example 3-1. Extensions in the php.ini file

extension=php_bz2.dll

extension=php_curl.dll

;extension=php_fileinfo.dll

extension=php_gd2.dll

;extension=php_gettext.dll

;extension=php_gmp.dll

;extension=php_intl.dll

;extension=php_imap.dll

;extension=php_interbase.dll

;extension=php_ldap.dll

extension=php_mbstring.dll

;extension=php_exif.dll

extension=php_mysql.dll

extension=php_mysqli.dll

;extension=php_oci8.dll

For a complete list and explanation of PHP extensions, please visit: http://php.net/manual/en/extensions.alphabetical.php .

PHP 有大量可用的库,有数千行经过良好测试的代码。示例 3-1 是php.ini文件的部分副本,显示了几个可以在 PHP 中激活的扩展(库)。每个库都是带有 PHP 包装器的 C 代码(为了更好地与 PHP 程序通信)。代码已经编译好了(注意.dll扩展)。存在于 PHP 环境中的库可以通过删除php.ini文件中扩展语句前面的注释符号(;)来激活。一旦保存了 INI 文件,必须重新加载 PHP 和 Apache(关于php.ini文件的位置和重新加载 PHP 和 Apache 的例子,参见第一章)。添加库的简便性是 PHP 如此受欢迎的原因之一。可以使用几种方法将附加的库“安装”到 PHP 中。比较流行的方法之一 Pear (PHP 扩展和应用库)处理第三方库的代码分发和维护。

The use of Pear and other third-party library installation methods is beyond the scope of this book. However, you can find more information in the links below. http://pear.php.net/manual/en/about.pear.php

此外,程序员可以通过requirerequire_once语句将他们自己的代码库(尚未编译)直接“安装”到应用中。在公司中,包含可以多次重用的代码是一种常见的做法(比如访问数据库)。通过在本地库中提供这种(经过良好测试的)代码,任何更改(比如将数据库移动到另一个服务器)都可以在一个位置(本地库文件)进行处理,而不需要更改多个文件。这也减少了代码冗余并增加了其可靠性。

虽然本地库可以只包含代码的方法(函数),但更常见的是模块(类)存在于这些库中。这允许程序员用前面提到的“黑盒”概念进行编码。三层架构(在第二章中解释)就是基于这个前提。通过引用包含代码的库,可以从库中访问代码类(通过requirerequire_once语句)。一旦进行了引用,就创建了类(对象)的一个实例。一旦创建了实例,程序代码就可以访问该对象的所有功能。

类和对象

一个类类似于一个房子的蓝图。蓝图包含建造房屋所需的所有要素的描述(特征)。然而,蓝图并不是实际的房子本身。它描述了如果我们雇佣一个团队来建造房子会发生什么。蓝图被认为是不存在的(就像房子会存在一样)。然而,它描述了建造房屋所需的物品(钉子、干墙和木材)和建造房屋的过程。

类描述了代码模块的特征(属性)以及代码中可能发生的动作(方法或函数)。但是,在创建该类的实例(称为对象)之前,它实际上并不存在(在内存中)。一旦创建了实例,就可以访问特征和方法。类和对象(当正确创建时)保护特征(属性)不被直接访问。这为对象提供了在更改发生之前验证任何更改属性值的请求是否有效的机会。这通常被称为封装。为了保护属性不被外界直接访问,应该使用private访问类型来声明它们。Private访问将只允许类中的方法能够更改属性中的值。Set方法(在本章后面讨论)被用来改变属性。Get方法(也将在本章后面讨论)通常用于检索属性值。

创建 PHP 类

让我们从创建一个类的基本结构开始。您将创建一个dog类,它将允许您设置狗的一些特征(大小、品种、颜色和名字),并且您将为狗提供说话和显示保存在每个属性中的值的能力。您将在一个单独的文件(库)中创建 dog 类,该文件可以在需要时加载到程序(或任何其他程序)中。

要创建一个 PHP 类,可以使用class关键字,并将类中的所有代码封装在{}中。

Example 3-2. Basic class structure in the dog.php file

<?php

class Dog

{

// all code is placed here

}

?>

如例 3-2 所示,class关键字是小写的。但是,类名Dog以大写字母开头。PHP 将允许你创建一个首字母小写的类。然而,通常的做法是通过使用大写的第一个字母来容易地识别类。包含您的类的实际文件名(dog.php)也应该匹配类名(Dog)。

For more information about classes, methods and properties, please visit PHP.net: http://php.net/manual/en/classobj.examples.php . For videos, please visit "New Boston: https://www.thenewboston.com/videos.php?cat=11&video=17175 .

类名不能包含空格。您还应该避免使用特殊字符。然而,_是允许的,通常用于连接两个单词(set_name)。您可能会注意到,一些类名在实际类名(__Myclass)前包含两个下划线(__)。然而,由于存在使用这种格式的“神奇的”类(我们将在本章后面讨论其中的两个类),这不是一种推荐的技术。

如前所述,类包含属性。属性也称为变量。属性包括类的特征。创建类的实例时,属性对于该对象是唯一的。操作系统会在内存中保留一个空间来保存这些属性。操作系统为我们处理内存管理,包括清理不再需要的属性。在 PHP 中,每当到达右括号(})时,操作系统的垃圾收集器就会安排删除已经创建的属性。此时,程序将无法再访问该属性。

属性可以保存许多不同类型的数据。在大多数语言中,当创建属性时,还必须包含一个数据类型来描述所存储的数据类型(如 string)。然而,PHP 不需要定义数据类型。第一次将数据放入属性中时,PHP 确定要存储在属性中的数据类型。属性是用一个首字母$和属性名创建的。属性名可以包括字母字符、数字和 可以用在属性的开头(在$之后)或者单词之间。不允许有空格。属性通常用小写字母创建。然而,PHP 允许大写字母。PHP 是区分大小写的,并且会考虑一个小写属性(speak)和一个大写属性(Speak)两种不同的属性。

Example 3-3. Basic class structure with properties in dog.php file

<?php

class Dog

{

private $dog_size = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

}

?>

程序设计建议——在 PHP 中动态创建属性。属性是在第一次使用时创建的。这既是帮助也是痛苦。如果你拼错了一个属性名,PHP 不会产生错误。相反,它将使用拼写错误的名称创建一个新属性。建议(如果可能的话)在程序(或方法,或类)的顶部用初始值创建属性,以便更容易地确定您的属性名是什么以及它是否已被创建。

如例 3-3 所示,Dog类的每个属性(除了$dog_size之外)都被声明为private,并且初始设置为一个字符串(文本)。$dog_size属性已经被设置为数字零(我们知道它是一个数字而不是一个字符串,因为零周围没有"")。操作系统将以 ASCII 格式将字符串值存储在属性($dog_breed$dog_color$dog_name中(用 0 和 1 的组合来表示每个字符),并将数值以数字格式存储在$dog_size属性中。当在程序中使用变量时,操作系统创建内存表来查找变量中值的实际内存地址。

Note

代码的格式还必须在代码语句的末尾包含分号(所有执行的代码行都必须包含分号)。

正如您可能注意到的,在这一点上,这个例子并不十分有用。即使要创建类的实例,也不能访问类中的任何内容或显示属性中的值。让我们向类中添加一个方法,让您可以显示类中包含的内容。为此,您需要创建一个使用print语句显示值的方法。您还可以借此机会使用字符串连接构建一个单独的输出字符串。

在 PHP 中,字符串连接有几种方式。在许多语言中,构建带有属性的字符串需要不断地打开和关闭一个字符串(使用"")。

print "Dog_weight is " . $this->dog_weight  . ". Dog breed is " . $this->dog_breed . "Dog color is " . $this->dog_color;

上面的print代码行在 PHP 中是有效的。然而,正如你所看到的,你有很多引号和很多句号。很难让一切都匹配正确。句点在 PHP 中是一个字符串连接字符,如果您选择使用这种技术,它将是必需的(在许多语言中,您将不得不使用类似的 madness)。但是,PHP 比这个友好多了。

This pointer-$this pointer is used to access the attributes contained in the object. this indicates that the code wants to retrieve the value contained in the attribute existing in a specific object (instance of class). Soon we will create an instance of the class named $lab. When the code that will exist in the $lab instance is executed, the $this pointer will tell the operating system that it only wants the values in the attributes (such as dog_weight) that exist in the $lab instance. Note that the format of this statement includes a $ symbol for the $this pointer, but does not include a symbol for the variable ($this->dog_weight). You may ask, why do we need the ?? pointer? The short answer is that you can create a property (called a static property) for each instance of the class. If the property of this type changes, it will change for all instances of the class. Our private property changes only in the specific instance ($lab) of the class that references it.

print "Dog weight is $this->dog_weight. Dog breed is $this->dog_breed. Dog color is $this->dog_color.";

PHP 允许在字符串(引号)中放置属性。这允许你使用更少的句号和引号(也许还能减少拔头发的次数)。

For an example of $this pointer, please visit: http://php.net/manual/en/language.oop5.basic.php For a video of $this pointer, please visit: https://www.thenewboston.com/videos.php?cat=11&video=17177

现在您已经有了生成输出所需的代码,您需要在类中添加一个方法来执行print行。一个类中发生的所有“动作”都必须包含在一个方法中。方法的创建方式与类类似(除了它们实际上包含在类中)。使用关键字function声明方法,后跟方法名和()。虽然 PHP 接受大写字符,但是方法名小写是常见的做法。_也可以包含在方法名的开头或内部。所有带有方法的代码都包含在{}中。

Example 3-4. Basic class structure with properties and a method in dog.php file

<?php

class Dog

{

private $dog_weight = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

function display_properties()

{

print "Dog weight is $this->dog_weight. Dog breed is $this->dog_breed. Dog color is $this->dog_color.";

}

}

?>

Program design suggestion-As shown in example 3-4 , it becomes more important to know the correct use of opening brackets and closing brackets ({and}). For every left parenthesis, there must be a right parenthesis. It's hard to know if you missed something. The editor (discussed in the first chapter) can help color-code everything to make it easier to see. In addition, indentation (as shown in example 3-4 ) helps to visually align the brackets. The PHP engine ignores extra spaces (called whitespace). Therefore, programmers can make the code look more comfortable and easier to debug.

这个类现在有能力执行一个动作(通过display_properties方法)。这样你就可以最终测试它的功能了。为此,来自示例 3-4 的代码必须放在dog.php文件(与类名相同)中,与将使用它的程序在同一位置。

我们现在需要创建一个程序来拉进这个库(通过require_once语句)。然后程序需要创建一个类的实例(Dog)。最后,程序将需要调用方法(display_properties)来显示属性的内容。

Programming suggestion-PHP will allow include, include_once, require and require_once statements to be used anywhere in the program code. This may lead to potential problems if it is used in the wrong place or used many times. It is strongly recommended to include these statements as close to the top of the code as possible, so as to facilitate viewing and determine whether the library has been installed. And security-PHP has several ways to introduce libraries into PHP programs. include The method will try to pull into a library. However, if the library does not exist, the program will continue to run (or crash). include The method does not care about the possibility that the library may have been attached to the code. A large program may accidentally try to pull into the same library several times (this will cause the program to crash due to duplicate methods and/or class names). include_once The method eliminates the possibility of multiple attempts to pull into the warehouse. If the library is already included, the statement will not be executed. If the library cannot be found, the require method does not allow the program to continue running. However, like the include method, it can try to pull into the same library several times. require_once method solves these potential problems. If you can't find the library, close the program; if you haven't installed the library, just install the library.

require_once语句的格式很简单。关键字require_once后面是库名(dog.php)。该语句应该包含在代码的顶部附近,并且在创建该类的实际实例之前(Dog)。

require_once("dog.php");

You can include the pathname in the require_once statement. However, it is recommended that you do not include absolute paths. For more information, please visit: http://php.net/manual/en/function.require-once.php For examples, please visit: https://www.thenewboston.com/videos.php?cat=11&video=17028 For videos, please visit: https://www.thenewboston.com/videos.php?cat=11&video=17029.

要创建一个类的实例,需要创建一个“指向”内存中该类实例的属性。包含关键字new是为了通知操作系统应该在内存中创建该类的一个实例(并且应该执行构造函数方法,后面会提到)。包含实际的类名是为了确定将哪个类构建到对象中。

$lab = new Dog;

这段代码将创建一个Dog类的实例,并用$lab属性(指针)引用它。为类的每个实例创建每个属性($dog_size$dog_breed$dog_color$dog_name的实际副本。这允许您更改该实例的属性($lab),而不更改其他实例的属性。

$lab->display_properties();

一旦创建了实例,就可以通过使用对象名($lab)和方法名(display_properties)来访问任何方法。

Example 3-5. Basic program structure including a library, object, and method call in lab.php

<?php

require_once('dog.php');

$lab = new Dog;

$lab->display_properties();

?>

图 3-1 显示了lab.php程序的成功输出,其中包含了dog.php文件中的Dog类。

For more examples of creating an object (an instance of a class), please visit: Example: http://php.net/manual/en/language.oop5.basic.php Video: https://www.thenewboston.com/videos.php?cat=11&video=17181.

A978-1-4842-1730-6_3_Fig1_HTML.jpg

图 3-1。

Output of lab.php

做它

Add a speak method to the Dog class to give each instance the ability to “bark”. Hint: Include a print (or echo) statement in your method. Add a call to your speak method after the call to the display_properties method in the lab.php file.   Create a second object from the Dog class in the lab.php file called $chow. Call the speak method (#1) to make him “bark”.   Create a new library file that contains a class of another animal of your choice. Within that class, create four properties that provide characteristics of that animal. Include a method that will print each of these characteristics. Also include a method that will cause the animal to speak. Create another file that will use this library file to create an instance of the class. The program should also call the method to display the properties and the method to cause the animal to speak.

Error—If you encounter an error when trying this example, please check the following: Do you name the class (Dog) and the file name (dog.php) the same name? Make sure that the end of dog.php and lab.php files is .php instead of .txt. Is the file name exactly the same as that of dog.php in require_once statement in lab.php file? Are dog.php and lab.php files in the same folder? Are there the same number of left ({) and right (}) brackets in the two programs? Have you forgotten any semicolons (; )? For each statement of $thi, make sure that $ is a part of this, not a part of the attribute ($this->dog_weight). For any other errors, copy and paste your errors into the search engine to find possible solutions. Remember, errors may appear in Apache or PHP error logs (see Chapter 1 for details).

返回方法

在第二章的中,业务规则层被定义为包含返回请求信息的代码模块,但不提供显示返回数据的接口或格式。在前面的例子中,Dog类违反了这个要求。然而,您可以通过对dog.phplab.php文件进行一些代码修改来解决这个问题。

应该替换Dog类中的print语句。但是,您希望将多个值(dog_weightdog_colordog_breeddog_name)传递回调用它的程序。有许多方法可以完成这项任务。不过,既然你是刚开始编程,那就简单点吧。通过重新格式化原始字符串,我们可以很容易地创建一个逗号分隔的字符串。您可以将print语句替换为

Return "$this->dog_weight, $this->dog_breed, $this->dog_color.";

新的Dog类现在将包含示例 3-6 中显示的内容。

Example 3-6. Basic Dog class with return statement—dog.php

<?php

class Dog

{

private $dog_weight = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

function get_properties()

{

return "$this->dog_weight,$this->dog_breed,$this->dog_color.";

}

}

?>

Note

在 PHP 7 中,可以启用标量类型提示。PHP 7 为开发人员提供了声明预期返回的数据类型的能力。示例 3-6 中的函数可以编码如下。

declare(strict_types=1);

function get_properties() : string

{

return "$this->dog_weight,$this->dog_breed,$this->dog_color.";

}

如果不包含declare语句或strict_types=0,则不会强制数据类型。当前可以使用的有效数据类型有stringintfloatbool

由于标量类型提示不是向后兼容的,所以在本书的例子中没有使用它们。

您现在需要调整lab.php文件,以便能够接受从get_properties方法传回的内容(display_properties被重命名为get_properties,以反映它不再显示属性;它现在返回它们)。您可以通过在lab.php文件中创建一个属性来接收从get_properties方法传回的内容,从而实现这一点。

$dog_properties = $lab->get_properties();

如果您要使用print函数在此时显示$dog_properties,您将显示:

no weight, no breed, no color

然而,我们打算产生一个类似的结果,如前所示。您可以这样做,但是您需要能够根据分隔符“,”将字符串分成三部分。幸运的是,有一些 PHP 方法可以轻松完成这项任务。explode方法将根据分隔符断开一个字符串。然后可以使用一个list对象将子字符串(字符串片段)放入单独的属性中。根据我们的需要,您可以将$dog_properties字符串拆分如下。

list($dog_weight, $dog_breed, $dog_color) = explode(',', $dog_properties);

For more information about the function of explode, please visit: http://php.net/manual/en/function.explode.php

这将使no weight变成$dog_weightno breed变成$dog_breedno color变成$dog_color。这三个属性也是在同一行代码中的lab.php程序中创建的。我碰巧给他们起了和他们在Dog类中的同伴一样的名字。然而,请记住,如果您没有创建Dog类,您就不会知道原始的变量名。这没关系,因为你可以叫他们任何你想叫的名字,并完成同样的任务。

现在您已经有了包含信息的变量,您可以在lab.php程序中而不是在dog.php库中重新创建原始的print语句。

print "Dog weight is $dog_weight. Dog breed is $dog_breed. Dog color is $dog_color.";

请注意,您没有包括$this指针。您没有在类中执行该语句。您不创建lab.php程序的实例。程序只有一个实例(因为它不是一个类,不能有多个实例)。所以$this指针是不必要的。

新的lab.php程序现在看起来像示例 3-7 。

Example 3-7. The lab.php program with print statement

<?php

require_once("dog.php");

$lab = new Dog;

$dog_properties = $lab->get_properties();

list($dog_weight, $dog_breed, $dog_color) = explode(',', $dog_properties);

print "Dog weight is $dog_weight. Dog breed is $dog_breed. Dog color is $dog_color.";

?>

假设您的程序中没有错误,输出将与图 3-1 相同,与之前版本的程序没有变化。然而,Dog类现在满足了业务规则层的一个标准,它将信息返回给调用它的程序,而不试图格式化输出。lab.php 程序现在处理输出的格式化。

做它

Adjust the speak method in the dog.php file to return the bark string but not print it. Also adjust the call to the method in the lab.php file to display the output of the string. You can accept the string from the method and print the string in one line of code using syntax similar to the following: print $lab->speak();   Adjust the $chow object in the lab.php file to properly handle the return of the properties string and the speak string.   Adjust the animal class to return any strings instead of printing them. Adjust the program that makes an instance of the animal class to accept and display the strings that are returned.

设置方法

这个例子仍然非常有限,因为您目前不能调整属性中的值来关联您创建的实际对象(比如$lab)。为了调整这些属性,您必须能够从使用该对象的程序中访问这些属性(lab.php)。但是,出于封装和安全方面的考虑,您不希望公开由调用程序直接操作的属性。面向对象的编程标准要求您将属性创建为“私有的”(正如您已经做的那样),然后使用类中的实际方法来更改任何值。

And security-creating a set method in a class provides the class with the ability to verify whether the information placed in the property is valid before the property is updated. If this verification is not performed before changing the attribute value, data corruption may occur. Afterwards, it may be impossible or very difficult to correct the accepted invalid data. Set The method can reject invalid data and return an error message to the caller.

一个set方法允许值被传递到方法中。然后,在更新对象中的属性之前,可以验证这些值。参数(值)被传递到方法调用中括号()之间的方法中。

$dog_error_message = $lab->set_dog_name('Fred');

如果set_dog_name方法存在于Dog类中,并且接受一个代表狗的名字的字符串,那么您可以使用一个类似于前面代码的方法调用。这个调用将把字符串"Fred"传递给set_dog_name方法。它还为set方法提供了向属性$dog_error_message返回值的能力,以指示属性是否被正确更新。您可以简单地从该方法返回一个'TRUE'或' FALSE '布尔值来指示更新的状态。然后,调用程序可以确定如何处理更新的状态。

如果你简单地传回一个'TRUE'或'FALSE',你可以使用 PHP 条件语句的简化版本,称为三元运算符来检查$dog_error_message

print $dog_error_message == TRUE ? 'Name update successful<br/>' : 'Name update not successful<br/>';

For more information about ternary conditional operators, please visit http://php.net/manual/en/language.operators.comparison.php Security and Performance-be careful when displaying error messages to actual users of applications. You can provide too much information to expose your program code unnecessarily. It may be safer to display general error messages to users. In real-time applications, log files should be created to record errors and access to the application itself.

使用这种格式,调用程序(lab.php)可以很容易地确定更新的状态并显示相应的消息。如果$dog_error_message中的字符串为“”),将显示?:(“Name update successful”)之间的信息。如果$dog_error_message中的值为'FALSE',将显示:;之间的字符串(“Name update not successful”)。

Example 3-8. The lab.php file with set methods and error checking

<?php

require_once("dog.php");

$lab = new Dog;

// -------------------Set Properties--------------------------

$dog_error_message = $lab->set_dog_name('Fred');

print $dog_error_message == TRUE ? 'Name update successful<br/>' : 'Name update not successful<br/>';

$dog_error_message = $lab->set_dog_weight(50);

print $dog_error_message == TRUE ? 'Weight update successful<br />' : 'Weight update not successful<br />';

$dog_error_message = $lab->set_dog_breed('Lab');

print $dog_error_message == TRUE ? 'Breed update successful<br />' : 'Breed update not successful<br />';

$dog_error_message = $lab->set_dog_color('Yellow');

print $dog_error_message == TRUE ? 'Color update successful<br />' : 'Color update not successful<br />';

//-----------------------------Get Properties---------------------------

$dog_properties = $lab->get_properties();

list($dog_weight, $dog_breed, $dog_color) = explode(',', $dog_properties);

print "Dog weight is $dog_weight. Dog breed is $dog_breed. Dog color is $dog_color.";

?>

在示例 3-8 中,lab.php现在能够将信息传递到Dog类的$lab对象的属性中。它还确定每个属性的更新是否成功,并做出相应的响应。在这个例子中,您有机会更有效地利用您创建的大量代码。然而,我们将推迟效率,直到你收集了更多的技能。

lab.php代码现在为每个要更新的属性(set_dog_nameset_dog_breedset_dog_weightset_dog_color)调用一个set方法,并将信息传递给每个方法。注意,字符串被传递到每个方法中,除了set_dog_weight方法,它接受一个整数。

现在您需要在Dog类中创建set方法。每个方法现在接受一个参数(字符串或整数)并返回一个'TRUE'或'FALSE'值。该方法的创建风格类似于您之前创建的get_properties方法。现在让我们保持验证过程简单,您将在后面的章节中学习如何改进它。

function set_dog_name($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) < 21) ? $this->dog_name = $value : $error_message = FALSE;

return $error_message;

}

set_dog_name方法将接受一个字符串到函数头(function set_dog_name($value))中定义的$value属性(参数)中。接下来,该方法创建属性$error_message,并提供初始值TRUE。该属性(连同$value属性)将只在方法执行时存在。一旦执行碰到了}右括号,这些属性将不再可用。

Programming considerations-TRUE and FALSE are constants and are part of PHP language. Constants cannot be changed, all of which are capitalized. TRUE is actually indicated internally as 1, while FALSE is indicated internally as 0.

Note

在 PHP 7 中,标量类型提示可以用来强制传递和返回的数据类型。

declare(strict_types = 1);

function set_dog_name(string $value) : string

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) < 21) ? $this->dog_name = $value : $error_message = FALSE;

return $error_message;

}

如果不包括声明行或strict_types = 0,数据类型将不会被强制。为了向后兼容,本教材中的例子不会显示标量类型提示的使用。

Programming considerations-&& is a AND operator. In order for (ctype_alpha($value) && strlen($value) < 21) statement to become TRUE, $value must contain only alphabetic characters and be less than 21 characters.

三元运算符查看$value属性的两种可能状态(包含传递给方法的任何内容)。

The ctype method is used to determine if the characters in $value are alphabetic (ctype_alpha($value)).   The strlen method is used to determine if the length of the string in $value is less than 21 characters (strlen($value) < 21).

要了解更多的ctype功能,请访问: http://php.net/manual/en/book.ctype.php

如果$value属性只包含字母字符,并且少于 21 个字符,那么$dog_name属性将被更新为已传递的值。如果有非字母字符或字符串长度超过 20 个字符,则用FALSE值更新$error_message(表示更新没有发生)。最后,$error_message(或者TRUE或者FALSE)中的值被返回给调用程序。

And security-this process may be a bit confusing now. However, it is important to create a secure program. Every time an application or object receives information from an external source, such as another program or user, the information must be verified. This verification should include restrictions on the amount of information accepted, as well as other constraints. Data transmitted through the Internet (for example, from the user's browser to the network server) may be intercepted and changed. Before using information, it is very important to verify it in the application on the server. You can verify in the browser (through JavaScript) to ensure that the user has entered the correct information. However, as mentioned earlier, the packet interceptor can intercept this information and change it before the application on the web server receives it.

在查看代码更改之前,最后一点需要注意。PHP 提供了几个操作来比较两个值。下表总结了这些操作符,包括新的 PHP 7 Spaceship 操作符。

Operation            Result – returns TRUE if...

$a == $b             $a and $b are equal ignoring cases

$a === $b            $a and $b equal if case is the same

$a != $b, $a <> $b   $a and $b are not equal ignoring cases

$a !== $b            $a and $b are not equal or not same case

$a < $b              $a is less than $b

$a <= $b             $a is less than or equal to $b

$a > $b              $a is greater than $b

$a >= $b             $a is greater than or equal to $b

------------------------------------------------------

(Available with PHP 7)  $a <=> $b            returns -1 if $a < b, returns 0   if $a equals $b

returns 1 if $a > $b

此外,Null Coalesce 运算符可用于在三元运算中使用某个值之前检查该值是否“已设置”(包含某些内容)。

$dog_name = $_POST['value'] ?? 'No Name';

在这个例子中,如果某个东西存在于value中,那么它被放置在$dog_name中。如果没有设置value,则No Name被放置在$dog_name。这个操作符在 PHP 7 中是可用的。考虑到向后的能力,本书中的例子不会演示这个操作符。

让我们更新Dog类以包含所有需要的set方法。

<?php

class Dog

{

// ------------------------------------ Properties -----------------------------------------

private $dog_weight = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

}

Example 3-9. Dog class with set methods in dog.php

// ------------------------------ Set Methods ----------------------------------------------

function set_dog_name($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_weight($value)

{

$error_message = TRUE;

(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_breed($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 35) ? $this->dog_breed = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_color($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $error_message = FALSE;

return $error_message;

}

}

function get_properties()

{

return "$this->dog_weight,$this->dog_breed,$this->dog_color.";

}

}

?>

Program design advice-when coding and testing your program, code only one set method. Then test the method to correct the error. When you have a successful set method, copy and paste it into your code, and make necessary changes. Don't try to code completely before testing the program. Program paragraph by paragraph, and then test. Although you may think this will slow down your coding speed, it is not the case. By catching every small error in the program, you can find them more easily. If you try to write a complete program, you may make many mistakes and spend a lot of time looking for each one. If it is difficult to find the error, comment out (use //) the new lines of code in the program, and then retest. If everything is all right, then gradually (only a few lines at a time) delete the comment line (//) from the code line and retest. This process should help you find lines of code that may cause problems. And security-in real world, programmers should not show users the details that cause the update to fail. Providing too much information may tell hackers what changes can be made to successfully update attributes containing invalid information. Pass the detailed information of the reason for the update failure to the security log file on the server.

代码开始变得冗长。然而,set的每个功能都非常相似。当您编写set函数时,您会发现这是一种常见现象。它还允许你通过复制粘贴工作方法并进行简单的修改,快速创建set方法。在示例 3-9 中,根据更新的信息类型确定不同的字符串长度。另外,$set_dog_weight方法检查传递的字符串中的数值,而不是字母字符。除此之外,方法几乎相同。

图 3-2 展示了当有效信息被传递到每个属性时的输出。显示“成功”消息。还要注意的是,get_properties方法显示了每个属性的新的更新值。在实际环境中,您可以考虑不显示成功的消息,而只显示不成功的消息。

A978-1-4842-1730-6_3_Fig2_HTML.jpg

图 3-2。

Output of successful update via set methods in Dog class—dog.php and lab.php

图 3-3 测试无效信息传递给set方法时产生的输出。请注意,默认值仍然在没有更新的属性中。这强调了包含默认值的需要,以防某些属性没有得到更新。在 PHP 中,显示为NULL(没有值)的属性通常会在输出中显示一个空格。例如,如果$dog_weight没有默认值,输出将显示"Dog weight is ."

Programming suggestion-Although PHP is friendly and tries to change null values to spaces when displaying, it is not a good programming method to assume this happens. Many programming languages will not do this conversion for you, and error messages will be displayed when trying to display attributes with a value of NULL. In addition, it is very important to set default values when using attributes for mathematical calculation. PHP will try again to convert the value of NULL to zero for calculation. However, in some cases, this will not happen and an error message will be displayed. In many programming languages, when trying to calculate with NULL value, an error message will appear. Establishing programming habits applicable to all languages will help you develop skills in multiple languages quickly.

A978-1-4842-1730-6_3_Fig3_HTML.jpg

图 3-3。

Output with invalid weight (1000) and invalid breed (‘Lab12’) in Dog class—dog.php and lab.php

做它

Create an additional property ($dog_gender) in the Dog class. Create a set method (set_dog_gender). Determine if a valid value (Male, Female) has been passed into the set method. You can use the following code or develop your own version to check for valid information. ($value == 'Male' || $value == 'Female') ? $this->dog_gender = $value : $error_message = FALSE;   Go to php.net and search for a method that will allow the checking of any case of “Male” or “Female” from #1. Update the conditional statement to allow any version (Male, MALE, male, Female, FEMALE, female, and so on). Hint: The characters in $value can be changed to all uppercase or all lowercase using the strtoupper or strtolower methods. Then check the string as all uppercase (MALE, FEMALE) or all lowercase (male, female) characters.

获取方法

在我们之前的例子中,您创建了一个同时返回多个属性的get_properties方法。这是一个有效且有用的方法。然而,通常有一个get方法来匹配每个set方法。这为方法中的属性提供了write ( set方法)和read ( get方法)能力。在某些情况下,您可能只想提供一个没有 set 方法的get方法(使属性为只读)。我们可以(尽管很少这样做)提供一个没有get方法的set方法(使属性只写)。

Get方法实际上比set方法更容易编码。不需要验证数据,因为您正在读取数据,而不是更新数据。

function get_dog_name()

{

return $this->dog_name;

}

get方法中所需要的就是return语句,它返回属性中的值,而对象的用户不需要直接访问该属性。

Example 3-10. Dog class with set and get methods—dog.php

<?php

class Dog

{

// ----------------------------------------- Properties ------------------------------------

private $dog_weight = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

}

// ---------------------------------- Set Methods ------------------------------------------

function set_dog_name($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_weight($value)

{

$error_message = TRUE;

(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_breed($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 35) ? $this->dog_breed = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_color($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $error_message = FALSE;

return $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_weight,$this->dog_breed,$this->dog_color.";

}

}

?>

代码很长。然而,如前所述,像set方法一样,get方法彼此非常相似。一旦您创建了一个成功的get方法,您可以复制并粘贴它来创建其他方法。每个get方法需要改变的只是方法的名称和返回的属性。

Example 3-11. The lab.php program using set and get methods

<?php

require_once("dog.php");

$lab = new Dog;

// ------------------------------Set Properties--------------------------

$dog_error_message = $lab->set_dog_name('Fred');

print $dog_error_message == TRUE ? 'Name update successful<br/>' : 'Name update not successful<br/>';

$dog_error_message = $lab->set_dog_weight(50);

print $dog_error_message == TRUE ? 'Weight update successful<br />' : 'Weight update not successful<br />';

$dog_error_message = $lab->set_dog_breed('Lab');

print $dog_error_message == TRUE ? 'Breed update successful<br />' : 'Breed update not successful<br />';

$dog_error_message = $lab->set_dog_color('Yellow');

print $dog_error_message == TRUE ? 'Color update successful<br />' : 'Color update not successful<br />';

// ------------------------------Get Properties--------------------------

print $lab->get_dog_name() . "<br/>";

print $lab->get_dog_weight() . "<br />";

print $lab->get_dog_breed() . "<br />";

print $lab->get_dog_color() . "<br />";

$dog_properties = $lab->get_properties();

list($dog_weight, $dog_breed, $dog_color) = explode(',', $dog_properties);

print "Dog weight is $dog_weight. Dog breed is $dog_breed. Dog color is $dog_color.";

?>

当查看示例 3-11 中的代码时,请注意print语句调用了get方法(print $lab->get_dog_name() . "<br/>";)。操作的顺序,将在后面的章节中详细讨论,导致方法(get_dog_name())首先执行,尽管通常一行代码从左到右执行。该方法返回dog_name(字符串"Fred")中的值。该字符串被放置在方法调用所在的相同位置。在get方法执行之后,代码行现在是

print "Fred" . "<br/>";

然后代码行从左到右执行,产生输出

Fred <br/>

Programming considerations—Unlike attributes, methods cannot be enclosed in quotation marks (""or "). You must use "."to separate and join strings, as shown in the example. If a method is enclosed in quotation marks, PHP will display an error message.

如图 3-4 所示,get方法成功地在属性中显示了更新后的值。如果没有更新任何属性,get方法将显示默认值。

Programming advice-It is a good idea to display the values in the attributes frequently during the coding and testing stages to ensure that they are updated at the appropriate time. However, when you transfer the program from test mode to production mode, you should reduce the number displayed to users. It may not be necessary to display the updated value in the attribute to the user. Generally, it is best to only indicate the success of the update to the user. Before production, you can simply comment out unnecessary lines of print code. This will help to quickly debug the future upgrade of the application by simply deleting the comments in these lines.

A978-1-4842-1730-6_3_Fig4_HTML.jpg

图 3-4。

Dog class output with set and get methods

做它

In addition to creating an additional property ($dog_gender) and set method in the Dog class, create a get method to display the updated values. Also update the lab.php file to include a print statement (similar to the examples in this section) to call the get methods.

构造函数方法

前面例子的一个难点是,如果您最初创建一个对象并用值填充该对象的属性,那么需要调用大量的set例程。Dog类要求您调用四个 set 方法(set_dog_nameset_dog_breedset_dog_colorset_dog_weight),以便在所有属性中放置值。您可以使用一种更有效的方式一次更新所有这些属性。这可以减少lab.php文件中的代码。在您提供初始值之后,您可以使用Dog类中的set例程来进行任何更改(也许狗增加了一些体重)。初始值不是默认值。您仍然需要默认值,以防初始值无效。

当在内存中创建一个类(对象)的实例时,操作系统执行一个构造函数方法,该方法用任何属性和方法构建对象。系统还在内存中建立表来跟踪对象的位置和对象属性中存在的值。当不再需要该对象时,操作系统的垃圾收集器将通过该对象的析构函数方法调用,这将把它从内存中移除。

您还可以包含一个构造函数方法,当对象被放入内存时,该方法将被自动调用。当创建对象($lab = new Dog;的代码行被执行时,它在对象中寻找一个构造函数方法。如果存在,则执行该方法。您可以通过创建对象的同一行将属性的所有初始值传递给这个构造函数方法。

$lab = new Dog('Fred', 'Lab', 'Yellow', 50);

这成为从lab.php程序中提供初始值的一种更有效的方式。

构造函数方法是一种通用格式,函数名为__ construct(注意在单词 construct 之前有两个下划线)。

<?php

class Dog

{

function __construct($value1, $value2, $value3, $value4)

{

// code to update properties

}

// other methods

}

?>

您可以在构造函数中使用现有的set方法来更新属性。您将需要收集任何消息(TRUE/FALSE)并将它们返回给调用程序(lab.php)。您可以使用与最初编码类似的过程来处理get_properties方法。

Example 3-12. Dog class with constructor—dog.php

<?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 = "??";

// ---------------------------------- Constructor ------------------------------------------

function __construct($value1, $value2, $value3, $value4)

{

$name_error = $this->set_dog_name($value1) == TRUE ? 'TRUE,' : 'FALSE,';

$breed_error = $this->set_dog_breed($value2) == TRUE ? 'TRUE,' : 'FALSE,';

$color_error = $this->set_dog_color($value3) == TRUE ? 'TRUE,' : 'FALSE,';

$weight_error= $this->set_dog_weight($value4) == TRUE ? 'TRUE' : 'FALSE';

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

}

//------------------------------------toString----------------------------------------------

public function __toString()

{

return $this->error_message;

}

//... There are no other code changes to dog.php below this line.

For more information about the __construct method, please visit: Example: http://php.net/manual/en/language.oop5.decon.php Video: https://www.thenewboston.com/videos.php?cat=11&video=17181

首先,让我们讨论一下在示例 3-12 中称为__ toString(注意两个下划线)的特殊方法的使用。构造函数方法不允许返回信息(默认情况下)。在构造函数中不能使用return语句。为了将在构造函数中创建的错误消息返回给调用程序(lab.php),你必须欺骗程序。__toString方法允许程序员决定如果试图使用带有对象名(print $lab;)的print(或echo)方法会发生什么。通常会出现一条错误消息,声称对象不能转换为字符串(printecho只能显示字符串)。这可以通过在返回字符串的语句中包含一个__toString方法来覆盖。如果执行了print $lab;语句,您可以通过允许返回$error_message属性中的值来克服能够返回错误消息的问题。

For more information about __toString method and other magic methods, please visit http://php.net/manual/en/language.oop5.magic.php .

set方法返回的TRUEFALSE常量也会引起问题,因为它们是常量而不是字符串。如果您试图使用一个方法(比如strval(TRUE);)将这些常量转换成一个字符串,它们所代表的值(1 代表TRUE,0 代表FALSE)将变成一个字符串,而不是'TRUE'或'FALSE'。因此,它们不能通过__toString方法返回。为了克服这个问题,我们在构造函数中创建了下面的代码来进行从TRUE到’TRUE'或者从FALSE到’FALSE'的转换。

$name_error = $this->set_dog_name($value1) == TRUE ? 'TRUE,' : 'FALSE,';

操作的顺序将导致set_dog_name方法在代码的任何部分之前执行。set_dog_name方法返回TRUEFALSE(常量)。假设该方法在执行后返回一个TRUE,代码行现在应该是

$name_error = TRUE == TRUE ? 'TRUE,' : 'FALSE,';

操作顺序要求对比较(TRUE == TRUE)进行评估。当然,这评估为TRUE。使用了?:之间的语句。

$name_error = 'TRUE,';

因此$name_error被设置为字符串"TRUE,",它现在是一个字符串,而不是一个常量。

还要注意,已经添加了一个',',为下一个'TRUE'或'FALSE''值做准备。传递的每个值(除了最后一个值)必须用','分隔,以便稍后分隔字符串。

评估其他三个类似的行,并在错误消息属性中放置一个'TRUE、'或'FALSE、'(重量错误评估不包括字符串末尾的逗号,因为它是最后一个评估的)。

计算构造函数中的最后一行代码。

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

这一行将每个错误属性的值放入error_message属性中。如果所有的更新都成功了,$error_message属性将包含

"TRUE,TRUE,TRUE,TRUE"

请注意,除了最后一项之外,传递的每一项都包含一个“,”用于分隔。这对于分解字符串的结果是必要的。

Example 3-13. The lab.php file calling a constructor

<?php

require_once("dog.php");

$lab = new Dog('Fred','Lab','Yellow','100');

list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $lab);

print $name_error == 'TRUE' ? 'Name update successful<br/>' : 'Name update not successful<br/>';

print $breed_error == 'TRUE' ? 'Breed update successful<br/>' : 'Breed update not successful<br/>';

print $color_error == 'TRUE' ? 'Color update successful<br/>' : 'Color update not successful<br/>';

print $weight_error == 'TRUE' ? 'Weight update successful<br/>' : 'Weight update not successful<br/>';

// ------------------------------Set Properties--------------------------

...There are no other changes to lab.php below this line.

示例 3-13 第三行的对象创建略有变化。

$lab = new Dog('Fred','Lab','Yellow','100');

您现在通过构造函数将初始值(FredLabYellow100)传递给对象。否则,您将不得不四次调用set方法(set_dog_nameset_dog_breedset_dog_colorset_dog_weight)来完成同样的事情。这允许您使用set方法进行最初设置对象后所需的更新($lab)。

为了确定对四个属性的更新是否成功,您必须从对象的$error_message属性中检索您的值(TRUETRUETRUETRUE)。在Dog类中的__toString方法允许你把$lab当作一个字符串来处理。这允许您在类似于get_properties方法输出的过程中使用explode方法。

list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $lab);

这一行代码将把$lab ( TRUETRUETRUETRUE)的内容用逗号分开,并将每个部分放入属性$name_error$breed_error$color_error$weight_error。现在,这些属性中的每一个都将包含字符串'TRUE'。然后,您可以评估您的消息,以类似于评估set方法结果的技术来查看更新是否成功。

print $name_error == 'TRUE' ? 'Name update successful<br/>' : 'Name update not successful<br/>';

在评估set方法的结果时,这种格式和类似的语句之间只有一些微小的区别。这些代码行中的每一行都使用不同的错误消息进行评估($name_error$breed_error$color_error$weight_error)。之前,您对 set 方法的所有结果使用了相同的属性($error_message)。您现在评估的是字符串'TRUE'而不是常量TRUE(代码中唯一的区别是实际的引号)。

在图 3-5 中,前四行输出是通过将值传递给构造函数来为每个属性提供初始设置而产生的。当使用set方法更改属性中的值时,将产生接下来的四行输出。最后四行是通过执行get方法产生的(显示每个属性的内容)。最后一行输出是由get_properties方法产生的。虽然初始值(FredLabYellow100)被成功地传递到构造函数中,但是每个属性中的值都是使用 set 方法(传递SallyLabradorBrown5)更改的。

And security-some people think that checking for errors every time you update is redundant. However, in the current environment where people are constantly trying to destroy data, we must be as careful as possible when updating. No program can 100% avoid data corruption. I hope you have noticed that once you develop a routine to check data, your data check code lines will become very similar every time. Therefore, by copying and pasting (slightly modifying) the code lines that successfully verify the data, you can greatly improve the security without adding a lot of extra coding time.

A978-1-4842-1730-6_3_Fig5_HTML.jpg

图 3-5。

Output from dog.php and lab.php with the constructor, set, and get methods

做它

This chapter has covered a lot of PHP examples and terms. To help clarify any difficulties you might currently be experiencing in understanding the PHP language, visit this web site for additional tutorials: http://www.w3schools.com/php/default.asp   Update the constructor in the Dog class to also allow the passing of the dog_gender value (Male or Female). Update the lab.php program to pass the gender through the constructor. Also update lab.php to explode the $lab string into five parts (one more for the dog_gender). Then evaluate the results ('TRUE' or 'FALSE') returned from the gender_error to display "Gender update successful" or "Gender update not successful".

章节术语

| PHP 扩展 | 梨树 |
| require | require_once |
| 班级 | 性能 |
| 变量 | 方法 |
| 功能 | 目标 |
| 包装 | 私人的 |
| set方法 | get方法 |
| $this指针 | 串并置 |
| Include | include_once |
| new关键字 | return |
| 逗号分隔的字符串 | explode方法 |
| 子链 | set方法 |
| 布尔值 | 条件语句 |
| 三元运算符 | 整数 |
| 参数 | 零合并运算符 |
| &&, And | ctype_alpha |
| strlen | ctype_digit |
| &#124;&#124;, Or | 宇宙飞船操作员 |
| 空 | 操作顺序 |
| 构造函数方法 | 析构函数方法 |
| __toString | 列表对象 |

第二章问题和项目

多重选择

PHP variables begin with which symbol? ?   !   $   &     A valid function name can start with which of the following? A letter   An underscore   A number   Both A and B     Which conditional statement syntax is valid for PHP? IF (condition) { execution };   IF (condition) { execution }   if (condition) { execution };   if (condition) {execution}     Which of the following includes properties and actions (methods or functions) that can occur in an application? Class   set method   get method   for loop     Which basic function does a constructor method perform? Defines properties   Defines methods   Places an instance of a class into memory   A and B     The && symbol and/or the word AND Are relational operators, when used, and require all statements checked to be true for the complete if statement to be considered true   Are conjunctions used in PHP to connect programs   Are relational operators, when used, and require that only one statement checked to be true for the complete if statement to be considered true   Are relational operators and conjunctions used in PHP to connect programs     An integer is which of the following? A string or text value   A floating-point number   A whole number   A fraction     A ternary operator is which of the following? An alternative to setting a variable   An alternative to using an if-else conditional statement   An alternative to using an embedded if-else conditional statement   B and C     How does one create an instance of the class fred and execute the constructor method in PHP? $this->fred   $variable = new fred   $load_class = fred   $fred     What purpose does the method ctype_digit serve? Randomly generates an integer value   Determines whether characters inputted are numeric   Determines whether a class output is numeric   Converts a string value to an integer     Pear is an acronym for PHP Extends and Applies Registry   PHP Excellence in Applied Requirements   PHP Extension and Application Repository   Properties of Extension and Application Registry     Creating set methods inside classes Allows properties to be changed automatically   Should not be done as it corrupts data   Provides the ability for the class to verify information before the property is updated   Provides the ability for the class to verify information after the property is updated     if statements Are also called conditional statements   Can compare two values to determine if they are the same or different   Use comparison operators (==, <, >, <=, >=) to determine if the statement is true or false   All of the above     $x && $y condition is TRUE if Both $x and $y are true   $x or $y are true   Both $x and $y are false   $x or $y are false     Select the statement about Boolean values that is false. Boolean is a data type having two possible values—TRUE and FALSE   Boolean values represent the truth values of logic   Boolean values are only associated with conditional statements   In PHP Boolean literals, TRUE and FALSE are case-sensitive     Which one of these numbers is an example of an integer? 1.01   2f   423   .002     Protecting an object’s data from code outside the class is called what? Inheritance   Encapsulation   Classification   Blocking     Which kind of methods are easier to code than set methods and are known as read only methods since they do not change any property values? explode methods   get methods   constructor methods   match methods     Why would you include objects in your program? To make notes in your code   To make mini mobile programs   To make your code more complex   To protect your code from direct access     How do you call a function named myFunction? call myFunction();   myFunction();   call function myFunction;   call.myFunction();     A class can contain all of the following except Properties   Methods or functions   Conditional statements   Machine code     When working with a comma-delimited string, which character is used to separate the data in the string? Space   Semicolon   Asterisk   Comma

对/错

Methods are functions that belong to a class.   You would use the symbol || or the word or when you want both conditions to be true.   When a function is private, it can only be used inside the class in which it exists.   The explode method can be used to separate a string at the specified delimiter.   The new keyword tells the operating system that an instance of that class should be created in memory.   The function Ctype_alpha will return true when a number is passed into it.   require_once will not execute if the file has already been attached to the program.   The ctype_digit function determines if the characters passed are alphabetic.   The ctype_alpha function determines if the characters passed are alphabetic.   A PHP constructor has the same name as the class.   A ternary operator is shorthand for a while loop.   If a file has already been included in a program, the require_once function will generate only a warning and allow the program to continue.   Destructor methods are called when there are no remaining references to an object or when that object has been explicitly destroyed.   Objects are blocks of code that have already been compiled for use in an application.   The else statement extends an if statement by allowing code to execute when the if statement evaluates to FALSE.   include or require statements can only be placed at the top of an PHP program.   Programmers can pull their own libraries of code into an application via the require or require_once statement.

简答/短文

Explain the meaning of encapsulation and how classes are encapsulated.   Why should a programmer use set and get methods?   Why should every entry on a web page from a user be validated?

项目

Create a PHP program with a class (Student) with the following properties: student_id, student_name, student_address, student_state, student_zip, and student_age. The program includes get and set methods for each property. Validate the proper type and size of data passed into each property. The program also includes the ability for each property to use the constructor to set values. Create an instance of the class passing properties through the constructor. Change two of the properties using set methods. Display the properties using get methods.   Create a PHP program to keep track of inventory within a grocery store. Each item (class) includes an item number, description, size, self, isle, amount, and price. Each field must be verified for proper information before the entries are accepted. Item numbers range from 00000 to 99999. The store has 16 isles (00-15). No price in the store is greater than $1000. All entries are coded via a constructor or set methods. After all entries have correctly been accepted, the program will generate a report of the entries (using get methods).

学期项目

Using the design from the Chapter 2 Term Project, create a PHP program that will provide the interface for entering in the ABC Computer Parts Corporation inventory items for the warehouse. The PHP class must verify the contents of the information passed from each field (via the set methods) to ensure that no corruption has taken place. Set and Get methods for each property must exist in the class. The constructor should use the set methods to populate the properties. Also create an interface program that will make an instance of the class and test the ability to populate the properties. The test program should generate a report of the item placed in inventory (similar to the output shown in this chapter). The files created should use logic similar to what was shown in the examples in this chapter.

四、安全用户界面

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1730-6_​4) contains supplementary material, which is available to authorized users.

对于失败者来说,所有伟大的想法都是糟糕的想法。与已知的失败者一起测试一个新想法,以确保他们不喜欢它,这总是好的。”—迪尔伯特 97 年 11 月 18 日( http://dilbert.com/strips/comic/1997-11-18/ )

章节目标/学生学习成果

完成本章后,学生将能够:

  • 解释为什么用户输入必须在界面层和业务规则层进行验证
  • 解释为什么必须在业务规则层过滤用户输入
  • 使用 HTML5 代码验证用户输入
  • 使用 JavaScript 代码验证用户输入
  • 使用 PHP if语句(条件语句)来验证和过滤输入
  • 使用foreach循环从 XML 文件中动态创建一个 HTML 选择框
  • 使用简单的数组进行过滤和验证
  • 将简单数组传递给方法(函数)
  • 了解如何使用依赖注入来控制代码版本变更

安全的用户交互

在第二章的中,Hello World的例子包括用户交互(点击提交按钮)来调用 PHP 程序。本章将使用 HTML web 表单接受来自用户的信息,然后将这些信息传递给一个 PHP 程序。

不要相信你的用户!您必须为用户将输入 web 表单的任何类型的信息做好准备。在程序中接受信息之前,您还必须确保每次都验证和保护信息。你必须记住,用户可以选择不允许 JavaScript 在他们的浏览器中运行。因此,您的程序不能依赖 JavaScript 代码来验证输入。您必须能够处理这三种情况:

If the user is using an HTML5 capable browser, you can verify all input using HTML5 before sending it to the PHP program on the web server.   If the user is using a browser that does not have HTML5 (or complete HTML5) capability, you can verify all (or some) input using JavaScript. Then you can send the validated information to the PHP program on the web server.   If the user is using a browser that does not have HTML5 capability and has JavaScript disabled, the user input can still be verified by the PHP program on the web server.

最好使用前两种方法中的任何一种来处理初始验证。这将在将信息发送到服务器之前验证信息的正确性。方法#3 将导致更多的服务器调用,因为必须将信息发送到服务器进行验证,然后必须将任何错误消息发送回浏览器供用户更正。

即使您使用方法#1 或#2 验证信息,当 PHP 程序在 web 服务器上接收信息时,您将再次评估您已经接收到有效的信息。即使用户可能发送了有效信息,数据包嗅探程序也会将有效信息变成有害信息。

HTML5 表单验证

构建 HTML5 表单时,可以验证文本框中输入的信息。但是,没有实现 HTML5 技术(或所有 HTML5 技术)的浏览器会将对象(如文本框)视为普通的非验证对象。框中的信息将被接受,无需任何验证。因此,您还必须包含一个 JavaScript 例程来捕捉任何未经 HTML5 验证的内容。

And security—Secure programming is only one part of the whole process of protecting your information. Files, directories, servers, networks and databases must also be properly protected. In addition, any very important information (such as credit card number) must be sent through a secure channel (HTTPS) to further protect the information. User id and password should be encrypted to make it difficult for packet sniffing software to find them.

您将继续使用第三章的中的示例,更新Dog类示例以接受来自用户的属性信息。Dog类已经有了安全性,所以您不需要对该类做任何额外的安全性更新。

Example 4-1. The lab.html file with some validation

<!DOCTYPE html>

<html lan="en">

<head>

<title>Dog Object</title>

<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="lab.php" onSubmit="return validate_input(this)">

<h2>Please complete ALL fields. Please note the required format of information.</h2>

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" /><br />

Your Dog's Breed (max 35 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*" title="Up to 35 Alphabetic Characters" maxlength="35" name="dog_breed" id="dog_breed" /><br />

Your Dog's Color (max 15 characters, alphabetic) <input type="text" pattern="[a-zA-Z]*" title="Up to 15 Alphabetic Characters" maxlength="15" name="dog_color" id="dog_color" /><br />

Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" /><br />

<input type="submit" value="Click to create your dog" />

</form>

</div>

<noscript>

<div id="noJS">

<form method="post" action="lab.php">

<h2>Please complete ALL fields. Please note the required format of information.</h2>

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" /><br />

Your Dog's Breed (max 35 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*" title="Up to 35 Alphabetic Characters" maxlength="35" name="dog_breed" id="dog_breed" /><br />

Your Dog's Color (max 15 characters, alphabetic) <input type="text" pattern="[a-zA-Z]*" title="Up to 15 Alphabetic Characters" maxlength="15" name="dog_color" id="dog_color" /><br />

Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" /><br />

<input type="submit" value="Click to create your dog" />

</form>

</div>

</noscript>

</body>

</html>

在示例 4-1 的head部分,在validator.js文件被插入到程序中之后,一些 CSS 被包含进来,隐藏了(display:none)一个 ID 为JS的 division ( div)标签。就在 CSS 代码的下面是少量的 JavaScript 代码。这段代码将JS分区切换为可见($("#JS").show())并隐藏noJS分区。在JSnoJS赛区的形式之间只有一个细微的区别。您应该注意到,noJS表单没有调用validate_input JavaScript 函数。

这是为什么?

答案很简单。如果浏览器没有启用 JavaScript,隐藏和显示部门的简短 JavaScript 例程将不会运行。因此,JS 分部不会显示在浏览器中(因为代码顶部的 CSS 将其显示设置为 none)。将会看到noJS分区。这允许没有 JavaScript 的浏览器将用户输入的信息直接发送到 web 服务器上的 PHP 程序。希望用户有一个 HTML5 浏览器,在信息被发送到服务器之前仍然可以验证信息(在这个场景中是第一个)。然而,即使他们不这样做,信息也将在服务器上得到验证(这种情况下的第三步,你将在本章后面看到。

如果浏览器中启用了 JavaScript,hide-show 例程将“隐藏”部门noJS并显示 JS 部门。将信息提交给validate_input函数的表单将显示在浏览器中。这将允许启用了 JavaScript 的浏览器验证使用validate_input JavaScript 函数输入的信息。如果用户有一个 HTML5 浏览器(在这个场景中是#1),这可能是多余的,因为 HTML5 可以验证信息。但是,您必须为没有升级浏览器的用户做好准备(在这种情况下是第二种)。

A978-1-4842-1730-6_4_Fig1_HTML.jpg

图 4-1。

The lab.html file

在示例 4-1 中,dog_namedog_breeddog_color文本框将字段长度设置为与第三章的Dog类中验证的最大值相同。他们还使用 HTML5 pattern属性(【a-zA-Z】)只允许字母字符。title属性用于在输入错误信息时向用户显示错误。dog_weight文本框使用输入类型number,它自动限制输入。minmax参数也被设置为将狗的体重限制在 1 到 120 磅之间。如上所述,如果浏览器没有完全实现 HTML5 标准,它可能不会解释这些限制。因此,您还必须使用 JavaScript validate_ input方法来验证数据,以确保所有的验证都已完成。

支持 HTML5 的浏览器会在信息被发送到validate_input函数之前对其进行验证。因此,如果浏览器完全是 HTML5,所有信息也将通过 JavaScript 方法的验证。您可以检查浏览器版本,以确定浏览器是否是 100% HTML5。然而,这将需要更多的 JavaScript 代码,并且是不必要的,因为应用不需要更多的代码就可以完成它的任务。

重要的是,在接受来自用户的信息并将该信息传递给应用的整个过程中,所有验证都是一致的。用户可以很容易地查看 HTML 和 JavaScript 代码(您可以在浏览器中“查看源代码”来查看代码)。任何高度安全格式的验证都应该在服务器上编译和保护的编程语言中进行。

And security-you may wonder why you bother to verify on the client side. Why not pass all the information to the program on the server and let the program tell you whether you need to fix anything? Some programmers actually do this. However, the goal is to have an efficient program. By trying to verify the information in the user's browser, you reduce the number of calls to the server. This improves the performance and efficiency of applications and web servers. In addition, as you will see, users can still adjust the contents of the text box by verifying in the browser. If the verification is done on the web server, the information in the HTML form will be lost, because every time information is sent and received from the web server, the web page will be reloaded.

在浏览器中进行验证的目的是确保用户提供的信息符合服务器上程序的要求。最初,您可能会害怕这个示例向用户显示所需信息的格式。但是,目标不是保护数据;目标是确保数据是有效的。通知用户所请求的数据格式并不违反安全。

<form method="post" action="lab.php" onSubmit="return validate_input(this)">

表单使用参数onSubmit将所有输入的信息传递给 JavaScript 方法(validate_input)。this关键字表示来自this对象(即表单本身和所有文本框)的所有信息都被传递给该方法。return关键字表示该方法将返回一个TRUEFALSE状态。如果从validate_input方法返回了TRUE状态,HTML 代码会将表单提交给服务器上的 PHP 程序(lab.php)。

做它

Adjust Example 4-1 to include gender information from the Do It in Chapter 3. Use a text box to receive the information from the user. Try to use HTML5 to restrict the type of information the user can enter in the text box. Test your code.

JavaScript 验证

虽然这不是一本 JavaScript 书,但它将简要介绍 JavaScript 方法validate_ input中使用的表单验证。

Example 4-2. Form validation via JavaScript—validator.js

function allalphabetic(the_string)

{

var letters = /^[a-zA-Z ]+$/;

if (the_string.match(letters))

{

return true;

}

else

{

return false;

}

}

// -------------------------------------------------------------------------------------------

function validate_dog_name(the_string)

{

if ((the_string.length > 0) && (allalphabetic(the_string)) && (the_string.length < 21))

{

return true;

}

else

{

return false;

}

}

// -------------------------------------------------------------------------------------------

function validate_dog_breed_color(the_string)

{

if ((the_string.length > 0) && (allalphabetic(the_string)) && (the_string.length < 35))

{

return true;

}

else

{

return false;

}

}

function validate_dog_weight(the_string)

{

if ((the_string > 0 && this_string <= 120) && (!isNaN(the_string)))

{

return true;

}

else

{

return false;

}

}

// -----------------------------------------------------------------------------------------

function validate_input(form)

{

var error_message = "";

if (!validate_dog_name(form.dog_name.value))

{

error_message += "Invalid dog name. ";

}

if (!validate_dog_breed_color(form.dog_breed.value))

{

error_message += "Invalid dog breed. ";

}

if (!validate_dog_breed_color(form.dog_color.value))

{

error_message += "Invalid dog color. ";

}

if (!validate_dog_weight(form.dog_weight.value))

{

error_message += "Invalid dog weight. ";

}

if (error_message.length > 0)

{

alert(error_message);

return false;

}

else

{

return true;

}

}

if (!validate_dog_breed_color(form.dog_color.value))

{

error_message += "Invalid dog color. ";

}

if (!validate_dog_weight(form.dog_weight.value))

{

error_message += "Invalid dog weight. ";

}

if (error_message.length > 0)

{

alert(error_message);

return false;

}

else

{

return true;

}

}

<script src="validator.js"></script>

在示例 4-1 中,JavaScript 文件validator.js通过 HTML script标签附加到head部分的lab.html文件中。通过将 JavaScript 验证放在一个单独的文件中,简化了 HTML 文件。JavaScript 文件依赖于 HTML 文件来显示。错误信息被传递给一个警告框(alert(error_message));)在方法的底部附近。编程建议——通过只将一个状态(错误消息)传回给一个调用程序,您可以灵活处理。然后,调用程序可以确定如何显示信息。这也使您可以轻松地为其他 HTML 表单重用该方法。

让我们向后看 JavaScript 代码(示例 4-2 )。在 HTML 文件中,form标签的onSubmit参数调用了validate_input JavaScript 方法。该方法控制表单上每个文本框的所有验证。该方法使用一系列的if语句来调用验证不同类型文本框的方法。每个if报表的格式都非常相似。

if (!validate_dog_name(form.dog_name.value))

如果验证通过,调用的每个方法将返回一个真响应,如果验证失败,将返回一个假响应。(在 JavaScript 中,常量truefalse是小写的。在 PHP 中,它们是大写的。)

通过将form传递给validate_input函数,HTML 表单中的所有参数和值同时被传递。您可以使用点符号来选择要使用的参数。

在前面的例子中,form.dog_name.value将提取在dog_name文本框中输入的值。用户在该文本框中输入的值被传递给validate_dog_name方法。

if (!validate_dog_name(form.dog_name.value))

{

error_message += "Invalid dog name. ";

}

函数名前面的!符号表示not。如果来自一个方法(或比较)的响应是true,一个if语句通常会执行。但是,如果从方法(validate_dog_name)返回的响应是false(它没有被正确验证),您希望在error_message属性中保存一条错误消息。如果函数返回一个 false 值,符号!会导致if语句执行与正常情况相反的操作。

其他每种验证方法都以类似的方式工作。这些方法检查输入的信息格式是否正确。如果信息正确,该方法返回true。如果信息不正确,该方法返回false

For more information about JavaScript if conditional statements, please visit https://www.thenewboston.com/videos.php?cat=10&video=16960 for more information about JavaScript functions (methods), please visit https://www.thenewboston.com/videos.php?cat=10&video=16952

function validate_dog_name(the_string)

{

if ((the_string.length > 0) && (allalphabetic(the_string)) && (the_string.length <= 20))

{

return true;

}

else

{

return false;

}

}

每个方法通过将值传递到the_string属性(function validate_dog_name(the_string))来接受来自特定文本框的值。每个方法中的if语句完成了大部分工作。在validate_dog_name方法中,if语句试图检查狗名的三个要求。语句的第一部分使用length方法来确保字符串的长度大于零。如果字符串的长度不大于零,则用户没有在文本框中输入任何内容。接下来,if语句将the_string传递给一个allalphabetic方法,以确定文本框是否只包含字母字符(您将很快看到这个函数)。最后,if语句再次检查字符串长度,以确保它没有超过 20 个字符。&&符号是一个AND符号,要求这三个检查都为真,完整的if语句才被认为是真的。如果这三个都为真,if语句将true返回给validate_input方法。如果三者中的任何一个为假,if语句将false返回给validate_input方法。

Programming instructions-JavaScript strings contain built-in methods that will automatically exist once variables are created and string values are set. In the example in this chapter, you used both the length method and the match method. length The method returns the length of the string. match The method determines whether the string contains a set of characters passed to the method (see the next example).

dog_namedog_breeddog_color都要求只允许字母字符。所以您创建了可以被每个验证方法调用的allalphabetic方法,而不是重复相同的代码。

function allalphabetic(the_string)

{

var letters = /^[a-zA-Z ]+$/;

if (the_string.match(letters))

{

return true;

}

else

{

return false;

}

}

allaphabetic方法使用正则表达式来检查字母字符。大多数编程语言(包括 PHP)允许程序员创建一个正则表达式来检查字符串是否符合要求的格式。

var letters = /^[a-zA-Z ]+$/;

这一行设置表达式来确定字符串是否只有小写和/或大写字母字符(A 到 Z,A 到 Z)。在后面的章节中,你将更详细地学习 PHP 中的正则表达式。

For more information about JavaScript regular expressions, please visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions

存在于所有 JavaScript 字符串中的方法之一是match方法。该方法将字符串(the_string)的内容与正则表达式(本例中为字母字符)进行比较。如果字符串满足表达式的要求(只包含字母字符),那么方法将返回true。否则将返回false。在本例中,如果字符串是字母,则true返回到validate_dog_name(或validate_dog_colorvalidate_dog_breed)方法中的if语句。如果字符串的任何部分包含除字母字符之外的内容,则向if语句返回 false(这反过来会使if语句的结果为 false,即使字符串大小是正确的)。

回到 JavaScript 代码的底部,您决定向 HTML 程序返回什么(lab.html)。

if (!validate_dog_weight(form.dog_weight.value))

{

error_message += "Invalid dog weight. ";

}

if (error_message.length > 0)

{

alert(error_message);

return false;

}

else

{

return true;

}

如果验证失败(如果从if语句返回一个false,在validate_input方法底部的每个if语句将构建一个错误消息(如"Invalid dog weight.")并将其放入error_message属性中。+=符号将当前属性中的内容与放入属性中的内容连接起来(这样就不会覆盖已经存在的任何消息)。

最后一个if语句(if (error_message.length > 0)检查error_message属性的长度。如果它大于零(错误消息已经被传递到属性中),程序将在一个警告框中显示消息,并将false返回给 HTML 程序(lab.html)。如果长度为零,则没有错误消息;true被传递回 HTML 程序。

<form method="post" action="lab.php" onSubmit="return validate_input(this)">

在示例 4-1 中,如果从validate_input方法返回一个truefalse,表单标签的格式将自动引起响应。如果返回true(没有验证问题),HTML 程序将把表单属性和值发送给 web 服务器上的lab.php程序。如果返回false(至少有一个验证问题),HTML 程序将不会传递信息,并将在浏览器中保持可见。将向用户显示警告框,以查看发生了什么错误。当用户关闭警告框时,他们仍将在 HTML 页面上(带有他们先前输入的信息)。他们可以更正任何不正确的信息,并单击提交按钮(再次)重新验证他们输入的信息。

A978-1-4842-1730-6_4_Fig3_HTML.jpg

图 4-3。

The lab.html file after failing JavaScript verification

A978-1-4842-1730-6_4_Fig2_HTML.jpg

图 4-2。

The lab.html file with incorrect entries Note

不同的浏览器对 JavaScript 代码有不同的反应。默认情况下,Internet Explorer 会尝试阻止 JavaScript 代码。如果您看不到任何结果,并且确定您的代码是正确的,但是却收到关于缺少方法(例如 validate_input)的错误消息,这可能是因为您的浏览器执行 JavaScript 的能力被关闭了。要么在浏览器的设置中打开它,要么尝试允许 JavaScript 运行的不同浏览器。

And security-although you have verified the input on the HTML page, the data may still harm the system. Users may try to send HTML, JavaScript or even PHP code, thus causing harm. This may be because an error message was entered in the HTML page, the packet sniffer intercepted the data transmitted to the PHP page, or a program tried to completely bypass the HTML page. No matter which of these activities produces harmful information, PHP programs need to detect it and deal with problems. Therefore, the receiver (lab.php) must try to filter (remove harmful codes) the data.

做它

Adjust Example 4-2 to validate the gender text box from the first Do It. Make sure to include an error message to indicate the problem(s). Also make sure the error message is added to the $``error_message property to be displayed with all other error messages. Make sure to test your code for all possible problems with the user entering data. Don’t forget to test if they do not enter any data.

PHP 过滤

是时候看看需要对 php 文件(lab.php)进行的更改了。您需要能够从 HTML 程序中接受属性和值(dog_namedog_breeddog_colordog_weight)。但是,您需要担心有人可能会试图发送可能会影响程序运行的信息,甚至会使运行该程序的系统崩溃。

本节采用两种方法来帮助减少有害数据的可能性。首先,您将确定是否收到了所有必需的信息。如果没有,您将要求用户返回到 HTML 页面(lab.html)来输入信息。这将至少确保您拥有所有数据,并且满足 HTML5 页面和/或 JavaScript 程序提供的验证(validator.js)。其次,您将使用一些现有的 PHP 方法从接收到的数据中过滤出 HTML、JavaScript 和 PHP 语法。这将减少可执行语句被传递到程序中的机会。

Example 4-3. Partial listing of the top of lab.php with the clean_input method

<?php

require_once("dog.php");

function clean_input($value)

{

$bad_chars = array("{", "}", "(", ")", ";", ":", "<", ">", "/", "$");

$value = str_ireplace($bad_chars,"",$value);

// This part below is really overkill because the string replace above removed special characters

$value = htmlentities($value); // Removes any html from the string and turns it into < format

$value = strip_tags($value); // Strips html and PHP tags

if (get_magic_quotes_gpc())

{

$value = stripslashes($value); // Gets rid of unwanted quotes

}

return $value;

}

if ((isset($_POST['dog_name'])) && (isset($_POST['dog_breed'])) && (isset($_POST['dog_color'])) && (isset($_POST['dog_weight'])))

{

$dog_name = clean_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']);

$lab = new Dog($dog_name,$dog_breed,$dog_color,$dog_weight);

list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $lab);

...

For more information about PHP function isset, please visit example: http://php.net/manual/en/function.isset.php Video: https://www.thenewboston.com/videos.php?cat=11&video=17087 For more information about $_POST, please visit example: http://php.net/manual/en/reserved.variables.post.php Video: https://www.thenewboston.com/videos.php?cat=11&video=17087

lab.php的顶部,您添加了几个项目来提供更安全的代码。

if ((isset($_POST['dog_name'])) && (isset($_POST['dog_breed'])) && (isset($_POST['dog_color'])) && (isset($_POST['dog_weight'])))

这个if语句使用isset方法和$_POST来验证所有四个属性(dog_namedog_breeddog_colordog_weight)已经通过POST方法传递到程序中。如果所有项目都已通过,那么您将过滤(清除)这些项目。如果其中任何一个没有通过,一个else语句(您将在后面看到)将要求用户返回到lab.html页面输入所有需要的信息。

$dog_name = clean_input($_POST['dog_name']);

Each property is passed to the clean_input method (after it has been retrieved using the $_POST method) to remove harmful tags. In this example, the $dog_name property (on the left side of the = sign) will receive the cleaned information.

function clean_input($value)

{

$bad_chars = array("{", "}", "(", ")", ";", ":", "<", ">", "/", "$");

$value = str_ireplace($bad_chars,"",$value);

$value = strip_tags($value); // Strips html and PHP tags

$value = htmlentities($value); // Removes any html from the string and turns it into < format

if (get_magic_quotes_gpc())

{

$value = stripslashes($value); // Gets rid of unwanted quotes

}

return $value;

}

Note

set_magic_quotes_runtimemagic_quotes_runtime从 PHP 5.4 开始已经被移除。魔术引号(从 PHP 5.4 开始)不能在运行时设置。

For more information about the str_ireplace method, please visit http://php.net/manual/en/function.str-ireplace.php

方法clean_input创建了一个数组$bad_chars,它包含了一个你想从字符串中删除的所有特殊字符的列表(你将在本章的后面和以后的章节中更详细地了解数组)。str_ireplace方法($value = str_ireplace($bad_chars,"",$value);将把所有这些字符替换为$value属性中的""(什么都没有,它把它们移除了)并将剩下的字符放入$value。有时,您可能需要这些特殊字符(例如在 SQL 语句中)。然而,在这个例子中,您不需要任何输入,所以您可以删除它们。

Note

对于这个例子来说,只删除无效的字符是有效的。在现实世界中,您不需要同时包含stripslasheshtmlentitiesstring_tags方法(如方法中所示)。这里包含它们是为了演示它们的用法,以及为什么在其他情况下,您可能会使用它们。

三个 PHP 方法(stripslasheshtmlentitiesstrip_tags)用于转换有害代码,使其无法执行。stripslashes方法删除引号中的反斜杠(实际上也可以通过str_ireplace方法完成)。htmlentities方法将 HTML 字符转换成等价的 HTML 实体(例如 a 被转换成')。方法删除任何 PHP 或 HTML 标签。如果您使用 SQL 来更新数据库,您需要添加额外的函数来禁用任何有害的 SQL 语句。

For more information about stripslashes, htmlentities and strip_tags, Please visit examples-stripes: http://php.net/manual/en/function.stripslashes.php examples-HTML entities: http://php.net/manual/en/function.htmlentities.php examples-stripe label: http://php.net/manual/en/function.strip-tags.php video-magic quotes and stripes: https://www.youtube.com/watch?v=NUqkUjbGuPY video-[ strip_tags: https://www.youtube.com/watch?v=rn_dnQFLt3U Safety and performance It is much easier to delete harmful data or reject data before using or saving data. In the face of today's major security vulnerabilities, this is absolutely necessary.

你也可以限制哪些程序调用 PHP 程序。$__SERVER全局变量可以帮助你。

if($__SERVER['HTTP_REFERER'] == 'http://www.mysite.com/lab.html

{ //  code executed if valid location }

else {exit;}

For more information about the $__SERVER array, please visit the example: http://php.net/manual/en/reserved.variables.server.php Video: https://www.thenewboston.com/videos.php?cat=11&video=17047

这个示例代码将拒绝任何调用lab.php程序的程序(除了位于站点的lab.html程序)。如果不是从正确的 HTML 页面调用的话,exit;命令会关闭程序。

演示的过滤方法不会阻止某人输入“asabsbabsa”作为狗名。然而,这些方法将防止任何进入有害。

Example 4-4. Partial list of the bottom of lab.php with the clean_input method

else

{

print "<p>Missing or invalid parameters. Please go back to the lab.html page to enter valid information.<br />";

print "<a href='lab.html'>Dog Creation Page</a>";

}

提到的if语句的true部分包括了lab.php文件中的所有活动代码。如果一个或多个参数丢失,else部分(在lab.php文件的底部)将要求用户返回到lab.html页面正确输入信息。

做它

在新的lab.php文件中调整代码(从该书的网站下载)。添加代码以过滤错误的性别代码,并确保已经从 HTML 文件接收到性别信息。确保通过clean_input方法传递您的属性,以删除任何有害的数据。

附加 HTML 输入安全性

从本章到目前为止的所有代码中可以看出,每当在 HTML 表单上使用文本框时,都必须包含几个区域的附加代码来验证用户输入的内容。当您需要允许用户灵活输入内容(例如包含姓名和地址的表单)时,文本框是必需的。但是,当您希望将用户的响应限制在一个特定的可能值列表中时,可以使用其他 form 对象(如州的两个字母缩写)。这将提供更多的有效数据,因为用户将无法输入错别字或输入无效数据。

HTML5 选择列表框和单选按钮

可以从原始 HTML 文件中更改的一项是狗的品种条目。美国养犬俱乐部目前在其网站上列出了超过 150 个品种。您可以在 HTML 文件中为每个品种键入一个选项值行。然而,这将是耗时的。此外,如果一个品种改变了,你将不得不回去调整名单。更好的选择是将品种放在一个文件中,然后使用该文件填充一个select列表。如果一个品种改变或增加,你只需在一个地方更新一个文件,所有使用它的程序将自动访问新的列表。如果这个文件托管在 web 服务器上,您也可以在dog.php代码中使用同一个文件来验证用户已经将正确的品种传递给 web 服务器。

Example 4-5. The breeds.xml file

<?xml version="1.0" encoding="UTF-8"?>

<breeds>

<breed>Affenpinscher</breed>

<breed>Afghan Hound</breed>

<breed>Airedale Terrier</breed>

<breed>Akita</breed>

<breed>Alaskan Malamute</breed>

<breed>American English Coonhound</breed>

<breed>American Eskimo Dog</breed>

<breed>American Foxhound</breed>

<breed>American Staffordshire Terrier</breed>

...

</breeds>

安全性和性能——在 HTML 表单上为用户提供一个可供选择的值列表,比使用文本框提供了更高的有效性和安全性。如果用户只能从列表中选择,他们不能选择错误,也不能输入无效或有害的信息。

breeds.xml文件包含简单的 XML 代码(两个标签— breedsbreed)列出了所有品种。如果你正在创建一个真正的狗品种网站,你可能会希望在这个文件中包含更多的信息。您可以稍后添加更多信息,而不会影响此程序。

您现在想要使用 XML 文件(来自示例 4-5 )来填充选择列表框。因为这个文件将驻留在服务器上,所以您需要在服务器上创建一个程序来调用和检索信息。假设该文件在服务器上是安全的,只能进行只读访问。您不会尝试更新或删除文件本身的任何信息。

您可以创建一个 PHP 程序,只需几行代码就可以检索您需要的信息。安全和性能—请记住,安全是一项团队工作。不仅程序需要安全,而且 web 服务器及其文件结构也必须得到适当的保护。

Example 4-6. The getbreeds.php file

<?php

$breed_file = simplexml:load_file("breeds.xml");

$xmlText = $breed_file->asXML();

print "<select name='dog_breed' id='dog_breed'>";

print "<option>Select a dog breed</option>";

foreach ($breed_file->children() as $name => $value)

{

print "<option value='$value'>$value</option>";

}

print "</select>";

?>

PHP 只需要几行代码就可以完成许多强大的任务。例 4-6 的第一行打开breeds.xml文件,将内容放入一个属性($breed_file,然后关闭breeds.xml文件。

跳到foreach语句:

foreach ($breed_file->children() as $name => $value)

XML 数据被视为父子关系。父级可以包含子级(子级可以有子级)。在 XML 文件中,初始父节点是breeds。存在于品种下的孩子都有标签breed

<breed>Affenpinscher</breed>

本例中每个品种(子品种)的值是存在于品种标签之间的文本(例如,Affenpinscher)。

$breed_file->children()指示foreach语句遍历每个子元素(本文件中的breed)。语句的as $name=> $value部分告诉系统将每个子标签名(在本例中总是breed,但是您可以有不同的子标签名)放在$name中。它还指示系统将子代(Affenpinscher)中包含的值放入$value

print "<option value='$value'>$value</option>";

foreach循环中,print语句将$value的内容放在两个地方——选项标签的值参数和选项标签之间。对第一个孩子来说,它会产生

Option value='Affenpinscher'>Affenpinscher</option>

foreach循环自动遍历文件,直到文件中不再有记录。文件中的每一个品种都将创建类似的品系。这些行与文件中的其他print行一起,根据 XML 文件的内容动态创建一个 HTML 选择框。

For more information about foreach loop, please visit the example: http://php.net/manual/en/control-structures.foreach.php Video: https://www.thenewboston.com/videos.php?cat=11&video=17027 For more information about reading XML files, please visit the video: https://www.thenewboston.com/videos.php?cat=11&video=17090

您现在需要从 HTML 文件中调用这个程序。您可以使用第二章中的示例 JavaScript 文件来做到这一点,该文件使用了 AJAX。这将允许您仅通过更新页面中将显示选择框的部分来检索选择框。与第二章的例子相比,唯一需要修改的是调用 PHP 程序的那一行。

xmlHttp.open("GET", "get_breeds.php", true);

您只需用检索选择框的程序(get_breeds.php)替换现有的文件名。然后,您可以重命名该文件(get_breeds.js)。你也可以把你想运行的程序的名字传递给 JavaScript 方法(你将在本章的后面做)。

现在您需要对 HTML 文件做一些修改,以使用get_breeds.js并为包含选择框的区域创建一个div标签。

Example 4-7. The lab.html file with a dynamic select box

<!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="lab.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" /><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">Mixed<br /><br />

Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" /><br /><br />

<script>

AjaxRequest();

</script>

<div id="AjaxResponse"></div><br />

<input type="submit" value="Click to create your dog" />

</form>

</div>

<noscript>

<div id="noJS">

<form method="post" action="lab.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" /><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">Mixed<br /><br />

Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" /><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" /><br />

<input type="submit" value="Click to create your dog" />

</form>

</div>

</noscript>

</body>

</html>

在示例 4-7 中,与创建动态选择框相关的唯一真实变化以粗体突出显示。

<script src="get_breeds.js"></script>

在代码的顶部附近添加了script标记,以拉入包含 AJAX 的 JavaScript 文件,从而调用将显示选择框的 PHP 程序(get_breeds.php)。

<script>

AjaxRequest();

</script>

<div id="AjaxResponse"></div><br />

在代码的JS部分放置了一个脚本区域(如果浏览器中启用了 JavaScript,将执行该部分)。)它只包含一行代码(AjaxRequest();)来执行 AJAX JavaScript 代码(来自get_breeds.js文件)。<div id="AjaxResponse"></div>放在结束脚本标签下面。这一行应该很眼熟。这是包含在第二章 AJAX 示例中的同一行。AjaxRequest()方法的输出将从 web 服务器返回的响应放在 ID 为AjaxResponsediv标签之间。在本例中,动态创建的选择框被放置在该位置。

A978-1-4842-1730-6_4_Fig4_HTML.jpg

图 4-4。

The lab.html file with dynamic select box and radio buttons

您还用单选按钮的静态选择替换了颜色文本框。对于狗来说,只有几种可能的颜色组合(好吧,假装我是对的),这些颜色组合不太可能改变,所以你不需要动态列表。将选择硬编码到 HTML 文件中是有意义的。

如果用户没有启用 JavaScript,将显示原始狗品种文本框(来自代码的 NJ 部分)。但是,由于您没有使用 JavaScript 来创建单选按钮,所以您在两个部分中都包含了单选按钮,以便为未启用 JavaScript 的浏览器提供一些更好的安全编码。

程序的界面(lab.html)现在通过减少使用的文本框数量,大大降低了输入无效数据的可能性。用户(假设他们启用了 JavaScript)别无选择,只能从选择框中选择一个品种,从单选按钮中选择一种颜色。

您现在还可以使用相同的 XML 文件来验证您已经从服务器端的用户那里收到了一个有效的品种名称。

做它

调整示例 4-7 中的lab.html文件,以包含单选按钮(而不是文本框)来接受用户的性别。确保新的lab.html文件可以与之前的lab.php文件兼容。

用 XML 文件验证输入

您可以在dog.php文件中添加几行代码来验证用户不仅发送了格式正确的字符串,还发送了 AKC 列出的品种。

Example 4-8. The validator_breed function (in dog.php)

private function validator_breed($value)

{

$breed_file = simplexml:load_file("breeds.xml");

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

您可以创建一个private函数(只在类内部使用)来检查正确的品种。该函数将接受传入$value属性的值。该函数将使用$breed_file = simplexml:load_file("breeds.xml");将 XML 文件的内容转储到$breed_file。下一行($xmlText = $breed_file->asXML();)将$breed_file的内容转换成格式良好的字符串。

if(stristr($xmlText, $value) === FALSE)

stristr方法比较其第二个参数(在本例中是$value)的内容,看它是否存在于第一个参数($xmlText)的字符串中。如果不存在,则返回FALSE。如果确实存在,它将返回字符串的位置。为了你的需要,你只需要知道它是否存在。如果没有,你返回FALSE。如果是,则返回 TRUE。

For more information about the stristr method, please visit http://php.net/manual/en/function.stristr.php

Example 4-9. The complete dog class with validation

<?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 = "??";

// ---------------------------------- Constructor -----------------------------------------

function __construct($value1, $value2, $value3, $value4)

{

if (method_exists('dog_container', 'create_dog_app')) {

$name_error = $this->set_dog_name($value1) == TRUE ? 'TRUE,' : 'FALSE,';

$breed_error = $this->set_dog_breed($value2) == TRUE ? 'TRUE,' : 'FALSE,';

$color_error = $this->set_dog_color($value3) == TRUE ? 'TRUE,' : 'FALSE,';

$weight_error= $this->set_dog_weight($value4) == TRUE ? 'TRUE' : 'FALSE';

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

}

else

{

exit;

}

}

//------------------------------------toString---------------------------------------------

public function __toString()

{

return $this->error_message;

}

// ---------------------------------- Set Methods -----------------------------------------

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;

((preg_match("/[a-zA-Z ]+$/", $value)) && ($this->validator_breed($value) === TRUE) && strlen($value) <= 35) ? $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_weight,$this->dog_breed,$this->dog_color.";

}

// ------------------------------General Method---------------------------------------------

private function validator_breed($value)

{

$breed_file = simplexml:load_file("breeds.xml");

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

}

?>

//------------------------------------toString----------------------------------------------

public function __toString()

{

return $this->error_message;

}

// ---------------------------------- Set Methods ------------------------------------------

function set_dog_name($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_weight($value)

{

$error_message = TRUE;

(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $error_message = FALSE;

return $error_message;

}

function set_dog_breed($value)

{

$error_message = TRUE;

((ctype_alpha($value))``&&``($this->validator_breed($value) === TRUE)``&&

return $error_message;

}

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $error_message = FALSE;

return $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_weight,$this->dog_breed,$this->dog_color.";

}

// ------------------------------General Method---------------------------------------------

private function validator_breed($value)

{

$breed_file = simplexml:load_file("breeds.xml");

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

}

?>

function set_dog_color($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $error_message = FALSE;

return $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_weight,$this->dog_breed,$this->dog_color.";

}

// ------------------------------General Method---------------------------------------------

private function validator_breed($value)

{

$breed_file = simplexml:load_file("breeds.xml");

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

}

?>

/ -------------------------------General Method---------------------------------------------

private function validator_breed($value)

{

$breed_file = simplexml:load_file("breeds.xml");

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

}

?>

现在,您可以对检查dog_breed值有效性的代码行做一点小小的修改(参见示例 4-9 中粗体突出显示的行)。

((ctype_alpha($value)) && ($this->validator_breed($value) === TRUE) && strlen($value) <= 35) ? $this->dog_breed = $value : $error_message = FALSE;

该语句将$value传递到validator_breed ($this->validator_breed($value)。如果返回一个TRUE,那么if语句的那部分将为真。否则就是假的。如果完整的if语句是TRUE,那么dog_breed属性被设置为在 XML 文件中找到的品种($this->dog_breed = $value)。如果为假,则将FALSE传递给$error_message属性($error_message = FALSE)。

Note: When you call a function in the same object, you must use the $this pointer ($this->validator_breed($value) === TRUE)).

你现在已经完成了一个更安全的程序。用户不能输入任何无效信息。他们可以尝试这样做的唯一字段是在dog_name字段中。然而,即使他们试图输入程序代码或其他代码,服务器端程序也会去掉特殊字符,使代码变得无害。如果数据包嗅探程序试图在服务器端程序收到数据之前更改数据,验证和/或过滤方法将导致数据被拒绝,或者再次使数据无害。

这个程序是有效的,因为它试图在信息发送到服务器之前对其进行验证。它减少了服务器和用户之间的通信量。完成的程序有两层(接口、业务规则),可以扩展到包括第三层(数据),而无需对当前代码进行重大更改。编程建议——对于不经常使用的小型 web 应用,没有必要像本例中那样将程序拆开。然而,如前所述,如果这是一个将来可能会扩展的应用,或者将来可能会获得大量用户,那么在创建程序时就应该考虑到这一点,将程序分成几个层次。

做它

访问该书的网站并运行示例程序。看看你能否“破解”程序内置的安全性。记住,你只能尝试尽可能的安全,没有什么是 100%安全的。您是否能够向影响程序的程序发送有害信息?如果是,你做了什么?允许这种情况发生的项目中可能缺少了什么?如果不是,是什么阻止了有害数据破坏程序?您可能会对该程序进行哪些需要包含数据层的更改?程序中还存在哪些低效之处?你能做些什么来修复它们?

依赖注入

正如你所看到的,当程序被创建时,开发人员在最终的应用完成之前要经历许多迭代。在这个过程中,有经验的程序员会保留他们程序的不同版本。这使得程序员可以快速备份到以前的版本,如果他们正在工作的版本有太多的重大问题。否则,他们将不得不尝试在不损害好代码的情况下去除“坏”代码。应用有许多文件(HTML、JavaScript、CSS、PHP 类和 PHP 库)。跟踪哪个版本与哪个版本一起工作,或者轻易地改变程序的一部分以使用另一部分的新版本,会变得令人困惑。尤其是当文件名和类名被编码在程序本身的代码中时。

第二章简单讨论了依赖注入。它允许将要使用一个代码块(比如一个类)的程序(客户端)不知道它将要使用的代码块的实际实现。客户端程序不知道实际的类名。

你可以用这个想法来帮助开发过程。您将要看到的例子并不适合大规模应用。然而,它确实给了你一个机会来看看依赖注入的好处。大规模的应用应该使用 MVC(模型-视图-控制)模型或者一个已建立的层(或者组件)系统,在组件之间提供更有效的通信。

And security-in many cases, there is a trade-off between being as safe as possible and having the highest performance program as possible. The following example will make multiple system calls when checking whether a program exists, and then use require_once to load the program. File presence check allows the program to handle missing files instead of crashing the program. In later chapters, you will see the use of try/catch block to allow the program to catch any problems without crashing the program. This will reduce the number of system calls (you no longer need to check whether files exist), thus improving the performance of the program.

dog应用使用包含在不同文件中的类和方法。这需要代码也包含require_once语句来将文件拉入程序。当前的设计将实际的文件名放在了require_once语句中。这不允许您更改文件的不同版本,除非您更改代码。现在,您将删除这些依赖项,以便为应用的未来开发提供更大的灵活性。

在你陷入代码泥潭之前,图 4-5 显示了与程序的关系流程。

A978-1-4842-1730-6_4_Fig5_HTML.jpg

图 4-5。

Data flow for the dog application

dog_interface.php程序将为dog应用的所有部分提供接口。此外,它还提供了您在前面的示例中已经设计好的安全性和过滤功能。除了这些其他活动,dog_interface创建并使用dog_container对象来包含、创建和传递任何其他需要的对象(不知道对象的名称)。dog_container对象使用一个 XML 文件(未显示)来发现包含它将创建的类的文件的位置和名称(提供依赖注入)。

应用将总是使用dog_interface程序来访问其他类。dog_interface程序将决定完成特定任务需要什么类。每当需要一个类时,dog_interface将使用dog_container来确定这个类的名称和位置(通过 XML 文件),并创建这个类的一个实例(对象)。通过使用 XML 文件列出类文件名和位置,可以在不对应用中的程序进行任何代码更改的情况下进行更改。

当从lab.htm l 页面请求品种选择框时,调用dog_interface程序。它将创建一个dog_container对象。dog_container对象将发现get_breeds类文件和 breeds XML 文件的位置和文件名。一旦被发现,dog_container对象将创建一个get_breeds对象。get_breeds对象将为选择框构建代码;最终将代码返回到lab.html中的表格以显示给用户。然后所有对象(dog_containerget_breeds)被销毁(从内存中移除)。

当点击lab.ht ml 表单上的提交按钮时(假设所有验证都通过了),它将调用dog_interface程序。这个程序将创建dog_container对象。然后,dog_container对象将发现dog.php类文件的位置和文件名。一旦被发现,dog_container对象将创建dog对象。dog对象的行为将与之前的例子完全一样(验证属性和显示属性)。一旦dog对象完成,对象(dog_containerdog)被销毁(从内存中删除)。这种设计允许对象完全独立,提供两层设计(接口和业务规则)和依赖注入。

Example 4-10. The dog_applications.xml file

<?xml version="1.0" encoding="UTF-8"?>

<dog_applications>

<application>

<type ID="dog">

<location>dog3.php</location>

</type>

</application>

<application>

<type ID="selectbox">

<location>get_breeds2.php</location>

</type>

</application>

<application>

<type ID="breeds">

<location>breeds.xml</location>

</type>

</application>

</dog_applications>

在示例 4-10 中,您已经创建了一个简单的 XML 文件,该文件将用于 PHP 应用中重要文件的版本更改。每个application标签标识文件的类型(狗、选择框、品种)。application标签中的每个location标签提供了文件名和位置(尽管在这个例子中所有文件都在同一个位置)。一旦你调整程序来使用这个文件,你就可以灵活地改变文件名(比如用dog3.php代替dog.php的例子)和位置,而不必改变任何程序代码。这可以帮助您在开发过程中在应用中交换版本。

您现在将创建一个包含两个方法的Dog_ container类。get_dog_application将用于“获取”XML 文件中列出的任何文件的名称和位置。create_object方法将创建一个狗类或get_breeds类的实例。

Example 4-11. The dog_container.php file

<?php

class Dog_container

{

private $app;

private $dog_location;

function __construct($value)

{

if (function_exists('clean_input'))

{

$this->app = $value;

}

else

{

exit;

} }

public function set_app($value)

{

$this->app = $value;

}

public function get_dog_application()

{

$xmlDoc = new DOMDocument();

if ( file_exists("dog_applications.xml") )

{

$xmlDoc->load( 'dog_applications.xml' );

$searchNode = $xmlDoc->getElementsByTagName( "type" );

foreach( $searchNode as $searchNode )

{

$valueID = $searchNode->getAttribute('ID');

if($valueID == $this->app)

{

$xmlLocation = $searchNode->getElementsByTagName( "location" );

return $xmlLocation->item(0)->nodeValue;

break;

}

}

}

return FALSE;

}

function create_object($properties_array)

{

$dog_loc = $this->get_dog_application();

if(($dog_loc == FALSE) || (!file_exists($dog_loc)))

{

return FALSE;

}

else

{

require_once($dog_loc);

$class_array = get_declared_classes();

$last_position = count($class_array) - 1;

$class_name = $class_array[$last_position];

$dog_object = new $class_name($properties_array);

return $dog_object;

}

}

}

?>

首先,在类的顶部(dog_container),声明两个私有属性— $app$dog_location。这些属性被声明为 private,而不是 public,以保持它们的值只在这个类中已知。

在构造函数中,$value接受您想要在 XML 文件中查找的应用类型的名称(比如selectbox)。在代码的后面,您将把$value与 XML 文件中的类型 ID 进行比较,看看能否找到应用类型和与之相关的文件。构造函数将$value放入$app属性中。然而,该方法还包括一个if语句,该语句使用方法function_exists来确定clean_input函数是否存在。

为什么呢?在本章的开始,你简要地看了允许你限制一个程序的使用到一个调用它的特定应用的代码。在这个例子中,您将看到另一种技术来限制哪些程序可以使用这个类。if语句要求任何创建该类实例的程序也必须有一个clean_input方法。如果有人试图使用另一个不包含clean_input方法的程序来创建该程序的实例,该语句的else部分将会执行,这将导致该对象无法被创建并将关闭该程序。

And security-whenever a program receives information from the Internet or network, it is a good idea to determine the source of the information. In addition to determining the application and function names, PHP programs can also check the source IP address.

public function get_dog_application()

{

$xmlDoc = new DOMDocument();

if ( file_exists("dog_applications.xml") )

{

$xmlDoc->load( 'dog_applications.xml' );

$searchNode = $xmlDoc->getElementsByTagName( "type" );

方法的第一部分应该看起来很熟悉。这段代码打开dog_applications.xml文件(在确保它存在之后)。然后它将内容加载到$xmlDoc中。最后一行调用 PHP 方法getElementsByTagName。该方法搜索“type”在$xmlDoc中的所有出现,并将每个出现放入$searchNode

foreach( $searchNode as $searchNode )

{

$valueID = $searchNode->getAttribute('ID');

if($valueID == $this->app)

{

$xmlLocation = $searchNode->getElementsByTagName( "location" );

return $xmlLocation->item(0)->nodeValue;

break;

}

}

foreach循环查看包含在$searchNode中的每一行。它使用 PHP 方法getAttribute将带有ID XML 属性的下一行放到属性$valueID中。一旦它被放入$valueID,该属性中的值(dogselectboxbreeds)将与$this->app进行比较。(属性应用是用构造函数中的值加载的。)如果在 XML 文件中找到了ID,那么getElementsByTagName将搜索包含location XML 标签的下一行。然后,返回行获取位置标记中的值(文件名及其位置),并将其返回给调用该方法的程序。break语句用于提前退出循环,因为您已经返回了前一行代码中所需的值。如果 XML 文件不存在,方法的else部分(如示例 4-11 所示)将返回FALSE

function create_object($properties_array)

create_object方法用于创建dogget_breeds对象。dog对象构造函数在其构造函数中需要四个值(dog_name, dog_weight, dog_breed,dog_color)。get_breeds应用不接受其构造函数中的任何值。为了在dog_container中提供更高效的代码,您更改了每个类的构造函数的签名(函数的第一行),以接受一个数组,而不是单个的值。这将允许您向任一构造函数传递任意数量的项。

function __construct($properties_array)

{ // dog class constructor

if (method_exists('dog_container', 'create_object')) {

$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';

$breed_error = $this->set_dog_breed($properties_array[1]) == 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';

这需要更改dog.php文件中的这些行来接受一个数组,然后使用数组中的值来设置每个属性。

注意第一行中的$properties_array没有任何东西将它声明为数组(除了名字)。请记住,PHP 属性通过属性中传递的内容来确定它们是什么数据类型。数组也是如此。如果一个数组被传入$properties_array,它就变成了一个数组。

数组的地址被传递到方法中。接受地址($properties_array)的属性实际上指向数组在内存中的位置。这样效率很高。如果您需要向一个方法中传递 50 个项,您可以一次传递一个,并在方法签名中为这 50 个项中的每一个声明不同的属性。但是,您可以创建一个包含 50 项的数组,并传递该数组。使用这种方法只传递一个值,即数组在内存中的地址。此外,如本例所述,使用数组允许您灵活地选择要传递到method签名中的项目数量。

$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';

要从数组中复制一个项,可以使用数组名($properties_array)和该项在数组中的位置([0])来引用该项。请注意,您使用了[]括号来声明头寸。这个位置通常被称为下标。数组下标从位置 0 开始,而不是位置 1。

class GetBreeds {

function __construct($properties_array)

{ //get_breeds constructor

if (!(method_exists('dog_container', 'create_object')))

{

exit;

}

}

你必须调整get_breeds程序。GetBreeds现在是一个班。您还创建了GetBreeds构造函数的签名来接受一个数组。然而,正如您从这段代码中看到的,您实际上将忽略传递到数组中的任何内容。这允许您在dog_container中使用相同的方法来创建任一类的对象(或者实际上是接受数组的任何类的对象)。您需要找到的只是类名(dogGetBreeds),这样您就可以创建一个实例。

function create_object($properties_array)

{

$dog_loc = $this->get_dog_application();

if(($dog_loc == FALSE) || (!file_exists($dog_loc)))

{

return FALSE;

}

然后,create_object方法调用get_dog_application方法,并将返回值(文件的位置和文件名)放入$dog_loc。如果$dog_loc中的值为假或者不是一个现有的文件,该方法将把FALSE返回给调用它的程序。

else

{

require_once($dog_loc);

$class_array = get_declared_classes();

$last_position = count($class_array) - 1;

$class_name = $class_array[$last_position];

$dog_object = new $class_name($properties_array);

return $dog_object;

} } }

?>

如果路径和文件名有效,将执行代码的else部分。$dog_loc中的值在require_once语句中使用(该语句将文件内容提取到该方法中)。

您现在必须确定文件中存在的类名(依赖注入要求您不仅要发现文件名和文件路径,还要发现类名)。

PHP 方法get_declared_classes返回程序中当前存在的所有类的数组。这些课从第一节到最后一节都是有序的。因此,因为您只是用一个类(或者是dog或者是get_breeds类)包含了文件,所以您可以在创建的数组中寻找最后一个条目。你已经将get_declared_classes创建的数组放置在$class_array中。现在,您可以确定数组的大小。PHP 方法count将返回一个数组的大小。记住数组的大小是数组中的项数,而不是最后一个位置。如果数组的大小为 10,实际下标是 0 到 9,而不是 1 到 10。

$last_position = count($class_array) - 1;

For more information about get_declared _ class, please visit http://php.net/manual/en/function.get-declared-classes.php

考虑到这一点,该语句确定了$class_array的大小,然后从大小中减去 1,并将该值放入$last_position。因此,如果数组的大小为 10,数字 9 将存储在$last_position中(因为数组下标是 0 到 9,而不是 10)。

$class_name = $class_array[$last_position];

然后,您可以使用$last_position中的值从数组中取出最后一个创建的类,并将其放入$class_name中。正如你所看到的,你实际上可以在[]下标括号之间传递属性$last_position,以指示程序你想要位于$last_position中的位置的值。这允许您能够从任意大小的数组中的最后一个位置提取信息(因为您不知道$class_array的大小)。

您现在可以创建该类的一个实例,因为您在$class_na me 中有了类名。

$dog_object = new $class_name($properties_array);

这一行代码现在将创建一个刚刚被包含(require_once)到程序中的任何类的实例(接受一个数组到构造函数中)。该方法将创建一个dog类或getBreeds类的实例。

return $dog_object;

最后,对象(或者是一个dog对象或者是一个getBreeds对象)被返回给调用该方法的程序。通过返回该对象,调用程序可以完全访问该对象及其属性和方法,即使它没有实际创建该对象(dog_interface可以使用该对象,即使dog_container创建了它)。

对象在内存中的位置的返回方式与前面示例中数组的位置传递给构造函数的方式类似。你可以把它想象成新对象暂时“包含”在dog_container对象中。然而,对象被返回(到dog_interface)。

Programming notes-what? What really happens is that the address in $dog_object memory is passed to the caller (dog_interface). This allows the caller to access the object and the $dog_container object. Therefore, there is only one copy of $dog_object in memory, but two different blocks can use it. If one of the blocks (dog_container or dog_interface) is closed, another object can still access it until it is also closed. Then the garbage collector will delete it from memory $dog_object.

Example 4-12. The get_breeds class

<?php

class GetBreeds {

function __construct($properties_array)

{ //get_breeds constructor

if (!(method_exists('dog_container', 'create_object')))

{ exit;}}

private $result = "??";

public function get_select($dog_app)

{ if (($dog_app != FALSE) && ( file_exists($dog_app))) {

$breed_file = simplexml:load_file($dog_app);

$xmlText = $breed_file->asXML();

$this->result = "<select name='dog_breed' id='dog_breed'>";

$this->result = $this->result . "<option value='-1' selected>Select a dog breed</option>";

foreach ($breed_file->children() as $name => $value)

{    $this->result = $this->result . "<option value='$value'>$value</option>";  }

$this->result = $this->result . "</select>";

return $this->result;

} else {

return FALSE;

}

}

}

?>

正如您从示例 4-12 中看到的,只需要进行微小的改动。如前所述,声明了一个类并添加了一个构造函数。构造函数验证这个类是从包含dog_ containercreate_breed_app方法的程序中创建的。这种安全性试图防止其他程序知道驻留在dog_application.xml文件中的Dog应用的文件名和位置。

Example 4-13. The dog_interface.php file

<?php

function clean_input($value) {

$bad_chars = array( "{", "}", "(", ")", ";", ":", "<", ">", "/", "$" );

$value = str_ireplace($bad_chars,"",$value);

$value = htmlentities($value);

$value = strip_tags($value);

if (get_magic_quotes_gpc())

{ $value = stripslashes($value); }

$value = htmlentities($value);

return $value;

}

function error_check_dog_app($lab) {

list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $lab);

print $name_error == 'TRUE' ? 'Name update successful<br/>' : 'Name update not successful<br/>';

print $breed_error == 'TRUE' ? 'Breed update successful<br/>' : 'Breed update not successful<br/>';

print $color_error == 'TRUE' ? 'Color update successful<br/>' : 'Color update not successful<br/>';

print $weight_error == 'TRUE' ? 'Weight update successful<br/>' : 'Weight update not successful<br/>';

}

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-------------------------------------

if ( file_exists("dog_container.php"))

{  require_once("dog_container.php"); }

else { print "System Error #1"; exit; }

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']);

$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight);

$lab = $container->create_object($properties_array);

if ($lab != FALSE) {

error_check_dog_app($lab);

get_dog_app_properties($lab);  }

else { print "System Error #2"; }

}

else {

print "<p>Missing or invalid parameters. Please go back to the dog.html page to enter valid information.<br />";

print "<a href='dog.html'>Dog Creation Page</a>";

}

}

else

{

$container = new dog_container("selectbox");

$lab = $container->create_breed_app();

if ($lab != FALSE) {

$container = new dog_container("selectbox");

$properties_array = array("selectbox");

$lab = $container->create_object($properties_array);

if ($lab != FALSE) {

$container->set_app("breeds");

$dog_app = $container->get_dog_application();

$method_array = get_class_methods($dog_data);

$last_position = count($method_array) - 1;

$method_name = $method_array[$last_position];

$result = $dog_data->$method_name($dog_app);

if ( $result == FALSE) {

print "System Error #3"; //select box not created

}

else

{

print $result; //pass back select box

}

}

else

{

print "System Error #4";

}

}

?>

dog_interface程序实际上是主要部分代码发生变化的lab.php程序。lab.php的方法没有任何改变。

if ( file_exists("dog_container.php"))

{  require_once("dog_container.php"); }

else { print "System Error #1"; exit; }

if (isset($_POST['dog_app']))

首先,程序使用 PHP file_exists方法确定dog_container是否存在。如果是的话,它使用require_once将代码拉入程序中。如果dog_container不存在,程序打印错误信息("System Error #1",然后关闭(exit;)。

接下来,程序使用isset来确定调用程序是否已经提供了$dog_app的值。如果这个值已经被传递,这表明调用程序想要创建一个Dog对象。

$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']);

$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight);

$lab = $container->create_object($properties_array);

if ($lab != FALSE)

{

error_check_dog_app($lab);

get_dog_app_properties($lab);

} else {

print "System Error #2";

} else {

print "<p>Missing or invalid parameters. Please go back to the lab.html page to enter valid information.<br />";

print "<a href='lab.html'>Dog Creation Page</a>";

} }

然后程序创建一个dog_container ( $container)的实例,将$dog_app中的值传递给$container对象。使用clean_input方法过滤Dog对象的每个属性。然后属性被传递到$properties_array数组中。然后将数组传递给dog_container对象($containercreate_object方法。如果成功创建了Dog对象($lab),那么就调用error_check_dog_app方法来验证每个属性都有有效的信息。调用get_dog_app_properties方法来显示每个属性。

如果Dog对象所需的任何属性缺失,用户将被要求返回到lab.html页面重新输入所需的信息。

else

{ //get breeds

$container = new dog_container("selectbox");

$properties_array = array("selectbox");

$lab = $container->create_object($properties_array);

if ($lab != FALSE)

{

$container->set_app("breeds");

$dog_app = $container->get_dog_application();

$method_array = get_class_methods($lab);

$last_position = count($method_array) - 1;

$method_name = $method_array[$last_position];

$result = $lab->$method_name($dog_app);

if ( $result == FALSE) // select box not created

{

print "System Error #3";

}

else

{

print $result; // select box created!

}

}

else

{

print "System Error #4";

}

如果$dog_app值没有传入到类中,则执行else语句。假设用户想要创建一个getBreeds对象。创建了dog_container的一个实例($container,它传递值selectbox。(如果不能创建对象,将显示"System Error #4")。单词“selectbox”被传递到数组$properties_array(注意:必须使用array关键字,否则您将创建一个属性而不是数组。)容器对象($container)然后将调用create_object(传递$properties_array来创建getBreeds类的实例($lab)。如果成功创建了getBreeds对象($lab !=FALSE),那么您需要找到get_breeds.xml文件(包含品种列表)的位置。因此,您将容器中的 app 属性(通过调用set_app)重置为“breeds”。这告诉容器程序你是一个getBreeds对象,而不是一个dog对象。然后使用容器的get_dog_application方法找到 breeds XML 文件的位置。

For more information about arrays, please visit the example: http://php.net/manual/en/function.array.php Video: https://www.thenewboston.com/videos.php?cat=11&video=17024

PHP 方法get_class_methods用于创建包含在getBreeds中的方法数组。由于get_select方法是唯一的方法(除了构造函数),它也是数组中的最后一个方法。它的名字从数组中取出,然后使用属性$method_name ($result = $dog_data->$method_name($dog_app)调用它。这使得getBreed级可以完全独立于dog_interface级。开发人员可以更改get_select方法的名称,一切仍然可以工作(只要它是类中的最后一个方法)。这提供了接口层和业务规则层之间的完全分离。

XML 文件的位置被传递给getBreeds对象($lab)的get_select方法,该方法使用 XML 文件来创建选择列表框。选择列表框的代码被放到$result中。如果代码确实被放到了$result中,代码就会显示(print $result)回 HTML 表单,供用户选择一个品种。如果文件名无效,将显示错误消息(print "System Error #3"),而不是选择框。

唯一需要的其他更改是对lab.htmlget_breeds.js文件的两个微小更改。

function AjaxRequest($value)

get_breed.js文件中,AjaxRequest方法的函数头已经被修改,以传递被调用的实际文件(这不是本设计的要求,但它允许该文件用于任何通过 AJAX 调用的程序)。

xmlHttp.open("GET", $value, true);

此外,open语句已经调整为使用$value代替文件名。

lab.html程序中,有一些额外的变化。

AjaxRequest('dog_interface.php');

对 JavaScript 函数的调用现在传递文件名,该文件名也被更改为dog_interface.php文件。

<form method="post" action="dog_interface.php" onSubmit="return validate_input(this)">

最后,HTML 表单的 action 标签(在两个表单标签位置)已经被修改为调用dog_interface.php程序。

请注意,现在,每当您想要使用Dog应用时,您首先调用接口。然后界面决定你想要完成什么(获得品种选择框或者处理你正在创建的狗的属性)。

您必须通过接口与您的所有类进行通信。反过来,接口必须使用容器创建任何所需的对象(当然,容器本身除外)。这遵循了分层设计的概念。

您将不会看到与之前看到的任何不同的输出(除非您有一些系统错误)。但是,现在您可以轻松地在应用中更改文件名和位置(通过 XML 文件),而无需更改任何程序代码!

做它

Download the files for this section from the book’s web site. Change the file names for the get_breeds.xml file, the get_breeds``.php file, and the dog.php file. Try to run the program via the lab.html file. The select box will not display and the program will not run. Now go to the dog_applications.xml file and change the data in the XML file to the new file names you just created. Go back to your lab.html file (reload it). You should now see the select box. Fill in and select the information and click the Submit button. The application should now work.

章节术语

| 生效 | validator方法 |
| JavaScript 隐藏/显示 | HTML onSubmit |
| 表单验证 | JavaScript 警告框 |
| HTML 传递“表单” | JavaScript 点符号 |
| JavaScript if语句 | JavaScript le n gth 方法 |
| && AND | &#124;&#124; OR |
| 正则表达式 | JavaScript match方法 |
| 过滤器/过滤 | isset |
| $_POST | str_ireplace |
| Stripslashes | htmlentities |
| strip_tags | $__SERVER |
| exit | else |
| HTML 选择列表 | 可扩展置标语言 |
| XML 数据格式 | XML 父子 |
| foreach循环 | HTML 单选按钮 |
| private功能 | stristr |
| 依赖注入 | function_exists |
| break声明 | method签名 |
| getElementsByTagName | getAttribute |
| array | array subscript |
| get_declared_classes | Count |
| 数组的大小 | 数组的最后位置 |
| array关键字 | 传递物体 |
| get_class_methods |   |

第二章问题和项目

多重选择

When using form validation, which of the following is true? The server automatically successfully processes input values with no errors.   A required field is checked to make sure it has content.   A dynamic web page is updated.   All information is sent to the server.     Which of the following can be verified with a validator? E-mails   IP addresses   Integers   All of the above     Why must you validate your code? To see if your browser can complete a task.   To make sure your information is correct and secure.   To make sure that your browser can run JavaScript and HTML5.   To verify that your computer can run the latest version of PHP.     The verification code in a PHP file does which of the following? Compares the information received to an expected standard format   Verifies user program interaction   Checks and eradicates harmful data being entered by the user   Checks for incorrect PHP functions being used     stripslashes do which of the following? Remove backslashes from quotes.   Convert HTML characters to their equivalent HTML entity.   Remove any PHP or HTML tags.   All of the above.     If the size of an array is 29, what is the subscript range? 1 through 30   1 through 29   0 through 29   None of the above     in_array does which of the following? Searches for the number of empty spaces in an array.   Searches for the number of characters in an array.   Searches for a value in an array.   None of the above.     The PHP method count will return which of the following of an array? last_position   subscript   size   None of these     Which method will produce a variable that you can use to refer to the last position of the array? $last_position=count($class_array) - 1;   $class_array=$last_position(count - 1);   $count=$last_position - 1($class_array);   $last_position=$class_array -1 (count);     The exit command does which of the following? Automatically directs the user to a new page.   Closes the program if it is not called from the correct HTML page.   Turns off your computer.   None of the above.     What is not true in relation to the foreach command? Works only with arrays and objects.   Used to parse through each key/value pair in an array.   Can be iterated by reference.   The equivalent to an if/then statement.     The break statement does which of the following? Ends execution of the for, foreach, do-while, and/or switch structure(s).   Executes the for, foreach, do-while, and/or switch structure(s).   Ensures the execution of for, foreach, do-while, and/or switch structure(s).   Breaks the for, foreach, do-while, and/or switch structure(s) before they are executed.     Using the getAttribute will do which of the following? Return the value of the attribute.   Print a list of data.   Load a new HTML page.   None of these.     Which function converts HTML tags into their entities versions? strlen   htmlentities   explode   getAttribute     Which is a commonly used function to find the length of a string? strlen   getLength   String concatenation   __toString

真/假

The count function returns the number of elements in an array.   When using an array, the index must not exceed the size of the array.   A subscript is the name given to position where the item currently exists in the array and is usually contained in [].   A private function is an event in PHP to network for a job.   One purpose of exit is to end the program.   The getElementsByTagName searches for occurrences that correspond to a specific XML tag.   get_declared_classes returns an array of all classes that currently exist in a program in order from first to last.   Dependency injection allows the program client to enter a block of code to know the implementation of the block of code it will be using.

简答/短文

Why should you validate user input both within the interface tier and business rules tier?   Why should input received in the business rules tier be filtered? What are the different ways you can filter the information?   Explain how you can reduce errors from user input by the type of HTML objects (such as radio buttons) used to accept information.   What causes the example code shown in the dependency injection section of this chapter inefficient? How does this code help a developer with version changes to the application?

项目

Create an application that registers a runner for the local 5K road race. The interface should accept all necessary personal information (name, address, gender, age, and T-shirt size). Whenever possible, use HTML objects that restrict input (such a select object for T-shirt size and state). Validate all information in the interface tier using both HTML5 and JavaScript. If the information is valid, pass the information to the business rules tier. The business rules tier will validate the information received and filter out any harmful information. Once all information has been accepted, the program will display the cost of entering the race ($25). Any shirts over XL will add an additional charge of $2. Any runner 65 or older will be charged $5 less.   Develop the application described in #1 to use dependency injection to allow the developer to change file name and locations without requiring code changes to the application itself.

学期项目

Update the Chapter 3 Term Project to validate all information as it is entered into an HTML form (via HTML and JavaScript as shown in Chapter 4) in the interface tier. After the information is validated it is passed to the business rules tier. The business rules tier will validate the information received and filter out any harmful information. Once the information is accepted (and stored in the properties) the application will display all fields of the product stored in the warehouse of the ABC Computer Parts Company. The interface tier and the business rules tier must be separated using dependency injection (via an XML file), as shown. Your completed project should use logic similar to the examples shown in this chapter.

五、处理和记录异常

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1730-6_​5) contains supplementary material, which is available to authorized users.

一个人的教育直到他死了才算完成——罗伯特·李(引自劳伦斯·J·彼得《彼得语录:我们时代的思想》(1977 年),第 175 页)

章节目标/学生学习成果

完成本章后,学生将能够:

  • 解释错误和异常之间的区别
  • 创建一个可以处理一般异常的 PHP 程序
  • 创建一个可以创建、引发和处理用户异常的 PHP 程序
  • 解释并使用switch和/或嵌入式if/else语句
  • 创建一个使用while循环和/或for循环的 PHP 程序
  • 创建一个使用二维数组读取/更新文本文件的程序
  • 创建一个 PHP 程序,记录异常并向支持人员发送电子邮件

处理异常

作为一名程序员,你想尽一切可能确保你的程序不会崩溃。任何使用你的应用的人,如果他们不得不处理系统崩溃,他们的嘴里都会有不好的味道。你可能也处理过这种情况。作为用户,你可能因为差评而选择了一个应用而不是另一个。一旦应用被确定为“有问题”,就很难说服客户使用该产品,即使新版本已经纠正了部分或全部问题。必须创建一个应用来处理每一个可能的意外事件。

一个程序必须考虑每一种情况,决定它是继续运行还是必须关闭。由于意外事件,应用始终有可能无法继续运行。正确开发的程序会让用户知道有问题,而不会导致程序崩溃。当应用要求用户“稍后再试”时,用户更容易理解(假设在他们返回网站之前问题已经解决)。

错误是由系统处理的导致程序关闭的程序事件。在某些情况下,系统可以关闭程序并显示错误消息。有些错误会立即导致程序崩溃(比如服务器本身崩溃)。错误通常是超出程序控制的事件,不是由程序中的代码(或缺少代码)直接引起的。例如,内存不足会导致应用错误。

异常是不属于程序逻辑正常流程的事件。所有的异常都应该由程序来处理。当应用预见到一个问题(一个丢失的文件)或者当用户做了一些不寻常的事情(试图输入无效的信息)时,可以“引发”异常。程序应该“捕捉”所有的异常。然后,它可以检查该异常,并确定是否可以纠正、忽略它,或者是否必须关闭应用。如果程序没有捕捉到异常,系统将显示异常消息,然后关闭应用。

PHP 会根据具体情况产生错误和异常。在 PHP 5 之前,异常处理是不存在的。因此,一些旧的 PHP 命令产生错误(关闭程序)而不是异常。在 PHP 7 中,异常处理是“规则”。PHP 7 错误可以用异常处理技术来处理。如果程序代码没有处理异常,程序将会像致命错误一样暂停。

任何时候一个应用依赖于外部的东西,很可能在某个时候这个动作不会发生。例如,在Dog应用中,用户需要输入正确的信息。应用必须预见到并非所有用户都会输入正确的信息。该应用还依赖于服务器上存在的几个文件(dog_interfacedog_containerdog_applicationsget_breeds)。如果缺少这些文件中的任何一个,应用将无法继续正常运行。

大多数面向对象编程语言使用标准格式来处理异常。PHP 的当前版本也使用这种方法。当您在互联网上研究 PHP 示例时,您会发现现有的 PHP 代码不使用这种标准格式。虽然这段代码仍然可以在当前版本的 PHP 中执行,但是建议使用标准技术。标准方法使用try-catch块。

try {

// code that might cause an exception

}

catch(Exception $e) {

// code that executes if there is an exception

}

catch(错误$e) {

// PHP 7+ capture and handle errors

}

任何可能导致异常的代码都应该包含在try块中。此外,您可能还想考虑在try块中放置其他条件(比如数学计算)。

try {

$result = $firstNumber / $secondNumber;

}

catch(Exception $e) {

// code that executes if there is an exception

}

catch(错误$e) {

// PHP 7+ capture and handle errors

}

如果$secondNumber包含一个零(除以零),这个例子可能会产生一个异常。如果异常发生,代码将跳转到catch块。然后将执行块中的任何代码。语句$e->getMessage();将显示任何与异常相关的系统消息(在本例中,是关于试图被零除的消息)。但是,您不必使用系统消息;您可以使用echoprint向用户显示信息。

try {

$result = $firstNumber /$secondNumber;

}

catch(Exception $e) {

echo "You entered zero for the second number. Your entry must be greater than zero";

}

然而,这些例子有一个问题。如果您试图在try块中捕获多种类型的异常,所有异常都将进入一个catch块。任何异常都会显示相同的消息。有几种不同的方法可以解决这个问题。

一种方法是抛出自己的异常,而不是让系统抛出。

try {

if ($secondNumber == 0)

{ throw new Exception("Zero Exception"); }

else { $result = $firstnumber / $secondnumber; }

// other code with exceptions }

catch(Exception $e) {

switch ($e->getMessage()) {

case "Zero Exception":

echo "The value of second number must be greater than zero";

break;

case "Some other exception":

echo "You did something else wrong";

break;

default:

echo $e->getMessage();

}

Precautions for programming-In addition to the getMessage method, the Exception and Error objects also include: getCode()-displaying the code causing the exception getFile()-displaying the file name containing the code causing the exception getLine()-displaying the line numbers getTrace() and getTraceAsString() causing the exception-displaying the backtracking (exception flowing through the program) information, and in some cases, displaying it to the user. However, other methods should only be used for debugging or log entries. Providing code information to users is usually unnecessary and violates security.

在这个例子中,在catch块中使用了一个switch语句来查看所有可能的异常消息。一条switch语句与一条嵌入式if语句完成相同的任务。你可以使用:

If($e->getMessage == "Zero Exception")

{ echo "The value of second number must be greater than zero"; }

else if($e->getMessage == "Some other exception")

{ echo "You did something else wrong"; }

else

{ echo $e->getMessage(); }

对于某些人来说,当查看同一个属性(变量)的多个可能值或执行方法的结果时,switch语句更容易理解(如本例所示)。switch语句的默认部分(或者嵌入式if语句中的最后一个else语句)捕捉任何您没有预料到的内容。在本例中,您只需显示其他异常的异常消息。

如前所述,处理所有异常和错误非常重要。通过包含默认代码,您能够处理您可能从未预料到的异常和错误。注意,每个case部分必须包含一个break作为最后一条语句。这阻止了代码进入下一个case语句。

Catch(Exception $e) {

switch($e->getMessage()) {

case "Zero Exception":

echo "The value of second number must be greater than zero";

case "Some other exception":

echo "You did something else wrong";

break;

default:

echo $e->getMessage():

}

在本例中,如果发生了Zero Exception,将显示消息("The value of the second number must be greater than zero"You did something else wrong"switch语句的使用在catch块中很常见。然而,如前所述,如果您愿意,可以使用嵌入式的if语句。

处理多个异常的另一种方法是创建自己的异常,抛出它们,然后捕获它们。您需要为自己的异常创建一个类。

class zeroException extends Exception {

public function errorMessage() {

$errorMessage = "Second Number cannot be " . $this->getMessage();

return $errorMessage;

}

}

try {

if ($secondNumber == 0)

{ throw new zeroException("Zero"); }

else

{ $result = $firstnumber / $secondnumber; }

// other code with exceptions }

catch(zeroException $e) {

echo $e->errorMessage();

}

catch(Exception $e) {

Echo $e->getMessage();

}

zeroException类扩展了Exception类。extends关键字用于继承Exception类的所有功能。继承是面向对象编程的另一个关键组件(还有封装和多态)。一个子类(比如zeroException)可以继承其父类(Exception)的所有属性和方法。然后,子类可以添加特定于该类的方法(比如函数errorMessage)。由于zeroException继承了Exception,它被视为与任何其他异常相同。zeroException可以被抛出(抛出新的zeroException("Zero"))并且可以被抓住(catch(zeroException $e))。

Program notes-exception classes created by programmers inherit from Exception. Therefore, all functions of the Exception class are available in any new exception class. Class zeroException extends Exception { } The previous code created an effective new zeroException class with no new method. catch(zeroException $e) { echo $e->getMessage(); } This catch block will be called by a new exception, and the exception message generated by the Exception class will be displayed.

对于创建和抛出的每个异常或错误类,必须有一个catch块来捕捉异常或错误。在这个例子中,有两个 catch 块;一个捕捉zeroException,另一个捕捉任何其他可能发生的异常。就像前面使用switch默认或if else语句的例子一样,您应该总是让最后的catch块处理任何剩余的异常或错误。如果通用catch模块首先列出,所有异常将被该模块捕获,而不是该异常的特定模块。

如上所述,开发人员应该尽一切努力防止应用崩溃。然而,错误是用来显示消息和关闭带有错误代码的程序(您认为是“崩溃”程序)。在 PHP 7 之前,在某些情况下,您可以通过创建一个处理错误的方法来覆盖这个功能。

function errorHandler($severity, $message, $file, $line) {

throw new errorException($message, 0, $severity, $file, $line); }

set_error_handler('errorHandler');

// set_error_handler() doesn’t work with all fatal errors, some can’t be thrown as Exceptions.

try { trigger_error( "User Error", E_USER_ERROR);

}

catch(errorException $e)

{ echo $e->getMessage(); }

catch(Exception $e)

{ echo $e->getMessage(); }

// Code placed here would execute after an error with this handler. It would not execute if

// there was not a handler.

在这个例子中,set_error_handler方法将所有错误(可以被重定向)重定向到方法errorHandler。当方法trigger_error导致E_USER_ERROR发生时,错误的处理被重定向到errorHandler方法。然后,这个方法从错误中收集信息,抛出一个异常(errorException)。该异常由catch(errorException $e)方法捕获,这将导致显示消息"User Error"

在 PHP 7 中,Error 对象捕获潜在的系统错误作为异常。

try {

call _ method(null);//没有这样的方法!

} catch (Error $e)

{ echo $e->getMessage; }

以前,调用不存在的函数会导致致命错误。使用EngineException对象允许程序员处理错误。前面显示的例子(在 PHP 7 之前)只能捕获一些错误;这项新技术旨在让程序员对错误有更多的控制。如果不包括这个catch块,任何“错误”都会导致程序因"Fatal error: Uncaught exception"消息而崩溃。

如果你安装了 PHP 7,使用新的Error对象和Exception对象来尽可能避免致命错误。

安全性和性能——通常使用抛出和捕获异常可以减少程序所需的代码量。然而,有一个权衡。对不同面向对象编程语言的几项研究得出结论,异常处理比使用开发人员创建的例程效率(性能)低。开发人员应该使用异常作为应用正常流程的真正“异常”。对于更频繁发生的情况,开发人员应该在应用中创建情况处理例程。

A978-1-4842-1730-6_5_Fig1_HTML.jpg

图 5-1。

Data flow for the dog application

Dog应用中,信息在许多不同的程序之间流动。这些程序中的每一个都必须能够正确地处理异常。然而,消息处理应该全部发生在接口中。任何属于业务规则层的对象(dog_containerdogget_breeds)都应该将任何异常消息传递给接口进行处理。起初,这听起来像是一项复杂而令人困惑的任务。然而,异常处理的层次结构将大大简化这项任务。正如您将要看到的,使用异常处理将减少必要的代码量。

当抛出异常时,环境会查看程序(或类)本身,以确定是否有一个catch块可以处理异常。如果没有一个catch块,它将在层次结构中上升一级,并检查任何调用程序(或生成了该类实例的程序)是否有一个catch块。这个过程将继续,直到发现一个catch块,或者已经确定环境本身必须处理该异常。

使用这个过程,您可以在不使用 catches 的情况下在dog_container、dog 和get_breeds模块中抛出异常。在dog_interface中,您可以围绕对这些文件的调用创建一个try块。可以在接口中创建多个catch块(或者一个带有switch语句的块)来处理来自接口和所有其他模块的异常。这满足了三层编程的要求之一。业务规则层(和数据层)将消息传递给接口层。然后,接口层决定如何处理这些消息。它可以向用户显示它们,将它们放在日志文件中(您将在本章后面看到),或者忽略它们(如果不会对应用的操作产生负面影响的话)。

在更改Dog应用代码之前,让我们看一个由层次结构处理的异常的例子。

Example 5-1. testerror.php with error and exception-producing methods

<?php

class testerror {

function produceerror() {

trigger_error( "User Error", E_USER_ERROR);

echo "This line will not display";  }

function throwexception() {

throw new userException("User Exception");

echo "This line will not display";  }  }

?>

Example 5-2. The handleerror.php file captures error or exception

<?php

function errorHandler($severity, $message, $file, $line) {

throw new errorException($message, 0, $severity, $file, $line);

}

class userException extends Exception { }

Set_error_handler(‘errorHandler’);

try {

require_once(“testerror.php”);

$tester = new testerror();

$tester->produceerror();

echo “This line does not display”;

$tester->throwexception(); // will not execute if produceerror() is executed

echo “This line does not display”; }

catch (errorException $e ){

echo $e->getMessage(); }

catch (userException $e) {

echo $e->getMessage(); }

catch (Exception $e) {

echo $e->getMessage(); }

echo “This line will display”;

?>

testerror类(在示例 5-1 中)包括一个导致错误的方法(produceerror)和一个抛出异常的方法(throwexception)。然而,该类没有trycatch块。它没有能力对可能发生的任何异常或错误做出反应。

handleerror程序(例如 5-2 )包括一个处理用户错误的方法(errorHandler),以及将错误重定向到该方法的set_error_handler命令。它还包括一个类(userException,当try块中抛出userException异常时,该类可以做出反应。require_once语句包含在try块中,试图在文件丢失时捕获错误。但是,这恰好是一个系统错误(不是用户错误),无法重定向。为了捕获 PHP 7 中的系统错误,Error 类必须在前面所示的catch块中使用。

require_once语句之后,创建了一个类testerror的实例。如果缺少该类,系统也会显示一条致命消息。这个块调用了produceerror方法,这导致了一个用户错误。这个错误被重定向到errorHandler,它抛出一个异常(errorException)。catch模块接收异常并显示错误信息。由于异常不会关闭程序(比如致命错误),所以在所有catch阻塞并执行echo语句(echo "This line will display";)后,程序流程跳转到第一行。对错误的反应将导致程序跳过try块中任何剩余的代码。在这个例子中,throwexception方法调用将被忽略。

如果注释掉了$tester->produceerror()行,就可以调用throwexception方法。方法中抛出了userExceptionuserException类继承了Exception类。userException中未包含特殊方法。程序流程将跳转到userExceptioncatch模块。这个块使用ExceptiongetMessage方法来显示消息。在catch阻塞并执行echo "This line will display"语句后,逻辑跳转到第一行代码。

Note-try/catch can also include a finally block after all catch blocks. After the relevant catch block is executed, finally block will be executed for all captured exceptions. PHP allows finally blocks without any catch blocks to exist (but try blocks must still exist). finally One of the most common uses of blocks is to close files and/or databases when an exception occurs. Programs should not be closed until files and databases are properly closed. If it is not properly closed, the data may be damaged and inaccessible.

做它

Go to the book’s web site and download the files for Examples 5-1 and 5-2. Adjust the testerror program to only create an error. Create an additional testexception program (with a testexception class) to throw an exception. Now adjust the handleerror program to create an instance of both programs. The handleerror program should now be able to handle errors or exceptions from either program (class).

异常和错误处理与 If/Else 条件

程序员总是可以选择使用第四章的中的dog应用文件所示的If/else条件语句来处理异常和错误。以这种方式处理错误并没有降低效率(甚至可能更有效率)。但是,您将会发现,如果使用异常处理,业务规则层(和数据层)中的代码的态度会发生变化。当你使用if/else语句时,程序流花费了大量时间悲观地为最坏的情况(错误和/或异常)做准备。在许多情况下,通过使用异常处理,大多数业务规则层(和数据层)的编码变得乐观,包括处理程序正常操作的代码。应用依靠接口层来处理任何问题。

Example 5-3. The dog.class with exception handling

<?php

class Dog

{

// ------------------------------------ Properties -----------------------------------------

private $dog_weight = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

private $error_message = "??";

private $breedxml = "";

// ------------------------------------ Constructor ----------------------------------------

function __construct($properties_array)

{

if (method_exists('dog_container', 'create_object')) {

$this->breedxml = $properties_array[4];

$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';

$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';

$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';

$breed_error = $this->set_dog_breed($properties_array[1]) == TRUE ? 'TRUE,' : 'FALSE,';

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

if(stristr($this->error_message, 'FALSE'))

{

throw new setException($this->error_message);

}

}

else { exit; }

}

function set_dog_name($value) {

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $this->error_message = FALSE;

return $this->error_message; }

function set_dog_weight($value) {

$error_message = TRUE;

(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $this->error_message = FALSE;

return $this->error_message; }

function set_dog_breed($value) {

$error_message = TRUE;

($this->validator_breed($value) === TRUE) ? $this->dog_breed = $value : $this->error_message = FALSE;

return $this->error_message; }

function set_dog_color($value) {

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $this->error_message = FALSE;

return $this->error_message; }

// ----------------------------------Get Methods--------------------------------------------

function get_dog_name() {

return $this->dog_name; }

function get_dog_weight() {

return $this->dog_weight; }

function get_dog_breed() {

return $this->dog_breed; }

function get_dog_color() {

return $this->dog_color; }

function get_properties() {

return "$this->dog_name,$this->dog_weight,$this->dog_breed,$this->dog_color."; }

// ----------------------------------General Method-----------------------------------------

private function validator_breed($value)

{

$breed_file = simplexml:load_file($this->breedxml);

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

}

?>

将示例 4-8 与示例 5-3 进行比较,您会注意到代码中仅有几处细微的变化。__toString方法已经被移除,取而代之的是一个if语句,它检查FALSE是否存在于error_message字符串中。如果确实存在,就会产生一条setException消息,将error_message字符串传递给异常处理程序。这导致了整个应用流程的逻辑变化。代替dog_interface程序(在例子 4-12 中)通过调用__toString方法检查用户输入错误,当用户错误发生时Dog类通知dog_interface(通过抛出的异常)。以前,接口必须从Dog类中提取错误。在这个例子中,Dog类将错误推送到接口类。正如您将看到的,这将从dog_interface程序中消除代码,因为它不再需要询问是否有任何错误。

And security-the __toString method "exposes" anything it returns to any program, which creates an instance of its class. Passing error messages in this way may allow hackers to determine what incorrect messages they sent to the program. In the example in Chapter 4, __toString returns the error_message string containing 'TRUE' or 'FALSE' responses. This is safer than returning an error message. However, you can provide better security by throwing a special exception instead of the __toString method. Hackers must now know not only what error_message means, but also the name of this exception (setException) in order to catch it in their own programs.

Example 5-4. The getbreeds.class with exception handling

<?php

class GetBreeds {

function __construct($properties_array) {

if (!(method_exists('dog_container', 'create_object')))

{ exit; }

}

private $result = "??";

public function get_select($dog_app)

{

if (($dog_app != FALSE) && ( file_exists($dog_app)))

{

$breed_file = simplexml:load_file($dog_app);

$xmlText = $breed_file->asXML();

$this->result = "<select name='dog_breed' id='dog_breed'>";

$this->result = $this->result . "<option value='-1' selected>Select a dog breed</option>";

foreach ($breed_file->children() as $name => $value)

{

$this->result = $this->result . "<option value='$value'>$value</option>";

}

$this->result = $this->result . "</select>";

return $this->result;

}

else

{

throw new Exception("Breed xml file missing or corrupt");

}

}

}

?>

将先前的GetBreeds类(在示例 4-11 中)与示例 5-4 进行比较,仅显示一处变化。它返回'FALSE'并抛出一个一般异常,表明breed.xml文件丢失或损坏。同样,GetBreeds类将任何异常推送到接口。接口不再需要确定是否有任何异常。尽管丢失文件错误不能被重定向为异常处理,但是如果文件丢失,代码使用file_exists抛出异常。

Example 5-5. The dog_container.php file with exception handling

<?php

class dog_container

{

private $app;

private $dog_location;

function __construct($value) {

if (function_exists('clean_input')) {

$this->app = $value;

} else { exit; }

}

public function set_app($value) {

$this->app = $value; }

public function get_dog_application($search_value) {

$xmlDoc = new DOMDocument();

if ( file_exists("e5dog_applications.xml") ) {

$xmlDoc->load( 'e5dog_applications.xml' );

$searchNode = $xmlDoc->getElementsByTagName( "type" );

foreach( $searchNode as $searchNode )  {

$valueID = $searchNode->getAttribute('ID');

if($valueID == $search_value)   {

$xmlLocation = $searchNode->getElementsByTagName( "location" );

return $xmlLocation->item(0)->nodeValue;

break;    }

} }

throw new Exception("Dog applications xml file missing or corrupt"); }

function create_object($properties_array) {

$dog_loc = $this->get_dog_application($this->app);

if(($dog_loc == FALSE) || (!file_exists($dog_loc))) {

throw new Exception("File $dog_loc missing or corrupt.");  }

else

{

require_once($dog_loc);

$class_array = get_declared_classes();

$last_position = count($class_array) - 1;

$class_name = $class_array[$last_position];

$dog_object = new $class_name($properties_array);

return $dog_object;

}

}

}

?>

dog_application.xml文件、dog.class文件和/或get_breeds文件丢失时,实例 5-5 中的dog_container代替实例 4-10 中的返回'FALSE'。相反,会引发一个异常,指出哪个文件丢失了。

Example 5-6. The dog_interface.php file with exception handling

<?php

function clean_input($value)

{

$value = htmlentities($value);

$value = strip_tags($value);

if (get_magic_quotes_gpc())

{

$value = stripslashes($value);

}

$value = htmlentities($value);

$bad_chars = array( "{", "}", "(", ")", ";", ":", "<", ">", "/", "$" );

$value = str_ireplace($bad_chars,"",$value);

return $value;

}

class setException extends Exception {

public function errorMessage() {

list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $this->getMessage());

$name_error   == 'TRUE' ? $eMessage = '' : $eMessage = 'Name update not successful<br/>';

$breed_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Breed update not successful<br/>';

$color_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Color update not successful<br/>';

$weight_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Weight update not successful<br/>';

return $eMessage;

}

}

function get_dog_app_properties($lab)

{

print "Your dog's name is " . $lab->get_dog_name() . "<br/>";

print "Your dog weights " . $lab->get_dog_weight() . " lbs. <br />";

print "Your dog's breed is " . $lab->get_dog_breed() . "<br />";

print "Your dog's color is " . $lab->get_dog_color() . "<br />";

}

//----------------Main Section-------------------------------------

try {

if ( file_exists("e5dog_container.php"))

{   Require_once("e5dog_container.php"); }

else

{    throw new Exception("Dog container file missing or corrupt"); }

if (isset($_POST['dog_app'])) {

if ((isset($_POST['dog_name'])) && (isset($_POST['dog_breed'])) && (isset($_POST['dog_color'])) &&  (isset($_POST['dog_weight'])))

{

$container = new dog_container(clean_input($_POST['dog_app']));

$dog_name = clean_input(filter_input(INPUT_POST, "dog_name"));

$dog_breed = clean_input($_POST['dog_breed']);

$dog_color = clean_input($_POST['dog_color']);

$dog_weight = clean_input($_POST['dog_weight']);

$breedxml = $container->get_dog_application("breeds");

$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml);

$lab = $container->create_object($properties_array);

print "Updates successful<br />";

get_dog_app_properties($lab); }

else {

print "<p>Missing or invalid parameters. Please go back to the lab.html page to enter valid information.<br />";

print "<a href='dog.html'>Dog Creation Page</a>";

}

} else // select box {

$container = new dog_container("selectbox");

$properties_array = array("selectbox");

$lab = $container->create_object($properties_array);

$container->set_app("breeds");

$dog_app = $container->get_dog_application("breeds");

$result = $lab->get_select($dog_app);

print $result;

}

} // try

catch(setException $e)

{

echo $e->errorMessage();

}

catch(Exception $e)

{

echo $e->getMessage();

}

catch(Error $e) // PHP 7+ only

{

echo $e->getMessage();

}

?>

当比较示例 4-12 和示例 5-6 时,处理异常所需的代码量少于使用if/else条件语句。使用很少的else语句,程序的逻辑流程更容易理解。这是因为这个应用中所有文件抛出的异常都是由dog_interface中的catch模块处理的。用户错误被抛出到一个特殊的setException异常中。系统错误由错误catch模块捕获。error_check_dog_app方法(在示例 4-12 中)已被setException类取代。类中的代码与error_check_dog_app中的代码非常相似。删除了在$eMessage字符串中显示的单个更新消息,因为该类对用户错误做出反应,而不是成功的更新。在代码主体中添加了一个普通的print行,让用户知道所有更新都已成功。该接口中的所有代码周围都添加了try块。这有助于捕捉该应用任何部分中的任何问题。注意,如果找不到dog_container文件,也会抛出异常。

该应用只需要三个catch模块。setException catch块从setException类调用errorMessage方法,该方法确定发生了什么用户错误。然后将信息显示给用户。Exception catch块处理所有其他异常。它当前向用户显示这些信息。然而,异常和错误捕捉块目前向用户提供了太多的信息。通知用户应用可能遇到的其他问题违反了安全性。你应该告诉他们,该系统目前不可用,请他们稍后再来查看。测试时显示详细的错误是可以的。然而,这对现实世界并不好。您将在下一节中解决这个安全漏洞。

For more information about exception handling, please visit the example: http://www.w3schools.com/php/php_exception.asp Video: https://www.thenewboston.com/videos.php?cat=11&video=17171

做它

Examine the code from this section. Are there any areas in which error checking could have been converted to exception handling? Go to the book’s web site and download the code for this section. Make the potential changes to the existing code to use additional exception handling.

记录异常

应用必须能够在出现问题时通知系统分析师。但是,不应该向应用的用户显示关于错误的特定消息。应该通知用户系统当前不可操作。应该通知系统分析师已经发生的特定问题。

提供这种能力的最简单方法是将错误消息放入日志文件中。PHP 应用可以将消息记录到默认的 PHP 错误文件或应用特定的文件中。可以编辑php.ini文件(参见第一章中的位置)来指定默认错误日志文件的位置和名称。一旦php.ini文件在编辑器中打开,搜索error_log。如果分号位于行首,则该位置已被注释掉。只需删除分号并指定一个位置,例如:

error_log = c:/temp/php_errors.log

-or-

error_log =http://www.asite.com/temp/php_errors.log

当写入默认错误日志时,PHP 将在您提交的错误消息中插入一个时间戳。您的默认时区可能没有在php.ini文件中正确设置。搜索date.timezone。美国大陆的有效时区设置为:

date.timezone = "America/New_York"

date.timezone = "America/Chicago"

date.timezone = "America/Los_Angeles"

For all other American time zones visit:http://www.php.net/manual/en/timezones.america.php

For worldwide time zones visit:http://php.net/manual/en/timezones.php

Apache httpd配置文件(位置见第一章)可以覆盖php.ini文件中的设置。你也应该打开这个文件,搜索date.timezone。用类似下面的格式替换现有行。

php_value date.timezone "America/New_York"

一旦你更新并保存了php.ini和/或apache.httpd文件,你必须重新加载你的 Apache 服务器以使更改生效(参见第一章)。

Note

时区也可以用程序代码来设置。PHP 7 不支持datefmt_set_timezone_id方法。对于 PHP 5.2+可以使用datefmt_set_timezone方法。关于用程序代码设置时区的更多信息,请访问 http://php.net/manual/en/datetime.settimezone.php

<?php

error_log("error message");

?>

输入此代码并将其保存在测试文件中。在您的环境中测试它。如果您的设置是正确的,PHP 将在error_log参数中指定的位置创建错误日志。不要自己创建文件。PHP 不会将信息记录到不是自己创建的日志文件中。发送到日志文件的消息格式应类似于以下内容:

[25-Jun-2015 17:01:12 America/New_York] error message

只用简单的一行代码,PHP 就在指定的位置创建了基于文本的文件,并将消息放在文件中。

如果您不能访问这些文件,您可以在 PHP 应用中指定一个特定的位置来发送您的消息。此功能还允许您设置多个应用日志文件。应用通常具有信息日志文件、身份验证(登录)日志文件、错误日志文件和安全日志文件。通过分离每种类型的消息,可以更容易地在日志文件中扫描特定类型的消息。

假设您希望在一个文件中记录用户错误,在另一个文件中记录其他错误。

<?php

const USER_ERROR_LOG = 'User_Errors.log';

const ERROR_LOG = 'Errors.log';

// sending a user error

error_log("A user error",3,USER_ERROR_LOG);

// sending all other errors

error_log("A general error",3,ERROR_LOG);

<?php

这段代码将使用常量(USER_ERROR_LOGERROR_LOG)将错误消息指向正确的位置。注意,3的第二个参数被用来让error_log方法知道一个不同的位置将被用来记录错误。应该使用标准格式向日志发送消息。格式应该包括时间/日期(如果环境中没有包括,如前所述)、消息类型(如果文件中有多种消息类型)、错误消息和任何其他相关信息。默认情况下,消息长度限制为 120 个字符。然而,这可以在php.ini文件中更改。

$date = date('m.d.Y h:i:s');

// For more info on data time format go to:http://php.net/manual/en/function.date.php

$errormessage = "This is the error";

$eMessage =  $date . " | User Error | " . $errormessage . "\n";

error_log($eMessage,3,USER_ERROR_LOG);

The above code would produce

06.06.2015 03:00:55 | User Error | This is the error

可以使用标准的文本编辑器(Notepad++或 Notepad)或日志监控软件(您将在本章的后面创建一个日志阅读器程序)来查看文件的内容。

系统将限制日志文件的大小。但是,假设每天没有太多的日志记录,应用可以创建特定于每天的日志。

$USER_ERROR_LOG = "User_Errors" . date('mdy') . ".log";

$ERROR_LOG = "Errors" . date('mdy') . ".log";

...

error_log($eMessage,3,$USER_ERROR_LOG);

And security—The location of the log file should be in a different folder from the application. The folder needs to allow write access to the application. However, it should be prevented from being read or written outside the server itself. Only authorized personnel can access the log.

请注意,由于日期方法创建了一个可能的变量输出(不同的日期),常量(USER_ERROR_LOGERROR_LOG)必须更改为变量。该格式将创建一个类似于User_Errors06062015.logErrors06062015.log的文件名。

PHP 还使得在日志文件中写入内容时发送电子邮件警报变得非常容易。web 服务器必须包括电子邮件服务器。您的本地机器可能没有此功能。然而,通常,web 主机提供商(具有 PHP 功能)包括电子邮件服务。要使用这种能力,您可以添加一个error_log语句:

error_log("Date/Time: $date - Serious System Problems with Dog Application. Check error log for details", 1, "noone@helpme.com", "Subject: Dog Application Error \nFrom: System Log <systemlog@helpme.com>" . "\r\n");

And security-although it is easy to tell the employees who receive e-mail the exact problems in the application, don't do it. By default, email is not encrypted. Sending an unencrypted e-mail containing application details will cause hackers to destroy your application. However, you should provide enough information in the message (such as date/time stamp and possibly error number) to help employees find the error message in the log file.

第一个参数指定电子邮件的消息。第二个参数通知error_log通过电子邮件发送该信息。第三个参数提供“收件人”电子邮件地址。第四个参数是一个额外的头字段。该字段通常用于包含电子邮件的主题和发送邮件的电子邮件地址。必须包含“发件人”地址,否则邮件将不会被发送。但是,“发件人”地址不必是现有的地址。

For more information about log errors, please visit the example: http://php.net/manual/en/function.error-log.php Example: http://www.w3schools.com/php/php_error.asp

Dog应用中,您可以通过调整dog_interfacecatch块来提供记录异常和通过电子邮件发送主要错误的能力(来自示例 5-6 )。

Example 5-7. The dog_inteface.php file with exception logging and e-mail

<?php

const USER_ERROR_LOG = "User_Errors.log";

const ERROR_LOG = "Errors.log";

function clean_input($value)

{

$value = htmlentities($value);

// Removes any html from the string and turns it into < format

$value = strip_tags($value);

if (get_magic_quotes_gpc())

{

$value = stripslashes($value);        // Gets rid of unwanted slashes

}

$value = htmlentities($value);        // Removes any html from the string and turns it into < format

$bad_chars = array( "{", "}", "(", ")", ";", ":", "<", ">", "/", "$" );

$value = str_ireplace($bad_chars,"",$value);

return $value;

}

class setException extends Exception {

public function errorMessage() {

list($name_error, $breed_error, $color_error, $weight_error) = explode(',', $this->getMessage());

$name_error == 'TRUE' ? $eMessage = '' : $eMessage = 'Name update not successful<br/>';

$breed_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Breed update not successful<br/>';

$color_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Color update not successful<br/>';

$weight_error == 'TRUE' ? $eMessage .= '' : $eMessage .= 'Weight update not successful<br/>';

return $eMessage;

} }

}

function get_dog_app_properties($lab)

{

print "Your dog's name is " . $lab->get_dog_name() . "<br/>";

print "Your dog weights " . $lab->get_dog_weight() . " lbs. <br />";

print "Your dog's breed is " . $lab->get_dog_breed() . "<br />";

print "Your dog's color is " . $lab->get_dog_color() . "<br />";

}

//----------------Main Section-------------------------------------

try {

if ( file_exists("e5dog_container.php"))

{

Require_once("e5dog_container.php");

}

else

{

throw new Exception("Dog container file missing or corrupt");

}

if (isset($_POST['dog_app']))

{

if ((isset($_POST['dog_name'])) && (isset($_POST['dog_breed'])) && (isset($_POST['dog_color'])) && (isset($_POST['dog_weight'])))

{

$container = new dog_container(clean_input($_POST['dog_app']));

$dog_name = clean_input(filter_input(INPUT_POST, "dog_name"));

$dog_breed = clean_input($_POST['dog_breed']);

$dog_color = clean_input($_POST['dog_color']);

$dog_weight = clean_input($_POST['dog_weight']);

$breedxml = $container->get_dog_application("breeds");

$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml);

$lab = $container->create_object($properties_array);

print "Updates successful<br />";

get_dog_app_properties($lab);

}

else

{

print "<p>Missing or invalid parameters. Please go back to the dog.html page to enter valid information.<br />";

print "<a href='dog.html'>Dog Creation Page</a>";

}

}

else // select box

{

$container = new dog_container("selectbox");

$properties_array = array("selectbox");

$lab = $container->create_object($properties_array);

$container->set_app("breeds");

$dog_app = $container->get_dog_application("breeds");

$result = $lab->get_select($dog_app);

print $result;

}

}

catch(setException $e)

{

echo $e->errorMessage(); // displays to the user

$date = date('m.d.Y h:i:s');

$errormessage = $e->errorMessage();

$eMessage =  $date . " | User Error | " . $errormessage . "\n";

error_log($eMessage,3,USER_ERROR_LOG); // writes message to user error log file

}

catch(Exception $e)

{

echo "The system is currently unavailable. Please try again later."; // displays message to the user

$date = date('m.d.Y h:i:s');

$errormessage = $e->getMessage();

$eMessage = $date . " | User Error | " . $errormessage . "\n";

error_log($eMessage,3,ERROR_LOG); // writes message to error log file

error_log("Date/Time: $date - Serious System Problems with Dog Application. Check error log for details", 1, "noone@helpme.com", "Subject: Dog Application Error \nFrom: System Log <systemlog@helpme.com>" . "\r\n");

// e-mails personnel to alert them of a system problem

}

catch (Error $e)

{

echo "The system is currently unavailable. Please try again later."; // displays message to the user

$date = date('m.d.Y h:i:s');

$errormessage = $e->getMessage();

$eMessage = $date . " | Fatal System Error | " . $errormessage . "\n";

error_log($eMessage,3,ERROR_LOG); // writes message to error log file

error_log("Date/Time: $date - Serious System Problems with Dog Application. Check error log for details", 1, "noone@helpme.com", "Subject: Dog Application Error \nFrom: System Log <systemlog@helpme.com>" . "\r\n");

// e-mails personnel to alert them of a system problem

}

在示例 5-7 代码的顶部,创建了常量USER_ERROR_LOGERROR_LOG来精确定位日志文件的名称和位置。在代码的顶部定位可能会发生变化的常数(例如税率),为负责支持应用的程序员提供了快速更改的便利。如前所述,日志文件的位置必须位于允许应用写访问的文件夹中。建议将日志文件与其他日志文件放在同一个文件夹中,以便数据中心人员(或系统分析师)轻松访问。

其他代码更改位于catch块中。setException catch块将由setException类生成的错误消息返回给用户。该消息让用户知道哪些属性(NameBreedColorWeight)没有更新。导致此异常的错误可能来自用户,也可能是由于信息从客户机传输到服务器时遭到破坏。这些消息仅提供关于属性需求的信息,而用户应该已经知道这些信息。catch模块也向用户错误日志中写入类似的消息。用户错误不是需要分析师解决的紧急错误。然而,跟踪用户问题的趋势可以提供所需的可能变化的指示,以确保用户对应用具有可能的最佳体验。

Exception and Error catch块捕获所有非用户生成的异常。这些异常导致的消息可能会泄露会破坏应用安全性的信息。因此,应该向用户显示一条通用消息(如"The system is currently unavailable. Please try again later.")。有关异常的详细信息(错误消息、文件位置、引发异常的代码行)应该放在错误日志中,以供进一步分析。这些catch块捕获的大多数异常会阻止应用运行。因此,将出现的问题告知相关人员非常重要。这个catch街区是向支持人员发送电子邮件以提醒他们任何问题的好地方。

既然您已经在程序中内置了异常处理和错误处理,那么您可以编辑php.ini文件来关闭对用户的错误报告。但是,您应该等到所有开发和测试都完成后再这样做。在php.ini文件中找到行"display_errors = On"。如果您将此设置更改为"display_errors = Off",大多数错误信息将不会显示给用户。这一改变不会影响程序通过echoprint方法(包括在任何catch块中)发回给用户的任何消息。这一改变将使开发人员能够更好地控制当系统出现问题时向用户显示的消息类型。

做它

Download the code for this section. Create or use an existing HTML page that does not check for user input errors. Run the program entering values for the name, breed, weight, and color, which should cause user errors. Stop the program and open the contents of the user error log file. Did the errors appear in the file? If not, check the security of the folder that contains the log file to make sure that it allows write access to the log file. Once you are satisfied that it has caught user errors, try to cause other system errors to occur. Hint: Change the file names to nonexistent names in the dog application XML file. Check the error log to determine if the errors have been written to the file. Were you able to cause any errors that are not captured by one of the log files? If so, is there a way to capture those errors?

读取日志和文本文件

在上一节中,您发现了error_log方法只使用一行代码就可以写入日志文件。如果日志文件不存在,它会创建日志文件。它将传递给文件内容的任何消息追加(添加到文件的末尾)。然后它关闭文件。如果您要创建自己的日志记录过程,需要几行代码。

$logFile = fopen("error_log.log", "a");

$eMessage = $e->getMessage();

fwrite($logFile, $eMessage);

fclose($logFile);

如果这个文件不存在的话,fopen方法也会创建这个文件。“a”参数表示写入文件的任何内容都应该被附加。“w”表示文件中的任何内容都将丢失(被覆盖)。然后,fwrite方法将把位于第二个参数($eMessage)中的字符串放入由第一个参数($logFile)指示的文件中。$logFile是指向文本文件位置的指针。方法关闭文本文件。

For more information about writing to a text file, please visit an example: visit w3schools at http://www.w3schools.com/php/php_file_create.asp Video: visit New Boston at https://www.thenewboston.com/videos.php?cat=11&video=17063

因为日志文件是基于文本的文件,所以您可以使用类似的逻辑创建自己的应用来打开日志文件并读取其内容。

$logFile = fopen("error_log.log", "r");

echo fgets($logFile);

fclose($logFile);

这段代码将打开日志文件,读取文件中的第一行(通过一个fgets方法)并关闭文件。但是,文件中可能有多行。您必须能够遍历并显示文件中的每一行。您可以使用这里显示的while循环来完成此操作。

$logFile = fopen("error_log.log", "r");

while(!feof($logFile))

{

echo fgets($logFile) . "<br>";

}

fclose($logFile);

只要条件语句为TRUE,则while循环将继续循环。一旦语句为FALSE,代码将退出循环,并在循环结束后跳转到下一行代码。在本例中,error_log文件以只读方式打开(r)。while循环查看日志文件的文件结束指示符(feof),以确定是否已到达文件的结尾。如果feof返回TRUE,则已经到达文件的末尾。当您还没有到达文件末尾时,循环必须继续。要使条件语句产生一个TRUE,同时仍有记录要读取,您必须反转逻辑,如果有记录,让feof产生TRUE,如果没有记录,让FALSE产生。您可以使用!操作符来完成此操作。!操作符是一个NOT操作符,它反转结果。A NOT TRUEFALSE或者 a NOT FALSETRUE。因此,当有更多记录时,!feof操作符将产生TRUE,当没有记录时产生FALSE。该循环结合fgets方法将显示文件中的每条记录。一旦显示了每条记录,它将使用fclose关闭文件。

For more information about reading text files, please visit w3schools for example: http://www.w3schools.com/php/php_file_open.asp Visit New Boston for video: https://www.thenewboston.com/videos.php?cat=11&video=17064 For more information about while loop:, please visit w3schools for example: http://www.w3schools.com/php/php_looping.asp Visit New Boston for video: https://www.thenewboston.com/videos.php?cat=11&video=17011

前一个例子产生的输出非常简单。

06.06.2015 03:00:55 | User Error | This is the error

这并不比在文本编辑器中打开日志文件更好。您可以使用 HTML 表格、explode方法和数组的组合来产生更好的输出。您可以使用explode方法将日志文件中的每一行放入一个二维数组中。二维数组将像 HTML 表一样有行和列。

$dogs = array

(

array("Sammy","Lab",18,"Yellow"),

array("Spot","Mixed",14,"Mixed"),

array("Princess","Chihuahua",4,"White"),

array("Max","Boxer",35,"Brown")

);

二维数组是信息行的集合。每行的每个位置(列)都有公共信息。在前面的例子中,所有狗的名字在位置 0,狗的品种在位置 1,狗的体重在位置 2,狗的颜色在位置 3。这直接与表中的位置相关联。

For more information about multidimensional arrays, please visit: Example: http://www.w3schools.com/php/php_arrays_multi.asp Video: https://www.thenewboston.com/videos.php?cat=11&video=17026

| 制革时将皮弄湿 | 工党 | Eighteen | 黄色 |
| 地点 | 混合的 | Fourteen | 混合的 |
| 公主 | 奇瓦瓦州 | four | 白色的 |
| 最大 | 拳击运动员 | Thirty-five | 褐色的 |

表格和二维数组中的每个位置都由列和行引用。在这张表中,萨米的位置是(0,0)。黄色在位置(0,3)。最大值在位置(3,0)。布朗就位(3,3)。第一个位置是列。第二个位置是行。在 PHP 中,[]用来定义数组的位置(下标)。

echo $dogs[0][0] // displays Sammy

echo $dogs[0][3] // displays Yellow

echo $dogs[3][0] // displays Max

echo $dogs[3][3] // displays Brown

现在可以调整循环,将日志内容放在一个二维数组中。但是,您不知道数组的大小。所以不能使用之前显示的格式。如果开发人员没有使用 PHP,这可能会让他们头疼。然而,PHP 允许您动态创建数组,就像它允许您在需要时创建变量(属性)一样。

$logFile = fopen("error_log.log", "r");

$row_Count = 0;

while(!feof($logFile))

{

print_r ($error_Array[$row_Count] = explode(' | ', fgets($logFile)));$row_Count++;

}

fclose($logFile);

在本例的循环中,explode方法通过|字符(实际上是一个空格、|和一个空格)从文本文件中断开输入行。它将每个分隔的字符串放入$error_Array中由$row_Count中的值指示的行。第一次循环时,日志文件的第一行放在$error_Array[0](数组的第一行)。因为explode命令分隔了字符串,这导致为每个片段创建列。

如果文件的第一行包含:

A general error | stuff | more stuff

那么数组的第一行将包含:

$error_Array[0][0] = "A general error"

$error_Array[0][1] = "stuff";

$error_Array[0][2] = "more stuff";

您可以使用示例中所示的print_r命令来验证这一点。print_r以下列格式显示数组的内容。

Array ( [0] => A general error [1] => stuff [2] => more stuff )

这种格式验证字符串的每一部分都已放入数组中的正确位置。

$row_count在循环继续之前增加 1。这将文件的下一行定位到数组中的下一个位置(如果是文件的第二行,则为$error_Array[1])。你当然不希望使用print_r向用户显示结果(这不是很漂亮)。

但是,它是一个很好的工具,可以帮助您确保程序将所有东西都正确地放置在数组中。您可以在循环中添加代码来构建一个表。

$logFile = fopen("Errors.log", "r");

$row_Count = 0;

echo "<table>";

while(!feof($logFile))

{        echo "<tr>";

$error_Array[$row_Count] = explode(' | ', fgets($logFile));

$displayString = "";

for($I=0; $I < 3; $I++)

{

echo "<td> " . $error_Array[$row_Count][$I] . " </td> ";

}

echo "</tr>";

$row_Count++;

}

echo "</table>";

fclose($logFile);

一个echo语句位于打开 HTML 表格的while循环之前。一个额外的echo语句(echo "<tr>")存在于while循环中,用于创建表中的一行。

For more information about the for loop, please visit: Example: http://www.w3schools.com/php/php_looping_for.asp Video: https://www.thenewboston.com/videos.php?cat=11&video=17013

同样在while循环中,已经创建了一个for循环来遍历该行的每一列。既然您知道有四列,那么for循环是一个不错的选择。当您确切知道要循环多少次时,可以使用for循环。for循环的第一个参数(在;之前)初始化计数变量($I=0)。该变量($I)用于计算每个循环。第二个参数($I < 3)包括确定逻辑流是否将停留在循环中的比较。如果比较结果为TRUE,循环将继续。如果是FALSE,逻辑流程跳转到循环后的第一条语句(echo "</tr>")。第三个参数($I++)可以增加或减少计数变量。for循环通过在一行代码中要求所有信息来帮助程序员记住初始化变量、检查条件和增加变量。

for循环中的echo语句使用$row_Count$I变量从当前行的每一列中提取信息。第一次循环时,$row_Count将为 0。echo语句将显示$errorArray[0][0]的内容。随着for循环的继续,将显示$errorArray[0][1]$errorArray[0][2]$errorArray[0][3]的内容。使用<td></td>标签将每个值放入表格的一个单元格中。一旦for循环完成,流程下降到循环以下并关闭行(回显</tr>)。然后row_Count变量递增。如果有更多行(文件中有更多记录),则while循环将继续下一行的过程,直到文件中没有更多记录。一旦流程跳出while循环,工作台关闭(echo "</table>")。然后文件被关闭。

文本(日志)文件是顺序文件。添加(追加)项目时,它们会被添加到列表的底部。您可能希望对信息进行排序,首先列出最新的信息。只需对代码稍加修改就可以实现这一点。

$logFile = fopen("Errors.log", "r");

$row_Count = 0;

while(!feof($logFile))

{

$error_Array[$row_Count] = explode(' | ', fgets($logFile));

$row_Count++;

}

$row_Count--;

fclose($logFile);

echo "<table>";

for ($J=$row_Count; $J >= 0; $J--)

{         echo "<tr>";

$displayString = "";

for($I=0; $I < 3; $I++)

{

echo "<td> " . $error_Array[$J][$I] . " </td> ";

}

echo "</tr>";

}

echo "</table>";

while循环现在用记录加载数组,并记录数组中的项数。在循环结束并且文件被关闭之后,一个for循环以相反的顺序遍历数组,回显表中的行。计数器变量$J从数组中的总行数开始($row_Count)。在循环之前,从$row_Count中减去 1,因为在检索到最后一条记录之后,它在while循环中增加,这使得计数多了 1。然后$J对于每个循环递减($J--),直到值小于零。内部的for循环(for($I=0;$I<3;$I++))没有改变,因为它仍然必须循环通过行的每一列来显示信息。

通过将记录加载到数组中,您可以根据需要修改它们。让我们假设您希望能够从日志中删除一条记录。只要知道要删除的行号,就可以从数组中删除该记录。然后,您可以用剩余的记录重新填充该文件。

首先,您将对已经完成的echo代码做一个小小的修改,在要删除的记录旁边添加一个链接。然后,您将添加一个delete方法,将显示代码移动到一个display方法(这样它可以在任何需要的时候被调用),并创建一个save changes方法来更新日志文件。

Example 5-8. The readerrorlog.php file

<?php

function deleteRecord($recordNumber, &&#x0024;row_Count, &&#x0024;error_Array) {

for ($J=$recordNumber; $J < $row_Count - 1; $J++) {

for($I=0; $I < 3; $I++)

{ $error_Array[$J][$I] = $error_Array[$J + 1][$I]; }

}

Unset($error_Array[$row_Count]);

$row_Count--;

}

function saveChanges($row_Count,$error_Array,$log_File) {

$logFile = fopen($log_File, "w");

for($I=0; $I < $row_Count; $I++) {

$writeString = $error_Array[$I][0] . " | " . $error_Array[$I][1] . " | " . $error_Array[$I][2];

fwrite($logFile, $writeString);

}

fclose($logFile);

}

function displayRecords($row_Count, $error_Array) {

echo "<html><head>";

echo "<style> table { border: 2px solid #5c744d;}  </style>";

echo "</head><body><table>";

echo "<caption>Log File: " . ERROR_LOG . "</caption>";

echo "<tr><th></th><th>Date/Time</th><th>Error Type</th><th>Error Message</th></tr><tr>";

for ($J=$row_Count; $J >= 0; $J--) {

echo "<td><a href='readlogfilea.php?rn=$J'>Delete</a></td>";

for($I=0; $I < 3; $I++)  {

echo "<td> " . $error_Array[$J][$I] . " </td> ";

}

echo "</tr>";

}

echo "</table>";

echo "</body></html>";

} // main section

const ERROR_LOG = "Errors.log";

$logFile = fopen(ERROR_LOG, "r");

$row_Count = 0;

while(!feof($logFile))

{

$error_Array[$row_Count] = explode(' | ', fgets($logFile));

$row_Count++;

}

fclose($logFile);

if(isset($_GET['rn']))

{

deleteRecord($_GET['rn'], $row_Count, $error_Array);

saveChanges($row_Count,$error_Array,ERROR_LOG);

}

displayRecords($row_Count,$error_Array);

?>

A978-1-4842-1730-6_5_Fig2_HTML.jpg

图 5-2。

The readerrorlog.php file with user errors

在示例 5-8 中,displayRecords方法包含了之前显示的大部分相同的代码。添加了额外的 CSS 代码,使显示更加专业。此外,显示的每条记录都包含一个 HTML href链接。该链接调用程序,传递用户想要删除的记录号。

“主要部分”中的代码集(执行的第一行代码)创建一个常量ERROR_LOG来定义日志文件的位置和名称。文件以与前面所示相同的方式打开并加载到数组中。

一旦加载了数组,程序就会检查它是否被每个记录的删除链接调用过。如果已经通过 HTTP GET传递了一个值,程序就会调用deleteRecord方法。一旦deleteRecord方法完成,程序就调用saveChanges方法。不管有没有值被传入程序,它都会执行最后一条语句,这条语句调用displayRecords方法。

Note-The title line of deleteRecords method (function deleteRecord($recordNumber, &&#x0024;row_Count, &&#x0024;error_Array)) allows $row_Count and $error_Array to be updated in the method by reference rather than by value. By default, the parameters passed to the method cannot be changed by the method (by value). & indicates that the parameter is passed by reference. This allowed value is changed. deleteRecords You can adjust the number of rows in the array ($row_Count) and the information of the array itself ($error_Array). Pass the content (data) contained in the parameter to the method by value. Pass the memory address of the parameter to the method by reference, which allows the method to change the value of the parameter in memory.

deleteRecords方法接受要删除的记录号作为其参数之一。数组中该记录号以上的任何位置都保持不变。低于该记录的任何位置都必须上移一个位置。例如,如果一个数组有十个位置(0-9 ),第五个位置(4)将被删除,那么位置 5-9 现在必须变成位置 4-8。

在下面的例子中,对于要删除的$recordNumber之后的任何记录,位置$J+1被放入位置 J:

A978-1-4842-1730-6_5_Figa_HTML.jpg

不再需要数组中的最后一个位置。释放数组的最后一个位置(使用unset)。这将通知操作系统不再需要内存空间。操作系统将调用其垃圾收集器来释放内存位置。当我们在后面的章节中讨论关联数组时,我们会发现一个更简单的方法来完成这个任务。一旦数组被重新配置,就会调用saveChanges方法来替换日志文件中的记录。显示的代码与本章前面的例子非常相似,只有一个例外。fopen方法使用参数“w”。“w”参数将删除文件中已经存在的任何内容,并用当前正在写入文件的内容替换它。在本例中,文件将被新的记录集更新(替换),该记录集不包括已删除的记录。在程序被调用的任何时候都会调用displayRecords方法(删除或不删除记录)。此方法显示日志文件的内容。

Programming instructions-A program that retrieves data to be used in the whole program usually retrieves information at the initial stage of the code and places it in a data structure (array, list or data set). When the data in the program is updated, the information in the data structure is also updated. After the data processing is completed, the information will be returned to the original location (text file or database). Updating a text file or database is usually completed as the last stage of the program. The process of the user exiting the program will provide an event to indicate that the updated data should be saved.

做它

Download the code for Example 5-8 from the book’s web site. Add a try catch block to handle any unexpected problems (such as a nonexistent log file). Test and save your changes.   Adjust the Example 5-8 code from either #1 or the book’s web site to allow the users to select which log file to read. The program should allow the users to select either the user log file or the general system log file. Test and save your changes.

章节术语

| 错误 | 例外 |
| try/catch 块 | $e->getMessage(): |
| 交换语句 | 嵌入式 if 语句 |
| 默认代码 | 延伸 |
| 异常类 | 遗产 |
| 子类别 | 父类 |
| 触发器 _ 错误 | 异常处理的层次结构 |
| 引发(抛出)异常 | 捕捉异常 |
| 代码的态度 | 推错误 |
| 找出错误 | 日志文件 |
| 默认 PHP 错误文件 | 错误日志 |
| 时间戳 | 日期.时区 |
| 应用日志文件 | 常数 |
| 电子邮件提醒 | 显示 _ 错误 |
| 文本文件 | 顺序文件 |
| 打开文件 | fwrite |
| 指针 | fclose(美国联邦航空局) |
| 从文件指针中读取一行 | while 循环 |
| 文件结尾判断 | !操作员 |
| 引擎异常 | 激增 |
| 二维数组 | 排 |
| 圆柱 | 下标 |
| 动态数组 | 数组的第一行 |
| 打印 _f | 增量 |
| 减量 | for 循环 |
| HTML HREF | 复原 |

第二章问题和项目

多重选择

Which of the folowing is true about PHP program errors? They are used for all exceptional situations before PHP 5.0.   They are handled by the operating system.   They might be displayed to the users.   All of these.     Which of the folowing is true about PHP program exceptions? They are used for all exceptional situations after PHP 5.0.   They can be handled by the operating system.   They might be displayed to the users.   All of these.     The try/catch block is used to do which of the following? Capture all errors.   Capture only known errors.   Capture all exceptions.   Capture only known exceptions.     Inheritance does which of the following? Is part of object-oriented programming.   Allows the child class to use all the attributes of its parent class.   Allows the child to create its own methods or properties.   All of these.     Text files do which of the following? Are easy to use in PHP.   Are more secure than using databases.   Are non-sequential.   None of these.     PHP log files do which of the following? Are created by the error_log method.   Are updated by the error_log method.   Should be located in a different directory than the application.   All of these     Which of the following describes two-dimensional arrays? They are similar to HTML tables. They contain rows and columns.   They should be avoided at all costs. They are inefficient and difficult to use.   They require all columns to be loaded with data before rows.   None of these.     Which of the following describes the first position of a two-dimensional array? It has a subscript of 0.   It has a subscript of 1.   It has a default value.   None of these.     Application log files should include which of the folllowing?. User log files.   Error log files.   Informational log files.   All of these.     E-mails generated because of program exceptions should do what? Include all error information, including the error descriptions.   Include the code lines that caused the error to occur.   Include the date and time the error occurred.   All of these.

对/错

All exceptions should be displayed to the users.   The for loop should be used when you know exactly how many loops will occur.   The while loop will continue to loop until the conditional statement becomes true.   unset can be used to release a position in an array.   All PHP arrays must be declared before being used.   A pointer points to the location in memory that an object resides.   The ! operator reverses a TRUE result to a FALSE result.   print_f can be used to display the contents of an array.   Try/Catch blocks should reside in the business rules and data tiers but not the interface tier.   Only Exceptions intentionally thrown by program code should be caught.

简答/短文

Explain how hierarchy of exception handling works with three-tier applications.   What is the difference between an error and an exception?   How do you correct the time zone if PHP is providing an incorrect timestamp?   How can PHP programmers try to capture errors so they can be treated as if they are exceptions?   Why is it important to have multiple log files produced by an application?

项目

Adjust the code from project #1 (or #2) from Chapter 4 to include exception handling and logging.   Create an application that will register contestants for your favorite game show. Include verification of the data entered using HTML5 and JavaScript. Also validate the data when it is passed to the application. The application should include an interface php program (interface tier) and a registration class (business rules tier). The registration class should include exception handling (both user exceptions and program exceptions). The interface program should handle the exceptions using try/catch blocks as shown in this chapter.

学期项目

Update the ABC Computer Parts Warehouse Inventory application to include exception handling. The application should attempt to handle all exceptions, and errors, when possible. User exceptions should be logged to a user log. All other exceptions should be logged to a system log. If the exception is considered to be extreme (will cause the program to otherwise crash), an e-mail should be sent to the system administrator. Hint: The Try/Catch block should only exist in the interface tier.

六、数据对象

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1730-6_​6) contains supplementary material, which is available to authorized users.

“我是一个理想主义者。我不知道我要去哪里,但我在路上。”——卡尔·桑德堡,杂项(1904)

章节目标/学生学习成果

完成本章后,学生将能够:

  • 创建插入、更新和删除 XML 或 JSON 数据的数据类
  • 解释如何创建一个使用 SQL 脚本更新 MySQL 数据的数据类
  • 创建一个 PHP 程序来创建一个变更备份日志
  • 创建一个 PHP 程序,可以从以前的备份中恢复数据
  • 应用更改以创建最新的有效信息
  • 使用依赖注入将数据类附加到 BR 层中的另一个类
  • 创建一个三层 PHP 应用

数据类

接口和业务规则层不应该存储应用信息。这些层甚至不应该知道信息是如何存储的(文本文件、XML 或数据库)或者存储信息的位置。存储的任何信息都必须从业务规则层传递到数据层。数据层还负责响应来自业务规则层的信息请求。

这使得接口层和业务规则层不知道存储方法类型(文本文件、XML 或数据库)和存储项位置的任何变化。签名(接受的参数)和从数据层返回的项应该在应用的生命周期中保持不变。只要这些没有改变,当数据层发生变化时,其他层就不需要改变。

安全性和性能—使用数据库时,在业务规则层构建一个 SQL 字符串并将该字符串传递给数据层似乎是合乎逻辑的。这将在应用中造成一个主要的安全漏洞。黑客可以传递任何 SQL 字符串(包括一个delete字符串)。将 SQL 更新命令(DELETEUPDATEINSERT)传递到数据层似乎也是合乎逻辑的。这又提供了一个大洞。为一个WHERE SQL 命令传递数据也是一个坏主意,因为它可能允许黑客删除或更改数据库中的任何数据组合。

数据类应该为操作信息提供完整的功能。这包括读取、插入、更新和删除信息的能力。即使当前应用不需要所有这些命令,从逻辑上讲,它们也应该存在于数据类中以备将来使用。

应该在性能和存储信息的需求之间取得平衡。虽然非常重要的信息可能需要立即存储,但其他信息可以保存在应用的数据结构(列表、数组和数据集)中,直到用户完成任何更新。在服务器的内存中保存信息并对其进行更改,比在存储位置更有效。仅在完成所有更改后存储信息会将对存储位置的几次调用减少到两次(初始信息检索和更新信息的保存)。对内存中的信息进行更改总是比在存储设备(如硬盘)上进行更改更有效。

使用数据类提供了自动填充数据结构并将信息保存在存储位置的逻辑能力。假设只有在需要更新信息时才会创建数据类的实例,则可以使用该类的构造函数从存储中检索信息,并将其放入服务器的内存中。当不再需要数据对象时,从逻辑上讲,不再需要对信息进行更改。类的析构函数可用于将信息从内存返回到存储区。

class dog_data

{

function __construct()

{

$xmlfile = file_get_contents(get_dog_application("datastorage"));

$xmlstring = simplexml:load_string($xmlfile);

$array = (array)$xmlstring;

print_r($array);

}

}

这个示例构造函数非常接近于从 XML 文件中提供有用的信息。PHP file_get_contents方法打开一个文本文件,将内容放入一个字符串中,然后关闭文件。构造函数调用这个方法和get_dog_application方法(与示例 5-5 中的dog_container使用的方法相同)来确定 XML 数据文件的文件名和位置。然后文件的内容被放在$xmlfile中。PHP simplexml:load_string方法然后格式化数据,以允许 SimpleXML 数据模型遍历信息。此时,可以使用 SimpleXML 方法来显示和操作数据。但是,下一行试图将 XML 数据转换成数组。(array)语句尝试使用类型转换。print_r语句显示结果。

<?xml version="1.0" encoding="UTF-8"?>

<dogs>

<dog>

<dog_name>Woff</dog_name>

<dog_weight>12</dog_weight>

<dog_color>Yellow</dog_color>

<dog_breed>Lab</dog_breed>

</dog>

<dog>

<dog_name>Sam</dog_name>

<dog_weight>10</dog_weight>

<dog_color>Brown</dog_color>

<dog_breed>Lab</dog_breed>

</dog>

</dogs>

假设 XML 文件的格式如下所示,输出包括:

Array ( [dog] => Array ( [0] => SimpleXMLElement Object ( [dog_name] => Woff [dog_weight] => 12

[dog_color] => Yellow [dog_breed] => Lab ) [1] => SimpleXMLElement Object ( [dog_name] => Sam

[dog_weight] => 10 [dog_color] => Brown [dog_breed] => Lab ) ) )

已经创建了多维数组和 SimpleXML 对象的组合。这不能提供易于操作的有用数据。但是,您可以使用 JSON 方法来欺骗 PHP 创建一个多维关联数组。

class dog_data

{

function __construct()

{

$xmlfile = file_get_contents(get_dog_application("datastorage"));

$xmlstring = simplexml:load_string($xmlfile);

$json = json_encode($xmlstring);

print_r($json);

}

}

{"dog":[{"dog_name":"Woff","dog_weight":"12","dog_color":"Yellow","dog_breed":"Lab"},{"dog_name"

:"Sam","dog_weight":"10","dog_color":"Brown","dog_breed":"Lab"}]}

使用 PHP json_encode方法将数据转换成结构良好的 JSON 数据。您可以使用几种 PHP 技术中的一种来操作 JSON 数据,或者通过一个额外的语句(json_decode),您可以创建一个结构良好的多维关联数组。

class dog_data

{

function __construct()

{

$xmlfile = file_get_contents(get_dog_application("datastorage"));

$xmlstring = simplexml:load_string($xmlfile);

$json = json_encode($xmlstring);

$dogs_array = json_decode($json,TRUE);

print_r($dogs_array);

}

}

Array ( [dog] =>

Array (

[0] => Array ( [dog_name] => Woff [dog_weight] => 12 [dog_color] => Yellow [dog_breed] => Lab )

[1] => Array ( [dog_name] => Sam [dog_weight] => 10 [dog_color] => Brown [dog_breed] => Lab ) ) )

如您所见,不再有数组和 SimpleXML 对象的混合。创建了一个关联数组,它使用关键字代替下标(索引)的数值。在前面的例子中,一个名为"dog"的数组已经创建了两行(每行由一个数组表示)。在每一行中,列(单元格)由列名(dog_namedog_weightdog_colordog_breed)而不是索引(0、1、2、3)引用。这些行和列可以使用您在前面章节中看到的一些技术来操作。

一旦您完成了对数组的所有更改(按照业务规则层的请求),您将把信息返回到析构函数中的存储位置。

private $dogs_array = array(); // defined as an empty array initially

function __construct()

{

$xmlfile = file_get_contents(get_dog_application("datastorage"));

$xmlstring = simplexml:load_string($xmlfile);

$json = json_encode($xmlstring);

$this->dogs_array = json_decode($json,TRUE);

}

function __destruct()

{

$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

$xmlstring .= "\n<dogs>\n";

foreach ($this->dogs_array as $dogs=>$dogs_value) {

foreach ($dogs_value as $dog => $dog_value)

{

$xmlstring .="<$dogs>\n";

foreach ($dog_value as $column => $column_value)

{

$xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";

}

$xmlstring .= "</$dogs>\n";

}

}

$xmlstring .= "</dogs>\n";  file_put_contents(get_dog_application("datastorage"),$xmlstring);

}

有许多方法可以在 PHP 中创建 XML 数据。前面的例子采用了一种简单的方法,从数组中提供 XML 标记。如结构所示,这个多维数组中有三组数组。第一个foreach循环用于流经第一个数组(dogs)。第二个foreach循环处理狗数组(行)。一旦进入这个循环,第三个foreach循环控制每个 dog 数组中的列(每行)。

第三个循环检索列名(从$column)并将它们放在 XML 标签中。$column也用于拉动($dog_value[$column]列中的值。$xmlstring提供了与原始 XML 文件相同的标签和结构。请注意,每一行都包含一个换行符(\n)来显示文件中的不同行。没有这种添加,该结构也能工作。但是,它使文件在文本编辑器中更具可读性。

一旦创建了$xmlstring,代码使用 PHP file_put_contents方法和get_dog_application方法的组合(来自第四章)来打开 XML 文件,用$xmlstring中包含的字符串替换内容,并关闭文件。

您需要对构造函数进行最后一次调整,以允许它处理 XML 解析错误。当 XML 结构有问题时,就会出现解析错误。之前的dog_breeddog_application XML 文件没有被应用更新,相当稳定。然而,狗的信息的 XML 文件将被频繁地更新。你需要处理任何可能出现的问题。您将提出一个一般性错误,该错误将被dog_interface视为一个重要错误,并记录下来,通过电子邮件发送给支持人员。它还会向用户显示一条"System currently not available please try again later"消息。

private $dogs_array = array(); defined as an empty array initially

libxml:use_internal_errors(true);

function __construct() {

$xmlfile = file_get_contents(get_dog_application("datastorage"));

$xmlstring = simplexml:load_string($xmlfile);

if ($xmlstring === false) {

$errorString = "Failed loading XML: ";

foreach(libxml:get_errors() as $error) {

$errorString .= $error->message . " ";  }

throw new Exception($errorString); }

$json = json_encode($xmlstring);

$this->dogs_array = json_decode($json,TRUE);

}

默认情况下,XML 解析错误将导致系统向用户显示错误并关闭程序。libxml:user_internal_errors(true)方法将抑制错误。当字符串通过simplexml:load_string方法被转换成 XML 格式时,XML 被解析以确定它是否有效。如果无效,该方法将返回FALSE而不是 XML 信息。所示的if语句将创建一个$errorString,并使用foreach语句遍历由libxml:get_errors方法返回的每个错误(返回一个包含错误的数组)。一旦收集了所有错误,它将通过$errorString引发一个异常。dog_interface程序将捕捉这个错误并处理它,如第五章所示。

这个例子做了一个不好的假设(这简化了这个例子)。它假设$errorString没有超过日志文件 120 个字符的最大容量。一个格式非常糟糕的文件可能会很快导致$errorString超过这个大小。这个限制可以在 PHP 配置文件中调整。

当数据对象从内存中删除时,数据会自动保存,插入、更新和删除方法只需要调整多维关联数组的内容。让我们先来看看如何创建一个delete方法,因为你已经在第五章中看到了一个例子。

readerrorlog程序中(在示例 5-8 中)你创建了一个deleterecord方法。该方法用于常规多维数组。我们可以对这个例程做一些调整,为dog_data类创建deleteRecord方法。

function deleteRecord($recordNumber) {

foreach ($this->dogs_array as $dogs=>&$dogs_value) {

for($J=$recordNumber; $J < count($dogs_value) -1; $J++)

{

foreach ($dogs_value[$J] as $column => $column_value)

{

$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

}

}

unset ($dogs_value[count($dogs_value) -1]);

}

}

在前面的deleterecord方法中,数组中的行数和数组本身被传递到方法中。dog_data类中的数组由包含狗信息的 XML 文件填充。没有设置记录数的属性。这不是问题。PHP 方法 count 将返回数组的大小。您可以使用$this指针访问和更新dogs_array(这是一个受保护的私有财产)。类中的方法可以使用this指针来访问和更新受保护的属性;没有必要将它们传递到方法中。您需要传递给deleteRecord方法的惟一属性是要删除的记录号($recordNumber)。

相关阵列具有三维。外部维度与 XML 结构中的 dogs 标记相关。虽然狗只有一个“行”,但是仍然需要一个循环来移动到下一个数组(dog)。foreach循环穿透 dogs 数组并提供对dog数组的访问(该数组是从 XML 文件中的 dogs 标记创建的)。$dogs将包含当前使用的狗排的数量。$dogs_value将包含该行的内容(包含dog_namedog_weightdog_colordog_breed中的值的数组)。

为了遍历 dog 数组中包含的每一行(数组),该方法使用了一个for循环。条件语句($J < count($dogs_value) -1)使用count方法来确定dog数组的大小。count方法返回数组的大小,而不是数组的最后一个位置。因此循环计数必须小于(count)。从这个值中减去一。如第五章中的所述,被删除行之后的任何一行都必须从当前位置上移一行。将不再需要数组的最后一个位置,这将所需的循环数减少了一个。

在第五章的例子中,使用了一个for循环从行中取出每一列,并将其放入上面的行中。对于关联数组,使用一个foreach循环。$column参数包含列名($J包含行号),用于将列中的值放入适当的位置。移动行中的值后,使用 PHP unset 方法移除数组的最后一个位置。

类似的逻辑几乎可以用在任何编程语言中。然而,PHP 关联数组允许跳过索引号。不像其他语言会在丢失的索引中放置一个空值,PHP 关联数组只是跳过实际的索引。因此,任何数组的索引都可以是 0、1、2、4、5,foreach 循环可以正确地遍历数组。考虑到这一点,我们可以大大简化前面的 delete 示例,使其只包含一行代码,只包含所示的 unset 命令。unset 命令将从dog_array中删除传递给该方法的索引。任何使用dog_array的 for 循环仍然可以正确地遍历数组。演示网站中包含的示例代码提供了这种能力。

您还可以对第五章中的displayRecords方法做一些调整,以返回调用程序(比如Dog类)请求的任何记录。

function readRecords($recordNumber) {

if($recordNumber === "ALL") {

return $this->dogs_array["dog"];

}

else {

return $this->dogs_array["dog"][$recordNumber];

} }

如你所见,readRecords方法比displayRecords方法更简单。该方法结果的所有格式都留给调用程序处理(如果需要)。请记住,输出的显示和格式化发生在接口层,而不是数据层(或业务规则层)。

该方法允许调用程序请求所有记录或特定记录。无论哪种情况,它都返回一个包含一行(所请求的特定记录)或所有行的数组。当返回所有行时,移除顶部数组(表示 rows XML 标记),以保持两个选择的维数(两个)相同。

insertRecords方法接受带有前面提到的下标名的关联数组。然而,为了在 XML 文件的标记名中允许依赖注入和灵活性,调用程序不需要知道标记名,直到创建了一个类的实例。这可以通过使用readRecords方法提取第一条记录,然后让调用程序检查从该记录返回的下标名来实现。

function insertRecords($records_array)

{

$dogs_array_size = count($this->dogs_array["dog"]);

for($I=0;$I< count($records_array);$I++)

{

$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

}

}

Note

使用前面显示的 JSON 函数创建dog_array的过程将在创建dog_array时产生一个不一致。如果dog_data.xml文件只包含一条记录,JSON 函数将不会创建一个数字索引(比如‘0’)。当 xml 文件中包含多条记录时,将创建数字索引(如“0”、“1”)。教科书网站上的演示文件中提供了处理这些差异的替代解决方案。

insertRecords方法中,所有记录都被添加到数组的末尾(如果需要,调用程序可以对它们进行排序)。dogs_array的当前大小由count方法确定并存储到$dogs_array_size中。在for结构中也使用了count方法来确定$records_array的大小和循环的数量。因为count方法的结果产生了数组的大小,它比上一个下标位置大 1,所以count的结果也给出了可用于插入记录的下一个位置。

在第一次循环中,$I为 0。$records_array的第一条记录放入$dogs_array_size加 0,或$dogs_array_size(放置记录的第一个开放行)。下一次循环时,$records_array ( $I被循环递增)的第二条记录被放入位置$dogs_array_size加 1。这是插入第一条记录后的下一个可用位置。循环将继续,直到$records_array中不再有记录。顺便说一下,这个方法也适用于只插入一条记录的情况(只要它是作为关联数组传递的)。该循环将只执行一次。

您需要检查的最后一个方法是update方法。这个方法是析构函数方法的一种非常简单的形式。

function updateRecords($records_array)

{

foreach ($records_array as $records=>$records_value) {

foreach ($records_value as $record => $record_value) {

$this->dogs_array["dog"][$records] = $records_array[$records];

}

}

}

这个小小的方法将接受任意大小的关联数组并更新dogs数组。它基于 PHP 动态构建数组的能力。

$records_array = Array (

0 => Array ( "dog_name" => "Jeffrey", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ),

2 => Array ( "dog_name" => "James", "dog_weight" => "21", "dog_color" => "Black", "dog_breed" => "Mixed" ));

动态构建的数组不需要数组中每个位置都有值。如果前面显示的动态数组被传递到updateRecords方法中,记录 0 和 2 将被更新为新的信息。在dogs数组中位置 1 的值将保持不变。

花点时间看看这些方法。在方法中只有两个 XML 标记被编码(dogsdog)。甚至这两个都可以从 XML 文件中检索到。然而,假设这些标签将一直存在于一个有效的 dog XML 文件中是合乎逻辑的。通过从 XML 文件中动态提取所有其他标签(dog_namedog_weightdog_colordog_breed),可以对文件进行更改,而不会导致任何代码更改。可以添加、移除和/或改变附加标签。

让我们把它们放在一起。

Example 6-1. The dog_data.php file

<?php

class dog_data

{

private $dogs_array = array(); //defined as an empty array initially

private $dog_data_xml = "";

function __construct() {

libxml:use_internal_errors(true);

$xmlDoc = new DOMDocument();

if ( file_exists("e5dog_applications.xml") )        {

$xmlDoc->load( 'e5dog_applications.xml' );

$searchNode = $xmlDoc->getElementsByTagName( "type" );

foreach( $searchNode as $searchNode )

{

$valueID = $searchNode->getAttribute('ID');

if($valueID == "datastorage")

{

$xmlLocation = $searchNode->getElementsByTagName( "location" );

$this->dog_data_xml = $xmlLocation->item(0)->nodeValue;

break;

}

}  }

else { throw new Exception("Dog applications xml file missing or corrupt"); }

$xmlfile = file_get_contents($this->dog_data_xml);

$xmlstring = simplexml:load_string($xmlfile);

if ($xmlstring === false) {

$errorString = "Failed loading XML: ";

foreach(libxml:get_errors() as $error) {

$errorString .= $error->message . " " ;  }

throw new Exception($errorString); }

$json = json_encode($xmlstring);

$this->dogs_array = json_decode($json,TRUE);

}

function __destruct()

{

$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

$xmlstring .= "\n<dogs>\n";

foreach ($this->dogs_array as $dogs=>$dogs_value) {

foreach ($dogs_value as $dog => $dog_value)

{

$xmlstring .="<$dogs>\n";

foreach ($dog_value as $column => $column_value)

{

$xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";

}

$xmlstring .= "</$dogs>\n";

}

}

$xmlstring .= "</dogs>\n";

file_put_contents($this->dog_data_xml,$xmlstring);

}

function deleteRecord($recordNumber)

{

foreach ($this->dogs_array as $dogs=>&$dogs_value) {

for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {

foreach ($dogs_value[$J] as $column => $column_value)

{

$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

}

}

unset ($dogs_value[count($dogs_value) -1]);

}

}

function readRecords($recordNumber)

{

if($recordNumber === "ALL") {

return $this->dogs_array["dog"];

}

else

{

return $this->dogs_array["dog"][$recordNumber];

}

}

function insertRecords($records_array)

{

$dogs_array_size = count($this->dogs_array["dog"]);

for($I=0;$I< count($records_array);$I++)

{

$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

}

}

function updateRecords($records_array)

{

foreach ($records_array as $records=>$records_value)

{

foreach ($records_value as $record => $record_value)

{

$this->dogs_array["dog"][$records] = $records_array[$records];

}

}

}

}

?>

Note

教科书网站上提供了另一种解决方案,它处理带有缺失索引的关联数组,以及dog_data.xml文件可能包含一个或零个记录的可能性。

在这个最终版本的dog_data类中,唯一的变化是在构造函数中包含了get_dog_application方法代码,以检索保存狗数据的 XML 文件的位置和名称。

Example 6-2. The testdata.php file

<?php

include("dog_data.php");

$tester = new dog_data();

$records_array = Array (

0 => Array ( "dog_name" => "Sally", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ));

$tester->insertRecords($records_array);

print_r ($tester->readRecords("ALL"));

print("<br>");

$records_array = Array (

1 => Array ( "dog_name" => "Spot", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ));

$tester->updateRecords($records_array);

print_r ($tester->readRecords("ALL"));

print("<br>");

$tester->deleteRecord(1);

print_r ($tester->readRecords("ALL"));

$tester = NULL; // calls the destructor and saves the xml records in the file

?>

示例 6-2 测试了一些使用dog_data类的可能场景。注意最后一行代码调用了析构函数(保存数据)。这是通过将指向对象的指针($tester)设置为NULL来实现的,这样就释放了对象。这将通知操作系统的垃圾收集器应该从内存中移除该对象。这将导致析构函数执行,从而更新 XML 文件并从服务器内存中移除对象。

JSON 数据

让我们花点时间回顾一下,看看读取和写入 JSON 数据的能力。使用本章中显示的示例代码,当您使用除 XML 之外的其他形式的数据时,只需要调整构造函数和析构函数。访问和使用 JSON 数据甚至比使用 XML 数据更容易。

...

$json = file_get_contents($this->dog_data_JSON);

$this->dogs_array = json_decode($json,TRUE);

if ($this->dogs_array === null && json_last_error() !== JSON_ERROR_NONE)

{

throw new Exception("JSON error: " . json_last_error_msg());

}

...

在构造函数中,在从dog_application.xml文件中检索数据位置的if else结构之后,访问和格式化 XML 数据的多行可以替换为前面显示的行。json_decode方法(如前所示)试图将文本文件中的数据格式化成相关的数组格式。如果数据不是有效的 JSON 格式,就会抛出一个异常,传递错误消息。由于使用了Exception类,dog_interface程序会将此信息记录在错误日志中,通过电子邮件发送给支持人员,并向用户显示一般消息。

$json = json_encode($this->dogs_array);

file_put_contents($this->dog_data_JSON,$json);

析构函数的完整代码只需要两行。json_encode方法将关联数组数据转换成 JSON 格式。然后,file_put_contents方法会将信息保存到 JSON 文件($this->dog_data_JSON的适当位置。不需要对dog_data中的任何其他方法进行修改。注意:使用 JSON 数据的示例应用可以在本书网站的第六章中找到。

mysql 日期

这本书旨在介绍 PHP 语言。因此,您不会花太多时间来学习数据库的用法。不过,现在是给出一个简单示例的好时机,您可以对构造函数和析构函数方法进行调整,以访问和更新数据库信息。

Note

mysql从 PHP5.5 开始已经被移除,建议大家使用mysqli或者pdo_mysql

$mysqli =mysqli_connect($server, $db_username, $db_password, $database);

if (mysqli_connect_errno())

{

throw new Exception("MySQL connection error: " . mysqli_connect_error());

}

$sql="SELECT * FROM Dogs";

$result=mysqli_query($con,$sql);

If($result===null)

{

throw new Exception("No records retrieved from Database");

}

$this->dogs_array = mysqli_fetch_assoc($result);

mysqli_free_result($result);

mysqli_close($con);

构造函数方法所需的大部分代码都与数据库的连接、检索和断开连接相关。mysqli_connect方法使用服务器位置($server)、数据库用户 ID ( $db_username)、数据库密码($db_password)和数据库名称($database)来连接数据库。如果mysqli_connect_errno包含任何错误,则抛出Exception描述错误。如果没有错误,使用 SQL SELECT 语句($sql)从数据库的Dogs表中检索所有记录。如果没有检索到记录,则会引发另一个异常。如果检索到记录,mysqli_fetch_assoc方法会将数据转换成一个关联数组。mysqli_free_result语句从$result中释放数据。mysqli_close方法关闭对数据库的访问。

析构函数需要更多的编码。然而,循环类似于保存 XML 数据。

$mysqli = new mysqli($server, $db_username, $db_password, $database);

if ($mysqli->connect_errno)

{

throw new Exception("MySQL connection error:" . $mysqli->connect_error);

}

If( (!$mysqli->query("DROP TABLE IF EXISTS Dogs") ||

(!$mysqli->query("CREATE TABLE IF NOT EXISTS Dogs (dog_id CHAR(4), dog_name CHAR(20), dog_weight CHAR(3), dog_color CHAR(15), dog_breed CHAR(35)") )

{

throw new Exception("Dog table can't be created or deleted. Error: " . $mysqli->error);

}

foreach ($this->dogs_array as $dogs=>$dogs_value) {

foreach ($dogs_value as $dog => $dog_value)

{

$dog_id = $dog_value["dog_id"];

$dog_name = $dog_value["dog_name"];

$dog_weight = $dog_value["dog_weight"];

$dog_color = $dog_value["dog_color"];

$dog_breed = $dog_value["dog_breed"];

If(!$mysqli->query("INSERT INTO Dogs(dog_id, dog_name, dog_weight, dog_color,

dog_breed) VALUES ('$dog_id', '$dog_name', '$dog_weight', '$dog_color',

'$dog_breed')"))

{

throw new Exception("Dog Table Insert Error: " . $mysqli->error);

}

}

}

...

析构函数方法试图连接到数据库。如果连接成功,该方法将删除任何预先存在的Dogs表,并创建一个包含必需字段的新表。(注意:重命名旧的并创建一个新的可能会更好。).如果可以移除旧表并创建新表,则该方法会尝试向表中插入行。SQL INSERT语句将来自$dog_name$dog_weight$dog_color$dog_breed的值放入表中的一行。foreach循环从关联数组中检索每一行,并放入表中。如果任何插入不成功,就会引发异常。在本书网站的第六章下有一个示例程序。

Programming shows that Apache server must be configured correctly and MySQL must be installed correctly to run this (or similar) database example. $server must be set to URL, "localhost" or "127.0.0.1". $db_username must be set to the user ID name to access the database ('root' if the user ID has not been configured). $db_password must be set to the database password (or '' if there is no password). $database must be set to the database name. There are many ways to access and operate databases in PHP.

做它

Download the example files for this section from the book’s web site. Adjust the deleteRecords method to allow the ability to delete multiple records. However, also include a check to limit the amount of records that can be deleted. It would not be very secure to allow all records to be deleted. If an attempt is made to delete all records (or too many records), an exception should be raised. The exception should cause the calling program (eventually dog_interface) to write an error message to the main log file, e-mail the support personnel, and display the general message to the users (shown in Chapter 5). Adjust the testdata program to test the ability to delete multiple records and catch the exceptions.   Download the example files for this section from the book’s web site. Adjust the testdata program to test all remaining scenarios that have not already been tested. These are related to inserting, updating (more than one), reading, and deleting records. Be sure to test improperly formatted information. Create a try catch block in the testdata program to capture any exceptions. You can use the try catch block from dog_interface in Chapter 5 as an example.

备份和恢复

对存储的信息进行更改时,总有可能出错。而一个开发良好的应用必须在保存数据之前过滤和清理数据;它还必须准备好处理坏数据仍可能流过并破坏信息的可能性。除了蓄意破坏之外,还可能出现无法预料的问题(如系统崩溃)。应用必须能够在不丢失数据的情况下进行恢复。这可以通过记录变更请求和备份有效信息来实现。通过使用有效的备份并对备份文件重新应用有效的更改以产生最新的信息,可以完成恢复。

您可以对dogdata文件(例如 6-1 )进行一些小的修改,以创建一个变更日志并提供备份和恢复功能。首先,您将创建一个 main 方法(processRecords),它将解释传递给该类的任何数据。该函数允许恢复程序将所有更改日志信息传递到一个方法中,从而简化了恢复过程。这也将使依赖注入更容易完成。

function processRecords($change_Type, $records_array)

{

switch($change_Type)

{

case "Delete":

$this->deleteRecord($records_array);

break;

case "Insert":

$this->insertRecords($records_array);

break;

case "Update":

$this->updateRecords($records_array);

break;

case "Display":

$this->readRecords($records_array);

Break;

default:

throw new Exception("Invalid XML file change type: $change_Type");

}

}

所有变更请求现在都将通过此方法传递。该方法接受变更类型(InsertDeleteUpdateDisplay)和数组(对于InsertUpdate)或记录号(对于DeleteUpdate)。传入$record_array. $record_array的值被动态创建为数组或字符串。这允许processRecords方法提供多态性(同一方法调用接受不同参数的能力),这是面向对象语言的需求之一(以及封装和继承)。switch语句查看$change_Type以确定调用哪个方法。然后它调用相关的方法。如果传递了无效类型,则会引发异常。

And security-in a "real-time" environment, it is safer to pass "code" to this type of method than to use a value indicating the action that will happen. For example, 101 can be used to indicate an update. You can easily adjust the switch statement to check the code to determine which method to call.

您之前检查过的每个方法(除了构造函数和析构函数)现在都被设置为'private'。这使得该过程更加安全;只有使用processRecords方法才能进行更改。在这三种方法的末尾还添加了三行代码,以提供备份和恢复功能。

...

$change_string = date('mdYhis') . " | Delete | " . $recordNumber . "\n";

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($change_string,3,$chge_log_file); // might exceed 120 chars

...

第一行格式化更改日志文件的字符串。使用的格式类似于你在第五章中看到的格式。在前面的例子中,记录号是按照delete方法的要求传递的。

$change_string = date('mdYhis') . " | Update | " . serialize($records_array) . "\n";

对于updateinsert,数组通过。但是,数组不能放在字符串中。serialize方法将一个数组转换成如下所示的字符串格式。

a:1:{i:0;a:4:{s:8:"dog_name";s:7:"Spot";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}

可以使用unserialize方法将序列化字符串中的数据返回到数组格式(或另一种格式)。第二行创建一个字符串($chge_log_file,它使用date方法和位于dog_applications XML 文件中的日志文件名来创建一个备份文件名(和位置)。然后使用error_log方法将创建的字符串传递给这个日志。日志文件的内容将类似于以下内容。

07142015042510 | Insert | a:1:{i:0;a:4:{s:8:"dog_name";s:7:"tester1";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}

07142015042510 | Update | a:1:{i:1;a:4:{s:8:"dog_name";s:7:"tester2";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}

07142015042510 | Delete | 1

此格式提供了帮助恢复过程所需的所有信息。如果 dog 数据文件的当前版本被破坏,则可以使用更改日志文件将更改应用到文件的良好版本,以开发新的当前版本。

data类唯一需要的其他改变是析构函数中的一些额外的代码行。

$new_valid_data_file = preg_replace('/[0-9]+/', '', $this->dog_data_xml);

// remove the previous date and time if it exists

$oldxmldata = date('mdYhis') . $new_valid_data_file;

if (!rename($this->dog_data_xml, $oldxmldata))

{

throw new Exception("Backup file $oldxmldata could not be created.");

}

file_put_contents($new_valid_data_file,$xmlstring);

在析构函数使用file_put_contents方法将更改应用到 XML 文件之前,应该创建一个备份,以防更改导致当前数据损坏。恢复过程将允许支持人员选择哪个数据文件包含好的数据,以及哪个(些)改变文件将被应用于数据以产生数据的正确的当前版本。

因为该过程可能使用数据的备份文件,其中包括带有日期和时间的文件名,所以使用preg_replace方法从数据文件名中删除任何数字信息。第一个参数中的正则表达式(/[0-9]+/)指示该方法在$this->dog_data_xml中搜索所有出现的数字。如果找到任何匹配项,它将被替换为第二个参数中的值('')。在这种情况下,什么都没有。然后新文件名被放入$new_valid_data_file。这不会导致“正常”非备份文件名的任何改变,因为它不包含任何数字信息。使用带有日期和时间信息的$new_valid_data_file中的文件名创建一个新的备份文件名。新的备份文件名保存在$oldxmldata中。

现在可以使用rename方法将最后的有效数据移动到新的备份文件中。$this->dog_data_xml中的数据(没有变化的好数据的位置)被复制到新的备份文件位置($oldxmldata)。如果文件无法重命名,将引发异常。

最后,有效的已更改数据(位于$xmlstring中)可以被放置到包含在$new_valid_data_file属性中的有效数据(没有任何日期信息的相同文件名)的新位置。

例如,如果07142015042510dog_data.xml包含最后可用的有效数据,则在应用任何更改之前,07152015001510dog_data.xml可能是该数据的新位置。dog_data.xml是应用更改后有效数据的位置。对dog数据类的最后一个编码更改是包含了一个set方法。

function setChangeLogFile($value)

{

$this->dog_data_xml = $value;

}

要允许恢复应用使用最后的有效数据,该应用必须能够更改有效数据的位置。setChangeLogFile方法改变$this->dog_data_xml中的值。该属性最初是根据 dog 应用 XML 文件中的定位信息设置的。但是,这可能不是当前有效数据的位置。添加到析构函数中的代码将使用有效数据的新位置,应用所需的更改,并将有效数据放回原始数据文件中。没有必要对data_application XML 文件做任何修改。析构函数完成后,数据文件现在将包含最新的有效数据。

Example 6-3. The dogdata.php file with logging as well as backup and recovery processes

<?php

class dog_data {

private $dogs_array = array(); //defined as an empty array initially

private $dog_data_xml = "";

private $change_log_file = "change.log";

function __construct() {

libxml:use_internal_errors(true);

$xmlDoc = new DOMDocument();

if ( file_exists("e5dog_applications.xml") )        {

$xmlDoc->load( 'e5dog_applications.xml' );

$searchNode = $xmlDoc->getElementsByTagName( "type" );

foreach( $searchNode as $searchNode )         {

$valueID = $searchNode->getAttribute('ID');

if($valueID == "datastorage") {

$xmlLocation = $searchNode->getElementsByTagName( "location" );

$this->dog_data_xml = $xmlLocation->item(0)->nodeValue;

break;

}

}

}

else { throw new Exception("Dog applications xml file missing or corrupt"); }

$xmlfile = file_get_contents($this->dog_data_xml);

$xmlstring = simplexml:load_string($xmlfile);

if ($xmlstring === false) {

$errorString = "Failed loading XML: ";

foreach(libxml:get_errors() as $error) {

$errorString .= $error->message . " " ;  }

throw new Exception($errorString); }

$json = json_encode($xmlstring);

$this->dogs_array = json_decode($json,TRUE);

}

function __destruct() {

$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

$xmlstring .= "\n<dogs>\n";

foreach ($this->dogs_array as $dogs=>$dogs_value) {

foreach ($dogs_value as $dog => $dog_value) {

$xmlstring .="<$dogs>\n";

foreach ($dog_value as $column => $column_value)

{

$xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";

}

$xmlstring .= "</$dogs>\n";

}   }

$xmlstring .= "</dogs>\n";

$new_valid_data_file = preg_replace('/[0-9]+/', '', $this->dog_data_xml); // remove the previous date and time if it exists

$oldxmldata = date('mdYhis') . $new_valid_data_file;

if (!rename($this->dog_data_xml, $oldxmldata)) {   throw new Exception("Backup file $oldxmldata could not be created."); }

file_put_contents($new_valid_data_file,$xmlstring);

}

private function deleteRecord($recordNumber)

{

foreach ($this->dogs_array as $dogs=>&$dogs_value) {

for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {

foreach ($dogs_value[$J] as $column => $column_value)

{

$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

}

}

unset ($dogs_value[count($dogs_value) -1]);

}

$change_string = date('mdYhis') . " | Delete | " . $recordNumber . "\n";

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($change_string,3,$chge_log_file); // might exceed 120 chars

}

private function readRecords($recordNumber)

{

if($recordNumber === "ALL") {

return $this->dogs_array["dog"];

} else {

return $this->dogs_array["dog"][$recordNumber];

}

}

private function insertRecords($records_array)

{

$dogs_array_size = count($this->dogs_array["dog"]);

for($I=0;$I< count($records_array);$I++) {

$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

}

$change_string = date('mdYhis') . " | Insert | " . serialize($records_array) . "\n";

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($change_string,3,$chge_log_file); // might exceed 120 chars

}

private function updateRecords($records_array)

{

foreach ($records_array as $records=>$records_value)

{

foreach ($records_value as $record => $record_value)

{

$this->dogs_array["dog"][$records] = $records_array[$records];

}

}

$change_string = date('mdYhis') . " | Update | " . serialize($records_array) . "\n";

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($change_string,3,$chge_log_file); // might exceed 120 chars

}

function setChangeLogFile($value)

{

$this->dog_data_xml = $value;

}

function processRecords($change_Type, $records_array)

{

switch($change_Type)

{

case "Delete":

$this->deleteRecord($records_array);

break;

case "Insert":

$this->insertRecords($records_array);

break;

case "Update":

$this->updateRecords($records_array);

break;

default:

throw new Exception("Invalid XML file change type: $change_Type");

} } }

Note

教科书网站上提供了一个替代解决方案,它允许数组与丢失的索引相关联,并且在dog_data.xml文件中有一个或没有记录。

既然您已经有能力提供备份和恢复,那么让我们对readerrorlog文件做一些调整(在示例 5-8 )。新的应用将需要允许支持人员选择(和修改)任何有效的变更日志文件,选择最有效的可用数据文件,并应用来自变更日志文件的变更以产生新的有效数据 XML 文件。

if(isset($_POST['data_File']))

{

update_XML_File_Process();

}

else if(isset($_GET['rn']))

{

delete_Process();

}

else if(isset($_POST['change_file']))

{

display_Process();

}

else

{

select_File_Process();

}

由于代码的长度增加了,如果大部分工作是在方法中完成的,那么遵循逻辑流程(并在必要时修改代码)会容易得多。与许多应用一样,程序的主要流程变成了一个嵌入式的if else语句。

A978-1-4842-1730-6_6_Fig1_HTML.jpg

图 6-1。

The readchangelog.php file requesting selection of a change log file

打开应用时,列表框将允许用户选择使用哪个有效的更改日志文件(以及可能的更新)。语句的else部分将把程序引导到select_File_Process,后者将处理这个请求。

A978-1-4842-1730-6_6_Fig2_HTML.jpg

图 6-2。

The readchangelog file displaying the selected log and requesting the valid data file

一旦用户选择了一个更改文件,文件的内容将以与第五章相同的方式显示。display_Process方法将提供这些信息。用户可以决定删除所选更改文件中的一些条目。如果需要删除的话,delete_Process方法将使用第五章中显示的相同技术完成这个过程。此外,相同的方法将允许用户选择最近的有效数据文件来应用更改。

一旦选择了数据文件,update_XML_File_Process将使用dogdata程序将更改应用到文件中(例如 6-3 )。该流程将向用户显示“更改已完成”消息。

select_File_Process方法使用类似于getbreeds.php程序中的逻辑(例如 4-5 )。

function select_File_Process()

{

$directory = "";

$files = glob($directory . "*change.log");

echo "<form id='file_select' name='file_select' method='post' action='readchangelog.php'>";

echo "<h3>Select a file to display</h3>";

echo "<select name='change_file' id='change_file'>";

foreach($files as $file)

{

echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

PHP glob方法将给定目录($directory)中的所有文件名放入一个数组($files)中。将$directory设置为""表示将搜索当前目录。第二个参数提供了过滤检索到的文件类型的能力。*change.log指示该方法提取所有以change.log结尾的文件。*(星号)是接受任何字符的通配符。这种组合将提取由dog_data类产生的所有变更日志文件。剩余的行创建一个 HTML 下拉列表,显示检索到的文件名。一个submit将导致程序使用驻留在change_file属性中的选定文件再次调用自身。

function display_Process()

{

$change_Array = load_Array();

$row_Count = count($change_Array) -1;

displayRecords($row_Count, $change_Array, $_POST['change_file']);

}

display_Process is called when a change file has been selected. This method calls the``load_Array method

function load_Array()

{

$change_File = $_POST['change_file'];

$logFile = fopen($change_File, "r");

$row_Count = 0;

while(!feof($logFile))

{

$change_Array[$row_Count] = explode(' | ', fgets($logFile));

$row_Count++;

}

$row_Count--;

fclose($logFile);

return $change_Array;

}

load_Array方法非常类似于dog_data类中的构造函数。该方法检索change_file中的值,并将其放在$change_File中。然后打开该文件,文件中的所有条目都被放入$change_Arrayexplode方法将产生三列(日期/时间、变更类型、用于变更的数组或字符串)。它将这个数组返回给调用程序(display_Process)。

数组返回到display_Process中的$change_Arraycount方法决定了这个数组的大小。它的值放在$row_co unt 中。displayRecords被调用,通过row_countchange_Arraychange_file进入displayRecords

function displayRecords($row_Count, $change_Array, $change_File)

{

echo "<html><head>";

echo "<style> table { border: 2px solid #5c744d;}  </style>";

echo "</head><body>";

echo "<table>";

echo "<caption>Log File: " . $change_File . "</caption>";

echo "<tr><th></th><th>Date/Time</th><th>Change Type</th><th>Change Data</th></tr><tr>";

for ($J=$row_Count -1; $J >= 0; $J--)

{

echo "<td><a href='readchangelog.php?rn=$J&change_File=$change_File'>Delete</a></td>";

for($I=0; $I < 3; $I++)

{

echo "<td> " . $change_Array[$J][$I] . " </td> ";

}

echo "</tr>";

}

echo "</table>";

echo "</body></html>";

$directory = "";

$files = glob($directory . "*dog_data.xml");

echo "<form id='data_select' name='data_select' method='post' action='readchangelog.php'>";

echo "<h3>Delete entries above or select a file to update with change log $change_File</h3>";

echo "<select name='data_File' id='data_File'>";

foreach($files as $file)

{

echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='hidden' id='change_file' name='change_file' value='$change_File'>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

displayRecords使用与readerrorlog程序的displayRecords方法几乎完全相同的逻辑显示变更日志文件的内容(例如 5-8 )。它还使用与selectFileProcess(前面解释过)几乎相同的逻辑来显示数据文件,以便用户选择最后一个未损坏的文件。

如果用户决定从变更日志中删除一些记录,就会调用delete_Process

function delete_Process()

{

$change_Array = load_Array();

deleteRecord($_GET['rn'], $row_Count, $change_Array);

saveChanges($row_Count,$change_Array,$change_File);

displayRecords($row_Count,$change_Array,$change_File);

}

如前所示,delete_Process方法将使用相同的change_Array将变更文件记录放入$change_Array中。它将把要删除的记录号($_GET['rn'])、数组中的行数($row_Count)和数组($change_Array)传递给deleteRecord方法。deleteRecords方法将使用与readerrorlog(例如 5-8 )程序中的deleteRecord方法相同的逻辑。然后delete_Process将调用saveChanges方法,传入row_countchange_Arraychange_File信息。

function saveChanges($row_Count,$change_Array,$change_File)

{

$changeFile = fopen($change_File, "w");

for($I=0; $I < $row_Count; $I++)

{

$writeString = $change_Array[$I][0] . " | " . $change_Array[$I][1] . " | " . $change_Array[$I][2];

fwrite($changeFile, $writeString);

}

fclose($changeFile);

}

saveChanges方法从change_Arr ay 构建了date/time-changetype-changedata格式,如前所述。该信息保存在$writeString中,用于替换更新版本的变更日志文件(减去被删除的记录)。

然后,delete_Process方法调用displayRecords方法(前面描述过)来显示更新的变更日志(减去删除的记录)和数据文件下拉列表。

一旦用户选择了要更改的数据文件,就会调用update_XML_File_Process方法。

function update_XML_File_Process()

{

$change_Array = load_Array();

require_once("dog_data.php");

$data_Changer = new dog_data();

$row_Count = count($change_Array) -1;

for($I=0;$I < $row_Count; $I++)

{

if($change_Array[$I][1] != "Delete")

{

$temp = unserialize($change_Array[$I][2]);

}

else

{

$temp = (integer)$change_Array[$I][2];

}

$data_Changer->processRecords($change_Array[$I][1], $temp);

}

该方法调用load_Array方法将更改返回到$change_Array中。dog_data文件被导入到方法中,为用户选择的数据文件的更改做准备。创建了一个data_data类的实例($data_Changer

使用一个for循环来仔细检查变化数组,并将每个变化传递给数据类的processRecords方法。但是,在传递记录之前,必须使用unserialize方法将序列化数据返回到关联的数组格式。如果更改请求是Delete,则必须进行类型转换,将数据(记录号)更改为整数。这是 PHP 要求类型转换的少数几次之一。序列化数据不被视为数据类型。必须通过取消序列化或类型转换来转换数据。变更类型(UpdateDeleteInsert)被传递到processRecords方法的第一个参数中。更改数组或记录号被传递到该方法的第二个参数中。对数据进行所有更改,备份文件,并创建新的更改日志,以防出现更多损坏问题。

Example 6-4. The displaychangelog.php file

<?php

function displayRecords($row_Count, $change_Array, $change_File) {

echo "<html><head>";

echo "<style> table { border: 2px solid #5c744d;}  </style>";

echo "</head><body><table><caption>Log File: " . $change_File . "</caption>";

echo "<tr><th></th><th>Date/Time</th><th>Change Type</th><th>Change Data</th></tr><tr>";

for ($J=$row_Count -1; $J >= 0; $J--) {

echo "<td><a href='readchangelog.php?rn=$J&change_File=$change_File'>Delete</a></td>";

for($I=0; $I < 3; $I++) {

echo "<td> " . $change_Array[$J][$I] . " </td> ";

}

echo "</tr>";

}

echo "</table>";

echo "</body></html>";

echo "</table>";

echo "</body></html>";

$directory = "";

$files = glob($directory . "*dog_data.xml");

echo "<form id='data_select' name='data_select' method='post' action='readchangelog.php'>";

echo "<h3>Delete entries above or select a file to update with change log $change_File</h3>";

echo "<select name='data_File' id='data_File'>";

foreach($files as $file) {

echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='hidden' id='change_file' name='change_file' value='$change_File'>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

function deleteRecord($recordNumber, &$row_Count, &$change_Array) {

for ($J=$recordNumber; $J < $row_Count - 1; $J++) {

for($I=0; $I < 3; $I++) {

$change_Array[$J][$I] = $change_Array[$J + 1][$I];

}

}

unset($change_Array[$row_Count]);

$row_Count--;

}

function saveChanges($row_Count,$change_Array,$change_File)

{

$changeFile = fopen($change_File, "w");

for($I=0; $I < $row_Count; $I++)

{

$writeString = $change_Array[$I][0] . " | " . $change_Array[$I][1] . " | " . $change_Array[$I][2];

fwrite($changeFile, $writeString);

}

fclose($changeFile);

}

function delete_Process() {

$change_Array = load_Array();

deleteRecord($_GET['rn'], $row_Count, $change_Array);

saveChanges($row_Count,$change_Array,$change_File);

displayRecords($row_Count,$change_Array,$change_File);

}

function load_Array() {

$change_File = $_POST['change_file'];

$logFile = fopen($change_File, "r");

$row_Count = 0;

while(!feof($logFile)) {

$change_Array[$row_Count] = explode(' | ', fgets($logFile));

$row_Count++;  }

$row_Count--;

fclose($logFile);

return $change_Array;

}

function display_Process() {

$change_Array = load_Array();

$row_Count = count($change_Array) -1;

displayRecords($row_Count, $change_Array, $_POST['change_file']);

}

function select_File_Process() {

$directory = "";

$files = glob($directory . "*change.log");

echo "<form id='file_select' name='file_select' method='post' action='readchangelog.php'>";

echo "<h3>Select a file to display</h3>";

echo "<select name='change_file' id='change_file'>";

foreach($files as $file) {

echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

function update_XML_File_Process() {

$change_Array = load_Array();

require_once("dog_datad.php");

$data_Changer = new dog_data();

$row_Count = count($change_Array) -1;

for($I=0;$I < $row_Count; $I++) {

if($change_Array[$I][1] != "Delete") {

$temp = unserialize($change_Array[$I][2]);

} else {

$temp = (integer)$change_Array[$I][2];

}

$data_Changer->processRecords($change_Array[$I][1], $temp);

}

$data_Changer->setChangeLogFile($_POST['data_File']);

$data_Changer = NULL;

echo "Changes completed";

}

// main section

if(isset($_POST['data_File'])) {

update_XML_File_Process();

} else if(isset($_GET['rn'])) {

delete_Process();

} else if(isset($_POST['change_file'])) {

display_Process();

} else {

select_File_Process();

}

?>

JSON 备份和恢复

为 JSON 数据而不是 XML 数据提供备份和恢复需要做哪些更改?实际上,一点都没变。只要实现了本章第一节中的更改,displaychangelog程序和对dog_data类的更改将以与 XML 数据相同的方式处理 JSON。

MySQL 备份和恢复

正如您所猜测的,只要实现了本章第二部分的更改,MySQL 数据的备份和恢复就不需要额外的更改。但是,您可以花点时间看看处理 MySQL 数据的另一种方法。

创建针对数据库执行的 SQL 脚本文件是一种常见的做法。脚本文件包含更新数据库所需的所有 SQL 代码。使用这种类型的文件将允许您执行正确的INSERTUPDATEDELETE SQL 命令,而不仅仅是前面显示的INSERT。前面的例子需要为关联数组中的每条记录创建一个INSERT命令。这包括未更改的记录。这对于大中型数据库来说是低效的。您只需要更新已更改的记录。

您可以从关联数组中已更改的记录开发脚本文件。您可以将更改日志用作脚本文件,因为 SQL 脚本列出了所有已请求的更改。可以重新运行它来修复任何损坏的数据。

例如,在updateRecords方法中,您可以创建任何需要的 SQL UPDATE命令。

private function updateRecords($records_array)

{

$chge_log_file = date('mdYhis') . $this->change_log_file;

$chge_string = "";

foreach ($records_array as $records=>$records_value)

{

$this->dogs_array["dog"][$records] = $records_array[$records];

$chge_string .=  "UPDATE Dogs ";

$chge_string .= "SET dog_name='" . $records_array[$records]['dog_name'] ."', ";

$chge_string .= "dog_weight='" . $records_array[$records]['dog_weight'] ."',

$chge_string .= "dog_color='" . $records_array[$records]['dog_color'] ."', ";

$chge_string .= "dog_breed='" . $records_array[$records]['dog_breed'] ."' ";

$chge_string .= "WHERE dog_id='" . $records_array[$records]['dog_id'] . "';\n";

}

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($chge_string,3,$chge_log_file); // might exceed 120 chars

}

这些更改将从关联阵列构建所有更新要求。对插入和删除方法也可以进行类似的更改。

private function deleteRecord($recordNumber)

{

foreach ($this->dogs_array as $dogs=>&$dogs_value) {

for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {

foreach ($dogs_value[$J] as $column => $column_value)

{

$dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

}

}

unset ($dogs_value[count($dogs_value) -1]);

}

$dog_id = $this->dogs_array['dog'][$recordNumber]['dog_id'];

$chge_string = "DELETE FROM Dogs WHERE dog_id='" . $dog_id . "';\n";

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($chge_string,3,$chge_log_file); // might exceed 120 chars

}

这个示例delete方法一次删除一条记录。因此,delete字符串是在循环之外构建的。update方法允许更新多条记录,因此update字符串构建在循环内部。insert方法也需要你在循环中构建字符串。

private function insertRecords($records_array)

{

$chge_string = "";

$dogs_array_size = count($this->dogs_array["dog"]);

for($I=0;$I< count($records_array);$I++)

{

$this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

$dog_id = rand(0,9999); // get a number between 0 and 9999

while (in_array($dog_id, $this->dogs_array, true)) // in array?

{ $dog_id = rand(0,9999); // if it is get another number

}

$chge_string .="INSERT INTO Dogs VALUES('";

$chge_string .= $dog_id . "', '" . $records_array[$I]['dog_name'] . "', '";

$chge_string .= $records_array[$I]['dog_weight'] . "', '";

$chge_string .= $records_array[$I]['dog_color'] . "', '";

$chge_string .= $records_array[$I]['dog_breed'] . "');";

}

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($chge_string,3,$chge_log_file); // might exceed 120 chars

}

如果您回顾一下changeRecords方法,就会发现一个 SQL WHERE子句是使用名为dog_id的属性构建的。在 XML 和 JSON 示例中,您没有这个字段。然而,SQL UPDATE需要一个where子句来决定更新哪些记录。使用的属性必须是唯一的,以标识准确的记录。代码必须生成这个dog_id的唯一地方是在数据库中创建新记录的时候(在insertRecords方法中)。这可以使用 PHP rand方法来完成。

PHP rand方法产生随机数。第一个参数是起始数字(0),第二个参数是最后一个数字(9999)。该字段的大小在数据库中设置为char(4),最多允许四个字符。这将允许你多达 10,000 只狗。我相信这已经足够了!

insertRecords方法中的while循环使用 PHP in_array方法来确定该数字是否已经在dogs_array中(包含数据库中所有的当前记录)。第三个参数决定是否应该进行strict搜索(比较数据类型),必须设置这个参数来产生多维关联数组的可靠结果。如果该数字确实存在,则逻辑继续生成新的随机数,直到找到唯一的一个为止。然后将该值放入$dog_id,它将与其他字段(dog_namedog_weightdog_colordog_breed)一起插入数据库。注意:这段代码假设数据库中的Dogs表是用显示的顺序(dog_iddog_namedog_weightdog_colordog_breed)的字段创建的。

更改日志(现在也是一个 SQL 脚本文件)现在将包含类似如下的语句:

INSERT INTO Dogs VALUES('2288', 'tester1', '19', 'Green', 'Lab');

UPDATE Dogs SET dog_name='tester1', dog_weight='19', dog_color='Green', dog_breed='Lab' WHERE dog_id='0111';

UPDATE Dogs SET dog_name='tester2', dog_weight='19', dog_color='Green', dog_breed='Lab' WHERE dog_id='1211';

DELETE FROM Dogs WHERE dog_id='1111';

当记录了所有更改后,可以对数据库运行该文件。析构函数现在可以执行这个文件(而不是删除表并将所有记录重新插入到一个新表中)。

$mysqli = new mysqli($server, $db_username, $db_password, $database);

if ($mysqli->connect_errno)

{

throw new Exception("MySQL connection error:" . $mysqli->connect_error);

}

$chge_log_file = date('mdYhis') . $this->change_log_file;

$sql = explode(";",file_get_contents($chge_log_file));

foreach($sql as $query)  {

If(!$mysqli->query($query))

{

throw new Exception("Dog Table Change Error: " . $mysqli->error);

}

}

析构函数的代码变得比最初的 MySQL 示例更简单。析构函数不需要格式化任何 SQL 语句。它只需要执行它们。该方法从变更日志中读取变更记录,通过每个 SQL 命令行末尾的;分割每个记录。每一行都放在数组$sql中。然后,逻辑循环遍历数组,并通过query命令执行每条语句。如果任何 SQL 语句有问题,就会抛出一个异常(它还会通过dog_interface程序向支持人员发送一封电子邮件)。该书的网站上有一个示例程序。

注意:如上所述,展示 MySQL 示例是为了帮助读者了解dog_data类的整体逻辑适用于所有数据类型。完整的书籍都是关于使用 PHP 与数据库交互的。这本书的目的不是训练用户完全掌握数据库操作的知识。

做它

The dog_data class creates a new log file every time it is run. This could cause a lot of log files to be created in a very short period of time. Your mission is to either update the readchangelog file (download it from the book’s web site) or to create your own maintenance program. The code will ask the users for the number of log files (and data files) to keep. The program will then keep the most recent number of files requested. The glob method, as shown previously, can be used to retrieve all the file names. The unlink method can be used to delete a file. unlink($file);   The MySQL examples shown now produce different contents in the change log file. Download the readchangelog program from the book’s web site and make any adjustments needed to the code to properly view and delete the change log. Assuming that the database administrator has reversed the contents of the database to the last valid set of data, adjust the program to execute the change log selected against the database. Hint: Your completed program will have less code than the example from the book’s web site.

连接数据层

既然已经创建了一个可靠的、经过良好测试的数据类,是时候将它连接到业务规则层了。Dog类将使用dog_data类在 XML 文件中存储狗的信息。

if (method_exists('dog_container', 'create_object')) {

$this->breedxml = $properties_array[4];

$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';

$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';

$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';

$breed_error = $this->set_dog_breed($properties_array[1]) == TRUE ? 'TRUE,' : 'FALSE,';

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

$this->save_dog_data();

if(stristr($this->error_message, 'FALSE'))

{

throw new setException($this->error_message);

}

} else

{

exit;

}

dog类的构造函数设置所有属性,如果有问题就抛出异常。如果没有问题,信息被保存(通过save_dog_data,程序关闭(exit)。

为了保持数据层独立于业务规则层,将使用依赖注入来发现dog_data类的位置和名称,并从该类调用processRecords方法。你将借用第四章中的逻辑。实际上,您可以使用示例 4-10 中的dog_container而无需任何更改。如果你不记得这堂课的细节,重温第四章。

dog_container类包含了get_dog_application方法,该方法使用多次讨论过的逻辑在 dog 应用 XML 文件中搜索所需的文件名(dog_data.php)。set_app方法允许您将应用类型(dogdata)传递到get_dog_application中进行搜索。它还包括create_object类,该类将确定类名(dog_data),创建该类的一个实例,并将该类(该类在内存中的地址)传递回调用程序。该类要求调用程序中存在一个clean_input函数。您目前在Dog类中还没有。但是,您可以在类中创建一个 shell(一个空函数)来满足这一要求。

要使用容器,您可以使用dog_interface程序中的逻辑来创建容器的实例,找到dog_data的位置,并创建dog_data的实例(不知道类名)。

function clean_input() { }

private function save_dog_data()

{

if ( file_exists("e5dog_container.php")) {

require_once("e5dog_container.php"); // use 第五章 container w exception handling

} else {

throw new Exception("Dog container file missing or corrupt");

}

$container = new dog_container("dogdata"); // sets the tag name to look for in XML file

$properties_array = array("dogdata"); // not used but must be passed into create_object

$dog_data = $container->create_object($properties_array); // creates dog_data object

$method_array = get_class_methods($dog_data);

$last_position = count($method_array) - 1;

$method_name = $method_array[$last_position];

$record_Array = array(array('dog_name'=>"$this->dog_name", 'dog_weight'=>"$this->dog_weight", 'dog_color'=>"$this->dog_color", 'dog_breed'=>"$this->dog_breed"));

$dog_data->$method_name("Insert",$record_Array);

$dog_data = NULL;

}

完成此操作的代码行与之前在dog_interface中看到的相同;您创建了dog_container,找到了dogdata文件的位置,并创建了dog_data类的一个实例。唯一的区别是“dogdata”是为搜索传递的。PHP 函数get_class_methods用于在dog_data类中创建一个方法列表。类中最后一个方法是processRecords。这个方法的名字被拉出来放入$method_name。然后构建record_Array以传递给processRecords。方法被调用,传递"Insert"record_Array。最后,dog_data对象被设置为NULL,这导致析构函数保存数据。

这允许完全的依赖注入。在被这段代码确定之前,dog对象不知道dog_data类的名称、dog_data类的位置或者要调用的方法的名称。这在数据层和业务规则层之间建立了一个完整的分界,这是三层设计所需要的。

Example 6-5. The dog.php file using dog_data.php to save data

<?php

class Dog

{

// ---------------------------------- Properties ------------------------------------------

private $dog_weight = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

private $error_message = "??";

private $breedxml = "";

// ---------------------------------- Constructor -----------------------------------------

function __construct($properties_array)

{

if (method_exists('dog_container', 'create_object')) {

$this->breedxml = $properties_array[4];

$name_error  = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';

$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';

$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';

$breed_error = $this->set_dog_breed($properties_array[1])  == TRUE ? 'TRUE,' : 'FALSE,';

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

$this->save_dog_data();

if(stristr($this->error_message, 'FALSE'))

{

throw new setException($this->error_message);

} }

else

{ exit; }

}

function clean_input() { }

private function save_dog_data()

{

if ( file_exists("e5dog_container.php")) {

require_once("e5dog_container.php"); // use 第五章 container w exception handling

} else {

throw new Exception("Dog container file missing or corrupt");

}

$container = new dog_container("dogdata"); // sets the tag name to look for in XML file

$properties_array = array("dogdata"); // not used but must be passed into create_object

$dog_data = $container->create_object($properties_array); // creates dog_data object

$method_array = get_class_methods($dog_data);

$last_position = count($method_array) - 1;

$method_name = $method_array[$last_position];

$record_Array = array(array('dog_name'=>"$this->dog_name", 'dog_weight'=>"$this->dog_weight", 'dog_color'=>"$this->dog_color", 'dog_breed'=>"$this->dog_breed"));

$dog_data->$method_name("Insert",$record_Array);

$dog_data = NULL;

}

function set_dog_name($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $this->error_message = FALSE;

return $this->error_message;

}

function set_dog_weight($value)

{

$error_message = TRUE;

(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $this->error_message = FALSE;

return $this->error_message; }

function set_dog_breed($value)

{

$error_message = TRUE;

($this->validator_breed($value) === TRUE) ? $this->dog_breed = $value : $this->error_message = FALSE;

return $this->error_message; }

function set_dog_color($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $this->error_message = FALSE;

return $this->error_message;

}

// ----------------------------- Get Methods -----------------------------------------------

function get_dog_name()

{

return $this->dog_name;

}

function get_dog_weight()

{

return $this->dog_weight;

}

function get_dog_breed()

{

return $this->dog_breed;

}

function get_dog_color()

{

return $this->dog_color;

}

function get_properties()

{

return "$this->dog_name,$this->dog_weight,$this->dog_breed,$this->dog_color.";

}

// ------------------------------General Method---------------------------------------------

private function validator_breed($value)

{

$breed_file = simplexml:load_file($this->breedxml);

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

}

?>

Dog应用现在有三个完整的层。

A978-1-4842-1730-6_6_Fig3_HTML.jpg

图 6-3。

Three-tier dog application

接口层包含lab.html文件和dog_interface.php程序。业务规则层包括dog.php类和get_breeds类。dog_data类位于数据层。来自dog_interface程序的任何与业务规则层通信的请求都由dog_container类处理。任何与数据层通信的请求(来自业务规则层)也由dog_container类处理。只能从业务规则层或通过业务规则层访问数据层。对接口层的访问只能通过业务规则层进行。

接口层不知道业务规则层中任何类或方法的位置/名称。这些信息是通过使用dog_container发现的。业务规则层不知道数据层中任何类或方法的位置/名称。该信息也是通过使用dog_container发现的。这允许每一层完全独立,允许在一层中发生变化,而不需要在其他两层中进行变化。

做它

Download all the files for the dog application from this chapter. Adjust file names in the dog_application XML file to discover how missing files are handled in the application. Does the application handle these problems properly? Adjust the dog_data XML file to include badly formed XML data. Run the application. Does it handle these problems properly? Empty the dog_data XML file (except for the dogs and dog tags). Run the application. Does it handle this situation properly? For any instance that causes the application to error instead of raising an exception, attempt to adjust the code to anticipate the problem and raise an exception.

章节术语

| 数据层 | 文件获取内容 |
| SimpleXML 数据模型 | simplexml:load_string |
| 铅字铸造 | JSON 数据 |
| JSON 方法 | 关联数组 |
| json_encode | json_decode |
| 关键词 | 新行字符 |
| 文件内容 | libxml:user _ internal _ errors(true) |
| libxml:get_errors | 数数 |
| $这个 | 复原 |
| 动态数组 | 释放对象 |
| mysqli_connect | mysqli_connect_errno |
| mysqli_fetch_assoc | mysqli _ free _ result |
| mysqli_close | 标准选取 |
| SQL 插入 | 多态性 |
| 更改日志文件 | 连载 |
| 不系列化 | 备份和恢复 |
| 怀孕 _ 替换 | 正则表达式 |
| 一团 | 嵌入 if then else |
| SQL 更新 | SQL 脚本文件/日志 |
| SQL WHERE | 边缘 |
| 严格搜索 | 使分开 |
| 壳 | 依赖注入 |

第二章问题和项目

多重选择

Which of the following describes type casting? It’s rarely needed in PHP   It exists in most languages   It’s needed for serialized data   All of the above     Which of the following describes an associative array? It has a key and value relationship (key->value)   It uses numerical subscripts   It does not have an index   A and B     Which of the following is the newline character? &   .   ;   None of these     Which of the following describes JSON data? It has a similar format to arrays   It cannot only be used with JavaScript   It requires much more coding than when using XML data   It is more secure than XML data     Which of the following describes unlink? It can be used to release a parameter of an array   It can be used to delete a file or directory   It can be used to split apart an array   None of these     Which of the following describes polymorphism? The container that holds and secures an application   The ability to pass multiple data types into the same signature of a method   The ability to use the methods and properties of a parent class   None of these     Which of the following describes rand? It can be used to produce a random number   The first parameter is the starting number   The second parameter is the last number   All of the above     When using in_array, strict search does which of the following? It compares data types   It is the third parameter (set to true)   It should be used when searching associative arrays   All of these     Which of the following describes a shell method? It contains no data   It has no signature   It includes JSON data   All of these     The data tier does which of the following? Updates data   Returns data   Filters data   All of the above

真/假

Log files are important for successful recovery of information.   Data is serialized to convert the data type to string.   After updates have been completed, all backup copies of data can be destroyed.   Dependency injection is necessary to keep the tiers (interface, business rules, and data) independent of each other.   SQL script files update all records including those that have not changed.

简答/短文

Explain the process used to correct data files that have been corrupted.   Why is data stored in a database usually more secure than data stored in a text file?   Compare and contrast the methods used to update XML data to the methods used to update MySQL data. Which is more efficient? Why?   When should an e-mail be sent to the system administrator when data is being updated? What should this e-mail contain? What should it not contain? Why?   How can a system administrator determine which data file is the last non-corrupted version?

项目

Adjust the code from Chapter 4 project #1 or #2 to include backup and recovery ability.   Create a complete storage (XML or JSON format) and a backup and recovery system for one of the previous projects you have completed. The system should include the ability for the users to limit the number of recovery files, the ability to adjust contents of a selected file (update, insert, and delete), and the ability to execute the file against the most recent valid data. When the process is complete, any corrupted files should automatically be removed. The system should also keep its own log file to indicate when changes have occurred.

学期项目

Update the ABC Computer Parts Inventory program to include storage of the data (XML or JSON format) and complete backup and recovery capabilities. The application should include a change log to indicate any data changes. Additional support programs should be included to allow for easy recovery of any corrupted data. Your complete application should use logic similar to the examples shown in this chapter.

七、验证

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 和密码可以存储在会话属性中。然后,应用的每个部分可以通过确定useridpassword属性中是否有值来验证用户已经登录。

在查看登录身份验证过程之前,我们先来看看如何确定用户是否已经登录到系统。本章中的示例不包括安全访问级别的验证。但是,确定这些级别的过程将使用类似于这些示例中所示的代码。

Programming considerations-session_start The method call must be the first statement at the top of the code. There can be no space or code between <?php tag and session_start. session_start The 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语句中)可以确定值是否存在于usernamepassword属性中。如果值不存在,则表明用户尚未通过身份验证。使用$_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_USER and PHP_AUTH_PW can 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。如果$validTRUE,应用重定向到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. Visit http://php.net/manual/en/function.password-hash.php Learn more. Programming instructions-you can't simply compare with the hash password created by PHP's password_hash method. 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 只需要包括第六章中所示的变更。useridpassword 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 文件中的标记名相同(useridpassword,那么构建的关联数组将类似于使用示例 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_hash provides many different options and configurations for advanced developers. For more information, please visit http://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 和旧密码被正确验证,那么新的passworddatestampattempts属性在 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.

章节术语

| 证明 | 会议 |
| 会话 _ 开始 | 没错 |
| 会话属性 | $ _ 会话 |
| 验证码 | .{8,} |
| ?=.\d | ?=.[A-Z] |
| ?=.*[a-z] | 页眉 |
| 数组 _ 关键字 | 密码哈希 |
| 密码 _ 验证 | 非注册用户 |
| $ _ 服务器['远程 _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_interfacelab)进行一些更改,以调用updatedelete方法并显示结果。所需的大部分编码(您很快就会看到)将发生在lab.php文件中。所以你将从这些改变开始。

使用 JavaScript 的数据处理

当前的lab.php文件只允许用户输入将被插入数据存储(XML、JSON 或 MySQL)的狗的信息。该界面需要修改,以允许用户指出他们想完成什么活动(InsertUpdateDelete)。它还需要允许用户选择一个当前的狗,如果这个过程涉及更新或删除。这应该,希望,表明你将需要一个列表框,其中填充了当前位于收容所的狗的信息。

在本章的后面几节中,你将会看到 PHP 的必要修改。现在,让我们假设dog_interface(接口层)将返回该信息,该信息是通过dog方法(业务规则层)从dog_data(数据层)检索的。你还将假设类似的编码dog_breeds程序(第四章)将被创建来产生一个狗列表框。

此外,lab.php需要访问被选中的特定狗的所有信息。代码需要将所有信息放在dog_namedog_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",以便在适当的时候显示。

A978-1-4842-1730-6_8_Fig1_HTML.jpg

图 8-1。

The lab.php file with dogs list box

图 8-2 展示了用户选择新建时的结果。如前几章所示,提供了相同的默认值。唯一的视觉变化是输入按钮的文本。当用户没有启用 JavaScript 时,这种方法就会出现弱点。对于本例,如果没有启用 JavaScript,您将需要输入所有信息。您可以从dog_data类请求单只狗的信息(通过Dog类)。虽然这种方法不如您将要使用的 JavaScript 方法高效,但它比要求输入所有信息更方便用户。

A978-1-4842-1730-6_8_Fig2_HTML.jpg

图 8-2。

The lab.php file with NEW selected

图 8-3 显示了选择现有狗的结果。列表框显示狗的名字和品种(使其尽可能独特)。此外,还可以提供狗 ID 来识别特定的狗。当狗被选中时,狗的信息(dog_namedog_colordog_weightdog_breed)被填充到表格中。然后,用户可以更新或删除狗的信息。

A978-1-4842-1730-6_8_Fig3_HTML.jpg

图 8-3。

The lab.php file with a dog selected Note

如前几章所述,本例中提供的代码不限制重复条目。因此,在真实环境中,需要使用一个附加字段(狗 ID)来使狗具有唯一性。

以前,当发生insert时,系统会显示一条消息,表明更改成功。如果需要另一个更改,用户需要重新加载lab.php文件。您可以通过让dog_interface程序用要返回的消息设置一个会话属性来解决这个问题。然后,dog_interface可以调用lab.php文件,后者可以检查是否有消息要显示。

A978-1-4842-1730-6_8_Fig4_HTML.jpg

图 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 按钮取代(一个用于插入,一个用于删除,一个用于更新)。无论点击哪个按钮,都会创建一个属性(insertdeleteupdate)并设置一个值。属性名是按钮的 ID,属性中的内容是选中按钮的value属性的内容。这将有助于dog_interface确定用户请求哪种类型的更改。这些按钮也包含在不支持 JavaScript 的表单中。请记住,在本例中,不支持 JavaScript 的浏览器将要求用户输入成功完成insertdeleteupdate所需的所有信息。

希望你刚才看到的变化是可以理解的。您现在将看到一些处理这些数据的 JavaScript 代码。如前所述,JavaScript 对数据的操作是 web 应用中的常见任务。虽然这不是一本 JavaScript 书,但是任何 web 应用开发人员熟悉 JavaScript 都是很重要的。我想你会看到 JavaScript 语言的结构和 PHP 语言的结构很相似。

首先你会看到对get_breeds.js文件的修改(来自第四章)。该文件被重命名为getlists.js,以反映它现在将处理getBreedsdogs列表框。

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 var to 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_namedog_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_namedog_colordog_breeddog_weight)。在前面的代码行中设置的index属性包含用户选择的狗索引。它将用于从obj中提取选定的狗信息,以填充表单对象。

因为您知道要检索的数据的确切位置(在索引中),所以不需要循环。

obj.dogs[index].dog_name使用 JSON 对象名(obj)、顶层数组名(dogs)、所需 dog 数组的编号(index)和所需字段的名称(dog_name)来访问所需信息。同样,这种格式类似于从 PHP 关联数组中提取信息。dog_namedog_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的插入、更新和删除狗信息的请求,并将这些请求传递给数据层进行处理。这听起来可能很多,但是正如您将看到的,大多数编码已经存在于这些程序中。

首先让我们看一下将对insertupdate的请求传递给数据层。

$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。该属性将以与传递的其他属性相同的方式被接受和清理。

为请求传递给insertupdate的信息类型几乎完全相同(所有的狗属性加上'index')。因此,这些过程非常相似。您确实需要一种方法来指明请求的类型(updateinsert)。

if ((isset($_POST['insert'])) || (isset($_POST['update'])))

{

if (isset($_POST['insert']))

{

$insert = TRUE;

}

else

{

$insert = FALSE;

}

您可以通过创建属性来实现这一点。在本例中,如果是插入请求,则$insert将被设置为TRUE,如果是update请求,则FALSE将被设置为TRUE。如果请求是一个updateinsert,属性数组必须被填充。

$properties_array = array($dog_name,$dog_breed,$dog_color,$dog_weight,$breedxml,$insert,$dog_index);

您可以像以前在许多示例中一样创建$properties_array。一旦创建,它将被传递到dog类的一个实例中。唯一真正的变化是增加了$insert$dog_indexupdate程序将使用$dog_index来指示要更改哪个记录。当有插入时,它将被设置为-1(由lab.php),因为所有记录都被插入到数据的末尾。(你可以使用-1作为插入的指示器,而不是创建$Insert。)

$lab = $container->create_object($properties_array);

使用$container(它是已经在代码中创建的dog_container的一个实例),create_object方法创建一个 dog 类的实例,并将属性数组传递给它。稍后,您将对dog类稍作修改,使用属性数组来确定是否应该调用来自dog_datainsertupdate方法来完成请求。

$_SESSION['message'] = "Dog $dog_name Insert/Update was successful<br />";

如果update一切顺利,程序将设置$_SESSION['message']信息,然后由lab.php显示,而不是使用echoprint显示信息。

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方法使用多态性来接受数组或字符串。这使得代码非常类似于updateinsert代码。

$lab = $container->create_object($properties_array);

$_SESSION['message'] = "Dog $dog_name Deletion was successful<br />";

header("Location: lab.php");

updatedelete所示,$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中的deleteinsertupdate方法的相同代码用于调用显示方法。但是,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_namedog_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类进行一些更改,以确定该类被调用的原因(insertdeleteupdate)。然后信息可以传递给dog_data类中的正确方法。如上所述,不需要对dog_data类做任何修改,因为insertupdatedelete的方法已经存在。此外,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_namedog_breeddog_weightdog_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_dataprivate方法被称为传入“插入/更新”。

下一个if语句使用 PHP 方法is_numeric来确定$properties_array实际上是否不是一个数组,以及它是否包含一个数字。如果这是真的,那么要删除的狗的索引已经通过。如果是这种情况,那么将$property_array(包含要删除的索引)放在$this->index中,调用change_dog_dataprivate方法在“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_colordog_breed)。然后“插入”和数组被传递到dog_objectprocessRecords方法中。

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>";

然后,用户被重定向到登录或创建帐户。还要注意,代码已经被修改,为wrapperheadercontentfooter添加了相同的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.

A978-1-4842-1730-6_8_Fig5_HTML.jpg

图 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 犬类收容所预订系统的最终逻辑设计展示了具有四层(身份验证、接口、业务规则和数据)的应用。额外的层也可以用于在多个服务器上拆分大型应用。

A978-1-4842-1730-6_8_Fig6_HTML.jpg

图 8-6。

The complete ABC Canine Shelter Reservation System

除了层级之外,维护程序(readerrorlogreadchangelog)提供了加强安全性、修复错误以及提供备份和恢复的能力。

限制

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.

posted @ 2024-08-03 11:23  绝不原创的飞龙  阅读(1)  评论(0编辑  收藏  举报