2009年11月30日星期一

Socket and PHP

php使用berkley的socket库来创建它的连接。socket只不过是一个数据结构, 这个socket数据结构去开始一个客户端和服务器之间的会话。这个服务器是一直在监听准备产生一个新的会话。当一个客户端连接服务器,它就打开服务器正在进行监听的一个端口进行会话。这时,服务器端接受客户端的连接请求,那么就进行一次循环。现在这个客户端就能够发送信息到服务器,服务器也能发送信息给客户端。
产生一个socket,需要三个变量:一个协议、一个socket类型和一个公共协议类型。产生一个socket有三种协议供选择,定义一个公共的协议类型是进行连接一个必不可少的元素。


A 协议





名字/常量描述
af_inet这是大多数用来产生socket的协议,使用tcp或udp来传输,用在ipv4的地址
af_inet6与上面类似,不过是来用在ipv6的地址
af_unix本地协议,使用在unix和linux系统上,它很少使用,一般都是当客户端和服务器在同一台及其上的时候使用


B socket类型







名字/常量描述
sock_stream这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用tcp来进行传输。
sock_dgram这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用udp来进行它的连接。
sock_seqpacket这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
sock_raw这个socket类型提供单一的网络访问,这个socket类型使用icmp公共协议。(ping、traceroute使用该协议)
sock_rdm这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序


C 公共协议





名字/常量描述
icmp互联网控制消息协议,主要使用在网关和主机上,用来检查网络状况和报告错误信息
udp 用户数据报文协议,它是一个无连接,不可靠的传输协议
tcp传输控制协议,这是一个使用最多的可靠的公共协议,它能保证数据包能够到达接受者那儿,如果在传输过程中发生错误,那么它将重新发送出错数据包。


在php中使用socket_create()函数来产生一个socket。这个socket_create()函数需要三个参数:一个协议、一个socket类型、一个公共协议。socket_create()函数运行成功返回一个包含socket的资源类型,如果没有成功则返回false。
[text]
resourece socket_create(int protocol, int sockettype, int commonprotocol);
[/text]
php提供了几个操纵socket的函数。
[php]
$commonprotocol = getprotobyname("tcp");
$socket = socket_create(af_inet, sock_stream, $commonprotocol);
socket_bind($socket, 'localhost', 1337);
socket_listen($socket);
// more socket functionality to come
[/php]


  • $commonprotocol = getprotobyname("tcp");
    使用公共协议名字来获取一个协议类型。在这里使用的是tcp公共协议,还有一个可选的办法是不使用getprotobyname()函数而是指定sol_tcp或sol_udp在socket_create()函数中。


  • $socket = socket_create(af_inet, sock_stream, sol_tcp);
    产生一个socket并且返回一个socket资源的实例。在有了一个socket资源的实例以后,就必须把socket绑定到一个ip地址和某一个端口上。


  • socket_bind($socket, 'localhost', 1337);
    绑定socket到本地计算机(127.0.0.1)和绑定socket到1337端口。



  • 然后就需要监听所有进来的socket连接。
    socket_listen($socket);



D socket函数





































函数名描述
socket_accept()接受一个socket连接
socket_bind() 把socket绑定在一个ip地址和端口上
socket_clear_error()清除socket的错误或者最后的错误代码
socket_close() 关闭一个socket资源
socket_connect()开始一个socket连接
socket_create_listen()在指定端口打开一个socket监听
socket_create_pair()产生一对没有区别的socket到一个数组里
socket_create()产生一个socket,相当于产生一个socket的数据结构
socket_get_option()获取socket选项
socket_getpeername()获取远程类似主机的ip地址
socket_getsockname()获取本地socket的ip地址
socket_iovec_add()添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc()这个函数创建一个能够发送接收读写的iovec数据结构
socket_iovec_delete()删除一个已经分配的iovec
socket_iovec_fetch()返回指定的iovec资源的数据
socket_iovec_free()释放一个iovec资源
socket_iovec_set()设置iovec的数据新值
socket_last_error()获取当前socket的最后错误代码
socket_listen() 监听由指定socket的所有连接
socket_read() 读取指定长度的数据
socket_readv() 读取从分散/聚合数组过来的数据
socket_recv() 从socket里结束数据到缓存
socket_recvfrom()接受数据从指定的socket,如果没有指定则默认当前socket
socket_recvmsg()从iovec里接受消息
socket_select() 多路选择
socket_send() 这个函数发送数据到已连接的socket
socket_sendmsg()发送消息到socket
socket_sendto()发送消息到指定地址的socket
socket_set_block()在socket里设置为块模式
socket_set_nonblock()socket里设置为非块模式
socket_set_option()设置socket选项
socket_shutdown()这个函数允许你关闭读、写、或者指定的socket
socket_strerror()返回指定错误号的详细错误
socket_write() 写数据到socket缓存
socket_writev()写数据到分散/聚合数组



以上所有的函数都是php中关于socket的,使用这些函数,必须把socket打开,如果你没有打开,请编辑你的php.ini文件,去掉下面这行前面的注释:
extension=php_sockets.dll
或使用下面的代码来加载扩展库:
[php]
if(!extension_loaded('sockets')) {
if(strtoupper(substr(php_os, 3)) == "win") {
dl('php_sockets.dll');
} else {
dl('sockets.so');
}
[/php]

产生一个服务器
[php]
$commonprotocol = getprotobyname("tcp");
$socket = socket_create(af_inet, sock_stream, $commonprotocol);
socket_bind($socket, 'localhost', 1337);
socket_listen($socket);
// accept any incoming connections to the server
$connection = socket_accept($socket);
if($connection) {
socket_write($connection, "you have connected to the socket...\n\r");
}
[/php]
这里需要命令提示符来运行这个例子。理由是因为这里将产生一个服务器,而不是一个web页面。如果尝试使用web浏览器来运行这个脚本,那么很有可能它会超过30秒的限时。可以使用下面的代码来设置一个无限的运行时间,但是还是建议使用命令提示符来运行。
set_time_limit(0);

在命令提示符中对这个脚本进行简单测试:
[bash]
php.exe example01_server.php
[/bash]
如果你没有在系统的环境变量中设置php解释器的路径,那么你将需要给php.exe指定详细的路径。当你运行这个服务器端的时候,你能够通过远程登陆(telnet)的方式连接到端口1337来测试这个服务器。

上面的服务器端有三个问题:1. 它不能接受多个连接。2. 它只完成唯一的一个命令。3. 不能通过web浏览器连接这个服务器。

在上一个代码的基础上再改进,产生下面的代码来做新的服务器端:
[php]
// set up our socket
$commonprotocol = getprotobyname("tcp");
$socket = socket_create(af_inet, sock_stream, $commonprotocol);
socket_bind($socket, 'localhost', 1337);
socket_listen($socket);
// initialize the buffer
$buffer = "no data";
while(true) {
// accept any connections coming in on this socket

$connection = socket_accept($socket);
printf("socket connected\r\n");
// check to see if there is anything in the buffer
if($buffer != "") {
printf("something is in the buffer...sending data...\r\n");
socket_write($connection, $buffer . "\r\n");
printf("wrote to socket\r\n");
} else {
printf("no data in the buffer\r\n");
}
// get the input
while($data = socket_read($connection, 1024, php_normal_read)) {
$buffer = $data;
socket_write($connection, "information received\r\n");
printf("buffer: " . $buffer . "\r\n");
}
socket_close($connection);
printf("closed the socket\r\n\r\n");
}
[/php]

this is what the server does. it initializes the socket and the buffer that you use to receive
and send data. then it waits for a connection. once a connection is created it prints
"socket connected" to the screen the server is running on. the server then checks to see if
there is anything in the buffer; if there is, it sends the data to the connected computer.
after it sends the data it waits to receive information. once it receives information it stores
it in the data, lets the connected computer know that it has received the information, and
then closes the connection. after the connection is closed, the server starts the whole
process again.


产生一个客户端
to solve the second problem is very easy. you need to create a php page that connects to
a socket, receive any data that is in the buffer, and process it. after you have processed the
data in the buffer you can send your data to the server. when another client connects, it
will process the data you sent and the client will send more data back to the server.

[php]
// create the socket and connect
$socket = socket_create(af_inet, sock_stream, sol_tcp);
$connection = socket_connect($socket,'localhost', 1337);
while($buffer = socket_read($socket, 1024, php_normal_read)) {
if($buffer == "no data") {
echo("<p>no data</p>");
break;
} else {
// do something with the data in the buffer
echo("<p>buffer data: " . $buffer . "</p>");
}
}
echo("<p>writing to socket</p>");
// write some test data to our socket
if(!socket_write($socket, "some data\r\n")) {
echo("<p>write failed</p>");
}
// read any response from the socket
while($buffer = socket_read($socket, 1024, php_normal_read)) {
echo("<p>data sent was: some data<br> response was:" . $buffer . "</p>");
}
echo("<p>done reading from socket</p>");
[/php]

这个例子的代码演示了客户端连接到服务器。客户端读取数据。如果这是第一时间到达这个循环的首次连接,这个服务器将发送"no data"返回给客户端。如果情况发生了,这个客户端在连接之上。客户端发送它的数据到服务器,数据发送给服务器,客户端等待响应。一旦接受到响应,那么它将把响应写到屏幕上。

2009年11月26日星期四

遇见

“如果你怀恋某个城市,实际上,是怀念那个城市的人。”
这是昨天晚上无意看到的一句话~
是吧
现在,我如此的留恋长沙
就是因为这样吧
只是我没勇气在众人面前承认~


看到周围的人,物,事情…
无不勾引起我对过去那些时光的回忆
好的,不好的,高兴的,悲伤的~
总是无法让我忘怀
习惯性的想到那个地方去
却总是在经过那个地方时
难以在自抑的紧张,低落~
放肆张开眼睛,期望看到自己熟悉的身影~
而当真正遇到以为是熟悉的身影时
却又马上低下头假装陌路人
等身影经过身边直到离开很远时
再掉转头去确认时,原来又只是自己一场误认而已~
怎么能又忽略掉
茫茫人海,要遇到想见的人怎会如此容易

2009年11月25日星期三

MySQL 查询优化器介绍

FROM: MySQL数据库Query的优化系列均来自51CTO读书频道

在 MySQL 中有一个专门负责优化 SELECT 语句的优化器模块 —— MySQL Query Optimizer,其主要的功能是通过计算分析系统中收集的各种统计信息,为客户端请求的 Query 给出最优的执行计划,也就是最优的数据检索方式。 当 MySQL Query Optimizer 接收到从 Query Parser (解析器) 过来的 Query 时,会根据 MySQL Query 语句的相应语法对该 Query 进行分解分析,同时还会做很多其他的计算转化工作,如常量转化, 无效内容删除, 常量计算等。所有这些工作都是为了 Optimizer 分析出最优的数据检索方式,也就是常说的执行计

在分析 MySQL Query Optimizer 的工作原理之前,先了解一下 MySQL 的 Query Tree。MySQL 的Query Tree是通过优化实现 DBXP 的经典数据结构Tree构造器 而生成的,是指导完成一个 Query 语句的请求须要处理的工作步骤,我们可以简单地认为就是一个的数据处理流程,只是以 Tree 的数据结构存放而已。通过 Query Tree可以很清楚地知道一个 Query 的完成须要经过哪些步骤,每一步的数据来源在哪里,处理方式是怎样的。在整个DBXP 的Query Tree 生成过程中,MySQL 使用了LEXYACC 这两个功能非常强大的语法 (词法) 分析工具。MySQL Query Optimizer的所有工作都是基于这个Query Tree进行的。各位朋友如果对MySQL Query Tree 实现生成的详细信息比较感兴趣,可以参考 Chales A. Bell 的《Expert MySQL》这本书,里面有比较详细的介绍。

MySQL Query Optimizer 并不是一个纯粹的CBO (Cost Base Optimizer) ,而是在CBO的基础上增加了一个被称为Heuristic Optimize (启发式优化) 的功能。也就是说,MySQL Query Optimizer在优化一个Query认为的最优执行计划时,并不一定完全按照系数据库的元信息和系统统计信息,而是在此基础上增加了某些特定的规则。 其实就是在CBO的实现中增加了部分RBO (Rule Base Optimizer) 的功能,以确保在某些特殊场景下控制 Query 按照预定的方式生成执行计划。

当客户端向MySQL 请求一条Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给MySQL Query Optimizer时,MySQL Query Optimizer 首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息 (如果有) ,看显示Hint信息是否可以完全确定该Query 的执行计划。如果没有 Hint 或Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。
Query 语句的优化思路和原则主要体现在以下几个方面:

  • 优化更需要优化的Query;

  • 定位优化对象的性能瓶颈;

  • 明确优化目标;

  • 从 Explain 入手;

  • 多使用Profile;

  • 永远用小结果集驱动大的结果集;

  • 尽可能在索引中完成排序;

  • 只取自己需要的Columns;

  • 仅仅使用最有效的过滤条件;

  • 尽可能避免复杂的Join和子查询。



上面所列的几点信息,前面4点可以理解为Query优化的一个基本思路,后面部分则是优化的基本原则。

2009年11月23日星期一

PHP文件上传处理

PHP 文件上传可以用户上传文本和二进制文件。用 PHP 的认证和文件操作函数,可完全控制允许哪些用户上传及文件上传后怎样处理。

PHP 能够接受任何来自符合 RFC-1867 标准的浏览器(包括 Netscape Navigator 3 及更高版本,打了补丁的 Microsoft Internet Explorer 3 或者更高版本)上传的文件。
相关的设置: 请参阅 php.ini 的 file_uploads, upload_max_filesize, upload_tmp_dir,post_max_size 以及 max_input_time 设置选项。
请注意 PHP 也支持 PUT 方法的文件上传, Netscape Composer 和 W3C 的 Amaya 客户端使用这种方法。请参阅 对 PUT 方法的支持 以获取更多信息。

[html]
<!-- The data encoding type, enctype, MUST be specified as below -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
<!-- MAX_FILE_SIZE must precede the file input field -->
<input type="hidden" name="MAX_FILE_SIZE" value="30000" />
<!-- Name of input element determines name in $_FILES array -->
Send this file: <input name="userfile" type="file" />
<input type="submit" value="Send File" />
</form>
[/html]


  • __URL__: 指向一个真实的 PHP 文件。

  • MAX_FILE_SIZE: 隐藏字段(单位为字节)必须放在文件输入字段之前,其值为接收文件的最大尺寸。这是对浏览器的一个建议,PHP也会检查此项。在浏览器端可以简单绕过此设置,因此不要指望用此特性来阻挡大文件。实际上,PHP设置中的上传文件最大值是不会失效的。但是最好还是在表单中加上此项目,因为它可以避免用户在花时间等待上传大文件之后才发现文件过大上传失败的麻烦。



注: 要确保文件上传表单的属性为 enctype="multipart/form-data",否则文件无法上传。
全局变量 $_FILES 自 PHP 4.1.0 起存在(在更早的版本中用 $HTTP_POST_FILES 替代)。此数组包含有所有上传的文件信息。
以上范例中 $_FILES 数组的内容如下所示。我们假设文件上传字段的名称如上例所示,为 userfile 。名称可随意命名。

$_FILES['userfile']['name']


客户端机器文件的原名称。

$_FILES['userfile']['type']


文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“ image/gif ”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。

$_FILES['userfile']['size']


已上传文件的大小,单位为字节。

$_FILES['userfile']['tmp_name']


文件被上传后在服务端储存的临时文件名。

$_FILES['userfile']['error']


和该文件上传相关的 错误代码 。此项目是在 PHP 4.2.0 版本中增加的。


文件被上传后,默认地会被储存到服务端的默认临时目录中,除非 php.ini 中的 upload_tmp_dir 设置为其它的路径。服务端的默认临时目录可以通过更改 PHP 运行环境的环境变量 TMPDIR 来重新设置,但是在 PHP 脚本内部通过运行 putenv() 函数来设置是不起作用的。该环境变量也可以用来确认其它的操作也是在上传的文件上进行的。

请查阅函数 is_uploaded_file() move_uploaded_file() 以获取进一步的信息。以下范例处理由表单提供的文件上传。
[php title="使文件上传生效"]
// In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used instead
// of $_FILES.
$uploaddir = '/var/www/uploads/' ;
$uploadfile = $uploaddir . basename ( $_FILES [ 'userfile' ][ 'name' ]);
echo '<pre>' ;
if ( move_uploaded_file ( $_FILES [ 'userfile' ][ 'tmp_name' ], $uploadfile )) {
echo "File is valid, and was successfully uploaded.\n" ;
} else {
echo "Possible file upload attack!\n" ;
}
echo 'Here is some more debugging info:' ;
print_r ( $_FILES );
print "</pre>" ;
[/php]

接受上传文件的 PHP 脚本为了决定接下来要对该文件进行哪些操作,应该实现任何逻辑上必要的检查。例如可以用 $_FILES['userfile']['size'] 变量来排除过大或过小的文件,也可以通过 $_FILES['userfile']['type'] 变量来排除文件类型和某种标准不相符合的文件,但只把这个当作一系列检查中的第一步,因为此值完全由客户端控制而在 PHP 端并不检查。自 PHP 4.2.0 起,还可以通过 $_FILES['userfile']['error'] 变量来根据不同的 错误代码 来计划下一步如何处理。不管怎样,要么将该文件从临时目录中删除,要么将其移动到其它的地方。
如果表单中没有选择上传的文件,则 PHP 变量 $_FILES['userfile']['size'] 的值将为 0, $_FILES['userfile']['tmp_name'] 将为空。
如果该文件没有被移动到其它地方也没有被改名,则该文件将在表单请求结束时被删除。

PHP 的 HTML 数组特性 甚至支持文件类型。
[html titel="上传一组文件"]
<form action="" method="post" enctype="multipart/form-data">
<p>Pictures:
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="Send" />
</p>
</form>
[/html]

[php]
foreach ( $_FILES [ "pictures" ][ "error" ] as $key => $error ) {
if ( $error == UPLOAD_ERR_OK ) {
$tmp_name = $_FILES [ "pictures" ][ "tmp_name" ][ $key ];
$name = $_FILES [ "pictures" ][ "name" ][ $key ];
move_uploaded_file ( $tmp_name , "data/$name" );
}
}
[/php]

错误信息说明
从 PHP 4.2.0 开始,PHP 将随文件信息数组一起返回一个对应的错误代码。该代码可以在文件上传时生成的文件数组中的 error 字段中被找到,也就是 $_FILES['userfile']['error'] 。

UPLOAD_ERR_OK


其值为 0,没有错误发生,文件上传成功。

UPLOAD_ERR_INI_SIZE


其值为 1,上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。

UPLOAD_ERR_FORM_SIZE


其值为 2,上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。

UPLOAD_ERR_PARTIAL


其值为 3,文件只有部分被上传。

UPLOAD_ERR_NO_FILE


其值为 4,没有文件被上传。

UPLOAD_ERR_NO_TMP_DIR


其值为 6,找不到临时文件夹。PHP 4.3.10 和 PHP 5.0.3 引进。

UPLOAD_ERR_CANT_WRITE


其值为 7,文件写入失败。PHP 5.1.0 引进。


注: 以上值在 PHP 4.3.0 之后变成了 PHP 常量。
常见缺陷
对 MAX_FILE_SIZE 设置的值,不能大于 ini 设置中 upload_max_filesize 选项设置的值。其默认值为 2M 字节。
如果内存限制设置被激活,可能需要将 memory_limit 设置的更大些,请确认 memory_limit 的设置足够的大。
如果 max_execution_time 设置的值太小,脚本运行的时间可能会超过该设置。因此,也请保证 max_execution_time 足够的大。
注: max_execution_time 仅仅只影响脚本本身运行的时间。任何其它花费在脚本运行之外的时间,诸如用函数 system() 对系统的调用、 sleep() 函数的使用、数据库查询、文件上传等,在计算脚本运行的最大时间时都不包括在内。
警告
max_input_time 以秒为单位设定了脚本接收输入的最大时间,包括文件上传。对于较大或多个文件,或者用户的网速较慢时,可能会超过默认的 60 秒 。
如果 post_max_size 设置的值太小,则较大的文件会无法被上传。因此,请保证 post_max_size 的值足够的大。
不对正在操作的文件进行验证可能意味着用户能够访问其它目录下的敏感信息。
请注意 CERN httpd 似乎会丢弃它从客户端获得的 content-type mime 头信息中第一个空格后所有的内容,基于这一点, CERN httpd 不支持文件上传特性。
鉴于文件路径的表示方法有很多种,我们无法确保用使用各种外语的文件名(尤其是包含空格的)能够被正确的处理。
开发人员不应将普通的输入字段和文件上传的字段混用同一个表单变量(例如都用 foo[] )。

上传多个文件
可以对 input 域使用不同的 name 来上传多个文件。
PHP 支持同时上传多个文件并将它们的信息自动以数组的形式组织。要完成这项功能,需要在 HTML 表单中对文件上传域使用和多选框与复选框相同的数组式提交语法。
注: 对多文件上传的支持是在 PHP 3.0.10 版本添加的。
[html title="上传多个文件"]
<form action="file-upload.php" method="post" enctype="multipart/form-data">
Send these files:
<input name="userfile[]" type="file" /
<input name="userfile[]" type="file" />
<input type="submit" value="Send files" />
</form>
[/html]
当以上表单被提交后,数组 $_FILES['userfile'] , $_FILES['userfile']['name'] 和 $_FILES['userfile']['size'] 将被初始化(在 PHP 4.1.0 以前版本是 $HTTP_POST_FILES )。如果 register_globals 的设置为 on,则和文件上传相关的全局变量也将被初始化。所有这些提交的信息都将被储存到以数字为索引的数组中。
例如,假设名为 /home/test/review.html 和 /home/test/xwp.out 的文件被提交,则 $_FILES['userfile']['name'][0] 的值将是 review.html ,而 $_FILES['userfile']['name'][1] 的值将是 xwp.out 。类似的, $_FILES['userfile']['size'][0] 将包含文件 review.html 的大小,依此类推。
此外也同时设置了 $_FILES['userfile']['name'][0] , $_FILES['userfile']['tmp_name'][0] , $_FILES['userfile']['size'][0] 以及 $_FILES['userfile']['type'][0] 。
对 PUT 方法的支持
PHP 3 和 PHP 4 对 PUT 方法的支持有所不同。在 PHP 4 中,必须使用标准的输入流来读取一个 HTTP PUT 的内容。
[php title="用 PHP 4 来保存 HTTP PUT 文件"]
/* PUT data comes in on the stdin stream */
$putdata = fopen ( "php://stdin" , "r" );
/* Open a file for writing */
$fp = fopen ( "myputfile.ext" , "w" );
/* Read the data 1 KB at a time
and write to the file */
while ( $data = fread ( $putdata , 1024 ))
fwrite ( $fp , $data );
/* Close the streams */
fclose ( $fp );
fclose ( $putdata );
[/php]

注: 以下文档的内容仅对 PHP 3 适用。
PHP 提供对诸如 Netscape Composer 和 W3C Amaya 等客户端使用的 HTTP PUT 方法的支持。PUT 请求比文件上传要简单的多,它们一般的形式为:
[bash]
PUT /path/filename.html HTTP/1.1
[/bash]

这通常意味着远程客户端会将其中的 /path/filename.html 存储到 web 目录树。让 Apache 或者 PHP 自动允许所有人覆盖 web 目录树下的任何文件显然是很不明智的。因此,要处理类似的请求,必须先告诉 web 服务器需要用特定的 PHP 脚本来处理该请求。在 Apache 下,可以用 Script 选项来设置。它可以被放置到 Apache 配置文件中几乎所有的位置。通常我们把它放置在 区域或者 区域。可以用如下一行来完成该设置:

[bash]
Script PUT /put.php
[/bash]

这将告诉 Apache 将所有对 URI 的 PUT 请求全部发送到 put.php 脚本,这些 URI 必须和 PUT 命令中的内容相匹配。当然,这是建立在 PHP 支持 .php 扩展名,并且 PHP 已经在运行的假设之上。
在 put.php 文件中,可以作如下操作:
[php]
copy ( $PHP_UPLOADED_FILE_NAME , $DOCUMENT_ROOT . $REQUEST_URI );
[/php]

这将会把文件拷贝到远程客户端请求的位置。可能希望在文件拷贝之前进行一些检查或者对用户认证 之类的操作。这里唯一的问题是,当 PHP 接受到 PUT 方法的请求时,它将会把上传的文件储存到和其它用 POST 方法 处理过的文件相同的临时目录。在请求结束时,临时文件将被删除。因此,用来处理 PUT 的 PHP 脚本必须将该文件拷贝到其它的地方。该临时文件的文件名被储存在变量 $PHP_PUT_FILENAME 中,也可以通过 $REQUEST_URI 变量获得建议的目标文件名(在非 Apache web 服务器上可能会有较大的变化)。该目标文件名是由远程客户端指定的。也可以不听从改客户端的信息,而把所有上传的文件存储到一个特殊的上传目录下。

使用远程文件

只要在 php.ini 文件中激活了 allow_url_fopen 选项,就可以在大多数需要用文件名作为参数的函数中使用 HTTP FTP的 URL 来代替文件名。同时,也可以在 include() include_once() require() require_once() 语句中使用 URL。

注: 要在 PHP 4.0.3 及其更早的版本中使用 URL 封装协议,需要在编译时用 --enable-url-fopen-wrapper 参数来配置 PHP。
注: Windows 版本的 PHP 4.3 版之前不支持以下函数的远程访问: include()include_once()require()require_once()以及 参考 Image 图像函数 中的 imagecreatefromXXX 函数。

例如,可以用以下范例来打开远程 web 服务器上的文件,解析需要的输出数据,然后将这些数据用在数据库的检索中,或者简单地以和自己网站其它页面相同的风格输出其内容。
[php]
$file = fopen ( 'http://www.*****.com/' , 'r' );
if (! $file ) {
echo 'Unable to open remote file.\n' ;
exit;
}
while (! feof ( $file )) {
$line = fgets ( $file , 1024 );
/* This only works if the title and its tags are on one line */
if ( eregi ( '<title>(.*)</title>' , $line , $out )) {
$title = $out [ 1 ];
break;
}
}
fclose ( $file );
[/php]

如果有合法的访问权限,以一个用户的身份和某 FTP 服务器建立了链接,还可以向该 FTP 服务器端的文件进行写操作。仅能用该方法来创建新的文件;如果尝试覆盖已经存在的文件, fopen() 函数的调用将会失败。
要以“anonymous”以外的用户名连接服务器,需要指明用户名(可能还有密码),例如 ftp://user:password@ftp.example.com/path/to/file (也可以在通过需要 Basic 认证的 HTTP 协议访问远程文件时使用相同的语法)。

[php title="将数据保存到远程服务器"]
$file = fopen ('ftp://ftp.example.com/incoming/outputfile" ,'w" );
if (! $file ) {
echo'Unable to open remote file for writing.\n" ;
exit;
}
/* Write the data here. */
fwrite ( $file , $_SERVER [ 'HTTP_USER_AGENT' ] .'\n" );
fclose ( $file );
[/php]
注: 或许可以从以上范例中得到启发,用该技术来存储远程日志文件。但是正如以上提到的,在用 fopen() 方式打开的 URL 中,仅能对新文件进行写操作。如果远程文件已经存在则 fopen() 函数的操作将会失败。要做类似于分布式日志的事,可以参考 syslog() 函数。

PHP 连接处理

注: 以下内容对 PHP 3.0.7 及更高版本适用。
在 PHP 内部,系统维护着连接状态,其状态有三种可能的情况:




0NORMAL正常
1ABORTED异常退出
2TIMEOUT超时

当 PHP 脚本正常地运行 NORMAL 状态时,连接为有效。当远程客户端中断连接时,ABORTED 状态的标记将会被打开。远程客户端连接的中断通常是由用户点击 STOP 按钮导致的。当连接时间超过 PHP 的时限(请参阅 set_time_limit() 函数)时,TIMEOUT 状态的标记将被打开。

可以决定脚本是否需要在客户端中断连接时退出。有时候让脚本完整地运行会带来很多方便,即使没有远程浏览器接受脚本的输出。默认的情况是当远程客户端连接中断时脚本将会退出。该处理过程可由 php.ini 的 ignore_user_abort 或由 WEB Sever 设置中对应的 php_value ignore_user_abort以及 ignore_user_abort() 函数来控制。如果没有告诉 PHP 忽略用户的中断,脚本将会被中断,除非通过 register_shutdown_function() 设置了关闭触发函数。通过该关闭触发函数,当远程用户点击 STOP 按钮后,脚本再次尝试输出数据时,PHP 将会检测到连接已被中断,并调用关闭触发函数。

脚本也有可能被内置的脚本计时器中断。默认的超时限制为 30 秒。这个值可以通过设置 php.ini 的 max_execution_time 或 Web Server 设置中对应的php_value max_execution_time 参数或者 set_time_limit() 函数来更改。当计数器超时的时候,脚本将会类似于以上连接中断的情况退出,先前被注册过的关闭触发函数也将在这时被执行。在该关闭触发函数中,可以通过调用 connection_status() 函数来检查超时是否导致关闭触发函数被调用。如果超时导致了关闭触发函数的调用,该函数将返回 2。

需要注意的一点是 ABORTED 和 TIMEOUT 状态可以同时有效。这在告诉 PHP 忽略用户的退出操作时是可能的。PHP 将仍然注意用户已经中断了连接但脚本仍然在运行的情况。如果到了运行的时间限制,脚本将被退出,设置过的关闭触发函数也将被执行。在这时会发现函数 connection_status() 返回 3。

PHP 数据库永久连接

永久的数据库连接是指在脚 本结束运行时不关闭的连接。当收到一个永久连接的请求时。PHP 将检查是否已经存在一个(前面已经开启的)相同的永久连接。如果存在,将直接使用这个连接;如果不存在,则建立一个新的连接。所谓“相同”的连接是指用相同的用户名和密码到相同主机的连接。


对 web 服务器的工作和分布负载没有完全理解的读者可能会错误地理解永久连接的作用。特别的,永久连接 不会 在相同的连接上提供建立“用户会话”的能力,也不提供有效建立事务的能力。实际上,从严格意义上来讲,永久连接不会提供 任何 非永久连接无法提供的特殊功能。


这和 web 服务器工作的方式有关。web 服务器可以用三种方法来利用 PHP 生成 web 页面。


第一种方法是将 PHP 用作一个“外壳”。以这种方法运行,PHP 会为向 web 服务器提出的每个 PHP 页面请求生成并结束一个 PHP 解释器线程。由于该线程会随每个请求的结束而结束,因此任何在这个线程中利用的任何资源(例如指向 SQL 数据库服务器的连接)都会随线程的结束而关闭。在这种情况下,使用永久连接不会获得任何地改变――因为它们根本不是永久的。


第二,也是最常用的方法,是把 PHP 用作多进程 web 服务器的一个模块,这种方法目前只适用于 Apache。对于一个多进程的服务器,其典型特征是有一个父进程和一组子进程协调运行,其中实际生成 web 页面的是子进程。每当客户端向父进程提出请求时,该请求会被传递给还没有被其它的客户端请求占用的子进程。这也就是说当相同的客户端第二次向服务端提出请 求时,它将有可能被一个不同的子进程来处理。在开启了一个永久连接后,所有请求 SQL 服务的后继页面都能够重新使用这个已经建立的 SQL Server 连接。


最后一种方法是将 PHP 用作多线程 web 服务器的一个插件。目前 PHP 4 已经支持 ISAPI、WSAPI 和 NSAPI(在 Windows 环境下),这些使得 PHP 可以被用作诸如 Netscape FastTrack (iPlanet)、Microsoft's Internet Information Server (IIS) 和 O'Reilly's WebSite Pro 等多线程 web 服务器的插件。永久连接的行为和前面所描述的多过程模型在本质上是相同的。注意 PHP 3 不支持 SAPI。


如果永久连接并没有任何附加的功能,那么使用它有什么好处?


答案非常简单――效率。当客户端对 SQL 服务器的连接请求非常频繁时,永久连接将更加高效。连接请求频繁的标准取决于很多因素。例如,数据库的种类,数据库服务和 web 服务是否在同一台服务器上,SQL 服务器如何加载负载等。但我们至少知道,当连接请求很频繁时,永久连接将显著的提高效率。它使得每个子进程在其生命周期中只做一次连接操作,而非每次在处理一个页面时都要向 SQL 服务器提出连接请求。这也就是说,每个子进程将对服务器建立各自独立的永久连接。例如,如果有 20 个不同的子进程运行某脚本建立了永久的 SQL 服务器永久连接,那么实际上向该 SQL 服务器建立了 20 个不同的永久连接,每个进程占有一个。


注意,如果永久连接的子进程数目超过了设定的数据库连接数限制,系统将会产生一些缺陷。如果数据库的同时连接数限制为 16,而在繁忙会话的情况下,有 17 个线程试图连接,那么有一个线程将无法连接。如果这个时候,在脚本中出现了使得连接无法关闭的错误(例如无限循环),则该数据库的 16 个连接将迅速地受到影响。请查阅使用的数据库的文档,以获取关于如何处理已放弃的及闲置的连接的方法。


警告


在使用永久连接时还有一些特别的问题需要注意。例如在永久连接中使用数据表锁时,如果脚本不管什么原因无法释放该数据表锁,其随后使用相同连接的脚 本将会被永久的阻塞,使得需要重新启动 httpd 服务或者数据库服务。另外,在使用事务处理时,如果脚本在事务阻塞产生前结束,则该阻塞也会影响到使用相同连接的下一个脚本。不管在什么情况下,都可以通 过使用 register_shutdown_function() 函数来注册一个简单的清理函数来打开数据表锁,或者回滚事务。或者更好的处理方法,是不在使用数据表锁或者事务处理的脚本中使用永久连接,这可以从根本上解决这个问题(当然还可以在其它地方使用永久连接)。


以下是一点重要的总结。永久连接是为通常连接建立一对一的分布而设计的。这意味着必须能够保证在将永久连接替换为非永久连接时,脚本的行为不会改变。使用永久连接将(非常)有可能改变脚本的效率,但不改变其行为!


参见 fbsql_pconnect() ibase_pconnect() ifx_pconnect() ingres_pconnect() msql_pconnect() mssql_pconnect() mysql_pconnect() ociplogon() odbc_pconnect() ora_plogon() pfsockopen() pg_pconnect() sybase_pconnect()

PHP 安全模式

PHP 的安全模式是为了试图解决共享服务器(shared-server)安全问题而设立的。在结构上, 试图在 PHP 层上解决这个问题是不合理的, 但修改 web 服务器层和操作系统层显得非常不现实。因此许多人, 特别是 ISP, 目前使用安全模式。
保安措施和安全模式

保安措施和安全模式配置指令

































































名称 默认值 可修改范围 更新记录
safe_mode 0 PHP_INI_SYSTEM
safe_mode_gid 0 PHP_INI_SYSTEM 自 PHP 4.1.0 起可用
safe_mode_include_dir NULL PHP_INI_SYSTEM 自 PHP 4.1.0 起可用
safe_mode_exec_dir PHP_INI_SYSTEM
safe_mode_allowed_env_vars PHP_ PHP_INI_SYSTEM
safe_mode_protected_env_vars LD_LIBRARY_PATH PHP_INI_SYSTEM
open_basedir NULL PHP_INI_SYSTEM
disable_functions 仅 php.ini 自 PHP 4.0.1 起可用
disable_classes 仅 php.ini 自 PHP 4.3.2 起可用

PHP_INI_* 常量的进一步详细说明与定义见 ini_set()
以下是配置选项的简要解释。



  • safe_mode boolean
    默认情况下, 安全模式在打开文件时会做 UID 比较检查。如果想将其放宽到 GID 比较, 则打开 safe_mode_gid。是否在文件访问时使用 UID ( FALSE )或者 GID ( TRUE )来做检查。

  • safe_mode_gid boolean
    默认情况下, 安全模式在打开文件时会做 UID 比较检查。如果想将其放宽到 GID 比较, 则打开 safe_mode_gid。是否在文件访问时使用 UID ( FALSE )或者 GID ( TRUE )来做检查。


  • safe_mode_include_dir string
    当从此目录及其子目录(目录必须在 include_path 中或者用完整路径来包含)包含文件时越过 UID / GID 检查。
    从 PHP 4.2.0 开始, 本指令可以接受和 include_path 指令类似的风格用冒号(Windows 中是分号)隔开的路径, 而不只是一个目录。
    指定的限制实际上是一个前缀, 而非一个目录名。这也就是说 safe_mode_include_dir = /dir/include将允许访问/dir/include/dir/incls, 如果它们存在的话。如果希望将访问控制在一个指定的目录, 那么需在结尾加上一个斜线, 例如: safe_mode_include_dir = /dir/incl/.
    如果本指令的值为空, 在 PHP 4.2.3 中以及 PHP 4.3.3 起具有不同 UID / GID 的文件将不能被包含。在较早版本中, 所有文件都能被包含。


  • safe_mode_exec_dir string
    如果 PHP 使用了安全模式, system() 和其它程序执行函数将拒绝启动不在此目录中的程序。必须使用 / 作为目录分隔符, 包括 Windows 中


  • safe_mode_allowed_env_vars string
    设置某些环境变量可能是潜在的安全缺口。本指令包含有一个逗号分隔的前缀列表。在安全模式下, 用户只能改变那些名字具有在这里提供的前缀的环境变量。默认情况下, 用户只能设置以 PHP_ 开头的环境变量(例如 PHP_FOO = BAR) >如果本指令为空, PHP 将使用户可以修改任何环境变量


  • safe_mode_protected_env_vars string
    本指令包含有一个逗号分隔的环境变量的列表, 最终用户不能用 putenv() 来改变这些环境变量。甚至在 safe_mode_allowed_env_vars 中设置了允许修改时也不能改变这些变量

  • open_basedir string
    将 PHP 所能打开的文件限制在指定的目录树, 包括文件本身。本指令不受 安全模式打开或者关闭的影响。
    当一个脚本试图用例如 fopen() 或者 gzopen() 打开一个文件时, 该文件的位置将被检查。当文件在指定的目录树之外时 PHP 将拒绝打开它。所有的符号连接都会被解析, 所以不可能通过符号连接来避开此限制。
    特殊值 . 指明脚本的工作目录将被作为基准目录。但这有些危险, 因为脚本的工作目录可以轻易被 chdir() 而改变。
    在 httpd.conf 文件中中, open_basedir 可以像其它任何配置选项一样用php_admin_value open_basedir none的方法关闭(例如某些虚拟主机中)。
    在 Windows 中, 用分号分隔目录, 在任何其它系统中用冒号分隔目录。作为 Apache 模块时, 父目录中的 open_basedir 路径自动被继承。
    用 open_basedir 指定的限制实际上是前缀, 不是目录名。也就是说open_basedir = /dir/incl也会允许访问/dir/include/dir/incls, 如果它们存在的话。如果要将访问限制在仅为指定的目录, 用斜 线结束路径名。eg:open_basedir = /dir/incl/支持多个目录需要 PHP > 3.0.7, 默认是允许打开所有文件。


  • disable_functions string

    本指令允许你基于 安全 原因禁止某些函数。接受逗号分隔的函数名列表作为参数。 disable_functions 不受 安全模式 的影响。
    本指令只能设置在 php.ini 中。例如不能将其设置在 httpd.conf 。


  • disable_classes
    本指令可以使你出于 安全 的理由禁用某些类。用逗号分隔类名。disable_classes 不受 安全模式 的影响。
    本指令只能设置在 php.ini 中。例如不能将其设置在 httpd.conf 。 PHP > 4.3.2



参见 register_globals , display_errors log_errors
当 safe_mode 设置为 on, PHP 将通过文件函数或其目录检查当前脚本的拥有者是否和将被操作的文件的拥有者相匹配。eg:
[bash]
-rw-rw-r-- 1 rasmus rasmus 33 Jul 1 19:20 script.php
-rw-r--r-- 1 root root 1116 May 26 18:01 /etc/passwd
[/bash]

[php title="运行 script.php"]
readfile ( '/etc/passwd' );
[/php]

如果安全模式被激活, 则将会导致以下错误:
[text]
Warning: SAFE MODE Restriction in effect. The script whose uid is 500 is not
allowed to access /etc/passwd owned by uid 0 in /docroot/script.php on line 2
[/text]

同时, 或许会存在这样的环境, 在该环境下, 宽松的 GID 检查已经足够, 但严格的 UID 检查反而是不适合的。可以用 safe_mode_gid 选项来控制这种检查。如果设置为 On 则进行宽松的 GID 检查;设置为 Off (默认值)则进行 UID 检查。
除了 safe_mode 以外, 如果设置了 open_basedir 选项, 则所有的文件操作将被限制在指定的目录下. eg:
[bash]
<Directory /docroot>
php_admin_value open_basedir /docroot
</Directory>
[/bash]
如果在设置了 open_basedir 选项后运行同样的 script.php, 则其结果会是:
[text]
Warning: open_basedir restriction in effect. File is in wrong directory in
docroot/script.php on line 2
[/text]

也可以单独地屏蔽某些函数。请注意 disable_functions 选项不能在 php.ini 文件外部使用, 也就是说无法在 httpd.conf 文件的按不同虚拟主机或不同目录的方式来屏蔽函数。如果将如下内容加入到 php.ini 文件:
[bash]
disable_functions readfile,system
[/bash]

则会得到如下的输出:
[text]
Warning: readfile() has been disabled for security reasons in
/docroot/script.php on line 2
[/text]









警告
当然, 这些 PHP 限制不适用于可执行文件。

被安全模式限制或屏蔽的函数
以下 安全模式 列表可能不完整或不正确。

安全模式限制函数













































































































































































函数名 限制
dbmopen() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
dbase_open() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
filepro() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
filepro_rowcount() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
filepro_retrieve() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
ifx_* sql_safe_mode 限制, (!= safe mode)
ingres_* sql_safe_mode 限制, (!= safe mode)
mysql_* sql_safe_mode 限制, (!= safe mode)
pg_loimport() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
posix_mkfifo() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
putenv() 遵循 ini 设置的 safe_mode_protected_env_vars 和 safe_mode_allowed_env_vars 选项。请参考 putenv() 函数的有关文档。
move_uploaded_file() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
chdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
dl() 本函数在 安全模式 下被禁用。
backtick operator 本函数在 安全模式 下被禁用。
shell_exec() (在功能上和 backticks 函数相同) 本函数在 安全模式 下被禁用。
exec() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因, 目前不能在可执行对象的路径中使用 .. 。 escapeshellcmd() 将被作用于此函数的参数上。
system() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因, 目前不能在可执行对象的路径中使用 .. 。 escapeshellcmd() 将被作用于此函数的参数上。
passthru() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因, 目前不能在可执行对象的路径中使用 .. 。 escapeshellcmd() 将被作用于此函数的参数上。
popen() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因, 目前不能在可执行对象的路径中使用 .. 。 escapeshellcmd() 将被作用于此函数的参数上。
fopen() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
mkdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
rmdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
rename() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
unlink() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
copy() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者). (on source and target )
chgrp() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
chown() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者).
chmod() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 另外, 不能设置 SUID、SGID 和 sticky bits
touch() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者).
symlink() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者). (注意:仅测试 target)
link() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者). (注意:仅测试 target)
apache_request_headers() 在安全模式下, 以“authorization”(区分大小写)开头的标头将不会被返回。
header() 在安全模式下, 如果设置了 WWW-Authenticate , 当前脚本的 uid 将被添加到该标头的 realm 部分。
PHP_AUTH 变量 在安全模式下, 变量 PHP_AUTH_USER 、 PHP_AUTH_PW 和 PHP_AUTH_TYPE 在 $_SERVER 中不可用。但无论如何, 您仍然可以使用 REMOTE_USER 来获取用户名称(USER)。(注意:仅 PHP 4.3.0 以后有效)
highlight_file() , show_source() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者). (注意, 仅在 4.2.1 版本后有效)
parse_ini_file() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者). 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者). (注意, 仅在 4.2.1 版本后有效)
set_time_limit() 在 安全模式 下不起作用。
max_execution_time 在 安全模式 下不起作用。
mail() 在安全模式下, 第五个参数被屏蔽。(注意, 仅自 PHP 4.2.3 起受影响)
任何使用 php4/main/fopen_wrappers.c 的函数  

PHP 命令模式

从版本 4.3.0 开始,PHP 提供了一种新类型的 SAPI (Server Application Programming Interface,服务端应用编程端口)支持,名为 CLI ,意为 Command Line Interface ,即命令行接口。顾名思义,该 SAPI 模块主要用作 PHP 的开发外壳应用。 CLI SAPI 和其它 SAPI 模块相比有很多的不同之处,我们将在本章中详细阐述。值得一提的是, CLI 和 CGI 是不同的 SAPI,尽管它们之间有很多共同的行为。

CLI SAPI 最先是随 PHP 4.2.0 版本发布的,但仍旧只是一个实验性的版本,并需要在运行 ./configure 时加上 --enable-cli参数。从 PHP 4.3.0 版本开始, CLI SAPI 成为了正式模块,--enable-cli 参数会被默认得设置为 on,也可以用参数 --disable-cli 来屏蔽。

从 PHP 4.3.0开始,CLI/CGI 二进制执行文件的文件名、位置和是否存在会根据 PHP 在系统上的安装而不同。在默认情况下,当运行 make 时,CGI 和 CLI 都会被编译并且分别放置在 PHP 源文件目录的 sapi/cgi/phpsapi/cli/php 下。可以注意到两个文件都被命名为了 php。在 make install 的过程中会发生什么取决于配置行。如果在配置的时候选择了一个 SAPI 模块,如 apxs,或者使用了 --disable-cgi 参数,则在 make install 的过程中,CLI 将被拷贝到 {PREFIX}/bin/php ,除非 CGI 已经被放置在了那个位置。因此,例如,如果在配置行中有 --with--apxs,则在 make install 的过程中,CLI 将被拷贝到 {PREFIX}/bin/php。如果希望撤销 CGI 执行文件的安装,请在 make install 之后运行 make install-cli 。或者,也可以在配置行中加上 --disable-cgi 参数。

注: 由于 --enable-cli--enable-cgi 同时默认有效,因此,不必再配置行中加上 --enable-cli 来使得 CLI 在 make install 过程中被拷贝到 {PREFIX}/bin/php 。

在 PHP 4.2.0 到 PHP 4.2.3 之间的 Windows 发行包中,CLI 的文件名为 php-cli.exe ,相同文件夹下的 php.exe 为 CGI。从 PHP 4.3.0 版本开始,Windows 的发行包中 CLI 的执行文件为 php.exe ,被放置在一个单独的名为 cli 的文件夹下,即 cli/php.exe 。在 PHP 5 中,CLI 存在于主文件夹中,名为 php.exe ,而 CGI 版本名为 php-cgi.exe

从 PHP 5 起,一个名为 php-win.exe 的新文件随包发布。它相当于 CLI 版本,但是 php-win 不输出任何内容,便不提供控制台(不会弹出 DOS 窗口)。这种方式类似于 php-gtk。需要使用 --enable-cli-win32 选项来配置它。

如何得知自己使用的是哪个 SAPI
在命令行下,运行 php -v 便能得知该 php 是 CGI 还是 CLI。请参考函数 php_sapi_name() 以及常量 PHP_SAPI
注: 在 PHP 4.3.2 中加入了 Unix 的 man 页面。可以在命令行中键入 man php 来查看。

以下为 CLI SAPI 和其它 SAPI 模块相比的显著区别:


  • 与 CGI SAPI 不同,其输出没有任何头信息。
    尽管 CGI SAPI 提供了取消 HTTP 头信息的方法,但在 CLI SAPI 中并不存在类似的方法以开启 HTTP 头信息的输出。
    CLI 默认以安静模式开始,但为了保证兼容性, -q--no-header 参数为了向后兼容仍然保留,使得可以使用旧的 CGI 脚本。在运行时,不会把工作目录改为脚本的当前目录(可以使用 -C 和 --no-chdir 参数来兼容 CGI 模式)。出错时输出纯文本的错误信息(非 HTML 格式)。


  • CLI SAPI 强制覆盖了 php.ini 中的某些设置,因为这些设置在外壳环境下是没有意义的。




























设置选项 CLI SAPI 默认值 备注
html_errors FALSE 无意义的 HTML 标记符会使得出错信息很凌乱,所以在外壳下阅读报错信息是十分困难的。因此将该选项的默认值改为 FALSE
implicit_flush TRUE 在命令行模式下,所有来自 print() echo() 的输出将被立即写到输出端,而不作任何地缓冲操作。如果希望延缓或控制标准输出,仍然可以使用 output buffering 设置项。
max_execution_time 0(无限值) 鉴于在外壳环境下使用 PHP 的无穷的可能性,最大运行时间被设置为了无限值。为 web 开发的应用程序可能只需运行几秒钟时间,而外壳应用程序的运行时间可能会长的多。
register_argc_argv TRUE 由于该设置为 TRUE ,将总是可以在 CLI SAPI 中访问到 argc (传送给应用程序参数的个数)和 argv (包含有实际参数的数组)。
对于 PHP 4.3.0,在使用 CLI SAPI 时,PHP 变量 $argc 和 $argv 已被注册并且设定了对应的值。而在这之前的版本,这两个变量在 CGI 或者 模块 版本中的建立依赖于将 PHP 的设置选项 register_globals 设为 on 。除了版本和 register_globals 设定以外,可以随时通过调用 $_SERVER 或者 $HTTP_SERVER_VARS 来访问它们。例如: $_SERVER['argv']

注: 这些设置无法在设置文件 php.ini 或任何指定的其它文件中被初始化为其它值。这些默认值被限制在所有其它的设置文件被解析后改变。不过,它们的值可以在程序运行的过程中被改变(尽管对于该运行过程来说,这些设置项是没有意义的)。

为了减轻外壳环境下的工作,我们定义了如下常量:

CLI 专用常量
















常量名称 描 述
STDIN 一个已打开的指向 stdin 的流。可以用如下方法来调用:
[php]
$stdin = fopen ( 'php://stdin' , 'r' );
[/php]
如果想从 stdin 读取一行内容,可以使用
[php]
$line = trim(fgets(STDIN)); // 从 STDIN 读取一行
fscanf ( STDIN , '%d\n' , $number ); // 从 STDIN 读取数字
[/php]
STDOUT 一个已打开的指向 stdout 的流。可以用如下方式来调用:
[php]
$stdout = fopen ( 'php://stdout' , 'w' );
[/php]
STDERR 一个已打开的指向 stderr 的流。可以用如下方式来调用:
[php]
$stderr = fopen('php://stderr' , 'w' );
[/php]


有了以上常量,就无需自己建立指向诸如 stderr 的流,只需简单的使用这些常量来代替流指向:
[bash]
php -r 'fwrite(STDERR, 'stderr\n');'
[/bash]
无需自己来关闭这些流,PHP 会自动完成这些操作。
CLI SAPI 不会 将当前目录改为已运行的脚本所在的目录。
以下范例显示了本模块与 CGI SAPI 模块之间的不同:
[php]
// 名为 test.php 的简单测试程序
echo getcwd(), "\n";
[/php]
在使用 CGI 版本时,其输出为
[bash]
$ pwd
/tmp
$ php-cgi -f another_directory/test.php
/tmp/another_directory
[/bash]
明显可以看到 PHP 将当前目录改成了刚刚运行过的脚本所在的目录。
使用 CLI SAPI 模式,得到:
[bash]
$ pwd
/tmp
$ php -q another_directory/test.php
/tmp
[/bash]
这使得在利用 PHP 编写外壳工具时获得了很大的便利。
注: 可以在命令行运行时给该 CGI SAPI 加上 -C 参数,使其支持 CLI SAPI 的功能。

以下是 PHP 二进制文件 (即 php.exe 程序) 提供的命令行模式的选项参数,随时可以运行带 -h 参数的 PHP 命令来查询这些参数。
Usage: php [options] [-f] [--] [args...]
[text]
php [options] -r <code> [--] [args...]</code>
php [options] [-B <begin_code>] -R <code> [-E <end_code>] [--] [args...]
php [options] [-B <begin_code>] -F <file> [-E <end_code>] [--] [args...]
php [options] -- [args...]
php [options] -a
-a
Run interactively
-c <path>|<file> Look for php.ini file in this directory
-n No php.ini file will be used
-d foo[=bar] Define INI entry foo with value 'bar'
-e Generate extended information for debugger/profiler
-f <file> Parse <file>.
-h This help
-i PHP information
-l Syntax check only (lint)
-m Show compiled in modules
-r <code> Run PHP <code> without using script tags <?..?>
-B <begin_code> Run PHP <begin_code> before processing input lines
-R <code> Run PHP <code> for every input line
-F <file> Parse and execute <file> for every input line
-E <end_code> Run PHP <end_code> after processing all input lines
-H Hide any passed arguments from external tools.
-s Display colour syntax highlighted source.
-v Version number
-w Display source with stripped comments and whitespace.
-z <file> Load Zend extension <file>.
args... Arguments passed to script. Use -- args when first argument
starts with - or script is read from stdin
[/text]

CLI SAPI 模块有以下三种不同的方法来获取要运行的 PHP 代码:
让 PHP 运行指定文件。
[bash]
php my_script.php
php -f my_script.php
[/bash]
以上两种方法 (使用或不使用 -f 参数) 都能够运行给定的 my_script.php 文件。可以选择任何文件来运行,指定的 PHP 脚本并非必须要以 .php 为扩展名,它们可以有任意的文件名和扩展名。
在命令行直接运行 PHP 代码。
[bash]
php -r 'print_r(get_defined_constants());'
[/bash]
在使用这种方法时,请注意外壳变量的替代及引号的使用。
注: 请仔细阅读以上范例,在运行代码时没有开始和结束的标记符!加上 -r 参数后,这些标记符是不需要的,加上它们会导致语法错误。
通过标准输入(STDIN)提供需要运行的 PHP 代码。

以上用法提供了非常强大的功能,使得可以如下范例所示,动态地生成 PHP 代码并通过命令行运行这些代码:
[bash]
some_application | some_filter | php | sort -u < final_output.txt
[/bash]
以上三种运行代码的方法不能同时使用。
和所有的外壳应用程序一样,PHP 的二进制文件 (php.exe 文件) 及其运行的 PHP 脚本能够接受一系列的参数。PHP 没有限制传送给脚本程序的参数的个数 (外壳程序对命令行的字符数有限制,但通常都不会超过该限制) 。传递给脚本的参数可在全局变量 $argv 中获取。该数组中下标为零的成员为脚本的名称 (当 PHP 代码来自标准输入获直接用 -r 参数以命令行方式运行时,该名称为 -) 。另外,全局变量 $argc 存有 $argv 数组中成员变量的个数 (而非传送给脚本程序的参数的个数)。

只要传送给脚本的参数不是以 - 符号开头,就无需过多的注意。向脚本传送以-开头的参数会导致错误,因为 PHP 会认为应该由它自身来处理这些参数。可以用参数列表分隔符 -- 来解决这个问题。在 PHP 解析完参数后,该符号后所有的参数将会被原样传送给脚本程序。

以下命令将不会运行 PHP 代码,而只显示 PHP 命令行模式的使用说明:
[bash]
php -r 'var_dump($argv);' -h
[/bash]
Usage: php [options] [-f] [args...]

# 以下命令将会把-h参数传送给脚本程序,PHP 不会显示命令行模式的使用说明:
[bash]
$ php -r 'var_dump($argv);' -- -h
[/bash]
[text]
array(2) {
[0]=>
string(1) "-"
[1]=>
string(2) "-h"
}
[/text]
除此之外,还有另一个方法将 PHP 用于外壳脚本。可以在写一个脚本,并在第一行以 #!/usr/bin/php 开头,在其后加上以 PHP 开始和结尾标记符包含的正常的 PHP 代码,然后为该文件设置正确的运行属性 (例如: chmod +x test )。该方法可以使得该文件能够像外壳脚本或 PERL 脚本一样被直接执行。
[php]
#!/usr/bin/php
<?php
var_dump ( $argv );
?>
[/php]
假设改文件名为 test 并被放置在当前目录下,可以做如下操作:
[bash]
$ chmod +x test
$ ./test -h -- foo
array(4) {
[0]=>
string(6) "./test"
[1]=>
string(2) "-h"
[2]=>
string(2) "--"
[3]=>
string(3) "foo"
}
[/bash]
正如所看到的,在向该脚本传送以 - 开头的参数时,脚本仍然能够正常运行。

PHP 4.3.3 以来有效的长选项:





































































































选项名称 长名称 说明
-a --interactive
交互式运行 PHP。如果编译 PHP 时加入了 Readline 扩展 (Windows 下不可用), 那
将会得到一个很好的外壳,包括一个自动完成的功能 (例如可以在键入变量名的时候,
按下 TAB 键,PHP 会自动完成该变量名) 以及命令历史记录,可以用上下键来访问。
历史记录存在 ~/.php_history 文件中。
注: 通过 auto_prepend_fileauto_append_file
包含的文件在此模式下会被解析,但有些限制,例如函数必须在被调用之前定义。
-c --php-ini
用该参数,可以指定一个放置 php.ini 文件的目录,或者直接指定一个自定义的
INI 文件 (其文件名可以不是 php.ini ), 例如:
[bash]
$ php -c /custom/directory/ my_script.php
$ php -c /custom/directory/custom-file.ini my_script.php
[/bash]
如果不指定此选项,PHP 将在默认位置搜索文件。
-n --no-php-ini 完全忽略 php.ini 。此参数在 PHP 4.3.0 以后有效。
-d --define 用该参数可以自行设置任何可以在 php.ini 文件中设置的配置选项的值,
其语法为:
[bash]-d configuration_directive[=value][/bash]
# 取值部分被省略,将会把配置选项设为 1
[bash]
$ php -d max_execution_time=
-r '$foo = ini_get("max_execution_time"); var_dump($foo);'
[/bash]
[text]
string(1) "1"
[/text]
# 取值部分为空白,将会把配置选项设为 ;
[bash]
php -d max_execution_time=
-r '$foo = ini_get("max_execution_time"); var_dump($foo);'
[/bash]
[text]
string(0) ""
[/text]

# 配置选项将被设置成为任何 = 字符之后的值
[bash]
$ php -d max_execution_time=20
-r '$foo = ini_get("max_execution_time"); var_dump($foo);'
[/bash]
[text]
string(2) "20"
[/text]
[bash]
$ php
-d max_execution_time=doesntmakesense
-r '$foo = ini_get("max_execution_time"); var_dump($foo);'
[/bash]
[text]
string(15) "doesntmakesense"
[/text]
-e --profile-info 激活扩展信息模式,被用于调试测试。
-f --file
解析并运行 -f 选项给定的文件名。该参数为可选参数,可以省略,
仅指明需要运行的文件名即可。
-h
--help and
--usage
使用该参数,可以得到完整的命令行参数的列表及这些参数作用的简单描述.
-i --info
该命令行参数会调用 phpinfo()函数并显示出结果。如果 PHP 没有正常
工作,建议执行 php -i 命令来查看在信息表格之前或者对应的地方是
否有任何错误信息输出。请注意当使用 CGI 摸索时,输出的内容为 HTML 格式,
因此输出的信息篇幅较大。
-l --syntax-check
参数提供了对指定 PHP 代码进行语法检查的方便的方法。如果成功,则向标准输出
写入 No syntax errors detected in <filename> 字符串,并且外
壳返回值为 0 。 如果失败,则输出 Errors parsing <filename>
以及内部解析器错误信息到标准输 出,同时外壳返回值将别设置为 255 。
该参数将无法检查致命错误 (如未定义函数), 如果也希望检测致命错误,请使
用 -f 参数。
注: 该参数不能和 -r 一同使用。
-m --modules
[bash]
$ php -m
[PHP Modules]
xml
tokenizer
standard
session
posix
pcre
overload
mysql
mbstring
ctype

[Zend Modules]
[/bash]
-r --run
使用该参数可以在命令行内运行单行 PHP 代码。 无需 加上 PHP 的起始和结束
标识符 ( <?php 和 ?>), 否则将会导致语法解析错误。
注: 使用这种形式的 PHP 时,应注意避免和外壳环境进行的命令行参数替换相冲突。
显示语法解析错误的范例
[bash]
$ php -r "$foo = get_defined_constants();"
[/bash]
[text]
Command line code(1) : Parse error - parse error,
unexpected '='
[/text]
这里的问题在于即使使用了双引号 ", sh/bash 仍然实行了参数替换。由于 $foo 没有
被定义,被替换后它所在的位置变成了空字符,因此在运行时,实际被 PHP 读取的
代码为:
[bash]
$ php -r " = get_defined_constants();"
[/bash]
正确的方法是使用单引号 ' 。在用单引号引用的字符串中,变量不会被
sh/bash 还原成其原值。
[bash]
$ php -r '$foo = get_defined_constants(); var_dump($foo);'
[/bash]
[text]
array(370) {
["E_ERROR"]=>
int(1)
["E_WARNING"]=>
int(2)
["E_PARSE"]=>
int(4)
["E_NOTICE"]=>
int(8)
["E_CORE_ERROR"]=>
[...]
[/text]
如果使用的外壳不是 sh/bash,可能会碰到更多问题。请将碰到的 Bug 向
http://bugs.php.net/ 报告。注意,
当试图将 shell 变量用到代码中或者使用反斜线时仍然很容易碰到问题。
注: -r 在 CLI SAPI 中有效,在 CGI SAPI 中无效。
注: 此选项只用于非常基本的用途。因此一些配置指令 (例如 auto_prepend_file 和
auto_append_file) 在此模式下被忽略。
-B --process-begin 在处理 stdin 之前先执行 PHP 代码。PHP 5 新加。
-R --process-code 对每个输入行都执行 PHP 代码。PHP 5 新加。
此模式下有两个特殊变量: $argn 和 $argi 。 $argn 包含 PHP 当前处理的行内容,
而$argi 则包含该行号。
-F --process-file 对每个输入行都执行 PHP 文件。PHP 5 新加。
-E --process-end 在处理完输入后执行的 PHP 代码。PHP 5 新加。
使用 -B, -R 和 -E 选项来计算一个项目总行数的例子。
[bash]
$ find my_proj | php -B '$l=0;'\
-R '$l += count(@file($argn));'\
-E 'echo "Total Lines: $l\n";'
[/bash]
[text]
Total Lines: 37328
[/text]
-s --syntax
-highlight
显示有语法高亮色彩的源代码。
该参数使用内建机制来解析文件并为其生成一个 HTML 高亮版本并将结果写到标准
输出。请注意该过程所做的只是生成了一个 <code> [...] </code>
的 HTML 标记的块,并不包含任何的 HTML 头。
注: 该选项不能和 -r 参数同时使用。
-v --version 将 PHP,PHP SAPI 和 Zend 的版本信息写入标准输出。例如:
[bash]
$ php -v
[/bash]
[text]
PHP 4.3.0 (cli), Copyright (c) 1997-2002 The PHP Group
Zend Engine v1.3.0,Copyright (c) 1998-2002 Zend
Technologies
[/text]
-w --strip 显示除去了注释和多余空白的源代码。
注: 该选项不能和 -r 参数同时使用。
-z --zend
-extension

加载 Zend 扩展库。如果仅给定一个文件名,PHP 将试图从当前系统扩展库的默认
路径(在 Linux 系统下,该路径通常由 /etc/ld.so.conf 指定) 加
载该扩展库。如果用一个绝对路径指定文件名,则不会使用系统的扩展库默认路径。
如果用相对路径指 定的文件名,则 PHP 仅试图在当前目录的相对目录加
载扩展库。

PHP 的命令行模式能使得 PHP 脚本能完全独立于 web 服务器单独运行。如果使用 Unix 系统,需要在 PHP 脚本的最前面加上一行特殊的代码,使得它能够被执行,这样系统就能知道用哪个程序去运行该脚本。在 Windows 平台下可以将 php.exe 和 .php 文件的双击属性相关联,也可以编写一个批处理文件来用 PHP 执行脚本。为 Unix 系统增加的第一行代码不会影响该脚本在 Windows 下的运行,因此也可以用该方法编写跨平台的脚本程序。以下是一个简单的 PHP 命令行程序的范例。
[php title="试图以命令行方式运行的 PHP 脚本(script.php)"]
#!/usr/bin/php
<?php
if ($argc != 2 || in_array($argv [ 1 ], array('--help', '-help', '-h', '-?'))) {
?>
This is a command line PHP script with one option.
Usage:
<?php
echo $argv [0];
?>
<option> can be some word you would like to print out. With the --help, -help, -h, or -? options, you can get this help. </option>
<?php
} else {
echo $argv [1];
}
?>
[/php]
在以上脚本中,用第一行特殊的代码来指明该文件应该由 PHP 来执行。在这里使用 CLI 的版本,因此不会有 HTTP 头信息输出。在用 PHP 编写命令行应用程序时,可以使用两个参数: $argc 和 $argv 。前面一个的值是比参数个数大 1 的整数(运行的脚本本身的名称也被当作一个参数)。第二个是包含有参数的数组,其第一个元素为脚本的名称,下标为数字 0($argv[0])。
以上程序中检查了参数的个数是大于 1 个还是小于 1 个。此外如果参数是 --help , -help , -h 或 -? 时,打印出帮助信息,并同时动态输出脚本的名称。如果还收到了其它参数,将其显示出来。
如果希望在 Unix 下运行以上脚本,需要使其属性为可执行文件,然后简单的运行 script.php echothis script.php -h 。在 Windows 下,可以为此编写一个批处理文件:
运行 PHP 命令行脚本的批处理文件(script.bat)
[bash]
@C:\php\php.exe script.php %1 %2 %3 %
[/bash]
假设将上述程序命名为 script.php ,且 CLI 版的 php.exe 文件放置在 c:\php\cli\php.exe ,该批处理文件会帮助将附加的参数传给脚本程序: script.bat echo this script.bat -h
请参阅 Readline 扩展模块的有关文档,以获取更多的函数的信息。这些函数可以帮助完善 PHP 命令行应用程序。

PHP 伪重载


<? php
// 今天在看书的时候,发现书上有这么一条:函数重载的替代方法————伪重载
//
//确实,在PHP中没有函数重载这个概念,让很多时候我们无法进行一些处理,甚至有时候不得不在函数后面定义好N个参数
//在看到了func_get_arg,func_get_args,func_num_args,这三个函数的时候,你们是不是想起了什么?


function testOne ( $a ) {
echo (' 一个参数就这样 ');
}

function testTwo ( $a , $b ){
echo (' 两个参数的就这样 ');
}

function testThree ($a, $b, $c ) {
echo (' 黑黑,这是三个参数的 ');
}

function test () {
$argNum = func_num_args ();
// 这一段其实可以用 $_arg = func_get_args() 来获得所有的参数,只是要用数组而已,不方便我下面的表达,呵呵
for ( $i = 0 ; $i < $argNum ; $i ++ ) {
$_arg_ { $i } = func_get_arg ( $i );
}
switch ( $argNum ) {
case 1 :
testOne( $_arg_1 );
break ;
case 2 :
testTwo( $_arg_1, $_arg_2 );
break ;
case 3 :
testThree( $_arg_1, $_arg_2, $_arg_3 );
break ;
default :
echo ( ' 这是没有参数的情况 ' );
break ;
}
}

/* *
* 例子的实现
* */
test();
echo ( '
' );
test( 1 );
echo ( '
' );
test( 1 , 2 );
echo ( '
' );
test( 1 , 2 , 3 );

// 这些只是在函数中的运用,其实最主要的还是在类中的运用
//如果这些用到类里面我就不需要担心构造函数是否有几个参数了,不是吗?

class test {
var $a = 0 ;
var $b = 0 ;

function test () {
$argNum = func_num_args ();
$_arg = func_get_args ();
switch ($argNum ) {
case 1 :
$this -> test1( $_arg [ 0 ] );
break ;
case 2 :
$this -> test2( $_arg [ 0 ] , $_arg [ 1 ]);
break ;
default :
$this -> a = 0 ;
$this -> b = 1 ;
break ;
}
}

function test1 ($a) {
$this -> a = $a ;
}

function test2 ($a, $b) {
$this -> a = $a ;
$this -> b = $b ;
}
}
?>

2009年11月14日星期六

PHP register_shutdown_function函数

脚本时常死掉,而且并不总是那么好看. 我们可不想给用户显示一个致命错误, 又或者一个空白页(在display_errors设为off的情况下) . PHP中有一个叫做 register_shutdown_function 的函数, 可以让我们设置一个当执行关闭时可以被调用的另一个函数. 也就是说当我们的脚本执行完成或意外死掉导致 PHP 执行即将关闭时, 我们的这个函数将会被调用. 所以, 我们可以使用在脚本开始处设置一个变量为false, 然后在脚本末尾将之设置为true的方法, 让PHP关闭回调函数检查脚本完成与否. 如果我们的变量仍旧是false,我们就知道脚本的最后一行没有执行, 因此它肯定在程序执行到某处死掉了. 我准备了一个非常基本的例子, 可以演示在一个致命错误需要显示时, 你应该怎么给用户一些合适的反馈. 你可以通过关闭致命错误的显示(译注: 可以设置display_errorserror_reporting), 让例子看起来好看些.
[php]
$clean = false;
function shutdown_func(){
global $clean;
if (!$clean){
die("not a clean shutdown");
}
return false;
}

register_shutdown_function("shutdown_func");

$a = 1;
$a = new FooClass(); //将因为致命错误而失败
$clean = true;
[/php]

正如你所看到,如果关闭回调函数运行时, clean变量没有被设为true, shutdown_func 函数将会打印出一些东西. 这个东西可以包装成一个类(不使用全局变量).

PHP提供 register_shutdown_function() 这个函数, 能够在脚本终止前回调注册的函数,也就是当 PHP 程序执行完成后执行的函数。
例子:
[html]
<!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">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>register_shutdown_function示例</title>
</head>
<body>
<?php
$starttime = microtime(true);
function Test() {
$starttime = microtime(true);
if(!file_exists('Test.txt')) {
//判断如果文件不存在!!
$Str = fopen('Test.txt', 'w+');
fwrite($Str, '在最后写进来的时间: $starttime');
fclose($Str);
echo "创建完成!创建时间: $starttime";
} else {
//如果存在;
echo '文件已经存在';
}
}
register_shutdown_function('Test');
echo "程序开始:" . $starttime . "<br>";

for($i = 0; $i < 1000; $i++){
echo "Echo<br/>";
}
exit;
?>
</body>
</html>
[/html]

register_shutdown_function 的作用是指定当本页面所有脚本执行完成之后执行的函数。
[php]
function aaa() {
echo '创建文件';
//此处要用绝对路径,用相对路径即无效。原因请看后面的解释
if($test= fopen('D:/test.txt', 'w+')) {
fwrite($test, 'you are write after exit');
fclose($test);
}
}

register_shutdown_function('aaa');  
// 函数名称无需带括号,用引号包住即可. 当本页面所有语句都执行完成,或者超时aa函数。
exit();
[/php]

register_shutdown_function 执行机制是: PHP 把要调用的函数调入内存。当页面所有 PHP 语句都执行完成时,再调用此函数。注意,在这个时候从内存中调用,不是从 PHP 页面中调用,所以上面的例子不能使用相对路径,因为 PHP 已经当原来的页面不存在了, 就没有什么相对路径可言。

注意: register_shutdown_function 是指在执行完所有 PHP 语句后再调用函数, 不要理解成客户端关闭流浏览器页面时调用函数。
可以这样理解调用条件:
1. 当页面被用户强制停止时
2. 当程序代码运行超时时
3. 当 PHP 代码执行完成时

PHP call_user_func和call_user_func_array详解

call_user_func 函数类似于一种特别的调用函数的方法,使用方法如下:
[php]
function a($b, $c) {
echo $b;
echo $c;
}
call_user_func('a', "111","222");
call_user_func('a', "333","444");
[/php]

//显示 111 222 333 444

调用类内部的方法用的是array
[php]
class a {
function b($c) {
echo $c;
}
}
call_user_func(array("a", "b"), "111");
[/php]
//显示 111

call_user_func_array 函数和 call_user_func 很相似,只不过是换了一种方式传递了参数,让参数的结构更清晰:
[php]
function a($b, $c) {
echo $b;
echo $c;
}
call_user_func_array('a', array("111", "222"));
[/php]
//显示 111 222

call_user_func_array 函数也可以用于调用类内部方法
[php]
Class ClassA {
function bc($b, $c) {
$bc = $b + $c;
echo $bc;
}
}
call_user_func_array(array('ClassA','bc'), array("111", "222"));
[/php]
//显示 333

call_user_func 函数和 call_user_func_array 函数支持引用
[php]
function a(&$b) {
$b++;
}
$c = 0;
call_user_func('a', &$c);
echo $c; //显示 1
call_user_func_array('a', array(&$c));
echo $c; //显示 2
[/php]
PHP手册上关于这两个函数的定义:
mixed call_user_func (callback function [, mixed parameter [, mixed ...]])

Call a user defined function given by the function parameter. Take the following:

[php]
function barber($type) {
echo "You wanted a $type haircut, no problem";
}
call_user_func('barber', "mushroom");
call_user_func('barber', "shave");
[/php]
Object methods may also be invoked statically using this function by passing array($objectname, $methodname) to the function parameter.
[php]
class myclass {
function say_hello() {
echo "Hello!\n";
}
}

$classname = "myclass";

call_user_func(array($classname, 'say_hello'));
[/php]

注: Note that the parameters for call_user_func() are not passed by reference.
[php]
function increment(&$var) {
$var++;
}

$a = 0;
call_user_func('increment', $a);
echo $a; // 0

call_user_func_array('increment', array(&$a)); // You can use this instead
echo $a; // 1
[/php]

mixed call_user_func_array( callback function, array param_arr )
Call a user defined function given by function, with the parameters in param_arr. For example:

call_user_func_array() example
[php]
function debug($var, $val) {
echo "***DEBUGGING\n VARIABLE: $var\nVALUE:";
if (is_array($val) || is_object($val) || is_resource($val)) {
print_r($val);
} else {
echo "\n$val\n";
}
echo "***\n";
}

$c = mysql_connect();
$host = $_SERVER["SERVER_NAME"];

call_user_func_array('debug', array("host", $host));
call_user_func_array('debug', array("c", $c));
call_user_func_array('debug', array("_POST", $_POST));
[/php]
这篇其实转载的(不好意思,忘了是来自哪...)上面有提到说,最后的这个函数有点相当于重载,看了下,确实有那么点重载的意思...

PHP 检测父类子类函数

string get_class(object obj)
返回对象实例 obj 所属类的名字。如果 obj 不是一个对象则返回 FALSE。
注: get_class() 返回用户定义的类名的小写形式。在 PHP 扩展中定义的类则返回其原有的名字。

string get_parent_class(mixed obj)
如果 obj 是对象,则返回对象实例 obj 所属类的父类名。
如果 obj 是字符串,则返回以此字符串为名的类的父类名。此功能是在 PHP 4.0.5 中增加的。

bool is_subclass_of(object object, string class_name)
如果对象 object 所属类是类 class_name 的子类,则返回 TRUE,否则返回 FALSE。

bool is_a(object object, string class_name)
如果对象是该类或该类是此对象的父类则返回 TRUE,否则返回 FALSE。

去除PHP文件中UTF8的BOM

[php]
if (isset($_GET['dir'])) {
//config the basedir
$basedir = $_GET['dir'];
} else {
$basedir = '.';
}
$auto = 1;
checkdir($basedir);
function checkdir($basedir){
  if ($dh = opendir($basedir)) {
    while (($file = readdir($dh)) !== false) {
      if ($file != '.' && $file != '..'){
        if (!is_dir($basedir."/".$file)) {
echo "filename: $basedir/$file ".checkBOM("$basedir/$file") . " <br />";
        } else {
$dirname = $basedir . "/" . $file;
checkdir($dirname);
        }
      }
    }
closedir($dh);
  }
}
function checkBOM ($filename) {
  global $auto;
  $contents = file_get_contents($filename);
  $charset[1] = substr($contents, 0, 1);
  $charset[2] = substr($contents, 1, 1);
  $charset[3] = substr($contents, 2, 1);
  if (ord($charset[1]) == 239 && ord($charset[2]) == 187 && ord($charset[3]) == 191) {
    if ($auto == 1) {
      $rest = substr($contents, 3);
      rewrite ($filename, $rest);
      return ("<font color=red>BOM found, automatically removed.</font>");
    } else {
      return ("<font color=red>BOM found. </font>");
    }
  }
  else return ("BOM Not Found.");
}
function rewrite ($filename, $data) {
  $filenum = fopen($filename, "w");
  flock($filenum, LOCK_EX);
  fwrite($filenum, $data);
  fclose($filenum);
}
[/php]

附:BOM 解释
BOM: Byte Order Mark
UTF-8 BOM又叫UTF-8 签名, 其实UTF-8 的BOM对UFT-8没有作用, 是为了支持UTF-16, UTF-32才加上的BOM, BOM签名的意思就是告诉编辑器当前文件采用何种编码,方便编辑器识别, 但是BOM虽然在编辑器中不显示, 但是会产生输出, 就像多了一个空行。

PHP脚本和COM

string COM::COM ( string module_name [, string server_name [, int codepage]] )
COM 类构造函数。参数:

  • module_name 被请求组件的名字或 class-id。

  • server_name DCOM 服务器的名字,组件在此服务器上被取用。如果是 NULL,则假定是 localhost。想要允许 DCOM,必须将 php.ini 中的 com.allow_dcom 设为 TRUE。

  • codepage 指定用于将 PHP 字符串(php-strings)转换成 UNICODE 字符串(unicode-strings)的代码页,反之亦然。可用的值为 CP_ACP, CP_MACCP, CP_OEMCP, CP_SYMBOL, CP_THREAD_ACP, CP_UTF7CP_UTF8



[php]
// 启动 word
$word = new COM("word.application") or die("Unable to instanciate Word");
print "Loaded Word, version {$word->Version}\n";

//将其置前
$word->Visible = 1;

//打开一个空文档
$word->Documents->Add();

//随便做些事情
$word->Selection->TypeText("This is a test...");
$word->Documents[1]->SaveAs("Useless test.doc");

//关闭 word
$word->Quit();

//释放对象
$word->Release();
$word = null
[/php]

[php]
$conn = new COM("ADODB.Connection") or die("Cannot start ADO");
$conn->Open("Provider=SQLOLEDB; Data Source=localhost;
Initial Catalog=database; User ID=user; Password=password");

$rs = $conn->Execute("SELECT * FROM sometable"); // 记录集

$num_columns = $rs->Fields->Count();
echo $num_columns . "\n";

for ($i=0; $i < $num_columns; $i++) {
$fld[$i] = $rs->Fields($i);
}

$rowcount = 0;
while (!$rs->EOF) {
for ($i=0; $i < $num_columns; $i++) {
echo $fld[$i]->value . "\t";
}
echo "\n";
$rowcount++; // rowcount 自增
$rs->MoveNext();
}

$rs->Close();
$conn->Close();

$rs->Release();
$conn->Release();

$rs = null;
$conn = null;
[/php]

COM:微软的组件对象模型

2009年11月12日星期四

overflow:hidden

很久以来
一直都对于overflow的用法很是模糊
也曾多次看过手册
却总是不太明白
昨日真真切切的感受了一回~
原来overflow 为hidden时是不显示超过对象尺寸的内容
....

2009年11月11日星期三

pre 设置自动换行


pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}

MySQL 排序

[sql]
SELECT user_id, (
SELECT COUNT(1) FROM user_scores WHERE score >= (
SELECT score
FROM user_scores
WHERE user_id = '" . $user_id . "' AND " . $condition . " ORDER BY score DESC LIMIT 1
)
AND " . $condition . "
) AS rank
FROM user_scores
WHERE user_id = '" . $user_id . "' AND " . $condition;
[/sql]

《高性能 MySQL》第二版,介绍用户自定义变量时,有列举一排序,笔记于这里
[sql]
SET @curr_cnt := 0, @prev_cnt := 0, @rank := 0;
SELECT actor_id,
@curr_cnt := AS cnt,
@rank := IF(@prev_cnt <> @curr_cnt, @rank + 1, @rank) AS rank,
@prev_cnt := @curr_cnt AS dummy
FROM (
SELECT actor_id, COUNT(*) AS cnt,
FROM sakia.film_actor
GROUP BY actor_id
ORDER BY cnt DESC
LIMIT 10
) AS der;
[/sql]

2009年11月10日星期二

判断浏览器函数

[js]
function justify_browser() {
var Sys = {};
var ua = navigator.userAgent.toLowerCase();
var s;
(s = ua.match(/msie ([\d.]+)/)) ? Sys.ie = s[1] :
(s = ua.match(/firefox\/([\d.]+)/)) ? Sys.firefox = s[1] :
(s = ua.match(/chrome\/([\d.]+)/)) ? Sys.chrome = s[1] :
(s = ua.match(/opera.([\d.]+)/)) ? Sys.opera = s[1] :
(s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] : 0;

if (Sys.ie) return 1;
if (Sys.firefox) return 2;
if (Sys.chrome) return 3;
if (Sys.opera) return 4;
if (Sys.safari) return 5;
}
[/js]

Javascript中使用document.getElement

页面具有 DTD (或者说指定了 DOCTYPE) 时,使用 document.documentElement。
页面不具有 DTD (或者说没有指定了 DOCTYPE) 时,使用 document.body。
在 IE 和 Firefox 中均是如此。
为了兼容,可以使用如下代码:
[js]
var scrollTop = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
|| 0;
[/js]

offsetTop、offsetLeft、offsetWidth、offsetHeight、style中的样式

假设 obj 为某个 HTML 控件

  • obj.offsetTop 指 obj 距离上方或上层控件的位置, 整型, 单位 px.

  • obj.offsetLeft 指 obj 距离左方或上层控件的位置, 整型, 单位 px.

  • obj.offsetWidth 指 obj 控件自身的宽度, 整型, 单位 px.

  • obj.offsetHeight 指 obj 控件自身的高度, 整型, 单位 px.



我们对前面提到的 "上方或上层" 与 "左方或上层" 控件作个说明.

例如:
[html]
<div id="tool">
<input type="button" value="提交">
<input type="button" value="重置">
</div>
[/html]

提交 按钮的 offsetTop提交 按钮距 tool 层上边框的距离, 因为距其上边最近的是 tool 层的上边框.
重置 按钮的 offsetTop重置 按钮距 tool 层上边框的距离, 因为距其上边最近的是 tool 层的上边框.

提交 按钮的 offsetLeft提交 按钮距 tool 层左边框的距离, 因为距其左边最近的是 tool 层的左边框.
重置 按钮的 offsetLeft重置 按钮距 提交 按钮右边框的距离, 因为距其左边最近的是 提交 按钮的右边框.


offsetTop 可以获得 HTML 元素距离上方或外层元素的位置, style.top 也是可以的, 二者的区别是:

  • offsetTop 返回的是数字, 而 style.top 返回的是字符串, 除了数字外还带有单位 px.

  • offsetTop 只读, 而 style.top 可读写.

  • 如果没有给 HTML 元素指定过 top 样式, 则 style.top 返回的是空字符串.



offsetLeft 与 style.left, offsetWidth 与 style.width, offsetHeight 与 style.height 也是同样道理.
offsetTop, offsetLeft 在IE6, IE7下不会工作.

  • scrollHeight 获取对象的滚动高度.

  • scrollLeft 设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离

  • scrollTop 设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离

  • scrollWidth 获取对象的滚动宽度

  • offsetHeight 获取对象相对于版面或由父坐标 offsetParent 属性指定的父坐标的高度

  • offsetLeft 获取对象相对于版面或由 offsetParent 属性指定的父坐标的计算左侧位置

  • offsetTop 获取对象相对于版面或由 offsetTop 属性指定的父坐标的计算顶端位置

  • event.clientX 相对文档的水平座标

  • event.clientY 相对文档的垂直座标

  • event.offsetX 相对容器的水平坐标

  • event.offsetY 相对容器的垂直坐标

  • document.documentElement.scrollTop 垂直方向滚动的值

  • event.clientX+document.documentElement.scrollTop 相对文档的 水平座标+垂直方向滚动的量



以上主要指IE之中, FireFox差异如下:
IE6.0, FF1.06+

  • clientWidth = width + padding

  • clientHeight = height + padding

  • offsetWidth = width + padding + border

  • offsetHeight = height + padding + border


IE5.0/5.5

  • clientWidth = width - border

  • clientHeight = height - border

  • offfsetWidth = width

  • offsetHeight = height


另外, CSS中的 margin 属性, 与clientWidth, offsetWidth, clientHeight, offsetHeight均无关

对于所有的属性, 这里有详细的说明并有实例.

在IE 浏览器下才会识别的HTML代码


<!--[if lte IE 6]><table><tr><td style="position:absolute;left:0;top:0;"><![endif]-->


如上HTMTL code: <!--[if lte IE 6]<![endif]-->中间的代码只有在IE6才会有作用
若是要写只有在IE7下才会作用的,则把6改为7就是

2009年11月6日星期五

如果再回到从前

如果再回到从前
所有一切重演
我是否会明白生活重点
不怕挫折打击
没有空虚埋怨
让我看的更远
...


总是有着莫名的情绪让我无法控制
却有无法找出理由
源头好像永远都只有一个
不管周围是怎样
总是在转几个弯后
又能回到我所在乎的
不知道
到底这是我太敏感
还是太脆弱
神经对于其他的越来越麻木
独处的时候
总是陷入深深的茫然中~
...

2009年11月4日星期三

HTML Autocomplete

1. 有时候我们并不需要AutoComplete,例如需要用户自己再次输入而非自动完成。只要将所在表单元素的autocomplete属性设置为off即可。
<form>
请双击文本框 <input type="text" name="wd" autocomplete="off"> <input type="text" name="email" autocomplete="off">
</form>

2. 如果所有表单元素都不想使用autocomplete功能

<form autocomplete="off">
请双击文本框 <input type="text" name="wd"> <input type="text" name="email">
</form>

2009年11月3日星期二

用于jQuery Ajax调用时显示加载进度

[js]
function show(tips){
var mainColor = "#369";
//创建一个div对象(背景层)
var bgObj = document.createElement("div");
bgObj.setAttribute("id","tips_a");
document.body.appendChild(bgObj);
bgObj.style.position = "absolute";
bgObj.style.zIndex = "9998";
bgObj.style.background = "#777";
bgObj.style.filter = "alpha(opacity=50)";
bgObj.style.opacity = "0.5";
bgObj.style.top = "0";
bgObj.style.left = "0";
bgObj.style.width = document.documentElement.scrollWidth + "px";
bgObj.style.height = document.documentElement.scrollHeight + "px";
//创建一个div对象(提示框)
var tsObj = document.createElement("div");
tsObj.setAttribute("id","tips_b");
document.body.appendChild(tsObj);
tsObj.style.position = "absolute";
tsObj.style.zIndex = "9999";
tsObj.style.background = "#fff";
tsObj.style.border = "1px solid " + mainColor;
tsObj.style.textAlign = "center";
//创建一个h4对象(标题栏/关闭)
var cls = document.createElement("h4");
cls.setAttribute("id","tips_t");
document.getElementById("tips_b").appendChild(cls);
cls.style.textAlign = "right";
cls.style.margin = "0";
cls.style.padding = "5px";
cls.style.background = mainColor;
//创建一个span对象(关闭×)
var cls_gb = document.createElement("span");
document.getElementById("tips_t").appendChild(cls_gb);
cls_gb.style.color = "#fff";
cls_gb.style.font = "12px 宋体";
cls_gb.style.cursor = "pointer";
cls_gb.innerHTML = "×";
cls_gb.onclick = hide;
//创建一个div对象(提示信息)
var strObj = document.createElement("div");
document.getElementById("tips_b").appendChild(strObj);
strObj.style.padding = "15px 30px";
strObj.style.font = "12px 宋体";
strObj.style.lineHeight = "21px";
strObj.innerHTML = tips;
//创建一个input对象(提示框按钮/关闭)
var btn = document.createElement("input");
btn.setAttribute("type","button");
btn.setAttribute("value","关闭");
document.getElementById("tips_b").appendChild(btn);
btn.style.border = 0;
btn.style.color = "#fff";
btn.style.background = mainColor;
btn.style.margin = "0 15px 15px 15px";
btn.style.padding = "0 8px";
btn.onclick = hide;
//定位提示层top/left
tsObj.style.top = document.documentElement.scrollTop + (document.documentElement.clientHeight -
tsObj.clientHeight)/2 + "px";
tsObj.style.left = (document.documentElement.clientWidth - tsObj.clientWidth)/2 + "px";
//删除提示层
function hide(){
document.getElementById("tips_t").removeChild(cls_gb);//删除标题栏里面的关闭×
document.getElementById("tips_b").removeChild(cls);//删除提示框里面的标题栏
document.getElementById("tips_b").removeChild(strObj);//删除提示框里面的提示信息
document.getElementById("tips_b").removeChild(btn);//删除提示框里面的关闭按钮
document.body.removeChild(tsObj);//删除提示框
document.body.removeChild(bgObj);//删除背景层
}
}
//创建一个div对象(背景层)
var bgObj = document.createElement("div");
//创建一个div对象(提示框)
var tsObj = document.createElement("div");
//创建一个div对象(提示信息)
var strObj = document.createElement("div");
//弹出提示层背景变灰不可操作
function showloading(){
var mainColor = "#369";
bgObj.setAttribute("id","tips_a");
document.body.appendChild(bgObj);
bgObj.style.position = "absolute";
bgObj.style.zIndex = "9998";
bgObj.style.background = "#777";
bgObj.style.filter = "alpha(opacity=50)";
bgObj.style.opacity = "0.5";
bgObj.style.top = "0";
bgObj.style.left = "0";
bgObj.style.width = document.documentElement.scrollWidth + "px";
bgObj.style.height = document.documentElement.scrollHeight + "px";
tsObj.setAttribute("id","tips_b");
document.body.appendChild(tsObj);
tsObj.style.position = "absolute";
tsObj.style.zIndex = "9999";
tsObj.style.background = "#fff";
tsObj.style.border = "1px solid " + mainColor;
tsObj.style.textAlign = "center";
document.getElementById("tips_b").appendChild(strObj);
strObj.style.padding = "15px 30px";
strObj.style.font = "12px 宋体";
strObj.style.lineHeight = "21px";
strObj.innerHTML = '<div>正在进行后台操作,请耐心等待……</div>
<img border="0" src="http://www.livingelsewhere/images/p4p_loading.gif" />';
//定位提示层top/left
tsObj.style.top = document.documentElement.scrollTop + (document.documentElement.clientHeight -
tsObj.clientHeight)/2 + "px";
tsObj.style.left = (document.documentElement.clientWidth - tsObj.clientWidth)/2 + "px";
}
//删除提示层
function hideloading(){
$("#tips_b").remove();
$("#tips_a").remove();
}
[/js]

调用:
[js]
$(window).ajaxStart(function(){
showloading();
});
$(window).ajaxStop(function(){
hideloading();
[/js]

2009年11月2日星期一

MYSQL时间戳转化为一般时间格式

UNIX_TIMESTAMP(date)
如果没有参数调用,返回一个Unix时间戳记(从'1970-01-01 00:00:00'GMT开始的秒数)。如果 UNIX_TIMESTAMP( ) 用一个date参数被调用,它返回从 '1970-01-01 00:00:00' GMT 开始的秒数值。date可以是一个DATE字符串, 一个 DATETIME字符串, 一个TIMESTAMP或以 YYMMDD 或 YYYYMMDD 格式的本地时间的一个数字。
[sql]
mysql> SELECT UNIX_TIMESTAMP();
-> 882226357
mysql> SELECT UNIX_TIMESTAMP('1997-10-04 22:23:00');
-> 875996580
[/sql]
当UNIX_TIMESTAMP被用于一个TIMESTAMP列,函数将直接接受值,没有隐含的 string-to-unix-timestamp 变换。

FROM_UNIXTIME(unix_timestamp)
以 YYYY-MM-DD HH:MM:SS 或 YYYYMMDDHHMMSS 格式返回 unix_timestamp 参数所表示的值,取决于函数是在一个字符串还是或数字上下文中被使用。
[sql]
mysql> SELECT FROM_UNIXTIME(875996580);
-> '1997-10-04 22:23:00'
mysql> SELECT FROM_UNIXTIME(875996580) + 0;
-> 19971004222300
[/sql]

FROM_UNIXTIME(unix_timestamp, format)
返回表示 Unix 时间标记的一个字符串,根据 format 字符串格式化。format 可以包含与DATE_FORMAT( )函数列出的条目同样的修饰符。
[sql]
mysql> select FROM_UNIXTIME(UNIX_TIMESTAMP(), '%Y %D %M %h:%i:%s %x');
[/sql]