У меня есть код, который сериализует PDOException, отправляет его по сети, а затем десериализует его позже. Когда я десериализую его, свойство $code, похоже, отсутствует. Остальная часть объекта остается без изменений.
Мой код работает с базой данных PostgreSQL. Используйте следующий DDL:
CREATE TABLE test (
id INTEGER
);
Воспользуйтесь следующим кодом, чтобы воспроизвести мою проблему (подставив свои собственные значения подключения 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";
}
?>
В моем выводе свойство $code отсутствует в окончательном выводе. Дополнительно я получаю следующее уведомление:
PHP Notice: Undefined property: PDOException::$code in /home/developer/test_serialize.php on line 20
Я обнаружил, что мне действительно нужно выполнить оператор SQL с ошибкой, чтобы увидеть эту проблему. В частности, если я выберу, например, неправильный номер порта, я получу PDOException, но он сохранит свойство $code после вызова unserialize.
Обратите внимание, что у сериализованной строки, похоже, есть свойство code, поэтому я предполагаю, что это проблема с функцией 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
Редактировать - Добавление вывода на печать
Ниже приводится вывод сценария воспроизведения. Обратите внимание, что я немного изменил, добавив несколько символов новой строки для удобства чтения и заменив 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"
В примере, где исключение PDOException выбрасывается из-за неправильного номера порта, сериализованный $e->getCode() имеет следующий вид:
i:7;
@LovepreetSingh Пробовал с и без - без разницы
Спасибо, что добавили награду, @ jh1711 :)
Каков точный вывод операторов print_r (можете ли вы сделать его var_dump и отредактировать вопрос)?
@ RobGwynn-Jones, добро пожаловать; Меня лично интересует этот вопрос. Не могли бы вы добавить выходы serialize($e->getCode()); для вашего репродуктора и пример неправильного номера порта.
Просто играл с этим и пробовал аналогичный код в mysqli_, и он действительно работает! Так выглядит что-то в PDO
Извините за мое отсутствие. Ответ @jstur привел к решению с использованием Reflection. Если вы хотите отправить отчет об ошибке с помощью PHP: Этот - это фиксация, которая ограничивает $code только int.






Прежде всего, взгляните на класс 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 )
}
Метод getCode () в PHP реализован примерно так (ref: https://github.com/php/php-src/blob/cd953269d3d486f775f1935731b1d6d44f12a350/ext/spl/spl.php):
/** @return the code passed to the constructor
*/
final public function getCode()
{
return $this->code;
}
Это конструктор исключения 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);
}
Что это нам говорит? Свойство $code исключения может быть заполнено только при генерации исключения и должно быть нулевым, если $code не был передан.
Так это ошибка PHP? Думаю, что нет, после некоторого исследования я нашел следующую замечательную статью: http://fabien.potencier.org/php-serialization-stack-traces-and-exceptions.html
По сути, это говорит:
Когда PHP сериализует исключение, он сериализует код исключения, сообщение об исключении, а также трассировку стека.
Трассировка стека - это массив, содержащий все функции и методы, которые уже были выполнены в этой точке скрипта. Трассировка содержит имя файла, строку в файле, имя функции и массив всех аргументов, переданных функции. Вы заметили проблему?
Трассировка стека содержит ссылку на экземпляр PDO, поскольку она была передана в функцию will_crash (), и поскольку экземпляры PDO не сериализуемы, возникает исключение, когда PHP сериализует трассировку стека.
Whenever a non-serializable object is present in the stack trace, the exception won't be serializable.
И я предполагаю, что это причина того, почему наш процесс serialize () / unserialize () терпит неудачу - потому что исключение не сериализуемо.
Решение:
Write a Serializable Exception which extends Exception
class SerializableException extends Exception implements Serializable {
// ... go ahead :-)
}
Хорошая статья о проблемах, с которыми вы можете столкнуться при (де) сериализации исключений. Но я не думаю, что несериализуемые объекты являются причиной проблемы в этом вопросе. serialize и unserialize не падают; просто отсутствует свойство. Я также не понимаю, как можно заставить PDO генерировать другое исключение. Поэтому я не уверен, чем может помочь расширение класса. Но может я что-то упустил.
$ code определяется до сериализации исключения, но не после этого, поэтому должна быть проблема с сериализацией, верно? Есть ли шанс, что он просто выйдет из строя, а PHP просто продолжит работу, хотя сериализация не работает должным образом?
Прямо сейчас у меня есть теория, что PDO иногда выдает исключение с неправильным типом для $ code; но он выбрасывает другие исключения с правильным типом. Я понятия не имею, почему, но я думаю, что могу объяснить, почему это влияет на (не) сериализацию. Я подожду еще 24-48 часов, чтобы дать OP изменение, чтобы ответить на мой комментарий, поскольку он может подтвердить или опровергнуть мою теорию. После этого я думаю, что смогу (надеюсь) безопасно наградить вас наградой за ту огромную ценность, которую ваш ответ добавил для решения этой и подобных проблем.
Утесы: Да, PHP автоматически отбрасывает свойства неправильного типа.
Спасибо за подробный ответ. Конечно, давайте подождем OP и посмотрим, что он обо всем этом думает. Награде всего один день, давайте посмотрим, может ли кто-то еще добавить какую-то ценность, хотя я думаю, что мы, вероятно, ее получили ?
Спасибо за ответ. Я обновил исходный пост, добавив данные для печати, как и требовалось, а также сериализованный код ошибки. Обратите внимание, что когда я указываю PDO недопустимый порт, свойство кода является целым числом, а когда трассировка фактически содержит объект PDO, т.е. когда он пытается запустить инструкцию SQL, это строка. Так какая теория преобладает? Это факт, что он пытается сериализовать экземпляр PDO? Изменение типа данных $code задокументировано, поэтому мне кажется маловероятным, что это будет несоответствие типов, но я могу ошибаться. Как лучше всего проверить эту теорию?
Что ж, это МОЖЕТ быть несоответствие типа (хороший момент), но я думаю, что основная проблема возникает из-за того, что вы не можете полагаться на сериализацию исключений. Хотя странно, что отсутствует только свойство $ code, а все остальное вроде работает. Однако эта проблема может оправдать отчет об ошибке для ясности: php.net/manual/en/install.problems.bugs.php. Возможно, вы захотите создать проблему и связать ее здесь?
Еще раз спасибо за подробный ответ. Ответ @jstur более точно попадает в точку, поэтому я приму его ответ. Я ценю время, которое вы потратили на изучение этой проблемы, и я также поддержал этот ответ.
Конечно, спасибо. Я также поддержал ответ @ jstur за добавление недостающей информации о свойстве кода и отличной идее с классом отражения.
Извините за мое отсутствие. Я только что наградил @jstor наградой, потому что считаю, что это более актуально для этого вопроса. Надеюсь, вы не чувствуете, что это приманка и подмена; я не планировал делать это таким образом. Если я найду способ это исправить, я это сделаю.
Система говорит, что нам нужно ждать 24 часа. Надеюсь, я больше не исчезну;)
@ jh1711 Нет проблем, оба ответа добавили важную ценность, в то время как jstur действительно немного больше подходит в конце этого конкретного вопроса. Я тоже за него проголосовал ?
Ответ 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";
}
Конечно, вы потеряете информацию, если код будет содержать буквенно-цифровое значение, а не целочисленное преобразование в виде строки. Сообщение может дублировать номер ошибки, но на это не стоит полагаться.
Несериализуемая трассировка стека, о которой заботится 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...
}
Ой.
Итак, ответ Блэкбэма и подход в его ссылке создания сериализуемого класса исключений, вероятно, являются правильным решением. Это позволяет сериализовать данные исключения, но не трассировку стека.
Опять же, в этот момент вы можете просто использовать json_encode и json_decode для передачи / сохранения информации об исключениях.
Увлекательно - спасибо за отличный ответ. Я не знал, что ReflectionClass можно использовать таким образом, и это определенно было для меня недостающим звеном. Вы знаете, почему возникает фатальная ошибка, когда вызов PDO выполняется в такой функции, но не когда вызов был встроенным? Я собираюсь принять этот ответ, потому что он предоставляет доказательство / решение заявленной проблемы с отсутствующим свойством $code. Что касается очевидной проблемы XY при попытке сериализации PDOException, я согласен, что правильный ответ - не пытаться. Спасибо, что нашли время изучить эту проблему.
Ах, я просто перечитал ответ @Blackbam, и я думаю, что понимаю, почему ваша модификация вызывает ошибку - потому что передача объекта $pdo функции означает, что трассировка стека на один уровень глубже и теперь включает объект PDO, который не может быть сериализованный. Ой.
@ RobGwynn-Jones Да, вот и все. Хотел бы я притвориться, что я все это знал раньше - ответ Blackbam действительно исключительный, и я научился немного складывать остальное воедино.
Отличный ответ; Я не знал трюка с отражениями. Здесь - это небольшой репродуктор, который показывает, что исключения не любят строки и когда он был введен (возможно, вам придется нажать на «eol версии».
Если вы используете пространства имен, попробуйте добавить косую черту перед
PDOExceptionкак\PDOException.