首页 > 新闻动态 >  

新闻动态
NEWS

PHP debug_backtrace的胡思乱想

添加时间:2013-5-9 点击量:

本文示例代码测试景象是Windows下的APMServ(PHP5.2.6)



简述


可能大师都知道,php中有一个函数叫debug_backtrace,它可以回溯跟踪函数的调用信息,可以说是一个调试利器。


好,来复习一下。



one();


function one() {
two();
}

function two() {
three();
}

function three() {
print_rdebug_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];
ifisset¥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.phppackage.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.phppackage.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 );
ifempty¥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 );
ifempty¥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_rdebug_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有很好的灵活性,只要稍加哄骗,可以实现一些有趣的功能。但同时我发明它并不是很好把握,因为每次调用任何一个办法或函数,都有可能改变它的值。若是要应用它来做一些逻辑处理惩罚(比如说我本文提到的一些设法),须要一个拥有杰出规范准则的体系,至少在加载文件方面吧。



分享到: