php动物书总结07-08
最近看了PHP编程动物书,在此记录下7-8章内容要点。
7.Web技术
PHP可以用在命令行和GUI脚本中,但网页是PHP的主要用途。一个动态网页包含表单、会话和一些重定向(上传文件、发送cookie)等功能。
HTTP协议
HTTP协议支配网页浏览器如何从服务器请求文件以及服务器如何发回文件
HTTP请求头:
GET /index.html HTTP/1.1 //请求方式 文档地址 HTTP版本 User-Agent: Mozilla/5.0(Windows 200;u) Opera6.0[en] //浏览器信息 Accept:image/gif, image/jpeg, text/*, */* //浏览器可接受的MIME类型 HTTP响应头: HTTP1.1 200 ok //HTTP版本 状态码 HTTP版本 Server: Apache/2.2.14(Ubuntu) //服务器信息 Content-Type: text/html //响应文档MIME类型 Content-Length: 1845 //响应体长度
变量
服务器信息、请求信息(请求参数、cookie)以不同方式在PHP中访问,这些信息放在一起称为EGPCS(environment/GET/POST/Cookie/Server)。
PHP中包含EGPCS的6个全局数组:$_ENV、$_GET、$_POST、$_FILES、$_COOKIE、$_SERVER
服务器信息:$_SERVER包含大量的服务器信息,大部分信息来自CGI规范要求的环境变量(http://bit.ly/Vw912h)。
CGI协议
CGI协议定义了Web服务器与CGI程序交互的数据格式。请求到来时,Web容器将解析后的数据按照约定的格式传递给CGI程序,CGI程序处理完后将结果按照约定格式返回给Web服务器。一个CGI程序是一个进程,FastCGI是CGI程序的多进程版本,Web服务器是CGI程序运行的容器和环境。PS:Java Servlet是另一种服务端程序与Web容器交互的协议。
处理表单
(1)表单方法
传递表单数据的两种http请求方法:GET和POST。表单通过form标签的method属性指定方法。
一个GET请求会把表单参数编码到URL中,叫做查询串,跟在?后面的就是查询串。一个POST请求在HTTP请求的主体中传递表单参数,URL不变。
GET用于查询,POST用于修改(包括添加)。$_SERVER['REMOTE_METHOD']保存http请求方法类型
(2)表单参数
可以在PHP代码中使用$_GET、$_POST、$_FILES数组来访问表单参数,键名是参数名,值是参数值
(3)自处理表单
1. GET方式请求时显示表单,POST方式请求时显示处理结果
<!DOCTYPE html> <html> <head> <title>Temperature Conversion 1</title> </head> <body> <?php if ($_SERVER['REMOTE_METHOD'] == 'GET') { ?> <form method="POST" action="<?php echo $_SERVER['PHP_SELF'] ?>"> Fahrenheit: <input type="text" name="fahrenheit"/><br> <input type="submit" value="Convert to Celsius"> </form> <?php
} elseif($_SERVER['REMOTE_METHOD'] == 'POST') { $fahrenheit = $_POST['fahrenheit']; $celsius = ($fahrenheit - 32) * 5 / 9; printf("%.2F is %.2fC", $fahrenheit, $celsius); } else { die('This script only works with GET and POST requests.'); } ?> </body> </html>
2. 根据是否提供了某个参数来决定显示表单还是进行处理
<!DOCTYPE html> <html> <head> <title>Temperature Conversion 2</title> </head> <body> <?php $fahrenheit = $_GET['fahrenheit']; if (is_null($fahrenheit)) { ?> <form method="GET" action="<?php echo $_SERVER['PHP_SELF'] ?>"> Fahrenheit: <input type="text" name="fahrenheit"/><br> <input type="submit" value="Convert to Celsius"/> </form> <?php } else { $celsius = ($fahrenheit - 32) * 5 / 9; printf("%.2fF is %.2fC", $fahrenheit, $celsius); } ?> </body> </html>
(4)粘性表单
表单默认值为上一次填的值
<!DOCTYPE html> <html> <head> <title>Temperature Conversion 3</title> </head> <body> <?php $fahrenheit = $_GET['fahrenheit']; ?> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET"> Fahrenheit temperature: <input type="text" name="fahrenheit" value="<?php echo $fahrenheit ?>"><br> <input type="submit" value="Convert to Celsius"> </form> <?php if (!is_null($fahrenheit)) { $celsius = ($fahrenheit - 32) * 5 / 9; printf("%.2fF is %.2fC", $fahrenheit, $celsius); }?> </body>
(5)多值参数
用select标签创建的HTML选择列表允许有多个选项,要确保PHP能够识别浏览器传递的多值,你需要在表单中使用[]结尾,例如:
<select name="languages[]"> <option name="c">C</option> <option name="c++">C++</option> <option name="php">PHP</option> <option name="perl">Perl</option> </select>
现在,当用户提交表单,$_GET['languages']会包含数组而不是字符串,这个数组包含用户选择的值
相同的技术可以用于任何传递多值的字段,如下使用多选框传递多值:
<!DOCTYPE html> <html> <head> <title>Personality</title> </head> <body> <form action="<?php $_SERVER['PHP_SELF'] ?>" method="GET"> Select your personality attributes:<br> <input type="checkbox" name="attributes[]" value="perky"/>Perky<br> <input type="checkbox" name="attributes[]" value="morose">Morose<br> <input type="checkbox" name="attributes[]" value="thinking">Thinking<br> <input type="checkbox" name="attributes[]" value="feeling">Feeling<br> <input type="checkbox" name="attributes[]" value="thrifty">Thrift<br> <input type="checkbox" name="attributes[]" value="shopper">Shopper<br> <input type="submit" value="Record my personality!"> </form> <?php if (array_key_exists('attributes', $GET)) { $description = join(' ', $_GET['attributes']); echo "You have a {$description} personality." } ?> </body> </html>
(6)文件上传
要处理文件上传(现代浏览器都支持),可以使用$_FILES数组。下面代码展示了一个上传文件页面:
<form enctype="multipart/form-data" action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST"> <input type="hidden" name="MAX_FILE_SIZE" value="10240"/> File name: <input type="file" name="toProcess"/> <input type="submit" value="Upload"> </form>
PS:1.上传文件表单enctype="multipart/form-data"(默认是application/x-www-form-urlencoded)2.上传文件表单使用POST方法
文件上传的最大风险是文件太大,PHP有两种方式来预防这个问题:php.ini中的upload_max_filesize选项给出了最大上传文件尺寸(默认2M);在文件字段前加一个名为MAX_FILE_SIZE的字段,PHP会使用它作为软限制。
PS:1.应该在浏览器提交请求之前,使用js判断文件大小是否符合要求。2.Nginx等服务器对于请求体的大小有限制,上传文件的请求体可能超过限制导致上传失败。
$_FILES中的元素key是上传文件的name(上面代码中是toProcess),value是表示上传文件信息的关联数组。value键名有:
name: 文件路径,跟客户端机器有关,如windows机器D:\aa\aa.txt
type: 文件MIME类型
size: 文件大小
tmp_name: 上传文件临时名,如果用户尝试上传一个很大的文件,名字会设置为none
检测文件是否上传成功
if (is_uploaded_file($_FILES['toProcess']['tmp_name'])) { //成功上传 }
文件存储的默认临时目录可以在php.ini中用upload_tmp_dir选项来指定。
move_uploaded_file($_FILE['toProcess']['tmp_name'], $path); //移动上传文件到指定路径
上传文件脚本运行结束后,临时文件会被删除。
(7)表单验证
客户端用JS对输入数据做初始验证,PHP对输入数据做二次验证
<!DOCTYPE html> <html> <head> <title>Validation</title> </head> <body> <?php $name = $_POST['name']; $email = $_POST['email']; $filename = $_POST['filename']; $tried = ($_POST['tried'] == 'yes'); if ($tried) { $validated = (!empty($name) && is_email($email) && is_file($file)) if (!$validated) { ?> <p>The name,email or filename format error, please fix them and continue.</p> <?php} } if ($tried && $validated) { echo "<p>The validation is success.</p>" } ?> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST"> Name: <input type="text" name="name" value="<?php =$name ?>"/><br> Email: <input type="text" name="email" value="<?php =$email ?>"/><br> Filename: <input type="text" name="filename" value="<?php =filename ?>"/><br> <input type="submit" value="Continue"> </form> </body> </html>
内容返回
(1)设置响应头
很多网页应用程序不用设置http响应头,然而如果想发回非html页面、设置页面过期时间、重定向客户端或者生成http错误,需要使用header函数。
头部必须在主体发送之前设置,header()、setcookie()的所有调用必须在输出内容之前,如果是html内容,必须在所有html标签之前。
PS:可以使用内容缓存,ob_start()/ob_end_flush()
(2)不同的内容类型
Content-Type定义返回文档的类型,通常为text/html。'text/plain'会强制浏览器把页面当做纯文本,后续我们会用不同的Content-Type来生成图片和pdf文件。
(3)重定向
发送到浏览器一个新的url,被称为重定向,通过设置Location头字段来完成。
header('Location: http://www.example.com'); exit(); //通常重定向后立即退出,不再输出其他内容
维护状态
http是无状态的协议,意味着一旦服务器完成客户端的请求,它们之间的连接就会关闭。换言之,无法让服务器识别一系列请求源自同一客户端。但在请求之间追踪状态有时很重要,比如购物车程序。
追踪状态的方法:表单隐藏域、url重写、cookie。使用表单隐藏域需要把所有链接做成表单的形式,实际不常用。
(1)cookie
1. setcookie(name, value [,expire,path,domain,secure]); name:cookie名,可以设置多个不同名的cookie value:cookie值,不能出超过3.5k expire: 过期时间,unix_timestamp对应的整数,单位秒。不指定过期时间浏览器退出时cookie消失 path: 访问该路径下的页面时携带cookie domain: 访问该域名url时携带cookie secure: 在https下传输cookie,默认false
2. 当cookie被发回服务端时,使用$_COOKIE访问cookie。
(2)会话
PHP内建了会话系统(Session)来帮助我们追踪状态,一个会话表示的是来自同一用户的请求。每个初次访问的客户都会分配一个会话ID,该ID存储到cookie中,叫做PHPSESSID,cookie不可用的情况下则通过URL传播,会话内容保存在服务端,每次可以根据ID读写。
<?php session_start(); //开启一个会话 $_SESSION['hits'] = $_SESSION['hits'] + 1; session_id(); //返回会话ID session_destroy(); //销毁当前会话,清空所有数据
PS:会话能做的只是针对一个用户这个范围的事情,比如统计该用户浏览时间、统计该用户访问页面数等,不能处理更大范围的工作,比如统计页面的整体访问量。
自定义存储
PHP会把会话信息存储到服务器临时文件夹,每个会话的变量序列化之后存储到不同文件,可以在php.ini中修改session.save_path改变会话信息的存储位置,也可以修改会话的序列化方式。
SSL
要保护页面不能在非安全连接上生成,可以用
if ($_SERVER['https'] !== 'on') { die('Must be a secure connection.'); }
PS:实际中应该是服务器层会把http连接重定向到https连接
8.数据库
在PHP中访问数据库有两种方式,一种是使用数据库特定的扩展,一种是使用不受数据库约束的PDO(PHP数据对象)库。
如果使用了数据库特定扩展,代码合所使用的数据库密切相关,比如使用了MySQL数据库,后边迁移到PostgreSQL,会引起代码的重大变动。而PDO,用一个抽象层隐藏了数据库特定的函数,因此迁移时很方便,但PDO这种抽象层的可移植性是有一定代价的,会比原生数据库扩展在执行速度上慢一点。
PHP数据对象
PDO扩展为空PHP访问数据库定义了一个轻量级的一致接口。注意利用PDO扩展自身并不能实现任何数据库功能,必须使用一个具体数据库PDO驱动来访问数据库服务。例如,要在windows上访问MySQL,需要在php.ini中配置如下两个扩展:
extension=php_pdo.dll
extension=php_pdo_mysql.dll
PDO也是一个面向对象的扩展。
PS:PDO实际上只是一种接口规范或者说协议,具体的实现还要依赖特定扩展。
PDO使用示例:
$dsn = 'mysql:localhost;db_name=library'; $username = 'user'; $password = 'pswd'; $db = new PDO($dsn, $username, $password); //建立一个数据库连接 $db->query('UPDATE books SET authorid = 4 WHERE pub_year=1982'); //执行一条sql /*使用预处理执行一条sql*/ $statement = $db->prepare('SELECT * FROM books'); $statement->execute(); while ($row = $statement->fetch()) { //每次取一行 print_r($row); } $statement = null; //释放结果集对象 /*预处理语句中使用占位符*/ $statement = $db->prepare("INSERT INTO books(authorid, title, ISBN, pub_year) VALUES (:authorid, :title, :ISBN, :pub_year)"); $statement->execute(array( 'authorid' => 4, 'title' => 'Foundation', 'ISBN' => '0-553-80781-9', 'pub_year' => 1993, )); //预处理语句的一个优势是可以执行同一个sql,每次通过数组传递不同的值。 /*预处理语句中使用占位符2*/ $statement = $db->prepare("INSERT INTO books(authorid, title, ISBN, pub_year) VALUES (?,?,?,?)"); $statement->execute(4, 'Foundation', '0-553-80781-9', 1993); //使用?做占位符,需要保证参数顺序是对的 /*事物*/ try { $db = new PDO(...); } catch (Exception $error) { die("Connection Failed." . $e->getMessage()); } try { $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->beginTransaction(); $db->exec("INSERT INTO accounts(account_id,amount) values(23,5000)"); $db->exec("INSERT INTO accounts(account_id,amount) values(27,-5000)"); $db->commit(); } catch (Exception $e) { $db->rollback(); echo "Transaction not completed:" . $e->getMessage(); } 如果调用了commit()或rollback(),但数据库不支持事物,方法返回DB_ERROR()
SQLite
SQLite是一种小型轻量的数据库工具,SQLite中所有数据存储都是基于文件的。PHP5以后版本附带了SQLite,使用SQLite不用安装服务器软件,构建一个简单的应用可以考虑使用SQLite。
PS:PHP内置SQLite功能的含义应该是说按照SQLite规定的格式取读写数据。
直接文件级别的操作
数据检索功能不需要太复杂时,可以考虑SQLite,也可以直接操作文件
一个调查问卷网站,用户需要输入email,我们为每个访问者在服务器上创建目录文件夹。用户可以输入一些意见,并可以离开该页面,稍后再来补充完整。
/* * 问卷首页email.php */ <?php session_start(); if (!empty($_POST['posted']) && !empty($_POST['email'])) { $folder = "/data/surveys/" . strtolower($_POST['email']); // 向会话发送路径信息 $_SESSION['folder'] = $folder; if (!file_exists($folder)) { mkdir($folder, 0777, true); } header("Location: question1.php"); } else { ?> <!DOCTYPE html> <html> <head> <title>Files & Folders - On-line Survey</title> </head> <body bgcolor="white" text="black"> <h2>Survey Form</h2> <p>Please enter your e-mail address to start recording your comments.</p> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST"> <input type="hidden" name="posted" value="1" /> Email address: <input type="text" name="email" size="45" /><br> <input type="submit" value="Submit" /> </form> </body> </html> <?php } ?> /** * 问题页面question1.php */ <?php session_start(); $folder = $_SESSION['folder']; $filename = $folder . '/question1.txt'; $file_handle = fopen($filename, 'a+'); $comments = fread($file_handle, filesize($filename)); fclose($file_handle); if (!empty($_POST['posted'])) { $question1 = $_POST['question1']; $file_handle = fopen($filename, 'w+'); if (flock($file_handle, LOCK_EX)) { //独占锁 if (fwrite($file_handle, $question1) == FALSE) { echo "Cannot write to file {$filename}"; } flock($file_handle, LOCK_UN); } fclose($file_handle); header('Location: page2.php'); } ?> <!DOCTYPE html> <html> <head> <title>Files & Folders - On-line Survey</title> </head> <body> <table border="0"> <tr><td>Please enter your response to the following survey question:</td></tr> <tr bgcolor="lightblue"><td>What's your opinion on the state of the world economy?</td></tr> <tr><td> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST"> <input type="hidden" name="posted"/> <textarea name="question1" rols=12 cols=35><?php =$comments ?></textarea> <br> <input type="submit" value="Submit"> </form> </td></tr> </table> </body> </html> /* * 最后跳转到一个last_page.php页面,这个页面的代码没有给出,因为只是用来感谢用户而已。 */
is_readable()/is_writable() //判断文件可读性和可写性