PHP debug_backtrace的胡思乱想
添加时间:2013-5-9 点击量:
本文示例代码测试景象是Windows下的APMServ(PHP5.2.6)
简述
可能大师都知道,php中有一个函数叫debug_backtrace,它可以回溯跟踪函数的调用信息,可以说是一个调试利器。
好,来复习一下。
one();
function one() {
two();
}
function two() {
three();
}
function three() {
print_r( debug_backtrace() );
}
/
输出:
Array
(
[0] => Array
(
[file] => D:\apmserv\www\htdocs\test\debug\index.php
[line] => 10
[function] => three
[args] => Array
(
)
)
[1] => Array
(
[file] => D:\apmserv\www\htdocs\test\debug\index.php
[line] => 6
[function] => two
[args] => Array
(
)
)
[2] => Array
(
[file] => D:\apmserv\www\htdocs\test\debug\index.php
[line] => 3
[function] => one
[args] => Array
(
)
)
)
/
趁便提一下类似的函数:debug_print_backtrace,与之不合的是它会直接打印回溯信息。
回来看debug_backtrace,从名字来看用处很明白,是让开辟者用来调试的。直到有一天我重视到它返回的file参数,file默示函数或者办法的调用脚底本源(在哪个脚本文件应用的)。忽然我想到,若是当前脚本知道调用起原,那是否可以按照这个起原的不合,来实现一些有趣的功能,比如文件权限经管、动态加载等。
实战
实现魔术函数
获取当前函数或办法的名称
尽管PHP中已经有了__FUNCTION__和__METHOD__魔术常量,但我还是想介绍一下用debug_backtrace获取当前函数或者办法名称的办法。
代码如下:
//函数外部输出getFuncName的值
echo getFuncName();
printFuncName();
Object::printMethodName();
//调用了上方两个函数后,再次在外部输出getFuncName,看看是否有‘缓存’之类的题目
echo getFuncName();
function printFuncName() {
echo getFuncName();
}
class Object {
static function printMethodName() {
echo getFuncName();
}
}
/
获取当前函数或者办法的名称
函数名叫getFuncName,好吧,其实method也可以当做function,其实想不出好名字
@return string name
/
function getFuncName() {
¥debug_backtrace = debug_backtrace();
//若是函数名是以下几个,默示载入了脚本,并在函数外部调用了getFuncName
//这种景象应当返回空
¥ignore = array(
include,
include_once,
require,
require_once
);
//第一个backtrace就是当前函数getFuncName,再上一个(第二个)backtrace就是调用getFuncName的函数了
¥handle_func = ¥debug_backtrace[1];
if( isset( ¥handle_func[function] ) && !in_array( ¥handle_func[function], ¥ignore ) ) {
return ¥handle_func[function];
}
return null;
}
//输出:
//null
//printFuncName
//printMethodName
//null
看上去没有题目,很好。
加载相对路径文件
若是在项目中要加载相对路径的文件,必须应用include或者require之类的原生办法,但如今有了debug_backtrace,我可以应用自定义函数去加载相对路径文件。
新建一个项目,目次布局如下:
我想在index.php中调用自定义函数,并应用相对路径去载入package/package.php,并且在package.php中应用同样的办法载入_inc_func.php
三个文件的代码如下(留心index.php和package.php调用import函数的代码):
index.php:
<?php
import( ./package/package.php );
/
加载当前项面前目今的文件
@param string ¥path 相对文件路径
/
function import( ¥path ) {
//获得backstrace列表
¥debug_backtrace = debug_backtrace();
//第一个backstrace就是调用import的起原脚本
¥source = ¥debug_backtrace[0];
//获得调用源的目次路径,和文件路径连络,就可以算出完全路径
¥source_dir = dirname( ¥source[file] );
require realpath( ¥source_dir . / . ¥path );
}
?>
package.php:
<?php
echo package;
import( ./_inc_func.php );
?>
_inc_func.php:
<?php
echo _inc_func;
?>
运行index.php:
//输出:
//package
//_inc_func
可以看到,我成功了。
思虑:这个办法我感觉很是强大,除了相对路径之外,可以按照这个思路引伸出相对包、相对模块之类的抽象特点,对于一些项目来说可以加强模块化的感化。
经管文件调用权限
我商定一个规范:文件名前带下划线的只能被当前目次的文件调用,也就是说这种文件属于当前目次‘私有’,其它目次的文件不容许载入它们。
如许做的目标很明白:为了降落代码耦合性。在项目中,很多时辰一些文件只被用在特定的脚本中。然则经常产生的工作是:一些法度员发明这些脚本有本身须要用到的函数或者类,是以直接载入它来达到本身的目标。如许的做法很不好,底本这些脚本编写的目标仅仅为了帮助某些接话柄现,它们并没有推敲到其它通用性。万一接口内部须要重构,同样须要批改这些特定的脚本文件,然则批改后一些看似与这个接口无关脚本却忽然无法运行了。一经搜检,却发明文件的引用错综错杂。
规范只是把守感化,不打消有工钱了一己私欲而违背这个规范,或者无意中违背了。好的办法是落实到代码中,让法度主动去检测这种景象。
新建一个项目,目次布局如下。
那么对于这个项目来说,_inc_func.php属于package目次的私有文件,只有package.php可以载入它,而index.php则没有这个权限。
package目次是一个包,package.php下供给了这个包的接口,同时_inc_func.php有package.php须要用到的一些函数。index.php将会应用这个包的接口文件,也就是package.php
它们的代码如下
index.php:
<?php
header(Content-type: text/html; charset=utf-8);
//定义项目根目次
define( APP_PATH, dirname( __FILE__ ) );
import( APP_PATH . /package/package.php );
//输出包的信息
Package_printInfo();
/
加载当前项面前目今的文件
@param string ¥path 文件路径
/
function import( ¥path ) {
//应当搜检路径的合法性
¥real_path = realpath( ¥path );
¥in_app = ( stripos( ¥real_path, APP_PATH ) === 0 );
if( empty( ¥real_path ) || !¥in_app ) {
throw new Exception( 文件路径不存在或不被容许 );
}
include ¥real_path;
}
?>
_inc_func.php:
<?php
function _Package_PrintStr( ¥string ) {
echo ¥string;
}
?>
package.php:
<?php
define( PACKAGE_PATH, dirname( __FILE__ ) );
//引入私有文件
import( PACKAGE_PATH . /_inc_func.php );
function Package_printInfo() {
_Package_PrintStr( 我是一个包。 );
}
?>
运行index.php:
//输出:
//我是一个包。
全部项目应用了import函数载入文件,并且代码看起来是正常的。然则我可以在index.php中载入package/_inc_func.php文件,并调用它的办法。
index.php中更改import( APP_PATH . /package/package.php );处的代码,并运行:
import( APP_PATH . /package/_inc_func.php );
_Package_PrintStr( 我载入了/package/_inc_func.php脚本 );
//输出:
//我载入了/package/_inc_func.php脚本
那么,这时可以应用debug_backtrace搜检载入_inc_func.php文件的路径来自哪里,我批改了index.php中的import函数,完全代码如下:
/
加载当前项面前目今的文件
@param string ¥path 文件路径
/
function import( ¥path ) {
//起首应当搜检路径的合法性
¥real_path = realpath( ¥path );
¥in_app = ( stripos( ¥real_path, APP_PATH ) === 0 );
if( empty( ¥real_path ) || !¥in_app ) {
throw new Exception( 文件路径不存在或不被容许 );
}
¥path_info = pathinfo( ¥real_path );
//断定文件是否属于私有
¥is_private = ( substr( ¥path_info[basename], 0, 1 ) === _ );
if( ¥is_private ) {
//获得backstrace列表
¥debug_backtrace = debug_backtrace();
//第一个backstrace就是调用import的起原脚本
¥source = ¥debug_backtrace[0];
//获得调用源路径,用它来和目标路径进行斗劲
¥source_dir = dirname( ¥source[file] );
¥target_dir = ¥path_info[dirname];
//不在同一目次下时抛出异常
if( ¥source_dir !== ¥target_dir ) {
¥relative_source_file = str_replace( APP_PATH, , ¥source[file] );
¥relative_target_file = str_replace( APP_PATH, , ¥real_path );
¥error = ¥relative_target_file . 文件属于私有文件, . ¥relative_source_file . 不克不及载入它。;
throw new Exception( ¥error );
}
}
include ¥real_path;
}
这时再运行index.php,将产生一个致命错误:
//输出:
//致命错误:/package/_inc_func.php文件属于私有文件,/index.php不克不及载入它。
而载入package.php则没有题目,这里不进行演示。
可以看到,我当初的设法成功了。尽管如许,在载入package.php后,其实在index.php中仍然还可以调用_inc_func.php的函数(package.php载入了它)。因为除了匿名函数,其它函数是全局可见的,包含类。不过如许或多或少可以让法度员警悟起来。关键还是见地度员本身,再好的规范和束缚也抵当不住烂法度员,他们老是会比你‘聪慧’。
debug_backtrace的BUG
若是应用call_user_func或者call_user_func_array调用其它函数,它们调用的函数里面应用debug_backtrace,将获取不到路径的信息。
例:
call_user_func(import);
function import() {
print_r( debug_backtrace() );
}
/
输出:
Array
(
[0] => Array
(
[function] => import
[args] => Array
(
)
)
[1] => Array
(
[file] => F:\www\test\test\index.php
[line] => 3
[function] => call_user_func
[args] => Array
(
[0] => import
)
)
)
/
重视输出的第一个backtrace,它的调用源路径file没有了,如许一来我之前的几个例子将会产生题目。当然可能你重视到第二个backtrace,若是第一个没有就往回找。但经过实践是不成行的,之前我就碰着这种景象,同样会有题目,然则如今无法找回那时的代码了,若是你发明,请将题目告诉我。就今朝来说,好不要应用这种办法,我有一个更好的解决办法,就是应用PHP的反射API。
应用反射
应用反射API可以知道函数很具体的信息,当然包含它声明的文件和所处行数
call_user_func(import);
function import() {
¥debug_backtrace = debug_backtrace();
¥backtrace = ¥debug_backtrace[0];
if( !isset( ¥backtrace[file] ) ) {
//应用反射API获取函数声明的文件和行数
¥reflection_function = new ReflectionFunction( ¥backtrace[function] );
¥backtrace[file] = ¥reflection_function->getFileName();
¥backtrace[line] = ¥reflection_function->getStartLine();
}
print_r(¥backtrace);
}
/
输出:
Array
(
[function] => import
[args] => Array
(
)
[file] => F:\www\test\test\index.php
[line] => 5
)
/
可以看到经由过程应用反射接口ReflectionMethod的办法,file又回来了。
类办法的反射接口是ReflectionMethod,获取声明办法同样是getFileName。
总结
在一个项目中,我凡是不会直接应用include或者require载入脚本。我喜好把它们封装到一个函数里,须要载入脚本的时辰调用这个函数。如许可以在函数里做一些断定,比如说是否引入过这个文件,或者增长一些调用规矩等,保护起来斗劲便利。
幸好有了如许的习惯,所以我可以即速把debug_backtrace的一些设法应用到全部项目中。
总体来说debug_backtrace有很好的灵活性,只要稍加哄骗,可以实现一些有趣的功能。但同时我发明它并不是很好把握,因为每次调用任何一个办法或函数,都有可能改变它的值。若是要应用它来做一些逻辑处理惩罚(比如说我本文提到的一些设法),须要一个拥有杰出规范准则的体系,至少在加载文件方面吧。