在web开发中,PHP,Python,Ruby都是比较常见的开源语言工具,有很多开源的框架和平台可以使用,从效率来说,PHP无疑是一种最高效的web语言。但配置PHP开发调试环境并不容易。
开发调试的方法
PHP调试可分为使用PHP调试器和不用PHP调试器两种方式。前者是指用PHP debugger和IDE来进行断点调试,后者则是利用PHP的错误报告和浏览器等方式来进行调试。Xdebug和Zend Debugger是两种常用的调试器,但在使用调试器的方式,难点在于生产和测试环境的分离,以及调试器的安装配置。非调试器的方式则比较灵活,本文将探索不用PHP调试器的代码调试的方法,不论你是个人开发者还是生产环境的运维人员,都将受益于这次实战记录。
- 使用PHP错误报告
当发现代码有错时,PHP引擎能生成非常有用的调试信息,可分为三大类:Error,Warning,Notice。举例来说:当有语法错误时,这将是个Error信息,当数据库连接不上时,这会是一个Warning信息,当变量名拼写错误等,这会是一个Notice信息。不过出于安全考虑,在生产环境中,你并不希望除你之外,还有其它用户可以看到这类信息。因此可以按下面方法处理 :
- 启用错语报告 :在php.ini中搜索error_reporting,设置为error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT ,这一般也是生产环境的默认设置
- 关闭全局的HTML错误输出 设置display_errors = Off 和 log_errors = On 这样不会在浏览器中看到PHP的错误报告,而是记录到php的log
- 使HTML错误输出仅自己看到,一般要用到运行时的ini_set()函数,有几种方法:
拷贝法: 复制一份代码到安全的测试环境,然后在要调试的文件中加入:
ini_set('display_errors', '1');
IP限制法: 可直接在生产环境中备份好原始代码,然后加入调试代码:
$myIP = '1.1.1.1'; /* Check the request IP address. */ if ($_SERVER['REMOTE_ADDR'] == $myIP) { /* Enable HTML error reporting. */ ini_set('display_errors', '1'); }
除此之外,还可以用指定用户法(可参见后续代码示例一并使用),或使用请求参数等方法配合init()在运行时显示仅自己可见的错误信息,这里不再敖述。
- 调试代码变量
用echo打印变量
虽然错误报告能给代码语法错误的解决带来帮助,但我们更常用的是查看变量情况来修复代码,最简单的做法就是直接用echo打印变量,比如下面这个例子,当以debug用户访问页面时打印domain变量:
getName() == 'debug') { /* Debugging. */ echo $domain; }
get_defined_vars()函数
echo的方式只适用于知道要检查哪个变量,有时我们需要检查脚本中的所有变量,这时可以用这个函数,它会返回所有已经定义的数组,例如
$addr = 'www.google.com'; $dotPos = mb_strpos($addr, '.'); $domainPos = $dotPos + 1; $domain = mb_substr($addr, $domainPos); /* Debug. */ echo '< pre>'; print_r(get_defined_vars()); echo '< /pre>;';
get_defined_vars函数显示用户定义的变量和系统变量,包括$_SERVER和web请求变量($_POST, $_GET…),这使得它成为非常强大的工具。上面的输出示例如下,出于可读性,我删除一些东西
Array ( [_GET] => Array ( ) [_POST] => Array ( ) [_COOKIE] => Array ( ) [_FILES] => Array ( ) [_REQUEST] => Array ( ) [_SERVER] => Array ( [MIBDIRS] => C:/xampp/php/extras/mibs [MYSQL_HOME] => \xampp\mysql\bin [OPENSSL_CONF] => C:/xampp/apache/bin/openssl.cnf [PHP_PEAR_SYSCONF_DIR] => \xampp\php .... [PHP_SELF] => /test.php [REQUEST_TIME_FLOAT] => 1595999862.718 [REQUEST_TIME] => 1595999862 ) [addr] => www.google.com [dotPos] => 3 [domainPos] => 4 [domain] => google.com )
如果您需要调试的是某个函数或类,可以在该函数或类中调用,如:
function myFunction($arg1, $arg2, $arg3) { $var1 = $arg1 + $arg2; $var2 = $arg1 * $arg2; $arrayVar = [$arg1, $arg2, $arg3, $var1, $var2]; /* This prints all the variables in the current scope. */ echo '< /pre>'; print_r(get_defined_vars()); echo '</pre>'; } /* Call myFunction() */ myFunction(5, 10, 15);
- 使用debug_backtrace()调试函数
这个方法会显示函数调用的全部历史,比如:
function f1(int $arg) { $a = f2($arg + 1); $b = $a + 2; return $b; } function f2(int $arg) { $a = f3($arg); $b = $a * 3; return $b; } function f3(int $arg) { $a = $arg * 10; echo '< /pre>'; print_r(debug_backtrace()); echo '< /pre>'; return $a; } $val = f1(5);
上面f1里调用f2,f2里调用f3,f3处插入debug_backtrace,上面f1里调用f2,f2里调用f3,f3处插入debug_backtrace,返回的是一个数组,依次由后到前的函数调用,即先显示f3函数 ,再显示f2,再显示f1,如下所示:
Array ( [0] => Array ( [file] => C:\xampp\htdocs\test.php [line] => 15 [function] => f3 [args] => Array ( [0] => 6 ) ) [1] => Array ( [file] => C:\xampp\htdocs\test.php [line] => 6 [function] => f2 [args] => Array ( [0] => 6 ) ) [2] => Array ( [file] => C:\xampp\htdocs\test.php [line] => 33 [function] => f1 [args] => Array ( [0] => 5 ) ) )
每个数组包括:
file:被调用函数所在的文件路径,如果函数是在include包含的文件,显示的是include文件的路径;
line:被调用函数所在行;
function:函数名称;
arg:函数参数;
您也可以使用 debug_print_backtrace()来替代debug_backtrace(),这样会更方便一些,例如:
function f3(int $arg) { $a = $arg * 10; echo '< /pre>'; debug_print_backtrace(); echo '< /pre>'; return $a; }
对应的输出结果是:
#0 f3(6) called at [C:\xampp\htdocs\test.php:15] #1 f2(6) called at [C:\xampp\htdocs\test.php:6] #2 f1(5) called at [C:\xampp\htdocs\test.php:33]
- 记录debug数据
上面讲了如何在HTML输出中显示调试信息,下面说说如何记录这些数据到log文件中,调试数据log不仅可以长久保存,你也无需使用技巧以防止其它访客可以看到,并且还能记录特定用户的活动。
把调试数据写到log文件中,最简单的办法是用fopen()函数,如:
> /* Open the log file. */ $handle = fopen('c:\xampp\debug.log', 'ab'); function addLog($handle, string $log) { /* Datetime to add at the beginning of the log line. */ $date = date('d/m/Y H:i:s'); /* Complete log line. */ $line = $date . ' ' . $log . "\n"; /* Add the new line to the log file. */ fwrite($handle, $line); }
上面fopen第一个参数是log文件路径,第二个参数是标记符,”a”代表添加到文件尾,“b”防止自动新行。
当然,需要log文件所在路径允许php写入。
上面函数用法示例:
$addr = 'www.google.com'; $domain = mb_substr($addr, mb_strpos($addr, '.') + 1); /* Log the $domain variable */ addLog($handle, 'domain is: ' . $domain);
或是记录前面说的get_defined_vars函数,打印的所有变量到log文件中,