王新阳

wangxinyang

PHP生成网站访问日志

以下两个文件放在根目录 wxylog 文件夹中

wxylog_class.php

<?php
/*
 * 生成网站访问日志
 * 调用方法:在入口页程序中加入 include('/wxylog/wxylog_class.php'); new wxylog_class();
 */
final class wxylog_class{
	private $path='';
	private $uri ='';
	public function __construct(){
		$this->path = dirname(__FILE__).'/log/';
		if(!file_exists($this->path) && !mkdir($this->path))return FALSE; //文件夹创建失败
		$this->uri = $this->server('REQUEST_URI');
		if(! $this->allow())return FALSE; //是否需要记录
		$this->log();
	}
	private function server($para){
		$para = strtoupper($para);
		return isset($_SERVER[$para]) ? str_replace(chr(9), chr(32), $_SERVER[$para]) : '';
	}
	private function log(){
		$file = $this->path.date('Ymd').'.log';
		$str = date('Y-m-d H:i:s');
		foreach(array('REMOTE_ADDR', 'REQUEST_METHOD', 'REQUEST_URI', 'HTTP_USER_AGENT', 'HTTP_REFERER') as $item){
			$str .= chr(9).$this->server($item);
		}
		$str .= "\n";
		//写入访问日志
		@file_put_contents($file, $str, FILE_APPEND);
		
		//获取蜘蛛类型
		$array = array('Baiduspider', 'Sogou web spider', 'Googlebot', 'bingbot', 'Sosospider', 'yisouspider', 'Yahoo! Slurp', '360Spider');
		$robot = '';
		foreach($array as $item){
			if(strpos(strtolower($this->server('HTTP_USER_AGENT')), strtolower($item)) !== FALSE){
				$robot = $item;
				break;
			}
		}

		$file = $this->path.date('Ymd').'.php';
		$array = @include $file;
		is_array($array) OR $array=array('total'=>0, 'not_robot'=>0, 'robot'=>array(), 'request_method'=>array(), 'page'=>array('index'=>0, 'inside'=>0, 'http_referer'=>0)); //访问总数、非蜘蛛访问数、各蜘蛛访问数、受访页面(首页、内页、通过其他链接进入)
		//访问总数
		$array['total'] += 1;
		//请求方式
		if($method = $this->server('REQUEST_METHOD')){
			isset($array['request_method'][$method]) OR $array['request_method'][$method]=0;
			$array['request_method'][$method]+=1;
		}
		//首页/内页访问数
		$array['page'][preg_match('/^\/(index.php)?(\/?\?.+)?$/i', $this->uri) ? 'index' : 'inside'] += 1;
		//通过其他链接进入数
		$this->server('HTTP_REFERER') AND $array['page']['http_referer'] += 1;
		//更新蜘蛛访问数
		if($robot){
			isset($array['robot'][$robot]) OR $array['robot'][$robot]=0;
			$array['robot'][$robot]+=1;
		}else{
			$array['not_robot'] += 1;
		}
		$code = "<?php\n return ". var_export($array, true) .";\n";
		//写入统计
		@file_put_contents($file, $code);
		
		//禁止访问某些页面
		$this->deny($robot);
	}
	//禁止访问某些页面,根据实际需要单独开发
	private function deny($robot){
		//比如禁止百度访问搜索页
		//if(strpos($this->uri, 'search')!==FALSE && $robot=='Baiduspider')exit('ACCESS DENIED');
	}
	//是否记录当前uri,根据实际需要单独开发
	private function allow(){
	/*
		//比如不记录验证码生成程序
		//以下均为正则表达式
		$array = array(
			'\/verifycode'
		);
		foreach($array as $item){
			if(preg_match('/^\/(index.php)?'.$item.'/i', $this->uri))return FALSE;
		}
	*/
		return true;
	}
}

 

wxylog_view.php

<?php
/*
 * 日志查看程序 wxy 20180106
 * 当前页面必须放在 /wxylog 目录下
 * /wxylog/wxylog_class.php 在入口页程序中加入以下代码调用: include('/wxylog/wxylog_class.php'); new wxylog_class();
 * /wxylog/wxylog_view.php
 * 自动生成的其他日志文件,以年月日8位数字开头。如 20180106.log, 20180106.php
 */

define('LOG_PATH', dirname(__FILE__).'/log/');
switch($_SERVER['REQUEST_METHOD']){
	case 'GET':
		if(! isset($_COOKIE['wxylog']) || $_COOKIE['wxylog'] !== 'ok'){
			show_login();
			exit;
		}
		if(isset($_GET['date'])){ //获取日志详情
			show_log();
		}else{
			show_list(); //显示列表
		}
		break;
	case 'POST':
		if(isset($_POST['pwd']) && sha1($_POST['pwd'])==='59b331507615f9f98bf17558ee678cafdbdb5bfe'){
			setcookie('wxylog', 'ok');
			header('Location: '.$_SERVER['REQUEST_URI']);
			exit;
		}elseif(isset($_POST['date'])){
			delete_log();
		}
		break;
	default:
		exit('ACCESS DENIED');
}

function show_log(){
	$date = $_GET['date'];
	if(! preg_match('/^20\d{6}$/', $date))exit('');
	echo '<pre style="font:14px/1.5 Arial;">';
	exit(@file_get_contents(LOG_PATH.$date.'.log'));
}

function show_data(){
	$array = @scandir(LOG_PATH, 1);
	if(! $array)return FALSE;
	foreach($array as $file){
		if(is_dir($file) || !preg_match('/^20\d{6}\.php$/', $file))continue;
		$data = @include LOG_PATH.$file;
		is_array($data) AND echo_html($file, $data);
	}
}

function format_bytes($size, $delimiter = '') {
	$units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
	for ($i = 0; $size >= 1024 && $i < 5; $i++) $size /= 1024;
	return round($size, 2) . $delimiter . $units[$i];
}

function delete_log(){
	$date = $_POST['date'];
	if(preg_match('/^20\d{6}$/', $date)){
		if(@unlink(LOG_PATH.$date.'.log')){
			exit(@unlink(LOG_PATH.$date.'.php') ? 'success' : 'fail');
		}else{
			exit('fail');
		}
	}else{
		exit('ACCESS DENIED');
	}
}

function echo_html($file, $data){
	if(! isset($data['total']))return;
	$date = substr($file,0,8);
	echo '<div class="col-sm-4 col-md-3"><ul class="list-group"><li class="list-group-item active"><strong>', $date, '</strong><div class="pull-right"><a href="javascript:;" data-del-file="', $date, '">删除</a>   <a target="_blank" href="?date=', $date, '">浏览</a></div></li>';
	echo '<li class="list-group-item">访问次数 <kbd>', $data['total'], '</kbd></li>';
	echo '<li class="list-group-item">搜索引擎 <kbd>', $data['total']-$data['not_robot'], '</kbd></li>';
	echo '<li class="list-group-item">正常访问 <kbd>', $data['not_robot'], '</kbd></li>';
	echo '<li class="list-group-item list-group-item-success"><strong>访问页面</strong></li>';
	echo '<li class="list-group-item">首页 <kbd>', $data['page']['index'], '</kbd></li>';
	echo '<li class="list-group-item">内页 <kbd>', $data['page']['inside'], '</kbd></li>';
	echo '<li class="list-group-item">通过其他链接访问 <kbd>', $data['page']['http_referer'], '</kbd></li>';
	echo '<li class="list-group-item list-group-item-info"><strong>请求方式</strong></li>';
	arsort($data['request_method']);
	foreach($data['request_method'] as $key=>$val){
		echo "<li class=\"list-group-item\">{$key} <kbd>{$val}</kbd></li>";
	}
	if($data['total'] != $data['not_robot']){
		echo '<li class="list-group-item list-group-item-warning"><strong>搜索引擎</strong></li>';
		arsort($data['robot']);
		foreach($data['robot'] as $key=>$val){
			echo "<li class=\"list-group-item\">{$key} <kbd>{$val}</kbd></li>";
		}
	}
	echo '<li class="list-group-item list-group-item-danger">日志文件 <kbd>', format_bytes(filesize(LOG_PATH.$date.'.log')), '</kbd></li>';
	echo '</ul></div>';
}

function show_list(){
?><!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>数据统计</title>
<link href="http://apps.bdimg.com/libs/bootstrap/3.3.4/css/bootstrap.css" rel="stylesheet">
<style>
body,kbd{font-size:medium;}
a{color:#bce8f1;}
.container{padding-top:20px;}
</style>
</head>
<body>
<div class="container">
	<div class="row">
<?php show_data(); ?>
	</div>
</div>
<script src="http://apps.bdimg.com/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
$('a[data-del-file]').click(function(){
	if(!confirm('确定要删除此日志吗?'))return;
	$.post('', {date:$(this).attr('data-del-file')}, function(text){
		if(text=='success'){
			alert('删除成功');
			self.location.reload();
		}else{
			alert('删除失败');
		}
	}, 'text');
});
</script>
</body>
</html>
<?php
}

function show_login(){
?><!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Log System</title>
<link href="http://apps.bdimg.com/libs/bootstrap/3.3.4/css/bootstrap.css" rel="stylesheet">
<script src="http://apps.bdimg.com/libs/jquery/1.11.3/jquery.min.js"></script>
<style>
body{background:#333;}
a{font-size:large !important;cursor:pointer;}
.mybox{margin:0 auto;width:300px;}
</style>
</head>
<body>
<div class="mybox">
<form class="form-inline" method="post">
	<div class="form-group form-group-lg">
		<div class="input-group">
			<input type="password" name="pwd" class="form-control" placeholder="password" autocomplete="new-password" autofocus>
			<a class="input-group-addon" onclick="$('form')[0].submit()">Login</a>
		</div>
	</div>
</form>
</div>
<script>$('.mybox').css('margin-top', $(window).height()/3);</script>
</body>
</html>
<?php
}

php class类的用法详细总结

转自:http://www.jb51.net/article/42172.htm

一:结构和调用(实例化):

class className{} ,调用:$obj = new className();当类有构造函数时,还应传入参数。如$obj = new className($v,$v2…);

二:构造函数和析构函数:

1、构造函数用于初始化:使用__construct(),可带参数。

2、但析构函数不能带参数(用于在销去一个类之前执行一些操作或功能)。析构函数用__destruct()做名称。在脚本执行结束时,会销掉内存中的对象,因此可不用析造函数,但有些比如COOKIE等,就应当要用此函数销掉。

知识点:在PHP4中也提供了构造函数,但使用的是与类同名的类方法,在PHP5仍能兼容这种做法,当一个类中没有包含__construct时,会查找与类同名的方法,如果找到,就认为是构造函数,如下:

代码如下:

class test
{ var $b;
function test() { $this->b=5; }
function addab($c) { return $this->b+$c; }
}
$a = new test(); echo $a->addab(4); // 返回 9

3、PHP不会自动调用父类的构造函数(不支持构造函数重载),必须使用parent关键字显式地调用。
代码如下:

class employee{
function __construct()….
}
class Manager extents Employee{
function __construct(){
parent::_construct();
echo '这个子类的父类构造函数调用了!';
}
}

当然也可以调用与该实例没有任何关系的其它类的构造函数。只需在__construct()前加上类名即可。如:
otherClassName::__construct();

类的主家庭成员:属性、方法、常量、静态成员

三、类的属性:
有两种方法对类的属性赋值或取值。
1、使用公共作用域public关键词。
2、使用__set()和__get()来分别赋值和取值,前者称为设置方法(setter)或修改方法(mutator),后者称为访问方法(accessor)或获取方法(getter)。建议使用这种方法:优点:
A、可在__set()统一进行数据验证。
B、便于统一管理属性。

注意:

第一:__set()和__get()只对私有属性起作用,对于用public定义的属性,它们两个都懒理搭理,如下:

代码如下:

class test{
protected $a=9,$b=2,$c;
public $d;
function __set($n,$v) { $this->$n = $v+2; }
function __get($name) { return $this->$name+2; }
}
$a = new test();
$a->b =5; echo “<br />”; echo $a->b;

实例只对$a,$b,$c的设置会经过__set和__get过滤与返回,对于$d,就不会起作用。如$a->d=5,再返回还是5。

第二:__set($n,$v)要带两个参数。而__get($n)只能有一个参数。实例:

代码如下:

class test{
private $a=5,$b=6,$c;
function __set($n,$v)
{
if($n=='a'&&$n>0)
$this->$n = $v;
else
$this->$n = $v+2;
}
function __get($name)
{
return $this->$name; //如果改为return $this->$name + $this->addab(); 如调用a的值,实际返回的是a+a+b的值。默认为5+5+6=16。
}
function addab()
{ return $this->a + $this->b; }
}
$e=new test();
$e->a = 11; //注意写法:类的内部用$this->$n即变量的写法,但外部实例要用$e->a的方式。
$e->b = 12; //get 14
$e->k = 22;

类的属性可自由扩展,如上例的k,不管是否用__set,当一个实例建立起来后,可以用$e->newProperty = xx;直接来创造一个属性,但不建议这么做。

四、类的方法:
理解成类当中的函数即可。

调用:

1、内部调用:可使用$this->Fanname();或$this->addab()或test::addab();

2、实例化调用时,用$e->addab();即可。对于在该方法中没有使用$this关键字的,如上例中的:
function addab() { return $this->a+$this->b; }
改为: function addab() { return 25; }那在在外部实例调用该方法,也可用“$e::addab();”或“test::addab();”

五、类的常量:
如果类的属性理解成类中的变量,那么类的常量和变量是不一样的,其定义方法为:

代码如下:

class test{
private $a;
const PI = '3.14′;
…..
//在类中调用上面的常量用两种方法,“$this::PI”,或 “类名::PI”,这里就是test::PI,如下:
function getvalue(){
return $this->a * $this::PI; //或$this->a * test::PI,用this关键字或类名均可,但都要用双冒号。
}
}
$e= new test();
$e->PI =5; //注意,这里用 ->只是创造了一个也是名为PI的属性,而不是改变类中的PI常量的值。
echo $e::PI; //这个才是调用类的常量。

常量只能用双冒号::来调用。并且不能更改其值。
在类外部实例化后调用类常量同样也有两种方法。方法为:
“$e::PI” 或 “test::PI”,共同点是都要用冒号,不同点是外部不能用this关键字,只能用实例名,但类名::PI是通用的。

六、类的静态成员(静态属性或静态方法):

如果需要创建供所有类的实例共享的字段或方法。就得用静态成员。有两个特征:

1、静态成员是共产主义者,它让脚本上的所有该类的实例调用,但不能借助类的特定实例名调用,而是在类的外部,统一使用“类名::$成员名”的方式调用。而类的内部则统一使用 “self::$成员名”来调用。

2、当每一次新创建实例时,静态成员会从上次创建的实例最后值开始重新计算,而不是类中初始的值开始计算。

3、对于用public定义的静态成员,可以在外部更改它的值。private等则不行。

代码如下:

class test{
public static $v = 0;
function __construct(){ self::$v++; }
static function getV(){ return self::$v; }
}
$a = new test();
echo test::getV(); // 返回 1
$b = new test();
echo test::getV(); // 返回 2
test::$v=8; //由于public定义的成员,改变静态成员的值。
$c = new test();
echo test::getV(); // 返回 9

七、关键字:
(一)this关键字:用于类的内部指代类的本身。来访问属性或方法或常量,如$this->属性名或方法名。$this::常量名。this还可以用在该类的子类中,来指代本身的属性或方法。

(二)双冒号“::”关键字:用于调用常量、静态成员。

(三)self关键字:在类的内部与双冒号配合调用静态成员,如 self::$staticVar.,在类的内部,不能用$this来调用静态成员。

(四)__toString():在类中使用__toString(),用于将类转成字串并打印类,用处不大:如:

代码如下:

class test{ public $p;
public function __toString(){ return var_export($this,TRUE); }
}
$a=new test();
echo $a; //输出:test::__set_state(array( 'p' => NULL, )),或写成:echo $a->__toString();

(五)__clone() :当克隆对象时,这个关键字才会发生作用,用于更改克隆时某些值。

(六)__call():方法重载,参下面示例:

代码如下:

class cB{
function __call($method,$n){
if($method=='showVarType'){
if(is_numeric($n[0])){ //不能用$n。要用$n[0];
$this->displayNum();
}else if (is_array($n[0])){
$this->displayArr();
}else{
$this->displayOther();
}
}
}
function displayNum() {
echo '<h3>这是数字!</h3>';
}
function displayArr() {
echo '<h3>这是数组!</h3>';
}
function displayOther() {
echo '<h3>不是数组也不是数字!</h3>';
}
}

$x='a';
$y=array('a','b');
$b=new cB;
$b->showVarType($x); //不是数组也不是数字
$b->showVarType($y); //这是数组


注意,不能在类中定义showVarType()方法,否则代码不能用。

(七)extends:继承: 如class a{} class b extends a{} 类b继承了类a

附:记忆:以后统一在调用方法或属性时用 “-> “,调用常量则用双冒号“::”,不会搞晕。

八、方法和属性的作用域:

共有6种:public(默认,可省略,也等同于php6的var声明),private(私有,也不能由子类使用),protected(私有,但可由子类使用) ,abstract(抽象,参下文),final(阻止在子类中覆盖—也称重载,阻止被继承,用于修饰类名及方法,如final class test{ final function fun(){}} ,但不能用于属性),static(静态)

九:抽象类和抽象方法(abstract——注意:没有所谓抽象属性):

抽象可以理解成父类为子类定义了一个模板或基类。作用域abstract只在父类中声明,但在子类中实现。注意事项:

1、抽象类不能被实例化,只能被子类(具体类)继承后实现。

2、抽象类必须在其子类中实现该抽象类的所有抽象方法。否则会出错。

3、在抽象方法中,只是声明,但不能具体实现:如abstract function gettow(){ return $this->p; }是错的,只能声明这个方法:abstract function gettow();(连方括号{}都不要出现),抽象方法和抽象类主要用于复杂的类层次关系中。该层次关系需要确保每一个子类都包含并重载了某些特定的方法。这也可以通过接口实现

4、属性不能被命名为抽象属性,如abstract $p = 5是错的。

5、只有声明为抽象的类可以声明抽象方法,但如果方法声明为抽象,就不能具体实现。如:

代码如下:

abstract class Employee
{
abstract function a(…);
abstract function b(…);
}

以后再对这个父类扩展,组成各种子类(如经理,员工,出纳)。

6、抽象类中,如果要实现具体的方法,不能声明为抽象。这样可能实际意义更大。可以把几个类库中共同的部分提取到抽象类中,其它的类继承抽象类即可。如下:

代码如下:

abstract class BaseShop{
Const TAX=0.06; // 在抽象类中定义常量
public function buy($gid) { // 如果定义为抽象方法abstract function buy()就不能在这里实现主体。
echo('你购买了ID为 :'.$gid.'的商品');
}
public function sell($gid) {
echo('你卖了ID为 :'.$gid.'的商品');
}
public function view($gid) {
echo('你查看了ID为 :'.$gid.'的商品');
}
}
class BallShop extends BaseShop{
var $itme_id = null;
public function __construct()
{
$this->itme_id = 2314;
}
public function open()
{
$this->sell($this->itme_id);
}
public function getTax()
{
echo printf('<h3>平均税率是 %d%%。</h3>',$this::TAX*100);
}
}
$s = new BallShop;
$s->open(); //你卖了ID为 :2314的商品
$shop->getTax();

十:类型提示:
注意,类型提示功能只能用于参数为对象的提示,而无法用于为整数,字串,浮点等类型提示。有些类的方法需要传入的参数为所期望的对象类型,可以用下面的方法达到强制实施此替则。要达到类型提示,只要在方法的对象型参数前加一个已存在的类的名称,如:function funname(OtherClassName $otherclassINSName,$c….),注意,OtherClassName必须是存在的类。如下:
代码如下:

class em{ var $k=56; }
class test{
function __construct()
{ echo $this->addab(new em(),2); }

function addab(em $j,$c) //这个方法,即可以在内部调用,也可以在外部调用。只要作用域许可。
{ return $j->k+$c; }
}
$a = new test();
$b = new em();
echo $a->addab($b,2); //或 $a->addab(new em(),2);


十一、类的管理:

1、instanceof关键字:用于分析一个对象是否是某一个类的实例或子类或是实现了某个特定的接口:如下例,但要注意: 类名没有任何引号等定界符,否则会出错。如test不能用'test'

代码如下:

class test2{}
class test{}
class testChilern Extends test{}
$a = new test2();
$m = new test();
$i = ($m instanceof test);
if($i)echo '$m是类test的实例!<br />'; // get this value
switch ($a instanceof test){
case true :
echo 'YES<br />';
break;
case false :
echo 'No<br />'; //get this value
break;
}
$d=new testChilern();
if($d instanceof test)echo '$d是类test的子类!<br />'; // get this value

2、确定类是否存在:boolean class_exists(string class_name): class_exists('test');

3、返回类名:string get_class(object),成功时返回实例的类名,失败则返回FALSE:

$a = new test2(); echo get_class($a); //返回 test2

4、了解类的公用属性:array get_class_vars('className') ,返回关键数组:包含所有定义的public属性名及其相应的值。这个函数不能用实例名做变量

5、返回类方法:get_class_methods('test'); //或: get_class_methods($a);可用实例名做参数,返回包括构造函数在内的所有非私有方法。

6、print_r(get_declared_classes())了解当前PHP版本中所有的类名。PHP5有149个。

7、get_object_vars($a)返回实例中所有公用的属性及其值的关联数组。注意它和get_class_vars()的区别:
/* (1) get_object_vars($a)是用实例名做参数,而get_class_vars('test')是用类名做参数。
* (2) get_object_vars($a)获得的属性值是实例运行后的值,而get_class_vars('test')获得的属性值是类中的初始定义。
* (3) 两者均返回关联数组,且均对未赋值的属性返回NULL的值。如类test中有定义了public $q;则返回Array ( [v] => 5 [q]=>) ,
*/

8、返回父类的名称:get_parent_class($b);//或get_parent_class('test2′); 返回test

9、确定接口是否存在:boolean interface_exists($string interface[,boolean autoload])

10、确定对象类型: boolean is_a($obj,'className'),当$obj属于CLASSNAME类时,或属于其子类时,返回TRUE,如果$obj与class类型无关则返回FALSE。如:is_a($a,'test')

11、确定是否是某类的子对象:当$b是继承自TEST类时,返回TRUE,否则FALSE。boolean is_subclass_of($b,'test');

12、确定类或实例中,是否存在某方法。method_exists($a,'getv') //或用method_exists('test','getv'),此函数适用于非public定义的作用域的方法。

以上函数实例:

代码如下:

class test{
public $v=2;
private $c=5;
function __construct(){
$this->v=5;
}
private function getv(){
return $this->v;
}
}
class test2 extends test{}

$a=new test();
$b=new test2();
print_r( get_class_methods('test')); //或:print_r( get_class_methods($a)); 均返回:Array ( [0] => __construct [1] => getv )
echo '<br />';
print_r( get_class_vars('test')); //返回:Array ( [v] => 2 ),和上面不一样,不能用print_r( get_class_methods($a));
echo '<br />';
echo get_parent_class($b);//或get_parent_class('test2′); 返回test
echo '<br />';
echo is_a($b,'test');// 返回1
echo '<br />';
if(is_subclass_of('test2′,'test'))echo '是子类!'; //或(is_subclass_of($b,'test')),返回1,当参数1为$a时则返回false,
echo '<br />';
echo method_exists($a,'getv') //或用method_exists('test','getv')返回1,本函数也适用于用private等定义域的方法。


十一、自动加载类库文件:

当类多了以后,比如要在一个文件中载入3个类库文件:a.class.php,b.class.php,c.class.php要用三个require_once('classes/a.class.php);

require_once('classes/b.class.php);
require_once('classes/c.class.php);

可以用PHP5自动加载的功能来处理:在全局应用配置文件中,定义一个特殊的函数__autoload($class)函数(__autoload并不是一个类的方法,只是单独的函数,和类没有关系):
function __autoload($class){
require_once(“classes/$class)
}

该函数放哪没有关系,在创建类实例时,也不必去调用这个autoload函数。PHP会自动完成。但务必注意一点:“在调用页面上创建实例所使用的类名称”、和“被调用的文件名”、以及“该文件中的类的名称”3个必须是一样的。这样就不需要去调用__autoload();如果不一样则必须单独调用__autoload('c');并给它一个文件名前缀。如:
c.class.php文件的代码是:

代码如下:

< ?php
class c{
public $m=7;
}
?>

这里代码的类名称是c,而文件名也是c,
现在要在index.php调用:
代码如下:

< ?php
function __autoload($class){
require_once “$class.class.php”;
}

$m = new c(); //创建实例调用的类也是c
echo $m->m;
?>


此时PHP会自动调用根目录下的c.class.php中的类C。

但如果c.class.php中的代码是:

代码如下:

< ?php
class mm{
public $m=7;
}
?>

而调用页index.php代码是:
代码如下:

< ?php
function __autoload($class){
require_once “$class.class.php”;
}
# __autoload('c'); //如果不加这一行就会出错。
$m = new mm();
echo $m->m;
?>

会出错,提示找不到mm.class.php文件。这时可以加一行__autoload('c');但这样就达不到简化代码的目的。

类的家族化扩展:类的高级功能:

一、对象克隆:
当克隆一个对象的实例时,其属性初始值继承了被克隆对象的当前值。

代码如下:

class test
{
public $p=5;
function __clone(){ //只在克隆发生时起作用。用于改变在克隆时某些值
$this->p=15;
}
}
$a=new test();
echo $a->p;
$a->p=8; //如果没有__clone()方法影响,$b的P值将为8
$b = clone $a;
echo $b->p; //15

二、对象继承:

没有被声明为final的类可以被继承,没有被final和private界定的方法也可以继承,没有被private界定的属性也可以继承。当子类继承了父类或超类后,可以直接使用父类或超类(祖父类以及祖父的祖父)的所有允许的方法,属性。
关键:理解构造函数和重载在继承中的特性!

(一)构造函数在继承中的特性:

1、当父类有构造函数而子类没有:则子类会在实例化时会自动执行父类的构造函数。这时如果要创建子类的实例,需要引入父类构造函数中所需的参数,否则出错。即使是“子类的子类”如果没有构造函数,也要在创建实例时输入其父类的父类的构造函数所需参数。PHP会从实例所在的子类会向上搜索合造的构造函数,一旦找到就停止,使用该构造函数。而不会再向上搜索,因此:子类本身如果没有构造函数,则以其最靠近的一个超类并且有构造函数的为准。

代码如下:

class cA{
public $name,$age;
function __construct($n) {
$this->name = $n;
$this->age = 25;
}
function __set($n,$v) {
$this->$n = $v;
}
function __get($n) {
return $this->$n;
}
}

class cB extends cA{
function funB1() { echo '<h3>Class cB execute success!</h3>'; }
}

class cC extends cB {
function funC1() { echo '<h3>Class cC FunC1!</h3>'; }
}
$b=new cB('Jack');
$b->name='John';
echo “$b->name : $b->age”;
$b->funB1();
$c=new cC(); //这里会出错,由于cB也没有构造函数,因此再向上以cA为准,需要一个参数。改为$c=new cC('David');即可。
echo $c->name(); //David


2、当子类也有构造函数时:这时,不管父类是否有构造函数,都会执行子类自己的构造函数。
如上:
代码如下:

class cB extends cA{
function __construct() {
echo '<h3>this is Class cB \'s __construct!</h3>';
}
function funB1() {
echo '<h3>Class cB execute success!</h3>';
}
}

现在类CB有自己的构造函数时,这时创建实例$b=new cB('Jack');参数JACK不会起作用,因为父类CA的构造函数没有得到执行。因此$b->name和$->age就不会初始化值。需要另外赋值$b->name='Jack',$b->age=25;
如果这时要执行父类CA的构造函数,可以这样:
代码如下:

function __construct($n) {
parent::__construct($n); // 或:cA::__construct($n);
echo '<h3>this is Class cB \'s __construct!</h3>';
}

由于parent::__construct($n); 只会向上搜索父类的构造函数,一找到就停止且执行当前找到的构造函数,因此在上面例子中,如果parent::__construct($n)是用在最后一层的类cC中,并且类CB,CA都有构造函数,那么cC的实例只会执行cB的构造函数。不会执行cA。这时,如果CC的实例想都调用CA和CB的构造函数,有两种方法:

A、在CB中也加入parent::__construct($n)
B、在CC中把构造函数改为:

代码如下:

function __construct($n) {
cA::__construct($n); //即:类名::构造函数。
cB::__construct();
echo '<h3>this is Class cB \'s __construct!</h3>';
}

(二)在子类中调用父类的属性或方法:

1、调用父类方法:在子类中调用父类的方法,有3种方法:
$this->ParentFunction(); 或
父类名::ParentFunction(); 或
parent::parentFun();

2、调用父类属性:只能用$this->ParentProperty;

(三)重载:

在子类中,可以定义与父类相同属性或方法,改变父类该属性或方法的值或操作,称做重载。如:
calss ParClass{ function pfun(){ ….}}
class ChildrenClass extends ParClass{function pfun(){ ….}}} //重载了父类的pfun的方法。
在子类中重载后,优先执行自己重载后的新定义的方法或属性。
也可以在子类中用parent::parentFun();调用父类的方法,但所得到的值是子类自己输入的参数运算值。而不是该方法在父类中运算的值。

三、接口:

接口:interface,可以理解成一组功能的共同规范,最大意义可能就是在多人协作时,为各自的开发规定一个共同的方法名称。

和抽象类中的抽象方法一样:

1、不能在接口中对方法具体实现进行定义。而是由具体类来实现(而抽象类中的非抽象方法可以不必再定义,只有抽象方法和接口是一样要求要在具体类中实现)。

2、和抽象类一样,可以在接口中定义常量,并由具体类直接继承。

3、具体类必须实现抽象类的所有抽象方法(非抽象方法除外),同样,具体类如通过implements实现了接口后,必须完成接口中的所有方法。

接口实现过程:1、定义接口,2、用..implement X,Y,…和具体类对接。

代码如下:

interface Info{ //定义接口
const N=22;
public function getage();
public function getname();
}

class age implements Info //如要多个接口 class age (extends emJob) implements Info,interB…
{
public $age=15;
public $name='Join';
function getage() {
echo “年级是$this->age”;
}
function getname() {
echo “姓名是$this->name”;
}
function getN(){
echo '<h3>在接口中定义的常量N的值是:'.$this::N.' </h3>'; //直接继承接口中的常量值。
}
}

$age=new age;
echo $age::N; //22,直接调用接口中的常量值。
$age->getN();


关于抽象类和接口类的使用区分:何时用接口,何时用抽象?

1、相关性:当创建的模型由一些紧密相关的对象采用时,用抽象。对于不相关对象采用的功能,用接口。

2、多重继承:PHP类可以继承多个接口,但不能扩展多个抽象类。

3、公共行为实现:抽象类可在其中实现公共的方法,但接口不行。

四、命名空间(PHP6)

类库脚本A.inc.php和脚本B.inc.php中都一个类的名称为 class CNAME,并且这两个文件要在同一个文件如index.php中被调用。这时要用到命名空间。

步聚:

1、打开上面的A和B两个文件,分别在上面的最前面各加一行:

namespace SPACEA; 和 namespace SPACEB; 名字自定。

2、在index.php中实例化类时,在类的前面添加命名空间和双冒号做为前缀:
include 'a.inc.php';
include 'b.inc.php';
$a=new SPACEA::CNAME();
$b=new SPACEB::CNAME();

这样就不会冲突了。
但在PHP6正式发布前,这个功能还未定下来。

五、实现迭代器和迭代。
参《PHP圣经》P142;

六、使用Reflection(反射)API 。
简易实例:
class a{ …. }
$c = new ReflectionClass('a'); //PHP 内置类。
echo '<pre>'.$c.'</pre>';
输出类a的结构和内容。参《PHP圣经》P145;


安装服务器安全狗后PHP 500错误

FastCGI ErrorThe FastCGI Handler was unable to process the request.
Error Details:

Error Number: 5 (0x80070005).
Error Description:
HTTP Error 500 - Server Error.
Internet Information Services (IIS)

两种解决办法:
1、服务器安全狗》系统防火墙》关闭“进程行为控制”
2、服务器安全狗》系统防火墙》防护设置》进程行为控制》添加PHP进程并取消“阻止”选项。
C:\Program Files\PHP\php-cgi.exe
C:\Program Files\PHP\php.exe

PHP header的几个用法

1. 跳转页面
header('Location:'.$url);  //Location和":"之间无空格。

2. 声明content-type
header('content-type:text/html;charset=utf-8');

3. 返回response状态码
header('HTTP/1.1 404 Not Found');

4. 在某个时间后执行跳转
header('Refresh: 10; url=http://www.baidu.com/');  //10s后跳转。
html代码实现:
<meta http-equiv="refresh" content="10; url=https://www.baidu.com/">
10秒后刷新页面(不跳转):
<meta http-equiv="refresh" content="10">

5. 控制浏览器缓存
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");

6. 执行http验证
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Top Secret"');

7. 执行下载操作
header('Content-Type: application/octet-stream'); //设置内容类型
header('Content-Disposition: attachment; filename="example.zip"'); //设置MIME用户作为附件
header('Content-Transfer-Encoding: binary'); //设置传输方式
header('Content-Length: '.filesize('example.zip')); //设置内容长度

生成CSV格式excel基本规则

以下为CodeIgniter中导出csv的完整示例

知识点:输出缓冲控制 http://php.net/manual/zh/book.outcontrol.php

//导出数据
public function export(){
	/*
	 * 不使用PHPExcel生成excel文件,因为太占内存
	 * 生成csv基本规则
	 * 1、列内容用逗号分隔
	 * 2、\r\n换行
	 * 3、如果当前字段内容中有回车、换行、逗号等任意内容时,字段两头要加双引号
	 * 4、如果当前字段内容中有双引号,但不包含回车、换行、逗号等任意内容时,直接输出不用处理;否则字段中的双引号要进行转义(两个双引号代替1个),且字段两头加又引号
	 * 5、超过11位的整数要写成 ="123456789012" 形式,防止因excel自动采用科学计数法而导致内容失真(如身份证)
	 */
	
	//获取表字段并删除无用项
	$arr_table_fields = array_diff_key($this->tableinfo(), array('id'=>0, 'sn'=>0, 'updatetime'=>0, 'createtime'=>0));
	//重置要查询的字段
	$select_fields = implode(',', array_keys($arr_table_fields));
	
	//清空缓冲区
	if (ob_get_level() !== 0 && @ob_end_clean() === FALSE)@ob_clean();
	//输出headers
	header('Content-Type: application/octet-stream');
	header('Content-Disposition: attachment; filename="观众数据-'. date('YmdHis') .'.csv"');
	header('Expires: 0');
	header('Content-Transfer-Encoding: binary');
	//header('Content-Length: '.$filesize);
	header('Cache-Control: private, no-transform, no-store, must-revalidate');
	
	$str = '';
	//输出表头
	foreach($arr_table_fields as $row){
		$str .= $row['comment'].',';
	}
	$str .= "\n";
	echo utf82gbk($str);
	
	//获取记录总数
	$num = $this->db->count_all('my_guanzhong');
	if(! $num)return;
	//每次获取1000条
	$pagesize = 1000;
	//循环获取数据并输入
	//如果数据记录很多而直接全部读取到内存会导致内存溢出
	ob_start();
	for($i=0; $idb->select($select_fields)->order_by('id DESC')->limit($pagesize, $i*$pagesize)->get('my_guanzhong');
		while($row=$res->unbuffered_row()){
			foreach($row as $item){
				if(is_numeric($item)){ //数值型
					if(preg_match("/^-?\d{1,11}$/", (string)$item)){ //不超过11位的整数
						echo $item, ',';
					}else{
						echo '="', $item, '",';
					}
				}else{
					if(strpos($item, ',') !== FALSE || strpos($item, "\r") !== FALSE || strpos($item, "\n") !== FALSE){ //逗号、回车、换行
						if(strpos($item, '"') !== FALSE){ //有双引号
							echo '"', str_replace('"', '""', $item), '",';
						}else{
							echo '"', $item, '",';
						}
					}else{
						echo $item, ',';
					}
				}
			}
			echo "\n";
		}
		//echo format_bytes(memory_get_peak_usage()), ', ', microtime(1)-NOW_TIME, "\n";
		//获取缓冲区内容并转为gbk编码
		$str = utf82gbk(ob_get_contents());
		//清空缓冲区
		ob_clean();
		//重新输出转码后的内容
		echo $str;
		//发送至浏览器
		ob_flush();
	}
	flush();
}

PHPExcel使用过滤器按块读取excel

PHPExcel读取EXCEL非常消耗内存(尤其是上万条数据,很容易出现内存不足),可以使用分批、异步读取,思路:
1、获取表中记录总行数
2、每次读取N条写入数据库,并返回最后一条的行号
3、根据上次返回的行号使用AJAX等方式循环第2步
相应代码可从下方示例中分离

//加载PHPExcel类库
require_once FCPATH.'app/manage/libraries/PHPExcel/IOFactory.php';
//按块读取EXCEL,减少内存占用
class chunkReadFilter implements PHPExcel_Reader_IReadFilter{
	private $_startRow = 0;
	private $_endRow = 0;
	//设置要读取的块开始行、结束行
	public function setRows($startRow, $chunkSize) {
		$this->_startRow	= $startRow;
		$this->_endRow		= $startRow + $chunkSize;
	}
	//确定当前行是否读取
	public function readCell($column, $row, $worksheetName = '') {
		//读取首行(标题行)和块区间内的行数据
		//return (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) ? TRUE : FALSE;
		//只读取块区间内的行数据
		return ($row >= $this->_startRow && $row < $this->_endRow) ? TRUE : FALSE;
	}
}

//执行导入
function import_do($arr_fields){
	$excelfields = P('fields'); //要导入的字段和顺序
	$excelfile = P('excelfile'); //要导入的excel文件
	$skiplines = P('skiplines'); //跳过行数
	$deletefile = P('deletefile'); //导入成功后删除excel文件
		
	if(!$excelfields){
		return $this->_display('请添加要导入的字段');
	}else{
		$excelfields = explode('|', $excelfields);
	}
	if(! $excelfile)return $this->_display('请上传要导入的文件');
	is_lng($skiplines) OR $skiplines=0;
	$deletefile = $deletefile=='1' ? TRUE : FALSE;
		
	$time=$this->input->server('REQUEST_TIME');
		
	//设置超时时间为99秒
	set_time_limit(99);
		
	$excelfile = FCPATH.substr($excelfile, 1);
	$inputFileType = PHPExcel_IOFactory::identify($excelfile);
	$objReader = PHPExcel_IOFactory::createReader($inputFileType);
	$objReader->setReadDataOnly(true);
		
	$chunkSize = 500; //每次读取记录数
	$chunkFilter = new chunkReadFilter(); //实例化按块读取过滤器
	$objReader->setReadFilter($chunkFilter); //使用过滤器
	$chunkFilter->setRows(1,1); //从第一条开始读取1条,据此获取activeShieetIndex
	$objPHPExcel = $objReader->load($excelfile);
	$activeSheetIndex = $objPHPExcel->getActiveSheetIndex();
		
	$worksheetData = $objReader->listWorksheetInfo($excelfile); // 列出工作表列表
	$activeSheetInfo = $worksheetData[$activeSheetIndex];
/*
 * $activeSheetInfo
Array(
    [worksheetName] => 信息
    [lastColumnLetter] => AQ
    [lastColumnIndex] => 42
    [totalRows] => 21
    [totalColumns] => 43
)
*/
	for ($startRow = $skiplines+1+5000; $startRow <= $activeSheetInfo['totalRows']; $startRow += $chunkSize) {
		echo 'Loading WorkSheet using configurable filter for headings row 1 and for rows ',$startRow,' to ',($startRow+$chunkSize-1),'<br />';
		//设置本次循环要读取的记录
		$chunkFilter->setRows($startRow,$chunkSize);
		//根据过滤器读取数据
		$objPHPExcel = $objReader->load($excelfile);
		$sheetData = $objPHPExcel->getActiveSheet()->toArray(null,true,true,true);
		$sheetData = array_slice($sheetData, $startRow-1);
		//echo '<pre>';
		//print_r($sheetData);
	}
	echo memory_get_peak_usage(), ', ', memory_get_usage(), ', ', (microtime(true)-$time);
	exit;
}

PHPExcel官方示例

PHPExcel已停止开发,需要使用新类库PhpSpreadsheet
https://github.com/PHPOffice/PhpSpreadsheet



转自: http://blog.csdn.net/beyond__devil/article/details/53457849

1.使用 PHPExcel_IOFactory 读取文件
$objPHPExcel = PHPExcel_IOFactory::load($inputFileName);
2.使用一个特定的读取类,读取文件
$objReader = new PHPExcel_Reader_Excel5();
objPHPExcel = $objReader->load($inputFileName);
3.使用 PHPExcel_IOFactory 创建一个特定的读取类
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objPHPExcel = $objReader->load($inputFileName);
读取类型有:
$inputFileType = 'Excel5';
$inputFileType = 'Excel2007';
$inputFileType = 'Excel2003XML';
$inputFileType = 'OOCalc';
$inputFileType = 'SYLK';
$inputFileType = 'Gnumeric';
$inputFileType = 'CSV';

4.使用 PHPExcel_IOFactory 来鉴别文件应该使用哪一个读取类
$inputFileType = PHPExcel_IOFactory::identify($inputFileName);
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objPHPExcel = $objReader->load($inputFileName);
5.只读取数据,忽略里面各种格式等(对于Excel读取,有很大优化)
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load($inputFileName);
6.加载Excel所有的工作表
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setLoadAllSheets(); // 加载所有的工作表
$objPHPExcel = $objReader->load($inputFileName);
$objPHPExcel->getSheetCount(); // 获取工作表的个数
$objPHPExcel->getSheetNames(); // 获取所有工作表的名字数组
7.加载单个的命名的工作表
$sheetname = 'Data Sheet #2'; // 单个工作表,传入字符串
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setLoadSheetsOnly($sheetname); // 加载单个工作表,传入工作表名字(例如:'Data Sheet #2')
$objPHPExcel = $objReader->load($inputFileName);
8.加载多个命名的工作表
$sheetnames = array('Data Sheet #1', 'Data Sheet #2'); // 多个工作表,传入数组
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setLoadSheetsOnly($sheetnames); // 加载多个工作表,传入工作表名字数组
$objPHPExcel = $objReader->load($inputFileName);
9.自定义一个读取过滤器
class MyReadFilter implements PHPExcel_Reader_IReadFilter
{
public function readCell($column, $row, $worksheetName = '') {
// 只读取1-7行&A-E列中的单元格
if ($row >= 1 && $row <= 7) {
if (in_array($column,range('A','E'))) {
return true;
}
}
return false;
}
}
$filterSubset = new MyReadFilter();
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadFilter($filterSubset); // 设置实例化的过滤器对象
$objPHPExcel = $objReader->load($inputFileName);
10.同样是自定义一个读取过滤器,但可配置读取的行和列范围
class MyReadFilter implements PHPExcel_Reader_IReadFilter
{
private $_startRow = 0; // 开始行
private $_endRow = 0; // 结束行
private $_columns = array(); // 列跨度
public function __construct($startRow, $endRow, $columns) {
$this->_startRow = $startRow;
$this->_endRow = $endRow;
$this->_columns = $columns;
}
public function readCell($column, $row, $worksheetName = '') {
if ($row >= $this->_startRow && $row <= $this->_endRow) {
if (in_array($column,$this->_columns)) {
return true;
}
}
return false;
}
}
$filterSubset = new MyReadFilter(9,15,range('G','K'));
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadFilter($filterSubset); // 设置实例化的过滤器对象
$objPHPExcel = $objReader->load($inputFileName);
11.分块读取Excel,原理还是:自定义读取过滤器
class chunkReadFilter implements PHPExcel_Reader_IReadFilter
{
private $_startRow = 0; // 开始行
private $_endRow = 0; // 结束行
public function __construct($startRow, $chunkSize) { // 我们需要传递:开始行号&行跨度(来计算结束行号)
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}
public function readCell($column, $row, $worksheetName = '') {
if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) {
return true;
}
return false;
}
}
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$chunkSize = 20; // 定义每块读取的行数
// 就可在一个循环中,多次读取块,而不用一次性将整个Excel表读入到内存中
for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) {
$chunkFilter = new chunkReadFilter($startRow, $chunkSize);
$objReader->setReadFilter($chunkFilter); // 设置实例化的过滤器对象
$objPHPExcel = $objReader->load($inputFileName);
// 开始读取每行数据,并插入到数据库
}
12.分块读取Excel的第2个版本
class chunkReadFilter implements PHPExcel_Reader_IReadFilter
{
private $_startRow = 0; // 开始行
private $_endRow = 0; // 结束行
// 定义了一个读取指定范围行的方法
public function setRows($startRow, $chunkSize) {
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}
public function readCell($column, $row, $worksheetName = '') {
if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) {
return true;
}
return false;
}
}
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$chunkSize = 20; // 定义每块读取的行数
// 在循环外部,实例化过滤器类,而不用循环内每次实例化(应该更优化)
$chunkFilter = new chunkReadFilter();
$objReader->setReadFilter($chunkFilter);
for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) {
// 循环内部,使用实例化的对象的方法,来调整读取的行范围即可
$chunkFilter->setRows($startRow,$chunkSize);
$objPHPExcel = $objReader->load($inputFileName);
}
13.读取多个CSV文件
$inputFileNames = array('./sampleData/example1.csv','./sampleData/example2.csv'); // CSV文件数组
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/*
说明下面是干啥的:
1.先载入第一个CSV作为第一个工作表 | 设置工作表的标题
2.依次将多个CSV再入到 objPHPExcel 对象中,依次追加到第N个工作表 | 设置工作表的标题
3.获取Excel此时所有的标题,通过标题来依次获取工作表,然后对工作表进行操作!
*/
$inputFileName = array_shift($inputFileNames); // 第一个CSV文件
$objPHPExcel = $objReader->load($inputFileName); // 读取第一个CSV文件
$objPHPExcel->getActiveSheet()->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME)); // 设置标题
foreach($inputFileNames as $sheet => $inputFileName) {
$objReader->setSheetIndex($sheet+1); // 将工作表切换到下个工作表
$objReader->loadIntoExisting($inputFileName,$objPHPExcel); // 将下一个CSV文件,载入到已存在的PHPExcel对象中
$objPHPExcel->getActiveSheet()->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME)); // 设置当前工作表的标题
}
// 循环所有的工作表名称
$loadedSheetNames = $objPHPExcel->getSheetNames();
foreach($loadedSheetNames as $sheetIndex => $loadedSheetName) {
$objPHPExcel->setActiveSheetIndexByName($loadedSheetName); // 通过 '工作表名称' 来设置当前工作表为激活状态
// 接着对当前激活的工作表,进行读取、数据库写入
}
14.将一个大的CSV文件,按 '块' 分成多个工作表(结合了12&13的示例)
class chunkReadFilter implements PHPExcel_Reader_IReadFilter
{
private $_startRow = 0; // 开始行
private $_endRow = 0; // 结束行
// 定义了一个读取指定范围行的方法
public function setRows($startRow, $chunkSize) {
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}
public function readCell($column, $row, $worksheetName = '') {
if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) {
return true;
}
return false;
}
}
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$chunkSize = 100; // 定义每块读取的行数
// 在循环外部,实例化过滤器类,而不用循环内每次实例化(应该更优化)
$chunkFilter = new chunkReadFilter();
$objReader->setReadFilter($chunkFilter)
  ->setContiguous(true); // 这里出现了一个没见过的方法(先放着,忘记是干啥的了)
$objPHPExcel = new PHPExcel();
$sheet = 0; // 第一个工作表下标
for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) {
$chunkFilter->setRows($startRow,$chunkSize);
    $objReader->setSheetIndex($sheet); // 切换工作表
    $objReader->loadIntoExisting($inputFileName,$objPHPExcel); // 将读取到的CSV块,载入到工作表
    $objPHPExcel->getActiveSheet()->setTitle('Country Data #'.(++$sheet)); // 设置工作表标题
}
// 循环所有的工作表名称
$loadedSheetNames = $objPHPExcel->getSheetNames();
foreach($loadedSheetNames as $sheetIndex => $loadedSheetName) {
$objPHPExcel->setActiveSheetIndexByName($loadedSheetName); // 通过 '工作表名称' 来设置当前工作表为激活状态
// 接着对当前激活的工作表,进行读取、数据库写入
}

15.使用 'Advanced Value Binder' 读取通过 'tab' 分隔值的文件
PHPExcel_Cell::setValueBinder( new PHPExcel_Cell_AdvancedValueBinder() ); // 设置单元格
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setDelimiter("\t"); // 设置分隔符为 '\t'(tab分隔)
$objPHPExcel = $objReader->load($inputFileName);
$objPHPExcel->getActiveSheet()->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME)); // 设置标题
$loadedSheetNames = $objPHPExcel->getSheetNames(); // 获取所有工作表名称

$objPHPExcel->getActiveSheet()->toArray($nullValue, $calculateFormulas, $formatData, $returnCellRef);
mixed $nullValue
单元格不存在时的返回值
boolean $calculateFormulas
是否对单元格进行计算,如 =1+2 True返回3
boolean $formatData
是否格式化值,尤其是时间列,false返回excel时间,true返回如 3/18/17 8:00
false时可以用 PHPExcel_Shared_Date::ExcelToPHP 把返回的excel时间转为php时间戳
boolean $returnCellRef
TRUE返回数组格式array(1=>array('A'=>'', 'B'=>''))
FALSE返回数组格式array(0=>array(0=>'', 1=>''))

1)格式化输出
foreach($loadedSheetNames as $sheetIndex => $loadedSheetName) {
$objPHPExcel->setActiveSheetIndexByName($loadedSheetName);
$sheetData = $objPHPExcel->getActiveSheet()->toArray(null,true,true,true); // 注意4个参数的区别
}
2)未格式化输出
foreach($loadedSheetNames as $sheetIndex => $loadedSheetName) {
$objPHPExcel->setActiveSheetIndexByName($loadedSheetName);
$sheetData = $objPHPExcel->getActiveSheet()->toArray(null,true,false,true); // 注意4个参数的区别
}
3)单元格原生值
foreach($loadedSheetNames as $sheetIndex => $loadedSheetName) {
$objPHPExcel->setActiveSheetIndexByName($loadedSheetName);
$sheetData = $objPHPExcel->getActiveSheet()->toArray(null,false,false,true); // 注意4个参数的区别
}
16.使用 'try/catch' 控制Excel加载时的异常
try {
$objPHPExcel = PHPExcel_IOFactory::load($inputFileName);
} catch(PHPExcel_Reader_Exception $e) {
die('Error loading file "'.pathinfo($inputFileName,PATHINFO_BASENAME).'": '.$e->getMessage());
}
17.获取Excel的工作表名称列表
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$worksheetNames = $objReader->listWorksheetNames($inputFileName); // 列出工作表名称
foreach($worksheetNames as $worksheetName) {
echo $worksheetName,'<br />';
}
18.不加载整个文件,或者Excel的工作表名称列表
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$worksheetData = $objReader->listWorksheetInfo($inputFileName); // 列出工作表列表
foreach ($worksheetData as $worksheet) {
echo '<li>', $worksheet['worksheetName'], '<br />';
echo 'Rows: ', $worksheet['totalRows'], ' Columns: ', $worksheet['totalColumns'], '<br />';
echo 'Cell Range: A1:', $worksheet['lastColumnLetter'], $worksheet['totalRows'];
echo '</li>';
}
19.全程,有一个方法:
$sheetData = $objPHPExcel->getActiveSheet()->toArray(null,false,false,true);
getActiveSheet() - 获取当前激活的工作表
toArray() - 将当前激活的工作表,解析全部放入数组中

PHPExcel笔记(CI框架中使用)

参考:
http://www.jquerycn.cn/a_12224
http://www.jquerycn.cn/a_12222

一、因PHPExcel命名规则不同于CodeIgniter(类名必须与文件名相同),为减少对PHPExcel源代码的修改,所以不使用CI的加载类$this->load->library(),而直接使用 require_once
require_once FCPATH.'libraries/PHPExcel.php';
$phpexcel = new PHPExcel;

必须修改的地方: PHPExcel/IOFactory.php  的构造函数 private function __construct() { } 
private 改为 public

二、设置列格式
1、设置A1单元格为文本格式,设置整列为 getStyle('A')
需要 PHPExcel/Style/NumberFormat.php
$this->phpexcel->getActiveSheet()->getStyle('A1')->getNumberFormat()->setFormatCode(PHPExcel_Style_NumberFormat::FORMAT_TEXT);
注意:此方法对长数字字符串还是以文本方式来显示科学计数法的结果,原因可能php在处理大数字时采用的科学计数法。

2、需要 PHPExcel/Cell/DataType.php
$objPHPExcel->getActiveSheet()->setCellValueExplicit('D1',123456789033,PHPExcel_Cell_DataType::TYPE_STRING);

3、在数字字符串前加一个空格使之成为字符串
$objPHPExcel->getActiveSheet()->setCellValue('D1', ' ' . 123456789033);

推荐第2种方法

Incorrect string value: '\xF0\x9F\x98\xAD",...' for column...的解决方法

最近在微信开发时遇到以下错误:
Incorrect string value: '\xF0\x9F\x90\xB3 \xE4...' for column 'nickname' at row 69302

原因:用户昵称中有emoji表情,而emoji表情为4字节utf-8字符,mysql只支持不超过3字节的utf-8字符,所以写入失败。

4字节utf-8字符包括Emoji表情(Emoji 是一种特殊的 Unicode 编码,常见于 iOS 和 Android 手机上),和一些不常用的汉字,以及任何新增的 Unicode 字符等等。

MySQL 版本>=5.5.3支持utf8mb4编码,使用这个编码可以支持4字节utf-8字符。
MySQL低于5.5.3版本,可以过滤掉4字节字符,或采用base64_encode进行编码后保存,输出时再base64_decode解码。

/**
 * 过滤掉emoji表情(4字节utf8编码字符)
 */
function filter_emoji($str){
  return preg_replace_callback('/./u', 
    function(array $match){return strlen($match[0]) >= 4 ? '' : $match[0];}, 
    $str);
}

参考:
http://blog.csdn.net/yan791124465/article/details/70738939
http://blog.csdn.net/hzw19920329/article/details/55670782
http://blog.csdn.net/secretx/article/details/21253559

我的.htaccess

RewriteEngine On
#以old开头的请求301跳转到www.XXX.com首页
RewriteCond %{request_uri} ^/old [NC]
RewriteRule ^(.*)$ http://www.XXX.com/ [L,R=301]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond $1 !^(robots\.txt|favicon\.ico)$
RewriteCond $1 !^.+\.(ico|js|css|jpg|jpe|jpeg|png|bmp|gif|mp4|flv|swf|txt|pdf|zip|rar|htm|doc|docx|xls|xlsx)$
#部分IIS中!-f无效,需要下面一行以防止手机站被index.php解析
RewriteCond $1 !^m\.php
RewriteRule ^(.*)$ index.php/$1 [L]

允许跨域

Header add Access-Control-Allow-Origin "http://aaa.example"

中文-未验证

RewriteRule ([\x80-\xffa-zA-Z]{1,})-([0-9]{1,}).html$ test.php?action=$1&id=$2

根据域名跳转,下面把abc.com的所有访问临时跳转到def.com(含querystring)

Rewritecond %{HTTP_HOST} ^(www\.)?abc.com$ [NC]
Rewriterule ^(.*)$ http://def.com/$1 [R=302,L]

RewriteCond %{request_uri} ^/old [NC]
RewriteRule ^(.*)$ http://www.XXX.com/ [L,R=301]
以上会把 http://abc.com/old?kw=text 跳转到 http://www.xxx.com/?kw=text
如果要舍弃QUERY_STRING,只需在跳转网址后加问号(正则结尾)即可,如下:
RewriteCond %{request_uri} ^/old [NC]
RewriteRule ^(.*)$ http://www.XXX.com/? [L,R=301]

下面可以用于不支持asp的服务器,把百度收录的原asp网页跳转到指定页面。

RewriteRule ^(.*)\.asp http://www.abc.com/baidu/index/$1 [R=301,L]

nginx伪静态

if (!-e $request_filename) {
	rewrite ^/manage\.php /manage.php last;
	rewrite ^.*$ /index.php last;
}

不是ico/jpg/png/gif中的一种时才进行伪静态重写

location ~* .*(?<!\.(ico|jpg|png|gif))$ {
	if (!-e $request_filename) {
		rewrite ^/manage\.php /manage.php last;
		rewrite ^.*$ /index.php last;
	}
}
2024-05-10 星期五 农历四月初三