常用知识 - 常用的PHP系统回调

  • 作者:KK

  • 发表日期:2016.09.15


常用回调快速介绍

常用的回调注册函数有:

  • set_error_handler:如果你想在程序发生错误时想做点什么处理就用这个

  • set_exception_handler:如果你想在程序发生异常时想做点什么处理就用这个

  • register_shutdown_function:当程序结束时想做点什么处理就用这个

  • spl_autoload_register:当调用一个不存在的类时自动加载这个类就用这个

  • __autoload:(旧版,不推荐)当调用一个不存在的类时自动加载这个类就用这个

下面一一解说(下面附加的测试代码直接复制粘贴到单独脚本就可以运行测试,不需要依赖任何其它东西)


set_error_handler

设置错误的处理函数

函数原型:mixed set_error_handler(callable $error_handler [, int $error_types = E_ALL | E_STRICT ])

测试代码:

set_error_handler(function($code, $message, $file, $line){
	echo <<<EOL
错误类型特征码:$code<br/>
错误消息:$message<br/><br/>

出错文件:$file<br/>
出错行号:$line
EOL;
});

error_reporting(-1); //只是为了保证会报错,以防php.ini配置屏蔽了下面的错误
$a = [];
echo $a[5];

用网页运行就会看到这样的报错:

错误代码:8
错误消息:Undefined offset: 5

出错文件:D:\phpStudy\www\test\index.php
出错行号:14

正是因为咱们通过set_error_handler设置了自定义的错误处理才有这样的输出

如果不set_error_handler的话,出错的时候会使用PHP自带的默认处理,输出内容大概是这样的:

Notice: Uninitialized string offset: 5 in D:\phpStudy\www\test\index.php on line 14
  • 应用场景:出了错就会引发这个回调函数的执行,然而程序出错当然是程序员不希望出现的,既然出错了我们就希望尽量知道是什么错误,所以通常在这个时候做日志记录或者发邮件通知等都可能会出现

set_exception_handler

设置异常的处理函数

函数原型:callable set_exception_handler(callable $exception_handler )

调用思路和set_error_handler一样,测试代码:

set_exception_handler(function($exception){
	$code = $exception->getCode();
	$message = $exception->getMessage();
	$file = $exception->getFile();
	$line = $exception->getLine();
	
	echo <<<EOL
异常类型特征码:$code<br/>
异常消息:$message<br/><br/>

抛出异常的文件:$file<br/>
抛出异常的行号:$line
EOL;
});

error_reporting(-1);
throw new Exception('测试异常');

则输出:

错误类型特征码:0
错误消息:测试异常

出错文件:D:\phpStudy\www\test\index.php
出错行号:19
  • 应用场景:跟set_error_handler一样

register_shutdown_function

注册程序结束时的回调函数

函数原型:void register_shutdown_function( callable $callback [, mixed $parameter [, mixed $... ]] )

测试代码:

register_shutdown_function(function(){
	echo '<br/>程序运行结束1,本次运行一共耗费内存:' . memory_get_peak_usage(true) . '字节';
});

register_shutdown_function(function(){
	echo '<br/>程序运行结束2,本次运行一共耗费内存:' . memory_get_peak_usage(true) . '字节';
});

$random = mt_rand(99, 999);
$result = [];
for($i = 0; $i < $random; $i++){
	$result[] = array_fill(0, $i, $i);
}
echo '一共创建了' . count($result) . '个数组';

输出:

一共创建了749个数组
程序运行结束1,本次运行一共耗费内存:15466496字节
程序运行结束2,本次运行一共耗费内存:15466496字节

上面的测试代码执行了两次register_shutdown_function,因为程序结束回调函数是可以注册多个的

另外就算中途执行exitdie也会触发这个回调

  • 应用场景:当程序结束后,如果判断出是调试模式,会进行内存使用情况和运行时间的统计;另外如果通过error_get_last函数获取到错误的话会进行日志记录,让程序员知道去修复

spl_autoload_register

注册自动加载函数

函数原型:bool spl_autoload_register([callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]])

测试代码:

spl_autoload_register(function($className){
	echo '代码加载了不存在的类:' . $className . '<br/>';
	if(!class_exists($className, false)){
		echo '经确认' . $className . ' 这个类确实是不存在的<br/>';
	}
	
	
	//创建这个类,实际应用中文件已经存在,我现在是动态创建文件
	$classCode = <<<EOL
<?php
class $className{
	public function test(){
		echo 'hi,我是' . __CLASS__ . '<br/><br/><br/>';
	}
}
EOL;

	file_put_contents($className . '.php', $classCode);
	
	include($className . '.php');
	
	if(class_exists($className, false)){
		echo '好了,' . $className . ' 这个类存在了<br/>';
	}else{
		return false; //如果加载不到类请返回false让系统自动调用下一个autoload回调
	}
	
	return new $className();
});

$a = new A();
$a->test();

$b = new B();
$b->test();

$c = new C();
$c->test();

输出结果:

代码加载了不存在的类:A
经确认A 这个类确实是不存在的
好了,A 这个类存在了
hi,我是A


代码加载了不存在的类:B
经确认B 这个类确实是不存在的
好了,B 这个类存在了
hi,我是B


代码加载了不存在的类:C
经确认C 这个类确实是不存在的
好了,C 这个类存在了
hi,我是C

并且和register_shutdown_function一样,spl_autoload_register可以注册多个自动加载函数,虽然很少见,但是偶尔还是有的

比如有两种情况是用spl_autoload_register注册多个自动加载函数的:

  1. 重构一个项目

    项目的代码比较老,用spl_autoload_register注册过他们自己的加载逻辑,但重构时需要一部分一部分重构,所以要基于旧代码改,此时再通过spl_autoload_register添加了新的加载逻辑,在加载新的类时,旧的加载代码是找不到文件的,于是就靠新注册的加载函数去加载了,两个加载函数会被轮流调用

  2. 双框架项目

    优秀的框架在本地化方面是做得很优秀的,基本没有全局变量和常量,所以有些特殊情况下会需要在A框架做的项目下引入B框架,利用B框架做它擅长的事,比如Codeception框架整合了Gazzle框架做Http处理

    而我自己做的项目也经历过双框架,甚至三框架模式

  • 应用场景:项目的类自动加载,现在已经越来越少人手动include一个类文件再new一个类了,都是直接new,不存在的时候就自动加载,并且加载逻辑方面开始有了PHP行业的统一规范:PSR4规范 自动载入类

__autoload

自动加载不存在的类

函数原型:void __autoload(string $class)

这个在PHP5.1.2以后就被spl_autoload_register取代了,所以不建议使用的,然而我为什么还要讲这个函数呢

其实由于大部分现存的PHP程序还是基于兼容旧版的设计的,所以在自动加载方面可能会有部分程序使用了__autoload而不使用spl_autoload_register

测试代码:

//直接声明这个函数就可以
function __autoload($className){
	file_put_contents($className . '.php', "<?php class $className{ public function test(){ echo 'hi wo shi A'; } }");
	return new $className();
}

$a = new A();
echo $a->test();

结果输出:

hi wo shi A