PHP debug_backtrace的胡思乱想,phpdebugbacktrace
PHP debug_backtrace的胡思乱想,phpdebugbacktrace
本文示例代码测试环境是Windows下的APMServ(PHP5.2.6)
简述
可能大家都知道,php中有一个函数叫debug_backtrace,它可以回溯跟踪函数的调用信息,可以说是一个调试利器。
好,来复习一下。
<span>one(); </span><span>function</span><span> one() { two(); } </span><span>function</span><span> two() { three(); } </span><span>function</span><span> three() { </span><span>print_r</span>( <span>debug_backtrace</span><span>() ); } </span><span>/*</span><span> 输出: 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 ( ) ) ) </span><span>*/</span>
顺便提一下类似的函数:debug_print_backtrace,与之不同的是它会直接打印回溯信息。
回来看debug_backtrace,从名字来看用途很明确,是让开发者用来调试的。直到有一天我注意到它返回的file参数,file表示函数或者方法的调用脚本来源(在哪个脚本文件使用的)。忽然我想到,如果当前脚本知道调用来源,那是否可以根据这个来源的不同,来实现一些有趣的功能,比如文件权限管理、动态加载等。
实战
实现魔术函数
获取当前函数或方法的名称
尽管PHP中已经有了__FUNCTION__和__METHOD__魔术常量,但我还是想介绍一下用debug_backtrace获取当前函数或者方法名称的方法。
代码如下:
<span>//</span><span>函数外部输出getFuncName的值</span> <span>echo</span><span> getFuncName(); printFuncName(); </span><span>Object</span>::<span>printMethodName(); </span><span>//</span><span>调用了上面两个函数后,再次在外部输出getFuncName,看看是否有‘缓存’之类的问题</span> <span>echo</span><span> getFuncName(); </span><span>function</span><span> printFuncName() { </span><span>echo</span><span> getFuncName(); } </span><span>class</span> <span>Object</span><span> { </span><span>static</span> <span>function</span><span> printMethodName() { </span><span>echo</span><span> getFuncName(); } } </span><span>/*</span><span>* * 获取当前函数或者方法的名称 * 函数名叫getFuncName,好吧,其实method也可以当做function,实在想不出好名字 * * @return string name </span><span>*/</span> <span>function</span><span> getFuncName() { </span><span>$debug_backtrace</span> = <span>debug_backtrace</span><span>(); </span><span>//</span><span>如果函数名是以下几个,表示载入了脚本,并在函数外部调用了getFuncName //这种情况应该返回空</span> <span>$ignore</span> = <span>array</span><span>( </span>'include', 'include_once', 'require', 'require_once'<span> ); </span><span>//</span><span>第一个backtrace就是当前函数getFuncName,再上一个(第二个)backtrace就是调用getFuncName的函数了</span> <span>$handle_func</span> = <span>$debug_backtrace</span>[1<span>]; </span><span>if</span>( <span>isset</span>( <span>$handle_func</span>['function'] ) && !<span>in_array</span>( <span>$handle_func</span>['function'], <span>$ignore</span><span> ) ) { </span><span>return</span> <span>$handle_func</span>['function'<span>]; } </span><span>return</span> <span>null</span><span>; } </span><span>//</span><span>输出: //null //printFuncName //printMethodName //null</span>
看上去没有问题,很好。
加载相对路径文件
如果在项目中要加载相对路径的文件,必需使用include或者require之类的原生方法,但现在有了debug_backtrace,我可以使用自定义函数去加载相对路径文件。
新建一个项目,目录结构如下:
我想在index.php中调用自定义函数,并使用相对路径去载入package/package.php,并且在package.php中使用同样的方法载入_inc_func.php
三个文件的代码如下(留意index.php和package.php调用import函数的代码):
index.php:
<?<span>php import( </span>'./package/package.php'<span> ); </span><span>/*</span><span>* * 加载当前项目下的文件 * * @param string $path 相对文件路径 </span><span>*/</span> <span>function</span> import( <span>$path</span><span> ) { </span><span>//</span><span>获得backstrace列表</span> <span>$debug_backtrace</span> = <span>debug_backtrace</span><span>(); </span><span>//</span><span>第一个backstrace就是调用import的来源脚本</span> <span>$source</span> = <span>$debug_backtrace</span>[0<span>]; </span><span>//</span><span>得到调用源的目录路径,和文件路径结合,就可以算出完整路径</span> <span>$source_dir</span> = <span>dirname</span>( <span>$source</span>['file'<span>] ); </span><span>require</span> <span>realpath</span>( <span>$source_dir</span> . '/' . <span>$path</span><span> ); } </span>?>
package.php:
<?<span>php </span><span>echo</span> 'package'<span>; import( </span>'./_inc_func.php'<span> ); </span>?>
_inc_func.php:
<?<span>php </span><span>echo</span> '_inc_func'<span>; </span>?>
运行index.php:
<span>//</span><span>输出: //package //_inc_func</span>
可以看到,我成功了。
思考:这个方法我觉得非常强大,除了相对路径之外,可以根据这个思路引伸出相对包、相对模块之类的抽象特性,对于一些项目来说可以增强模块化的作用。
管理文件调用权限
我约定一个规范:文件名前带下划线的只能被当前目录的文件调用,也就是说这种文件属于当前目录‘私有’,其它目录的文件不允许载入它们。
这样做的目的很明确:为了降低代码耦合性。在项目中,很多时候一些文件只被用在特定的脚本中。但是经常发生的事情是:一些程序员发现这些脚本有自己需要用到的函数或者类,因此直接载入它来达到自己的目的。这样的做法很不好,原本这些脚本编写的目的仅仅为了辅助某些接口实现,它们并没有考虑到其它通用性。万一接口内部需要重构,同样需要改动这些特定的脚本文件,但是改动后一些看似与这个接口无关脚本却突然无法运行了。一经检查,却发现文件的引用错综复杂。
规范只是监督作用,不排除有人为了一己私欲而违反这个规范,或者无意中违反了。最好的方法是落实到代码中,让程序自动去检测这种情况。
新建一个项目,目录结构如下。
那么对于这个项目来说,_inc_func.php属于package目录的私有文件,只有package.php可以载入它,而index.php则没有这个权限。
package目录是一个包,package.php下提供了这个包的接口,同时_inc_func.php有package.php需要用到的一些函数。index.php将会使用这个包的接口文件,也就是package.php
它们的代码如下
index.php:
<?<span>php </span><span>header</span>("Content-type: text/html; charset=utf-8"<span>); </span><span>//</span><span>定义项目根目录</span> <span>define</span>( 'APP_PATH', <span>dirname</span>( <span>__FILE__</span><span> ) ); import( APP_PATH </span>. '/package/package.php'<span> ); </span><span>//</span><span>输出包的信息</span> <span>Package_printInfo(); </span><span>/*</span><span>* * 加载当前项目下的文件 * * @param string $path 文件路径 </span><span>*/</span> <span>function</span> import( <span>$path</span><span> ) { </span><span>//</span><span>应该检查路径的合法性</span> <span>$real_path</span> = <span>realpath</span>( <span>$path</span><span> ); </span><span>$in_app</span> = ( <span>stripos</span>( <span>$real_path</span>, APP_PATH ) === 0<span> ); </span><span>if</span>( <span>empty</span>( <span>$real_path</span> ) || !<span>$in_app</span><span> ) { </span><span>throw</span> <span>new</span> <span>Exception</span>( '文件路径不存在或不被允许'<span> ); } </span><span>include</span> <span>$real_path</span><span>; } </span>?>
_inc_func.php:
<?<span>php </span><span>function</span> _Package_PrintStr( <span>$string</span><span> ) { </span><span>echo</span> <span>$string</span><span>; } </span>?>
package.php:
<?<span>php </span><span>define</span>( 'PACKAGE_PATH', <span>dirname</span>( <span>__FILE__</span><span> ) ); </span><span>//</span><span>引入私有文件</span> import( PACKAGE_PATH . '/_inc_func.php'<span> ); </span><span>function</span><span> Package_printInfo() { _Package_PrintStr( </span>'我是一个包。'<span> ); } </span>?>
运行index.php:
<span>//</span><span>输出: //我是一个包。</span>
整个项目使用了import函数载入文件,并且代码看起来是正常的。但是我可以在index.php中载入package/_inc_func.php文件,并调用它的方法。
index.php中更改import( APP_PATH . '/package/package.php' );处的代码,并运行:
import( APP_PATH . '/package/_inc_func.php'<span> ); _Package_PrintStr( </span>'我载入了/package/_inc_func.php脚本'<span> ); </span><span>//</span><span>输出: //我载入了/package/_inc_func.php脚本</span>
那么,这时可以使用debug_backtrace检查载入_inc_func.php文件的路径来自哪里,我改动了index.php中的import函数,完整代码如下:
<span>/*</span><span>* * 加载当前项目下的文件 * * @param string $path 文件路径 </span><span>*/</span> <span>function</span> import( <span>$path</span><span> ) { </span><span>//</span><span>首先应该检查路径的合法性</span> <span>$real_path</span> = <span>realpath</span>( <span>$path</span><span> ); </span><span>$in_app</span> = ( <span>stripos</span>( <span>$real_path</span>, APP_PATH ) === 0<span> ); </span><span>if</span>( <span>empty</span>( <span>$real_path</span> ) || !<span>$in_app</span><span> ) { </span><span>throw</span> <span>new</span> <span>Exception</span>( '文件路径不存在或不被允许'<span> ); } </span><span>$path_info</span> = <span>pathinfo</span>( <span>$real_path</span><span> ); </span><span>//</span><span>判断文件是否属于私有</span> <span>$is_private</span> = ( <span>substr</span>( <span>$path_info</span>['basename'], 0, 1 ) === '_'<span> ); </span><span>if</span>( <span>$is_private</span><span> ) { </span><span>//</span><span>获得backstrace列表</span> <span>$debug_backtrace</span> = <span>debug_backtrace</span><span>(); </span><span>//</span><span>第一个backstrace就是调用import的来源脚本</span> <span>$source</span> = <span>$debug_backtrace</span>[0<span>]; </span><span>//</span><span>得到调用源路径,用它来和目标路径进行比较</span> <span>$source_dir</span> = <span>dirname</span>( <span>$source</span>['file'<span>] ); </span><span>$target_dir</span> = <span>$path_info</span>['dirname'<span>]; </span><span>//</span><span>不在同一目录下时抛出异常</span> <span>if</span>( <span>$source_dir</span> !== <span>$target_dir</span><span> ) { </span><span>$relative_source_file</span> = <span>str_replace</span>( APP_PATH, '', <span>$source</span>['file'<span>] ); </span><span>$relative_target_file</span> = <span>str_replace</span>( APP_PATH, '', <span>$real_path</span><span> ); </span><span>$error</span> = <span>$relative_target_file</span> . '文件属于私有文件,' . <span>$relative_source_file</span> . '不能载入它。'<span>; </span><span>throw</span> <span>new</span> <span>Exception</span>( <span>$error</span><span> ); } } </span><span>include</span> <span>$real_path</span><span>; }</span>
这时再运行index.php,将产生一个致命错误:
<span>//</span><span>输出: //致命错误:/package/_inc_func.php文件属于私有文件,/index.php不能载入它。</span>
而载入package.php则没有问题,这里不进行演示。
可以看到,我当初的想法成功了。尽管这样,在载入package.php后,其实在index.php中仍然还可以调用_inc_func.php的函数(package.php载入了它)。因为除了匿名函数,其它函数是全局可见的,包括类。不过这样或多或少可以让程序员警觉起来。关键还是看程序员本身,再好的规范和约束也抵挡不住烂程序员,他们总是会比你‘聪明’。
debug_backtrace的'BUG'
如果使用call_user_func或者call_user_func_array调用其它函数,它们调用的函数里面使用debug_backtrace,将获取不到路径的信息。
例:
<span>call_user_func</span>('import'<span>); </span><span>function</span><span> import() { </span><span>print_r</span>( <span>debug_backtrace</span><span>() ); } </span><span>/*</span><span> 输出: 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 ) ) ) </span><span>*/</span>
注意输出的第一个backtrace,它的调用源路径file没有了,这样一来我之前的几个例子将会产生问题。当然可能你注意到第二个backtrace,如果第一个没有就往回找。但经过实践是不可行的,之前我就碰到这种情况,同样会有问题,但是现在无法找回那时的代码了,如果你发现,请将问题告诉我。就目前来说,最好不要使用这种方法,我有一个更好的解决办法,就是使用PHP的反射API。
使用反射
使用反射API可以知道函数很详细的信息,当然包括它声明的文件和所处行数
<span>call_user_func</span>('import'<span>); </span><span>function</span><span> import() { </span><span>$debug_backtrace</span> = <span>debug_backtrace</span><span>(); </span><span>$backtrace</span> = <span>$debug_backtrace</span>[0<span>]; </span><span>if</span>( !<span>isset</span>( <span>$backtrace</span>['file'<span>] ) ) { </span><span>//</span><span>使用反射API获取函数声明的文件和行数</span> <span>$reflection_function</span> = <span>new</span> ReflectionFunction( <span>$backtrace</span>['function'<span>] ); </span><span>$backtrace</span>['file'] = <span>$reflection_function</span>-><span>getFileName(); </span><span>$backtrace</span>['line'] = <span>$reflection_function</span>-><span>getStartLine(); } </span><span>print_r</span>(<span>$backtrace</span><span>); } </span><span>/*</span><span> 输出: Array ( [function] => import [args] => Array ( ) [file] => F:\www\test\test\index.php [line] => 5 ) </span><span>*/</span>
可以看到通过使用反射接口ReflectionMethod的方法,file又回来了。
类方法的反射接口是ReflectionMethod,获取声明方法同样是getFileName。
总结
在一个项目中,我通常不会直接使用include或者require载入脚本。我喜欢把它们封装到一个函数里,需要载入脚本的时候调用这个函数。这样可以在函数里做一些判断,比如说是否引入过这个文件,或者增加一些调用规则等,维护起来比较方便。
幸好有了这样的习惯,所以我可以马上把debug_backtrace的一些想法应用到整个项目中。
总体来说debug_backtrace有很好的灵活性,只要稍加利用,可以实现一些有趣的功能。但同时我发现它并不是很好控制,因为每次调用任何一个方法或函数,都有可能改变它的值。如果要使用它来做一些逻辑处理(比如说我本文提到的一些想法),需要一个拥有良好规范准则的系统,至少在加载文件方面吧。

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











저는 springboot 프로젝트를 처음 접했습니다. (1) 저는 중단점 디버깅이 효과가 없다는 것을 알았고 매우 우울해서 온라인에서 해결책을 찾았습니다. 내가 본 것은 원격 디버깅이라고 하는 매우 복잡한 솔루션뿐이었지만 추가 오프닝 슬로건도 필요했습니다. 이는 전통적인 프로젝트와 다르기 때문에 필요하지 않다고 생각합니다. 그래서 몇 가지 탐색 후에 더 간단한 방법이 있다는 것을 발견했습니다. pom 파일의 플러그인 부분에 구성을 추가하면 됩니다. (2) SpringBoot 프로젝트의 오류에 관해서는 다음과 같습니다. 기존 웹 프로젝트에는 web.xml 파일이 필요하지만 SpringBoot 프로젝트에는 web.xml 파일이 필요하지 않기 때문에 web.xml 파일이 없습니다.

Vscode를 사용하여 Node와 같은 프로젝트를 작성해 본 친구들은 모두 문제를 해결하고 싶다면 주로 console.log를 통해 인쇄하여 문제가 더 복잡한 경우에는 Through Vscode를 선택한다는 것을 알고 있습니다. ...

1. 먼저 원격 디버깅을 위한 데모를 생성합니다. 빌드 프로젝트 4.0.0org.springframework.bootspring-boot-starter-parent2.1.4.RELEASEcom.remote.testremote_test0.0.1-SNAPSHOTremote_testDemoprojectforSpringBoot1.8org.springframework의 구성에 주의하세요. bootspring-boot- starterorg.springframework.bootspring-bo

Nocalhost는 Kubernetes 애플리케이션의 디버깅 및 배포를 지원하는 개발자 도구입니다. Python 개발에 Nocalhost를 사용하려면 다음 단계를 완료해야 합니다. Nocalhost CLI를 설치합니다. Nocalhost 공식 홈페이지에서 제공하는 설치 패키지를 통해 설치할 수 있습니다. Kubernetes 클러스터를 구성하고 Nocalhost 플러그인을 설치합니다. Nocalhost 공식 문서에서 제공되는 지침을 참조할 수 있습니다. Python 자체 pdb 또는 타사 라이브러리 pudb, ipdb 등과 같은 Python 인터프리터 및 디버거를 로컬 컴퓨터에 설치합니다. Python 프로젝트를 만들고 디버거 호출 문을 코드에 추가합니다.

오버플로 문제를 올바르게 처리하는 방법 오버플로는 특히 숫자나 배열을 처리할 때 일반적인 컴퓨터 프로그래밍 문제입니다. 오버플로는 데이터 유형의 허용 범위를 초과하는 값을 저장하려고 할 때 발생합니다. 이 문제를 해결하는 열쇠는 데이터 경계를 올바르게 처리하고 검증하는 데 있습니다. 몇 가지 일반적인 오버플로 문제와 해당 솔루션이 아래에 소개됩니다. 정수 오버플로 정수 오버플로는 계산 중에 결과가 정수 유형의 표현 범위를 초과함을 의미합니다. 예를 들어, 32비트 부호 있는 정수 유형에서는

UCIe(Universal Chiplet Interconnect Express) 컨소시엄은 UCIe 2.0 사양 출시를 발표하여 개방형 칩렛 생태계를 더욱 발전시켰습니다. 최신 사양에는 몇 가지 주요 개선 사항이 도입되었습니다. 먼저, 지원 f를 추가합니다.

이 기사에 사용된 테스트 코드: fromtorchvisionimporttransformsfromtorchvision.datasetsimportFashionMNISTimportosos.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"#Dataset 준비 train_data=FashionMNIST(root="./data/FashionMNIST",train=True,transform=transforms.

PHP 애플리케이션을 개발할 때 코드를 디버깅해야 하는 상황에 자주 직면하게 됩니다. 디버깅은 프로그램 문제를 해결하고, 코드에서 오류를 찾고, 프로그램 성능을 향상시키는 데 필요한 단계입니다. PHP에서는 디버거를 사용하여 디버깅이 수행됩니다. 이 기사에서는 디버거를 사용하여 PHP에서 코드를 디버깅하는 방법을 소개합니다. 1. 디버깅 환경을 구성합니다. 디버깅을 시작하기 전에 디버깅 환경을 구성해야 합니다. 디버깅 환경을 구성하는 목적은 디버거를 실행하여 코드를 디버그하는 데 도움을 주는 것입니다. PHP 디버거에는 XDebug와 ZendDebug라는 두 가지 주요 유형이 있습니다.
