PHP之MVC项目实战(三)
本文主要包括以下内容
- 标准错误错误处理
- http操作
- PDO
- 文件操作
标准错误错误处理
PHP在语法层面上发生的错误
两个过程:
触发阶段(发生一个错误)
处理阶段(如何处理该错误)
触发阶段
系统触发,php自己触发
典型的都是由php的核心在执行或者编译php代码时,发现的错误,并触发该错误!用户触发,自定义错误
但是可以通过用户的php代码,手动触发一个错误!
利用函数 trigger_error();触发一个用户自定义的错误!
错误处理阶段
有三种典型的错误处理方法
第一:报告错误信息
将错误信息获取后,输出到浏览器。
因此,典型的一个错误信息应包含:
级别,消息,发生的文件,行号:
想要管理错误报告,需要基于错误的级别进行管理:
php允许管理:
1,是否报告。
2,报告哪些级别的错误。
利用php的配置:
display_erorrs:是否显示错误信息,布尔。
error_reporting:报告的级别!
需要利用位运算,将所有需要报告的级别都设置上!
典型的级别是:
显示所有的错误:
也可以通过脚本代码进行以上配置
ini_set('error_reporting', E_ALL | E_STRICT);
ini_set('display_errors', 1);
第二:错误日志
将错误的信息写到日志文件中!
也是通过两个配置完成处理?
log_errors:是否开启错误日志
error_log: 错误文件位置
可以同归ini_set进行配置!
//错误日志
ini_set('error_log', 'e:/php1016/apache/htdocs/test/test.error.log');
ini_set('log_errors', 1);
error_reporting:针对 错误日志同样有效!
上面的两种都是php内置的错误方式!
第三:自定义的错误处理
当触发错误时,php自身不处理,交由用户脚本代码进行处理!
用户要提供一个方法(函数)来处理发生的错误:定义函数!
告知php,一旦发生错误,由用户定义的函数处理!
定义一个处理函数:
设置成错误处理器:
有参数,四个参数:
级别,消息,文件,行号:
set_error_handler('user_error_handler');
function user_error_handler($level, $msg, $file, $line) {
switch($level) {
case E_NOTICE:
case E_USER_NOTICE:
echo '写到公告栏中,看到了再解决即可<br>';
break;
case E_WARNING:
case E_USER_WARNING:
echo '发一封e-mail,明天加班解决<br>';
break;
case E_USER_ERROR:
echo '发送一个信息,马上来解决<br>';
// exit;
}
// return false;
}
用户定义的处理器一但设置,则系统的(报告,日志)就不可以使用了!
但是通过使 用户的错误处理器返回 false的形式!。此时错误处理继续交由系统的处理器来处理。
级别管理
每个标准错误都有一个级别:
在php中,是采用位运算的形式,管理各个标准的错误级别!
可以在php代码中通过,php的预定义常量,看到,设置该级别:
例如:
系统触发错误典型级别:E_NOTICE, E_WARNING,E_ERROR
用户触发错误的典型级别:E_USER_NOTICE, E_USER_WARNING, E_USER_ERROR
一个典型的是E_ALL,表示所有的错误级别:
操作
ini_set(‘error_reporting’, 2047);
开启所有级别的错误:2047 二进制后,所有的位都为1!
生产环境与开发环境典型错误配置:
生产:开启所有的错误级别,不显示错误信息在页面上,而记录在日志内!
开发:开启所有级别,显示在页面上。关闭错误日志!
项目中增加一个配置项,表示当前的环境模型:
dev,pro
增加一个配置项:
app/config/app.config.php
然后在framework中初始化
private static function initErrorHandler() {
if('dev' == $GLOBALS['config']['app']['run_mode']) {
ini_set('error_reporting', E_ALL | E_STRICT);
ini_set('display_errors', 1);
ini_set('log_errors', 0);
} elseif ('pro' == $GLOBALS['config']['app']['run_mode']) {
ini_set('display_errors', 0);
ini_set('error_log', APP_DIR . 'error.log');
ini_set('log_errors', 1);
}
}
致命错误的处理
会终止脚本运行!
但是如果是用户定义了错误处理器,并且触发的是用户的error级别,是可以恢复的(继续执行)
但是,系统触发的致命错误,是不能被自定义的处理器所处理的!
致命错误都会终止脚本执行么?
不是,用户脚本触发的致命错误在,用户自定义了错误处理器时,可以恢复,脚本继续运行!
http操作
PHP模拟GET请求
<?php
$host_ip = '127.0.0.1';
$port = '80';
if (!$link = fsockopen($host_ip, $port)) {
//连接失败
die('连接失败');
}
//var_dump($link);
//构建get请求字符串数据
$request_str = 'GET /test.php HTTP/1.1' . "\r\n";//请求行
//请求头
$request_str .= 'Host: shop.100.com' . "\r\n";//请求主机
$request_str .= 'User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:25.0) Gecko/20100101 Firefox/25.0' . "\r\n";//请求代理标识,模拟的firefox
//请求头以空行结束
$request_str .= "\r\n";//空行
//没有请求主体
//发请求
$result = fwrite($link, $request_str);
//var_dump($result);
//等待接收响应数据
echo '<pre>';
echo fread($link, 500);
//while (!feof($link)) {
// echo fgets($link);
//}
模拟POST请求
<?php
$host_ip = '127.0.0.1';
$port = '80';
if (!$link = fsockopen($host_ip, $port)) {
//连接失败
die('连接失败');
}
//post数据
$admin_name = 'admin';
$admin_pass = '1234abcd';
$post_data = 'username='.$admin_name . '&password='.$admin_pass;
//username=admin&password=1234abcd
//构建post请求字符串数据
$request_str = 'POST /index.php?p=back&c=Admin&a=signin HTTP/1.1' . "\r\n";//请求行
//请求头
$request_str .= 'Host: shop.100.com' . "\r\n";//请求主机
$request_str .= 'User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:25.0) Gecko/20100101 Firefox/25.0' . "\r\n";//请求代理标识,模拟的firefox
//post数据相关的请求头
$request_str .= 'Content-Type: application/x-www-form-urlencoded' . "\r\n";//post主体数据类型
$request_str .= 'Content-Length: ' . strlen($post_data) . "\r\n";
//请求头以空行结束
$request_str .= "\r\n";//空行
//请求主体部分
$request_str .= $post_data;//不需要\r\n
//发送!
fwrite($link, $request_str);
//获得相应数据
echo '<pre>';
//echo fread($link, 8000);
while (!feof($link)) {
echo fgets($link);
}
http下载
下载,将服务器端的数据,保存到浏览器端!
http下载,就是将原本应该在浏览器上打开的数据数据,让浏览器保存起来即可!
我们服务器需要工作:
1,将需要下载的内容输出到浏览器!
2,告知浏览器,接收到的响应数据应该保存起来!、
利用一个响应头,Content-disposition: 告知浏览器内容的处理方法:
Content-disposition: attachment 将响应数据作为附件来对待!
http下载,只是下载的响应主体!
<?php
//test.php得到文件的标识,id
//利用id,获得文件信息。
$img_file = 'E:\php1016\apache\htdocs\shop\app\upload\2013111909\goods_528abce32871b.png';
header('Content-Disposition: attachment; filename=' . basename($img_file));
$finfo = finfo_open(FILEINFO_MIME);//获得一个可以得到文件的MIME信息的资源
$mime = finfo_file($finfo, $img_file);//利用这个资源获得文件的信息
header('Content-Type: ' . $mime);
readfile($img_file);
curl:client URL
php的专门用于模拟请求的扩展!
(curl,独立工具,php支持curl库而已)
使用之前开启扩展
<?php
$curl = curl_init();
//var_dump($curl);
curl_setopt($curl, CURLOPT_URL, 'http://shop.100.com/index.php?p=back&c=Admin&a=signin');
curl_setopt($curl, CURLOPT_POST, true);//false
curl_setopt($curl, CURLOPT_POSTFIELDS, array('username'=>'admin', 'password'=>'1234abcd'));
curl_setopt($curl, CURLOPT_HEADER, true);
//curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_COOKIEJAR, 'e:/php1016/apache/htdocs/test/curl_cookie.txt');
echo '<pre>';
curl_exec($curl);
curl_close($curl);
控制缓存(浏览器的缓存
控制的浏览器端的缓存
典型的响应头,Expires
告知浏览器,当前你收到的响应数据应该的有效期到什么时候!
header()函数完成响应头的设置:
注意,此时的时间应该是一个格林威治平时
Tue, 19 Nov 2013 07:27:14 GMT
因此,Expires也是GMT时间:
gmdate();与date功能一致,将一个时间戳格式化成一个时间!date格式成本地时间(带时区的)。而gmdate格式化成一个GMT时间!
<?php
header('Expires: ' . gmdate('D, d M Y H:i:s', time()-1) . ' GMT');
header('Cache-Control: no-store no-cache must-revalidate');
echo gmdate('Y-m-d H:i:s');
echo '<br>';
echo gmdate('D, d M Y H:i:s') . ' GMT';
echo '<br>';
echo date('Y-m-d H:i:s');
echo '<hr>';
?>
<a href="121.php">self</a>
防止盗链
利用 请求时携带的请求头:
referer:表示当前请求的来源!
只要在请求图片的响应,判断一下当前的请求是否是来源本站即可!
得到来源。判断来源!
可以在Apache上,或者 php上都可以!
对于图片的请求都是用php生成!
通过判断$_SERVER[‘HTTP_REFERER’] 是否是当前网站!
PDO
另一种php操作mysql(或其他数据库)方法
PHP Data Object
语法上 是 oop,对象编程(类,方法,属性,对象)
PDO 是一个 数据库抽象层,PDO可以完成对大多数主流数据的操作
pdo操作具体的数据库,至少要:
开启PDO,同时需要开启相应的驱动!
PDO已经内置,mysql的驱动没有内置!
extension=php_pdo_mysql.dll
简单使用
/**
* @abstract PDO study: PDO操作MySQL增删查改类
* @date 2014/06/30
* @author Silov[bluebird237@gmail.com]
*/
class mysqlPdoClass{
private $db;
private $db_name = 'study';
private $db_serv;
private $db_user = 'root';
private $db_pass = 'root';
private $db_host = 'localhost';
//构造函数以及连接数据库
public function __construct( $username = 'root' , $password = 'root' , $connect = 'false'){
$this->db_serv = "mysql:dbname=".$this->db_name.";host=".$this->db_host.";charset=utf-8";
$this->db_user = $username;
$this->db_pass = $password;
}
//管理数据库,析构函数
public function __destruct(){
$this->db = null;
}
//执行SQL语句,返回值为sql执行结果
public function run_query($sql){
$this->db = new PDO($this->db_serv, $this->db_user, $this->db_pass);
$this->db->query("set names utf8"); //设置PHP+MySQL连接的编码格式为UTF8
$row = $this->db->query($sql);
$row->setFetchMode(PDO::FETCH_ASSOC); //设置查询结果显示为键值对数组模式
return $row;
}
//执行SQL语句,返回值为sql影响行数
public function run_exec($sql){
$this->db = new PDO($this->db_serv, $this->db_user, $this->db_pass);
$this->db->query("set names utf8"); //设置PHP+MySQL连接的编码格式为UTF8
return $this->db->exec($sql);
}
/**
* @abstract 插入操作
* @param $table:表名; $data:插入数据键值对数组; $return:是否返回值,为true时,返回插入字段的id; $debug:是否测试,true时返回sql语句,不执行
* @return 返回值:插入结果,boolean
*/
public function data_insert($table, $data, $return = true,$debug=false){
if(!$table) {
return false;
}
$fields = array();
$values = array();
foreach ($data as $field => $value){
$fields[] = '`'.$field.'`';
$values[] = "'".addslashes($value)."'";
}
if(empty($fields) || empty($values)) {
return false;
}
$sql = 'INSERT INTO `'.$table.'`
('.join(',',$fields).')
VALUES ('.join(',',$values).')';
if($debug){
return $sql;
}
$query = $this->run_exec($sql);
return $return ? $this->db->lastInsertId() : $query;
}
/**
* @abstract 更新操作
* @param $table:表名; $condition:更新查询条件; $data:更新数据键值对数组; $limit:更新数据条数上限
* $debug:测试时给true,则不执行update,直接返回完整的sql语句
* @return 返回值:插入结果,boolean
*/
public function data_update($table, $condition, $data, $limit = 1,$debug=false) {
if(!$table) {
return false;
}
$set = array();
foreach ($data as $field => $value) {
$set[] = '`'.$field.'`='."'".addslashes($value)."'";
}
if(empty($set)) {
return false;
}
$sql = 'UPDATE `'.$table.'`
SET '.join(',',$set).'
WHERE '.$condition.' '.
($limit ? 'LIMIT '.$limit : '');
if($debug){
return $sql;
}
return $this->run_exec($sql);
}
/**
* @abstract 查询单个字段值
* @param $sql:sql语句
* @return 返回值:string
*/
public function getOne($sql){
$row = $this->run_query($sql);
$data = $row->fetch();
$data = array_shift($data);
return $data;
}
/**
* @abstract 查询单条记录,多个字段
* @param $sql:sql语句
* @return 返回值:键值对数组
*/
public function getRow($sql){
$row = $this->run_query($sql);
$data = $row->fetch();
return $data;
}
/**
* @abstract 查询多条记录
* @param $sql:sql语句
* @return 返回值:以键值对数组为元素的数组
*/
public function getRows($sql){
$row = $this->run_query($sql);
$data = $row->fetchAll();
return $data;
}
}
预处理的SQL的执行方式
如果需要重复地执行结构相同的SQL。此时结构相同的SQL意味着SQL的编译结果是类似的。除掉数据部分,在做重复的工作!
应该如何解决?
将SQL中结构相同的先提取,编译。
将不一样的地方,独立的处理。
最后执行将数绑定了的结果可以
<?php
$dsn = 'mysql:dbname=itcast_shop;host=127.0.0.1;port=3306';
$username = 'root';
$password = '123456';
$pdo = new PDO($dsn, $username, $password);
//$sql = "insert into it_admin (admin_id, admin_name, admin_pass) values (null, 'itcast', md5('1234abcd'))";
//$rows = $pdo->exec($sql);
//$sql = "insert into it_admin (admin_id, admin_name, admin_pass) values (null, 'php', md5('1234abcd'))";
//$rows = $pdo->exec($sql);
//$sql = "insert into it_admin (admin_id, admin_name, admin_pass) values (null, '1016', md5('1234abcd'))";
//$rows = $pdo->exec($sql);
//$sql = "insert into it_admin (admin_id, admin_name, admin_pass) values (null, 'han', md5('1234abcd'))";
//$rows = $pdo->exec($sql);
$sql_struct = "insert into it_admin (admin_id, admin_name, admin_pass) values (null, :name, :pass)";
$stmt = $pdo->prepare($sql_struct);
$data = array(
array('java', '1234'),
array('php', '12345'),
array('.net', '4567')
);
foreach($data as $row) {
$stmt->bindValue(':name', $row[0]);
$stmt->bindValue(':pass', md5($row[1]));
$stmt->execute();
}
错误的处理
mysql的扩展,错误的处理方式不会主动报错,称之为 静默模式。
pdo的错误的处理:默认的也是 静默模式!
此时需要使用 错误函数获得错误信息:
pdo还支持其他的错误模式:
需要通过修改PDO对象的错误模式属性进行修改:
静默模式:Silent mode
默认的
警告模式:Warning Mode
一旦发生错误,则触发一个警告级别的错误!
异常模式:Exception mode
异常,类似于标准错误,是属于php在处理oop语法时,新的一种错误的处理模式!
分成三个阶段进行处理:
抛出,监听,捕获!
使用如下语法实现:
抛出:throw
监听:try
捕获:catch
抛出的异常,其实就是将所有的错误信息封装到一个异常对象中!
异常:语法上就是一个 内置的Exception类(其扩展类也算)的对象!
典型的语法如下:
<?php
$dsn = 'mysql:dbname=itcast_shop;host=127.0.0.1;port=3306';
$username = 'root';
$password = '123456';
$pdo = new PDO($dsn, $username, $password);
//设置错误模式:
//$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
//$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo '<pre>';
try {
$sql = 'show database';
if (!$pdo->query($sql)) {
echo $pdo->errorCode();
echo '<br>';
var_dump($pdo->errorInfo());
}
} catch (PDOException $e) {
echo $e->getMessage();
}
文件操作
<?
class FSC{
// 函数名: getfilesource
// 功能: 得到指定文件的内容
// 参数: $file 目标文件
// test passed
function getfilesource($file){
if($fp=fopen($file,'r')){
$filesource=fread($fp,filesize($file));
fclose($fp);
return $filesource;
}
else
return false;
}
// 函数名: writefile
// 功能: 创建新文件,并写入内容,如果指定文件名已存在,那将直接覆盖
// 参数: $file -- 新文件名
// $source 文件内容
//test passed
function writefile($file,$source){
if($fp=fopen($file,'w')){
$filesource=fwrite($fp,$source);
fclose($fp);
return $filesource;
}
else
return false;
}
// 函数名: movefile
// 功能: 移动文件
// 参数: $file -- 待移动的文件名
// $destfile -- 目标文件名
// $overwrite 如果目标文件存在,是否覆盖.默认是覆盖.
// $bak 是否保留原文件 默认是不保留即删除原文件
// test passed
function movefile($file,$destfile,$overwrite=1,$bak=0){
if(file_exists($destfile)){
if($overwrite)
unlink($destfile);
else
return false;
}
if($cf=copy($file,$destfile)){
if(!$bak)
return(unlink($file));
}
return($cf);
}
// 函数名: movedir
// 功能: 这是下一涵数move的附助函数,功能就是移动目录
function movedir($dir,$destdir,$overwrite=1,$bak=0){
@set_time_limit(600);
if(!file_exists($destdir))
FSC::notfate_any_mkdir($destdir);
if(file_exists($dir)&&(is_dir($dir)))
{
if(substr($dir,-1)!='/')$dir.='/';
if(file_exists($destdir)&&(is_dir($destdir))){
if(substr($destdir,-1)!='/')$destdir.='/';
$h=opendir($dir);
while($file=readdir($h)){
if($file=='.'||$file=='..')
{
continue;
$file="";
}
if(is_dir($dir.$file)){
if(!file_exists($destdir.$file))
FSC::notfate_mkdir($destdir.$file);
else
chmod($destdir.$file,0777);
FSC::movedir($dir.$file,$destdir.$file,$overwrite,$bak);
FSC::delforder($dir.$file);
}
else
{
if(file_exists($destdir.$file)){
if($overwrite)unlink($destdir.$file);
else{
continue;
$file="";
}
}
if(copy($dir.$file,$destdir.$file))
if(!$bak)
if(file_exists($dir.$file)&&is_file($dir.$file))
@unlink($dir.$file);
}
}
}
else
return false;
}
else
return false;
}
// 函数名: move
// 功能: 移动文件或目录
// 参数: $file -- 源文件/目录
// $path -- 目标路径
// $overwrite -- 如是目标路径中已存在该文件时,是否覆盖移动
// -- 默认值是1, 即覆盖
// $bak -- 是否保留备份(原文件/目录)
function move($file,$path,$overwrite=1,$bak=0)
{
if(file_exists($file)){
if(is_dir($file)){
if(substr($file,-1)=='/')$dirname=basename(substr($file,0,strlen($file)-1));
else $dirname=basename($file);
if(substr($path,-1)!='/')$path.='/';
if($file!='.'||$file!='..'||$file!='../'||$file!='./')$path.=$dirname;
FSC::movedir($file,$path,$overwrite,$bak);
if(!$bak)FSC::delforder($file);
}
else{
if(file_exists($path)){
if(is_dir($path))chmod($path,0777);
else {
if($overwrite)
@unlink($path);
else
return false;
}
}
else
FSC::notfate_any_mkdir($path);
if(substr($path,-1)!='/')$path.='/';
FSC::movefile($file,$path.basename($file),$overwrite,$bak);
}
}
else
return false;
}
// 函数名: delforder
// 功能: 删除目录,不管该目录下是否有文件或子目录,全部删除哦,小心别删错了哦!
// 参数: $file -- 源文件/目录
//test passed
function delforder($file) {
chmod($file,0777);
if (is_dir($file)) {
$handle = opendir($file);
while($filename = readdir($handle)) {
if ($filename != "." && $filename != "..")
{
FSC::delforder($file."/".$filename);
}
}
closedir($handle);
return(rmdir($file));
}
else {
unlink($file);
}
}
// 函数名: notfate_mkdir
// 功能: 创建新目录,这是来自php.net的一段代码.弥补mkdir的不足.
// 参数: $dir -- 目录名
function notfate_mkdir($dir,$mode=0777){
$u=umask(0);
$r=mkdir($dir,$mode);
umask($u);
return $r;
}
// 函数名: notfate_any_mkdir
// 功能: 创建新目录,与上面的notfate_mkdir有点不同,因为它多了一个any,即可以创建多级目录
// 如:notfate_any_mkdir("abc/abc/abc/abc/abc")
// 参数: $dirs -- 目录名
function notfate_any_mkdir($dirs,$mode=0777)
{
if(!strrpos($dirs,'/'))
{
return(FSC::notfate_mkdir($dirs,$mode));
}else
{
$forder=explode('/',$dirs);
$f='';
for($n=0;$n<count($forder);$n++)
{
if($forder[$n]=='') continue;
$f.=((($n==0)&&($forder[$n]<>''))?(''):('/')).$forder[$n];
if(file_exists($f)){
chmod($f,0777);
continue;
}
else
{
if(FSC::notfate_mkdir($f,$mode)) continue;
else
return false;
}
}
return true;
}
}
}
?>