文档库 最新最全的文档下载
当前位置:文档库 › (4)高级技巧、面向对象与核心技术(原书第3版)4

(4)高级技巧、面向对象与核心技术(原书第3版)4

2.4 回顾和启示 ◆

71

小提示如果在系统上安装了cURL ,就可以运行下面的命令来查看页面的标头了,如图2-16所示。10. Save the file as view_tasks.php , place in your Web directory, and test in your Web browser C .

Note that caching is, in theory, a very good thing, designed to minimize unnecessary server requests. If properly controlled, caches are great for both the server and the client.

If you have cURL installed on your system, you can run this command to see a page’s headers D :

curl --head https://www.wendangku.net/doc/559787926.html,/ ?page.php

with PHP.”

the META with some browsers as the method. covers server-side caching.

eaders returned by

?("r", ($max + $interval)));

The expiration value is the current plus the defined interval.

8. Set the Cache-Control header:header("Cache-Control: ?max-age=$interval");?>

This is just the HTTP 1.1 equivalent the Expires header. Instead of givin date value, set max-age in seconds

9. Delete the database connection th existed later on in the original scrip

This has been moved to the top of script in Step 3.

10. Save the file as view_tasks.php , p

in your Web directory, and test in y Web browser C .

Note that caching is, in theory, a ve good thing, designed to minimize unnece

server requests. If properly controlled, ca

are great for both the server and the clie

If you have cURL installed on your

system, you can run this command to see a page’s headers D :

curl --head http://www.example.co ?page.php

ter 4, “Basic Object-Oriented Programmi covers server-side caching.

C The cache-

controlled Web page.

D 图2-16

使用cURL 查看view_tasks.php 页面返回的标头curl 将会在第10章中进行讨论。

小提示

放置在HTML 代码的head 部分的META 标签也会影响页面的缓存,但是在某些浏览器上不如使用header()方法更加可靠。

小提示

本章的这一部分特别讨论了关于浏览器端缓存的控制,在第4章中我们将会讨论服务器端的缓存。

2.4 回顾和启示

如果你对以上章节有任何问题,可以访问本书的支持论坛(forums/)。

1. 回顾

1)什么是引导文件?2)为什么让所有的用户请求都通过引导页面来访问很重要(也就是说,为什么不能让单

个模块被直接访问到)?

3)为什么下面的这行代码十分不安全?include($_GET['p']);

4)要配置Apache 网站服务器,可能会用到哪两个文件?这两个文件有何不同?5)如何测试在一个特定的目录下mod_rewrite 是否可用?

6)当使用mod_rewrite 的时候,为什么使用相对路径来引用外部资源,比如图片、

72◆ 第2章 开发Web应用程序

JavaScript以及样式表文件,可能会导致问题?

7)什么是缓存?为什么缓存是有用的?什么时候缓存可能带来不必要的问题?

8)什么PHP函数可以用来影响页面的缓存?

2. 启示

1)在本章示例的站点中增加更多的模块使其完整。

2)如果你有一个真实的项目可以使用本章介绍的技术来模块化,那么就去做吧,记得增加一个数据库的配置文件。

3)练习在模块化的网站中创建错误提示,在线上环境和测试环境都试一下,以测试错误处理的流程。

4)如果你想要,可以创建一个验证–重定向的包含文件,用于检查一个模块是否被直接访问,如果是,就重定向这个用户。然后每个模块都包含一下这个新文件。

5)修改搜索页面,使其也展示提交的搜索关键字。

6)了解更多关于Apache配置的知识,并了解Apache服务器还提供了哪些其他的特性。

7)如果你正在使用mod_rewrite,修改HTML代码,确保再也没有链接或者表单引用到index.php文件。

8)学习更多关于缓存的知识。

第3章

高级数据库概念在这本书中,我想做的事情有很多。首先,是为了展示和解释我所认为的“高级”的

PHP编程的概念和方法,其次,我也想要展示对于常见问题的现有解决方案。本章就会同时涉及这两方面的内容。

在第一个例子中,我们将看一下如何使用数据库来保存会话数据。这样做有很多好处,能够提高前端的安全性。接下来讨论如何处理美国的邮政编码,包括如何计算两地的距离。第三个例子介绍存储函数,它是数据库中一个有用的概念。本章的最后,我们将回答一个常见的问题:如何水平安排查询结果,而不是通常的按照垂直的方式显示?

3.1 在数据库中保存会话

在默认情况下,PHP会把全部会话数据保存在服务器上的文本文件里。这些文件通常是保存在系统的临时目录里(比如UNIX和Mac OS X系统中就是/tmp目录),其文件名匹配会话ID(比如ei26b4i2nup742ucho9glmbh84)。然而,PHP还提供一种机制可以让我们以其他的方式来管理会话,比如在数据库中存储这些会话数据。

这样做的主要原因是提高系统的安全性。在共享主机服务器上,如果没有进行特别的设置,所有网站站点都将会使用同一个临时目录,这意味着数十个程序都在同一个位置对文件进行读写操作。知道了这些之后,我们就可以很容易地编写一个脚本从这个目录里读取会话文件夹中所有文件的内容,这样一来就可以从其他站点上访问到存储的用户数据了。

另外,把会话数据保存在数据库里还可以更方便地搜索Web站点会话更多的信息,我们可以查询活动会话的数量,还可以对会话数据进行备份。

把会话数据保存到数据库还有第三个原因,那就是如果站点运行于多个服务器上,在这种情况下,同一个用户在一个会话过程中有可能会对不同的服务器上的多个页面发送请求。但是会话数据如果保存在某一个服务器上的文件里,就不能被这台服务器之外的其他服务器上的页面所使用。这并不是大多数开发人员可能会面临的状态,但是如果遇到这种情况,除

74

◆ 第3章 高级数据库概念

了使用数据库保存会话数据之外,就没有其他的解决办法了。

既然我们理解了为什么要把会话数据保存在数据库中,那么就可以开始下面的步骤来实

现这种方式。

小提示

在共享主机上,涉及安全问题的另一个技巧就是改变会话的目录。安全起见,我们可以在每次调用session_start()之前调用函数session_save_path()。当然,我们还需要确保新目录存在并且具有适当的权限。

3.1.1 创建会话表

要在数据库中存储会话信息,为了达到这个目的,首先创建一个特殊的数据库表。这个表可以位于某个已有的数据库中(就像在其他应用中的做法一样),也可以单独位于一个独立的数据库内。根据需要,这个表至少要包含三个字段,见表3-1。

会话表里还可以有这三个字段之外的其他字段,但是,这三个字段是必需的。需要记住的是,有很多似乎应该用单独的字段表示的数据—举例来说,一个用户的ID ,最好是保存在会话数据的字段中。【示例】创建会话表

使用mysql 客户端访问MySQL 数据库。

我们还可以使用phpMyAdmin 或者其他任何我们喜欢的界面程序。

选择test 数据库,如图3-1所示。2. Select the test database A .

USE test;

n the test

e

e.

图3-1 本例中我们将把会话表保存到test 数据库内

既然这里只是一个示例程序,那么我们将在test 数据库中创建这个表。创建会话表,如图3-2所示:3. Create the sessions table B :

CREATE TABLE sessions (id CHAR(32) NOT NULL,data TEXT ,

n the test

e

表3-1 会话表的字段

字段类型保存的数据

CHAR(32)会话ID TEXT 会话数据

TIMESTAMP

会话数据最后一次的访问时间

3.1 在数据库中保存会话 ◆

75

data TEXT ,

last_accessed TIMESTAMP NOT NULL,PRIMARY KEY (id));

If your application stores a lot of data

the session data column to MEDIUMTEXT or

LONGTEXT .

the

re.

这个表包含三个最基本的字段,id 是主键。它是个32个字符长的字符串,不能为空(NULL )。data 字段是TEXT 类型的,可以为空(NULL )(当会话第一次启动时是没有数据的)。字段last_accessed 的类型是时间戳格式(TIMESTAMP ),当会话被创建(执行INSERT 语句)或者被修改(执行UPDATE 语句)时,这个字段都会被更新。确认会话表的结构,如图3-3所示:4. Confirm the sessions table structure C :DESCRIBE sessions;

If your application stores a lot of data

MEDIUMTEXT or .

the

re.

A I’ll put the sessions table within the test

database for this example.B This one table will handle all the session data.

C Confirming the table’s structure. If your app

in sessions, you the session data LONGTEXT .

A I’ll put the sessions table within the test database for this example.

B This one table will handle all the

session data.

C

图3-2 这个表将处理全部会话数据 图3-3 确认表的结构 小提示

如果程序需要在会话内保存大量的数据,会话数据字段的数据类型可能就需要改变为MEDIUMTEXT 或者LONGTEXT 类型。

3.1.2 定义会话函数

在创建了数据表之后,把会话数据保存到数据库需要两个步骤(从PHP 的角度来说):1)定义与数据库交互的函数。2)让PHP 来使用这些函数。

76

◆ 第3章 高级数据库概念

下一个脚本中我们将创建这些函数,在那个时候,我们将简要地讨论每个函数的参数和功能。在此先要说明一下的是,除了“读取”函数之外,其他函数都必须返回一个布尔值;而“读取”函数必须返回一个字符串,哪怕只是一个空字符串。

在进入这个脚本之前,我们需要更深地理解这些函数应该在何时被调用,如图3-4所示。每次会话启动时,“打开”和“读取”函数将会立即被调用。当“读取”函数被调用的时候,可能发生垃圾回收的过程(这取决于多种因素)。

当脚本执行结束时,“写入”函数将会被调用,然后就会调用“关闭”函数,除非会话被销毁了,而这种情况下,不会调用“写入”函数。但是,在“关闭”函数之后,“销毁”函数将会被调用。【示例】新建会话处理函数

在文本编辑器或者集成开发环境中新建一个PHP 脚本,并命名为db_sessions. inc.

php (脚本3.1):tor or IDE, to be named db_sessions.

inc.php (Script 3.1):

then make it global in each function. Note that I’m specifically giving this vari-able a different name than a standard

database connection variable (e.g.,

$dbc ), so as to minimize bugs. Normally,

you’d want to use only a single data-base connection, in which case you’d change some of this code accordingly.

continues on page 88

变量$sdbc 将保存数据库连接。我们在此对其进行初始化,然后在每个函数里让它成为全局变量。注意到这里我们给数据库连接起了一个特殊的名字,而不是使用标准的数据库连接变量名字(比如,$dbc ),这样可以有效减少bug 。通常情况下,我们可能只需要使用到一个单独的数据库连接,在那种情况下,我们需要相应地修改一些代码。 脚本3.1 这个脚本定义了把会话数据保存到数据库所需要的全部函数,任何需要这种功能的页面都可以包含它

1

2

3 /*

4 * This page creates the functional interface for

5 * storing session data in a database.

6 * This page also starts the session.

7 */

8 9 // Global variable used for the database 10 // connections in all session functions:11 $sdbc = NULL;

12 13 // Define the open_session() function:14

// This function takes no arguments.

To create new session handlers:

1. Begin a new PHP script in your text edi-

tor or IDE, to be named db_sessions.

inc.php (Script 3.1):

The $sdbc variable will store the data-base connection. I initialize it here and

then make it global in each function.

Note that I’m specifically giving this vari-able a different name than a standard database connection variable (e.g.,

$dbc ), so as to minimize bugs. Normally, you’d want to use only a single data-base connection, in which case you’d

change some of this code accordingly.

continues on page 88

?>

"open" function

"close"

function

"read" function "write" function "destroy" function

"clean/gc"

function ?

图3-4 使用会话以及各种会话处理函数的逻

辑流程图

3.1 在数据库中保存会话 ◆

77

13 // Define the open_session() function:14 // This function takes no arguments.

15 // This function should open the database connection.16 // This function should return true.17 function open_session() {18 global $sdbc;19

20 // Connect to the database:

21 $sdbc = mysqli_connect ('localhost', 'username', 'password', 'test');22

script continues on next page 23 return true;

24 } // End of open_session() function.

25

26 // Define the close_session() function:27 // This function takes no arguments.

28 // This function closes the database connection.29 // This function returns the closed status.30 function close_session() {31 global $sdbc;32

33 return mysqli_close($sdbc);

34 } // End of close_session() function.35

36 // Define the read_session() function:

37 // This function takes one argument: the session ID.38 // This function retrieves the session data.

39 // This function returns the session data as a string.40 function read_session($sid) {41 global $sdbc;42

43 // Query the database:

44 $q = sprintf('SELECT data FROM sessions WHERE id="%s"', mysqli_real_escape_string($sdbc, $sid)); 45 $r = mysqli_query($sdbc, $q);46

47 // Retrieve the results:

48 if (mysqli_num_rows($r) == 1) {

49 list($data) = mysqli_fetch_array($r, MYSQLI_NUM);50

51 // Return the data:52 return $data;53

54 } else { // Return an empty string.55 return '';56 }

57 } // End of read_session() function.58

59 // Define the write_session() function:60 // This function takes two arguments: 61 // the session ID and the session data.62 function write_session($sid, $data) {63 global $sdbc;64

65 // Store in the database:

66 $q = sprintf('REPLACE INTO sessions (id, data) VALUES ("%s", "%s")', ?mysqli_real_escape_string($sdbc, $sid), mysqli_real_escape_string($sdbc, $data)); Script 3.1 continued script continues on next page

67 $r = mysqli_query($sdbc, $q);

68

78◆ 第3章 高级数据库概念

67 $r = mysqli_query($sdbc, $q);

68

69 return true;

70 } // End of write_session() function.

71

72 // Define the destroy_session() function:

73 // This function takes one argument: the session ID.

74 function destroy_session($sid) {

75 global $sdbc;

76

77 // Delete from the database:

78 $q = sprintf('DELETE FROM sessions WHERE id="%s"', mysqli_real_escape_string($sdbc, $sid));

79 $r = mysqli_query($sdbc, $q);

80

81 // Clear the $_SESSION array:

82 $_SESSION = array();

83

84 return true;

85 } // End of destroy_session() function.

86

87 // Define the clean_session() function:

88 // This function takes one argument: a value in seconds.

89 function clean_session($expire) {

90 global $sdbc;

91

92 // Delete old sessions:

93 $q = sprintf('DELETE FROM sessions WHERE DATE_ADD(last_accessed, INTERVAL %d SECOND) < NOW()',

?(int) $expire);

94 $r = mysqli_query($sdbc, $q);

95

96 return true;

97 } // End of clean_session() function.

98

99 # **************************** #

100 # ***** END OF FUNCTIONS ***** #

101 # **************************** #

102

103 // Declare the functions to use:

104 session_set_save_handler('open_session', 'close_session', 'read_session', 'write_session', ?'destroy_session', 'clean_session');

105

106 // Make whatever other changes to the session settings, if you want.

107

108 // Start the session:

109 session_start();

定义打开会话的函数:

2. Define the function for opening a session:

function open_session() {

global $sdbc;

$sdbc = mysqli_connect

?('localhost', 'username',

?'password', 'test');

return true;

}

This function takes no arguments (which is to say that when PHP does something to open a session, it will call this function without sending any values to it). The

intent of this function is merely to estab-4. Define the function for reading the ses-

sion data:

function read_session($sid) {

global $sdbc;

$q = sprintf('SELECT data FROM

?sessions WHERE id="%s"',

?mysqli_real_escape_string

?($sdbc, $sid));

$r = mysqli_query($sdbc, $q);

if (mysqli_num_rows($r) == 1) { list($data) = mysqli_fetch_

?array($r, MYSQLI_NUM);

3.1 在数据库中保存会话 ◆79

这个函数不接收任何参数(也就是说,当PHP 需要打开一个会话时,可以不传递参数就调用这个函数)。这个函数的作用是建立一个数据库的连接。在一个真实的应用中,如果没有一本书中关于有限的篇幅的限制,我倾向于更新这个函数,使其返回一个布尔值来指示给定的操作成功与否,而不是像现在这样一直返回true 。定义关闭会话的函数:}

This function takes no arguments (which is to say that when PHP does something to open a session, it will call this function without sending any values to it). The intent of this function is merely to estab-lish a database connection.

In a real application, without the

constraints of a book’s limited pages, I would update the function so that it would return a Boolean indicating the success of the given operation instead

of always returning true.

3. Define the function for closing a session:function close_session() {

global $sdbc;

return mysqli_close($sdbc);}

This function also takes no arguments. It will close the database connection,

returning the success of that operation.

$r = mysqli_query($sdbc, $q); if (mysqli_num_rows($r) == 1) {

list($data) = mysqli_fetch_

?array($r, MYSQLI_NUM); return $data; } else {

return ''; }

}This function will receive one argument: the session ID (e.g., ei26b4i2nup742u-cho9glmbh84). The function needs

to retrieve the data for that session ID from the database and return it. If the function can’t do that, it should return

an empty string instead. Although the

session ID should be safe to use in a URL, you shouldn’t make assump-tions when it comes to security, so the mysqli_real_escape_string() function is used to make it safe (alter-natively, you could use prepared

statements).

If you’re not familiar with the sprintf() function, which I use to compile the query, see Chapter 1, “Advanced PHP Techniques.”

这个函数同样也不接收任何参数。它将关闭数据库连接,返回操作成功与否的状态。定义读取会话数据的函数:rname', );

guments (which does something call this function es to it). The merely to estab-on.

out the

mited pages, on so that it ndicating the eration instead

osing a session:

on() {

e($sdbc);

no arguments. connection, that operation.

4. Define the function for reading the ses-sion data:function read_session($sid) {

global $sdbc; $q = sprintf('SELECT data FROM

?sessions WHERE id="%s"'

, ?mysqli_real_escape_string ?($sdbc, $sid));

$r = mysqli_query($sdbc, $q); if (mysqli_num_rows($r) == 1) { list($data) = mysqli_fetch_ ?array($r, MYSQLI_NUM); return $data; } else { return ''; }}

This function will receive one argument: the session ID (e.g., ei26b4i2nup742u-cho9glmbh84). The function needs

to retrieve the data for that session ID from the database and return it. If the

function can’t do that, it should return an empty string instead. Although the

session ID should be safe to use in

a URL, you shouldn’t make assump-tions when it comes to security, so

the mysqli_real_escape_string() function is used to make it safe (alter-natively, you could use prepared statements).

If you’re not familiar with the sprintf() function, which I use to compile the query, see Chapter 1, “Advanced PHP Techniques.”

这个函数将会接收一个参数:会话ID (举例来说,比如ei26b4i2nup742u cho9glmbh84)。函数从数据库检索指定会话ID 的数据,并且返回它。如果不能完成相应的操作,它应该返回一个空的字符串。虽然在URL 地址里使用会话ID 理论上应该是安全的,但是安全不能建立在任何假设之上,所以我们在这里使用了函数mysqli_real_escape_string()来进一步保证其

安全(或者替代的,我们可以使用其他一些事先准备好的代码)。如果读者不是很熟悉脚本中用于生成查询语句的sprintf()函数,可以参考第1章的内容。定义向数据库写入数据的函数:?sessions WHERE id="%s"',

?

mysqli_real_escape_string ?($sdbc, $sid));

$r = mysqli_query($sdbc, $q); $_SESSION = array(); return true;}

5. Define the function for writing data to the database:function write_session($sid, ?$data) {

global $sdbc; $q = sprintf('REPLACE INTO ?sessions (id, data) VALUES ?("%s", "%s")', mysqli_real_ ?escape_string($sdbc, $sid), ?

mysqli_real_escape_string

?($sdbc, $data));

$r = mysqli_query($sdbc, $q); return true;

80

◆ 第

3章 高级数据库概念?sessions WHERE id="%s"', ?mysqli_real_escape_string ?($sdbc, $sid));

$r = mysqli_query($sdbc, $q); $_SESSION = array(); return true;}

This function, which will be called when the PHP session_destroy() function is invoked, receives one argument, the session ID. This function then runs a DELETE query in the database and clears

the $_SESSION array.As an example of changing the returned value, you could have this function return the number of affected would be false.

continues on next page

E Session data is stored in the database (or in a file) as a serialized array. This serialized value says that

indexed at blah is a string six characters long with a value of umlaut . Indexed at this is a decimal with a value of 3615684.4500 (and so on). Indexed at that is a string four characters long with a value of blue .

the database:function write_session($sid, ?$data) {

global $sdbc;

$q = sprintf('REPLACE INTO ?sessions (id, data) VALUES ?("%s", "%s")', mysqli_real_

?escape_string($sdbc, $sid), ?mysqli_real_escape_string ?($sdbc, $data));

$r = mysqli_query($sdbc, $q);

return true;

}This function receives two arguments: the session ID and the session data.

The session data is a serialized ver-sion of the $_SESSION array E . For the query, an INSERT must be run the

first time the session record is created

in the database and an UPDATE query

every time thereafter. The lesser-known result. If a record exists whose primary key is the same as that given a value in this query (i.e., the session ID), an update will occur. Otherwise, a new record will be made.

这个函数接收两个参数:会话ID 和会话数据。会话数据是数组$_SESSION 序列化的结果,如图3-5所示。对于这个查询,在数据库里首次创建会话记录时必须使用INSERT 语句,之后就要使用UPDATE 语句。另外一个不被人熟知的REPLACE 语句也可以同样得到这样的

结果。如果查询指定的主键与现有的记录中的某个主键相同(比如会话ID ),就会发生更新操作。否则,就会产生一条新记录。 $_SESSION = array(); return true;}

This function, which will be called when the PHP session_destroy() function is invoked, receives one argument, the session ID. This function then runs a DELETE query in the database and clears the $_SESSION array.

As an example of changing the

returned value, you could have this

function return the number of affected rows: 1 would effectively be true and 0 would be false.continues on next page E ?($sdbc, $data));

$r = mysqli_query($sdbc, $q); return true;

}

This function receives two arguments: the session ID and the session data. The session data is a serialized ver-sion of the $_SESSION array E . For the query, an INSERT must be run the

first time the session record is created in the database and an UPDATE query every time thereafter. The lesser-known

REPLACE query will achieve the same

result. If a record exists whose primary

key is the same as that given a value in this query (i.e., the session ID), an update will occur. Otherwise, a new record will be made.

图3-5 会话数据以数组序列化的方式保存在数据库(或者文件)里。这个序列化值表示索引blah 对应

的是个6字符长的字符串,值为umlaut 。而索引this 对应的是个数值,其值为3615684.4500(诸如此类)。索引that 对应的是个4字符长的字符串,其值为blue

创建销毁会话数据的函数:session data:

function destroy_session($sid) { global $sdbc; $q = sprintf('DELETE FROM

?sessions WHERE id="%s"'

, ?mysqli_real_escape_string ?($sdbc, $sid));

$r = mysqli_query($sdbc, $q); $_SESSION = array(); return true;}

This function, which will be called when the PHP session_destroy() function is invoked, receives one argument, the

session ID. This function then runs a DELETE query in the database and clears the $_SESSION array.

As an example of changing the

returned value, you could have this

function return the number of affected rows: 1 would effectively be true and 0 would be false.

continues on next page

ting data to

($sid,

E INTO

) VALUES li_real_ c, $sid), _string

dbc, $q);

arguments: sion data. alized ver-y E . For be run the rd is created DATE query lesser-known e the same hose primary ven a value on ID), an

这个函数通常在session_destroy()函数被调用的时候调用,它接收一个参数,也就是会话ID 。这个函数会在数据库里执行一个DELETE 查询,并且清除数组$_SESSION 。作为一个改变返回值的例子,我们可以使该函数返回所影响的数据记录行数:1表示为真,0表示为假。定义垃圾回收函数:

3.1 在数据库中保存会话 ◆

81

7. Define the garbage collection function:

function clean_session($expire) { global $sdbc;

$q = sprintf('DELETE FROM ?sessions WHERE DATE_ADD

?(last_accessed, INTERVAL %d ?SECOND) < NOW()', (int) ?$expire);

$r = mysqli_query($sdbc, $q); return true;

}Garbage collection is something most PHP programmers do not think about. Garbage collection is a language or application’s tool for cleaning up resources that are no longer needed. With sessions, PHP’s garbage collection can wipe out old sessions that weren’t formally destroyed.There are two relevant settings in PHP:

what is considered to be “old” and how likely it is that garbage collection is per-formed. For all session activity in a site, there is an X percent chance that PHP will go into garbage collection mode (the exact percent is a PHP setting; the default value is 1%). If it does, then all “old” session data will be destroyed. So garbage collection is triggered by any session but attempts to clean up every session .As for the garbage collection function, it will receive a time, in seconds, as to what is considered to be old. This can be used in a DELETE query to get rid of any session that hasn’t been accessed in more than the set time.8. Tell PHP to use the session-handling functions:

session_set_save_handler ?('open_session', 'close_session', ?'read_session', 'write_session', ?'destroy_session', ?'clean_session');

9. Start the session:

session_start();

Two important things to note here: First, the session_set_save_handler() func-tion does not start a session. Y ou still

have to invoke session_start(). Sec-ond, you must use these two lines in this order. Calling session_start() prior to

session_set_save_handler() will result in your handlers being ignored.The reason I’m choosing to start the session within this file is that this file will be included by any script that needs

sessions. My concern is that were you

to start the sessions separately in each

script, it would allow for the possibility that a script could start a session with-out including this file first, thereby using the file system for that page’s session and creating bugs.10. Save the file as db_sessions.inc.php and place it in your Web directory.As is my rule for all PHP scripts being included by other scripts, this one does not have a terminating PHP tag.垃圾回收是大多数PHP 程序员根本不会考虑的事情。垃圾回收是某种语言或者应用中用于清理不会再被用到的资源的工具。在使用会话时,PHP 的垃圾回收机制会将那些没有正式销毁的“旧”的会话清除掉。

PHP 里与之有关的设置有两个:定义“旧”会话的标准以及垃圾回收机制运行的方式。对于站点里全部会话的行为来说,有X%的概率PHP 会进入垃圾回收模式(举例的百分比需要根据PHP 的设置而定,默认值是1%)。如果是这样,全部“旧”的会话数据就会被销毁。因此,垃圾回收过程可以由任何会话触发,但是会尝试清理每一个会话。对于这里创建的这个垃圾回收函数,它接收一个以秒为单位的时间,用以决定什么是

“旧”的会话。这个数值用于一个DELETE 查询语句中,用于删除超过指定时间没有被访问过的会话。告诉PHP 使用会话处理函数:TE_ADD NTERVAL %d (int) $sdbc, $q);mething most t think about. anguage leaning up nger needed. bage collection s that weren’t ettings in PHP: “old” and how ollection is per-ctivity in a site, ance that PHP ection mode HP setting; the oes, then all e destroyed. triggered by 8. Tell PHP to use the session-handling functions:session_set_save_handler ?('open_session', 'close_session', ?'read_session', 'write_session', ?'destroy_session', ?'clean_session');9. Start the session:session_start();Two important things to note here: First, the session_set_save_handler() func-tion does not start a session. Y ou still have to invoke session_start(). Sec-ond, you must use these two lines in this order. Calling session_start() prior to session_set_save_handler() will result in your handlers being ignored.The reason I’m choosing to start the session within this file is that this file will be included by any script that needs sessions. My concern is that were you to start the sessions separately in each script, it would allow for the possibility that a script could start a session with-out including this file first, thereby using the file system for that page’s session and creating bugs.10. Save the file as db_sessions.inc.php and place it in your Web directory.启动会话:TE_ADD

NTERVAL %d (int)

$sdbc, $q);

mething most t think about. anguage leaning up nger needed. bage collection s that weren’t

ettings in PHP:

“old” and how ollection is per-ctivity in a site, ance that PHP ection mode HP setting; the oes, then all e destroyed. triggered by

8. Tell PHP to use the session-handling

functions:

session_set_save_handler ?('open_session', 'close_session', ?'read_session', 'write_session', ?'destroy_session', ?'clean_session');

9. Start the session:session_start();Two important things to note here: First, the session_set_save_handler() func-tion does not start a session. Y ou still have to invoke session_start(). Sec-ond, you must use these two lines in this order. Calling session_start() prior to

session_set_save_handler() will result in your handlers being ignored.

The reason I’m choosing to start the

session within this file is that this file will

be included by any script that needs sessions. My concern is that were you

to start the sessions separately in each script, it would allow for the possibility that a script could start a session with-out including this file first, thereby using the file system for that page’s session and creating bugs.10. Save the file as db_sessions.inc.php and place it in your Web directory.

有两件比较重要的事情需要在这里说明。首先,函数session_set_save_handler()并不会启动会话,我们还需要调用session_start()函数用于启动会话。其次,必须要以上面的次序来使用这两个函数。如果在session_set_save_handler()之前调用函数session_start(),脚本中定义的处理函数就会被忽略而得不到执行。我们之所以在这个文件中选择以这种方式来启动会话,是因为这个文件将会被所有需要用到会话的地方包含。这里我们担心是可能单独在每个脚本中启动会话,也可能存在不包含

这里的文件使用会话的可能性,因此为该页面的会话使用文件系统会产生bug 。保存文件并命名为db_sessions.inc.php ,放置于我们的Web 目录内。作为我们对被其他脚本包含的所有PHP 脚本文件的规则,不会为其添加一个PHP 结束标签。

82

◆ 第3

章 高级数据库概念

小提示

注意到在所有的输出被发送到Web 浏览器之后,“写入”会话函数才会被调用,如图3-4所示。之后,

“关闭”函数会被调用。

小提示

如果在PHP 设置里session.auto_start 被打开了(这意味着每个页面将会自动启动会话),就不能使用函数session_set_save_ handler()

小提示

对PHP 5.4版本来说,我们可以为ses sion_set_save_handler()函数提供一个单一的参数:一个实现了SessionHandlerInter face 的任何类型的对象。举例来说,我们可以使用Session- Handler 类。在后面章节中讲解面向对象编程之后我们将对该问题探讨更多。

3.1.3 使用新会话处理程序

使用新会话处理程序不过是像前面小节中介绍的那样来调用函数session_set_save_handler()罢了。其他关于会话的操作都没有变化,包括在会话中存储数据、访问保存的会话数据以及销毁会话。

为了展示这个功能,下一个脚本将在没有会话信息时创建一些会话数据,并显示全部的会话数据,甚至在用户点击了返回到本页的链接之后销毁会话数据。像其他工作一样,这其中只有一点复杂之处……这里所有的会话行为都需要数据库,当然也就需要数据库连接了。这个连接是在会话打开的时候创建的,在会话结束的时候关闭。除非在脚本运行结束之后调用“写入”和“关闭”函数,否则这里就不会有什么问题,如图3-4所示。可能你已经知道了,PHP 会在脚本运行结束时自动关闭所有的数据库连接。对于接下来的示例程序,这意味着在脚本运行结束之后,数据库连接是自动关闭的,然后会话函数会尝试向数据库写入数据并关闭连接。这样就会导致产生一系列莫名其妙的问题(相信我,搜索“我的数据库连接哪里去了”会得到很多结果)。

为了避免这一系列的问题,我们应该在脚本执行结束之前调用session_write_close()函数,他会调用“写入”和“关闭”函数,而这时数据库连接还是存在的。【示例】使用新会话处理程序

在文本编辑器或者集成开发环境中创建一个新的PHP 文件,命名为sessions.php (脚本3.2):D .

As you may already know, PHP does you

the favor of automatically closing any database connections when a script stops

running. For this next script, this means that after the script runs, the database connection is automatically closed, and then the session functions attempt to

write the data to the database and close the connection. The result will be some

confusing errors (and a—trust me on this—long “Where in the World Is My Database Connection?” search). To avoid this sequential problem, the

session_write_close() function should

be called before the script terminates. This

function will invoke the “write” and “close” functions, while there’s still a good data-base connection.

To use the new session handlers:1. Begin a new PHP script in your text edi-

tor or IDE, to be named sessions.php

(Script 3.2):

continues on page 93

on function is ut has been en the “close”

s turned on ning that ses-for each page), on_set_save_

ovide the ses-unction with a

ny class type lerInter- the Session-more after the gramming.

ndlers

ion

voking the () function, g section. ould do with storing data in a to destroy-

script will

doesn’t ta, and even ink back to is often the issue…

db_sessions.inc.php page (Script 3.1) so that session data is stored

ons.php

lly things with sessions.

sions.inc.php script

3.1 在数据库中保存会话 ◆83

脚本3.2 这段脚本包含db_sessions.inc.php 页面(脚本3.1),因此会话数据是保存在数

1. Begin a new PHP script in your text edi-tor or IDE, to be named sessions.php (Script 3.2):

exist, show all the session data, and even destroy the session data if a link back to this same page is clicked. As is often the case, there is one little tricky issue…

Script 3.2 This script includes the db_sessions.inc.php page (Script 3.1) so that session data is stored

in a database. DB Session Test

/ Store some dummy data in the session, if no data is present:$_SESSION['blah'] = 'umlaut';$_SESSION['this'] = 3615684.45;;/ Print a message indicating what's going on:

84

◆ 第

3章 高级数据库概念包含进来db_sessions.inc.php 文件:

?

' . print_r($_SESSION, 1) . ?'

';}

The second time the page is loaded, the existing data will be available. As a quick way to print the session data, the print_r() function will be used.6. Create the logout functionality:if (isset($_GET['logout'])) {session_destroy(); echo '

Session destroyed.

';} else { echo 'Log Out';}Again, this conditional is used to fake a multipage site. When the page is accessed, a “Log Out” link is displayed. If the user clicks that link, ?logout=true

is passed in the URL, telling this page to destroy the session.7. Print the session data:echo '

Session Data:

' . ?print_r($_SESSION, 1) . ?'

';This is mostly a repeat of the code in Step 5. Unlike that line, this one will apply the first time the page is loaded. It will also be used to reveal the effect of destroying the session.continues on next page 2. Include the db_sessions.inc.php file:require('db_sessions.inc.php ');?>The session_start() function, which is in db_sessions.inc.php , must be called before anything is sent to the Web browser, so this file must be included prior to any HTML.3. Create the initial HTML: DB Session Test The style sheet can be downloaded along with all of the other code from https://www.wendangku.net/doc/559787926.html, .4. Store some dummy data in a session if it is currently empty:Session data stored.

';Storing data in a database-managed session is no different than the regular method. This conditional is being used to replicate sessions on multiple pages. The first time the page is loaded, new data will be stored in the session. 这里的session_start()函数位于db_sessions.inc.php 文件中,它必须在任何内容被发送到浏览器之前调用,所以这个文件必须包含在任何HTML 代码之前。创建初始的HTML 代码:?
' . print_r($_SESSION, 1) . ?'

';}The second time the page is loaded, the existing data will be available. As a quick way to print the session data, the print_r() function will be used.6. Create the logout functionality:if (isset($_GET['logout'])) { session_destroy();

Session destroyed.

';} else {

echo 'Log Out';}Again, this conditional is used to fake a multipage site. When the page is accessed, a “Log Out” link is displayed. If the user clicks that link, ?logout=true

is passed in the URL, telling this page to

destroy the session.7. Print the session data:echo '

Session Data:

' . ?print_r($_SESSION, 1) . ?'

';This is mostly a repeat of the code in Step 5. Unlike that line, this one will apply the first time the page is loaded. It will also be used to reveal the effect of destroying the session.continues on next page 2. Include the db_sessions.inc.php file:require('db_sessions.inc.php ');?>

The session_start() function, which

is in db_sessions.inc.php , must be called before anything is sent to the Web browser, so this file must be included prior to any HTML.3. Create the initial HTML: DB Session Test

The style sheet can be downloaded along with all of the other code from https://www.wendangku.net/doc/559787926.html, .4. Store some dummy data in a session if it is currently empty:

$_SESSION['this '] = 3615684.45; $_SESSION['that'] = 'blue '; echo '

Session data stored.

';Storing data in a database-managed

session is no different than the regular method. This conditional is being used to replicate sessions on multiple pages. The first time the page is loaded, new data will be stored in the session. 这里涉及的样式表文件可以和其他代码一样从网站https://www.wendangku.net/doc/559787926.html, 中下载。如果会话是空的,那么存入一些填充的假数据。?

' . print_r($_SESSION, 1) .

?'

';

}The second time the page is loaded, the existing data will be available. As a quick way to print the session data, the print_r() function will be used.6. Create the logout functionality:if (isset($_GET['logout'])) { session_destroy(); echo '

Session destroyed.

';} else {

echo 'Log Out';}Again, this conditional is used to fake

a multipage site. When the page is accessed, a “Log Out” link is displayed. If the user clicks that link, ?logout=true is passed in the URL, telling this page to

destroy the session.7. Print the session data:echo '

Session Data:

' . ?print_r($_SESSION, 1) . ?'

';This is mostly a repeat of the code in Step 5. Unlike that line, this one will apply the first time the page is loaded. It will also be used to reveal the effect of destroying the session.continues on next page 2. Include the db_sessions.inc.php file:

require('db_sessions.inc.php ');

?>

The session_start() function, which is in db_sessions.inc.php , must be called before anything is sent to the Web browser, so this file must be included prior to any HTML.3. Create the initial HTML:

DB Session Test

The style sheet can be downloaded

along with all of the other code from https://www.wendangku.net/doc/559787926.html, .4. Store some dummy data in a session if it is currently empty:Session data stored.

';Storing data in a database-managed session is no different than the regular method. This conditional is being used to replicate sessions on multiple pages. The first time the page is loaded, new

data will be stored in the session.

把数据保存到数据库管理的会话里和一般的方法其实没有什么区别。在这里使用条件语句是为了在多个页面复制会话。当页面第一次加载时,新数据会保存在会话里。否则,打印当前存储的数据。5. Otherwise, print the currently stored data:} else { echo '

Session Data Exists: ?

' . print_r($_SESSION, 1) . ?'

';}The second time the page is loaded, the existing data will be available. As a quick way to print the session data, the

print_r() function will be used.6. Create the logout functionality:if (isset($_GET['logout'])) {session_destroy(); echo '

Session destroyed.

';} else { echo 'Log Out';

}Again, this conditional is used to fake

inc.php file:nc.php ');

ction, which p , must be ent to the must be

L.

">stheet"

当页面第二次被加载的时候,就可以使用已经存在的数据了。为了方便地打印出会话数据,脚本里使用了函数print_r()。创建注销功能:?

' . print_r($_SESSION, 1) . ?'

';}The second time the page is loaded, the existing data will be available. As a quick way to print the session data, the print_r() function will be used.6. Create the logout functionality:

if (isset($_GET['logout'])) {

session_destroy(); echo '

Session destroyed.

';} else {

echo 'Log Out';}

Again, this conditional is used to fake

a multipage site. When the page is inc.php file:

nc.php ');

ction, which

p , must be ent to the must be L.">

st

heet"

wnloaded

code from

3.1 在数据库中保存会话 ◆

85

print_r() function will be used.

6. Create the logout functionality:

if (isset($_GET['logout'])) { session_destroy(); echo '

Session destroyed.

';} else {

echo 'Log Out';}

Again, this conditional is used to fake a multipage site. When the page is accessed, a “Log Out” link is displayed.

If the user clicks that link, ?logout=true is passed in the URL, telling this page to destroy the session.

7. Print the session data:

echo '

Session Data:

' .

?print_r($_SESSION, 1) . ?'

';

This is mostly a repeat of the code in Step 5. Unlike that line, this one will

apply the first time the page is loaded. It will also be used to reveal the effect of

destroying the session.continues on next page

-8">

Test

sheet"

ownloaded r code from

in a session if

umlaut';

615684.45;

'blue ';

a stored.

';

se-managed

an the regular is being used multiple pages. loaded, new session.

同样,这里使用条件语句也是为了“假装”多页面的网站。当这个页面被访问时,将显

示一个“注销”的链接。当用户点击这个链接的时候,地址栏里会包含?logout=true ,用于告诉这个页面去销毁会话数据。打印出会话数据:print_r() function will be used.6. Create the logout functionality:if (isset($_GET['logout'])) { session_destroy(); echo '

Session destroyed.

';} else { echo 'Log Out';}Again, this conditional is used to fake

a multipage site. When the page is accessed, a “Log Out” link is displayed. If the user clicks that link, ?logout=true is passed in the URL, telling this page to destroy the session.7. Print the session data:echo '

Session Data:

' . ?print_r($_SESSION, 1) . ?'

';This is mostly a repeat of the code in Step 5. Unlike that line, this one will

apply the first time the page is loaded. It will also be used to reveal the effect of destroying the session.

continues on next page -8">Testsheet" ownloaded r code from in a session if umlaut';615684.45;'blue ';a stored.

';se-managed an the regular is being used multiple pages. loaded, new session. 这基本上是第5步的重复。但是与那一行不同的是,这个语句将用在页面首次被加载的时候。它也可以用于展示销毁会话的结果。完成HTML 代码:8. Complete the HTML:echo '

';

调用session_write_close()函数完成页面。

tion and complete the page:session_write_close(); ?>

只要会话数据的修改结束了,这个函数在脚本的调用的位置就显得无关紧要了。如果不使用这个函数,就会看到如图3-6

所示的“丑陋”的结果。

F 图3-6 由于PHP 会非常友好地在脚本调用结束后关闭数据库连接,因此这之后调用

write_session()和close_session()函数会找不到数据库连接

86

◆ 第3章 高级数据库概念

保存文件为sessions.php ,把它放到Web 目录里(与db_sessions.inc.php 文件在同一个目录内),然后在Web 浏览器中进行测试,结果如图

3-7~图3-9所示。 Y ou should also call session_write_close() before redirecting the browser with a header() call. This only applies when you’re using your own session handlers.

G header() call. T using your own s

G The result the first time the page is

loaded.H

图3-7 页面被首次加载时的结果 图3-8 重新加载页面时可以访问之前已经保存的会话数据

Y ou should also call session_write_

close() before redirecting the browser with a call. This only applies when you’re

using your own session handlers.

d.

oading the page allows it to

the already-stored session data.

I 图3-9 点击“注销”链接将会销毁会话

3.2 处理美国的邮政编码 ◆87

小提示

在使用header()函数重定向浏览器之前也应该调用session_write_ close()函数,当然,这

只适合于使用自己的会话处理程序的情况下。

3.2 处理美国的邮政编码

Web站点的通常需求之一就是计算两个地址之间的距离是多少。虽然利用MapQuest 或

者Google Maps地图可以完成这个任务,但是我们现在可以只利用邮政编码就能实现简单的

距离估算(只限于美国境内)。

每个邮政编码都有一个对应的经度和纬度(从技术上来说,邮政编码应该对应着多组经

度和纬度,但是这里我们可以只考虑位于正中心的那一对)。在地球上找到这样两个点,把

数据经过复杂的数学计算,我们就可以计算出一个大概的距离。在这一小节内,我们将会介

绍如何获取必要的邮政编码数据,创建一个store locator表,它将用来提供两个点中的一个点;然后,介绍一下用到的计算距离的公式。

3.2.1 创建邮政编码表

这个示例程序需要一个数据库,其中要保存美国境内每个邮政编码对应的经度和纬度。我们可以从下面三个渠道获取这些类型的信息:

商业邮政编码数据库

?

免费的邮政编码数据库

?

免费ZCTA数据库

?

第一个选择能够得到最准确的、最新的信息,但是需要付费(通常情况下,价格还不算

太高)。第二个选择是免费的,但是很难找到,而且往往不是最新的数据。我们可以在互联

网上搜索“免费邮政编码数据库”来寻找这种免费的数据库。

最后一种选择是ZCTA,这是由美国人口调查局出于自己需要而建立的数据库,它忽略

了大约10000个由美国邮政或者专门公司使用的邮政编码,还把一些邮政编码编成一个组,用字符代表其他编码。但是在通常的使用场景,这些信息就足够了。ZCTA数据库的一个来

源地址是http:// https://www.wendangku.net/doc/559787926.html,。我们可以在https://www.wendangku.net/doc/559787926.html,网站上搜索“zip code”(邮

政编码)关键字找到。

尽管在本章下面的例子中我们确实有必要拥有一个邮政编码数据库,然而接下来的步骤

中还会传达如何将一个包含一系列数据的CSV文件转换为可用的数据库表。

【示例】创建邮政编码数据库

找到数据库源。

具体使用哪个资源可能还依赖于具体的使用场景。精度对我们有多重要?可以花费

多少钱?还有一点需要考虑的是,当前存在着哪些资源可供使用(在互联网上或者直接在

相关文档