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



    分享到: