Rob*_*nes 5 php serialization pdo exception php-7.1
我有序列化的代码PDOException,通过网络发送,然后稍后反序列化.当我反序列化它时,该$code属性似乎丢失了.对象的其余部分显示不变.
我的代码是针对PostgreSQL数据库运行的.使用以下DDL:
CREATE TABLE test (
id INTEGER
);
Run Code Online (Sandbox Code Playgroud)
使用以下代码重现我的问题(替换您自己的PostgeSQL连接值):
<?php
$dsn = "pgsql: dbname=postgres;host=/var/run/postgresql;port=5432";
$user = "postgres";
$password = "";
try
{
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$res = $pdo->exec("INSERT INTO test (id) VALUES (999999999999999)");
}
catch (PDOException $e)
{
var_dump((array) $e);
print "\n";
print $e->getCode();
print "\n";
$s = serialize($e);
print $s;
print "\n";
$d = unserialize($s);
var_dump((array) $d);
print "\n";
print $d->getCode();
print "\n";
print serialize($e->getCode());
print "\n";
}
?>
Run Code Online (Sandbox Code Playgroud)
在我的输出中,$code最终输出中缺少该属性.另外,我收到以下通知:
PHP Notice: Undefined property: PDOException::$code in /home/developer/test_serialize.php on line 20
我发现我实际上必须执行一个失败的SQL语句才能看到这个问题.特别是如果我选择了错误的端口号,那么我会得到一个,PDOException但它会$code在unserialize调用后保留该属性.
请注意,序列化字符串似乎具有代码属性,所以我假设这是unserialize函数的问题.
任何见解都会受到赞赏 - 我是否误解了一些基本的东西?这是一个PHP错误吗?别的什么?我正在使用以下PHP版本:
PHP 7.1.6 (cli) (built: Jun 18 2018 12:25:10) ( ZTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
Run Code Online (Sandbox Code Playgroud)
编辑 - 添加打印输出
以下是复制脚本的输出.请注意,我已经修改了稍微添加一些新行以便于阅读,并替换print_r为var_dump:
array(8) {
["*message"]=>
string(75) "SQLSTATE[22003]: Numeric value out of range: 7 ERROR: integer out of range"
["Exceptionstring"]=>
string(0) ""
["*code"]=>
string(5) "22003"
["*file"]=>
string(34) "/home/developer/test_serialize.php"
["*line"]=>
int(10)
["Exceptiontrace"]=>
array(1) {
[0]=>
array(6) {
["file"]=>
string(34) "/home/developer/test_serialize.php"
["line"]=>
int(10)
["function"]=>
string(4) "exec"
["class"]=>
string(3) "PDO"
["type"]=>
string(2) "->"
["args"]=>
array(1) {
[0]=>
string(73) "INSERT INTO km_role (role_id, role_name) VALUES (999999999999999, 'test')"
}
}
}
["Exceptionprevious"]=>
NULL
["errorInfo"]=>
array(3) {
[0]=>
string(5) "22003"
[1]=>
int(7)
[2]=>
string(28) "ERROR: integer out of range"
}
}
22003
O:12:"PDOException":8:{s:10:"*message";s:75:"SQLSTATE[22003]: Numeric value out of range: 7 ERROR: integer out of range";s:17:"Exceptionstring";s:0:"";s:7:"*code";s:5:"22003";s:7:"*file";s:34:"/home/developer/test_serialize.php";s:7:"*line";i:10;s:16:"Exceptiontrace";a:1:{i:0;a:6:{s:4:"file";s:34:"/home/developer/test_serialize.php";s:4:"line";i:10;s:8:"function";s:4:"exec";s:5:"class";s:3:"PDO";s:4:"type";s:2:"->";s:4:"args";a:1:{i:0;s:73:"INSERT INTO km_role (role_id, role_name) VALUES (999999999999999, 'test')";}}}s:19:"Exceptionprevious";N;s:9:"errorInfo";a:3:{i:0;s:5:"22003";i:1;i:7;i:2;s:28:"ERROR: integer out of range";}}
array(7) {
["*message"]=>
string(75) "SQLSTATE[22003]: Numeric value out of range: 7 ERROR: integer out of range"
["Exceptionstring"]=>
string(0) ""
["*file"]=>
string(34) "/home/developer/test_serialize.php"
["*line"]=>
int(10)
["Exceptiontrace"]=>
array(1) {
[0]=>
array(6) {
["file"]=>
string(34) "/home/developer/test_serialize.php"
["line"]=>
int(10)
["function"]=>
string(4) "exec"
["class"]=>
string(3) "PDO"
["type"]=>
string(2) "->"
["args"]=>
array(1) {
[0]=>
string(73) "INSERT INTO km_role (role_id, role_name) VALUES (999999999999999, 'test')"
}
}
}
["Exceptionprevious"]=>
NULL
["errorInfo"]=>
array(3) {
[0]=>
string(5) "22003"
[1]=>
int(7)
[2]=>
string(28) "ERROR: integer out of range"
}
}
PHP Notice: Undefined property: PDOException::$code in /home/developer/test_serialize.php on line 24
s:5:"22003"
Run Code Online (Sandbox Code Playgroud)
在通过错误的端口号抛出PDOException的示例中,序列化为$e->getCode():
i:7;
Run Code Online (Sandbox Code Playgroud)
首先看一下PDOException类:
PDOException extends RuntimeException {
/* Properties */
public array $errorInfo ;
protected string $code ;
/* Inherited properties */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
/* Inherited methods */
final public string Exception::getMessage ( void )
final public Throwable Exception::getPrevious ( void )
final public mixed Exception::getCode ( void )
final public string Exception::getFile ( void )
final public int Exception::getLine ( void )
final public array Exception::getTrace ( void )
final public string Exception::getTraceAsString ( void )
public string Exception::__toString ( void )
final private void Exception::__clone ( void )
}
Run Code Online (Sandbox Code Playgroud)
PHP中的getCode()方法就像这样实现(参考:https://psithub.com/php/php-src/blob/cd953269d3d486f775f1935731b1d6d44f12a350/ext/spl/spl.php):
/** @return the code passed to the constructor
*/
final public function getCode()
{
return $this->code;
}
Run Code Online (Sandbox Code Playgroud)
这是PHP异常的构造函数:
/** Construct an exception
*
* @param $message Some text describing the exception
* @param $code Some code describing the exception
*/
function __construct($message = NULL, $code = 0) {
if (func_num_args()) {
$this->message = $message;
}
$this->code = $code;
$this->file = __FILE__; // of throw clause
$this->line = __LINE__; // of throw clause
$this->trace = debug_backtrace();
$this->string = StringFormat($this);
}
Run Code Online (Sandbox Code Playgroud)
这告诉我们什么?Exception 的$code属性只能在生成Exception时填充,如果没有$code传递则应为零.
这是一个PHP Bug吗?我想不是,经过一些研究,我发现了以下很棒的文章:http://fabien.potencier.org/php-serialization-stack-traces-and-exceptions.html
实质上它说:
当PHP序列化异常时,它会序列化异常代码,异常消息以及堆栈跟踪.
堆栈跟踪是一个数组,其中包含已在脚本此时执行的所有函数和方法.跟踪包含文件名,文件中的行,函数名以及传递给函数的所有参数的数组.你发现了这个问题吗?
堆栈跟踪包含对PDO实例的引用,因为它已传递给will_crash()函数,并且由于PDO实例不可序列化,因此当PHP序列化堆栈跟踪时会引发异常.
每当堆栈跟踪中存在非可序列化对象时,异常将不可序列化.
我想这就是我们的serialize()/ unserialize()进程失败的原因 - 因为Exception不可序列化.
解:
编写一个可扩展Exception的Serializable Exception
class SerializableException extends Exception implements Serializable {
// ... go ahead :-)
}
Run Code Online (Sandbox Code Playgroud)
Blackbam 的回答很特别,但不可序列化的 PDO 对象是一个红鲱鱼。$code正如在他的帖子的评论中所讨论的那样,您的代码的问题确实是由于属性的类型造成的。在某些情况下,异常使用错误代码的字符串表示而不是整数进行初始化。这打破了反序列化,它非常合理地决定丢弃具有无效类型的属性。
PDOException 文档页面上的评论几乎都在谈论由于错误代码被创建为字符串而不是 int 导致的问题。
您可以使用反射将受保护的值设置为整数。见下文:
try
{
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$res = $pdo->exec("INSERT INTO test_schema.test (id) VALUES (999999999999999)");
}
catch (PDOException $e)
{
// the new bit is here
if (!is_int($e->getCode())) {
$reflectionClass = new ReflectionClass($e);
$reflectionProperty = $reflectionClass->getProperty('code');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($e, (int)$reflectionProperty->getValue($e));
}
// the rest is the same
var_dump((array) $e);
print "\n";
print $e->getCode();
print "\n";
$s = serialize($e);
print $s;
print "\n";
$d = unserialize($s);
var_dump((array) $d);
print "\n";
print $d->getCode();
print "\n";
print serialize($e->getCode());
print "\n";
}
Run Code Online (Sandbox Code Playgroud)
当然,如果代码包含字母数字值而不是作为字符串的整数,您将丢失信息。该消息可能会重复错误编号,但这可能不太值得信赖。
如果您稍微调整一下代码,Blackbam 所关注的不可序列化堆栈跟踪可能会成为一个问题:
function will_crash($pdo) {
$res = $pdo->exec("INSERT INTO test_schema.test (id) VALUES (999999999999999)");
}
try
{
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
will_crash($pdo);
}
catch (PDOException $e)
{
$s = serialize($e);
// PHP Fatal error: Uncaught PDOException: You cannot serialize or unserialize PDO instances in...
}
Run Code Online (Sandbox Code Playgroud)
哎呀。
所以 Blackbam 的回答,以及他创建可序列化异常类的链接中的方法,可能是要走的路。这允许您序列化异常的数据而不是堆栈跟踪。
再说一次,在这一点上,您也可以只使用json_encode和json_decode传递/存储异常信息。