PHP Unserialize Удаление свойства объекта - PullRequest
0 голосов
/ 02 июля 2018

У меня есть код, который сериализует 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.

Обратите внимание, что в сериализованной строке, по-видимому, содержится свойство кода, поэтому я предполагаю, что это проблема с функцией 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;

Ответы [ 2 ]

0 голосов
/ 09 июля 2018

Ответ Blackbam исключительный, но объект PDO, не подлежащий сериализации, представляет собой красную сельдь. Проблема с вашим кодом на самом деле связана с типом свойства $code, о котором говорилось в комментариях к его сообщению. Исключение инициализируется строковым представлением кода ошибки вместо целого числа в некоторых случаях. Это нарушает десериализацию, которая очень разумно решает отказаться от свойств с недопустимыми типами.

Комментарии на странице документации PDOException почти все говорят о проблемах, вызванных тем, что код ошибки создается в виде строки вместо целого.

Вы можете установить защищенное значение целым числом, используя отражение. Смотрите ниже:

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...
}

Упс.

Таким образом, ответ Blackbam и подход в его ссылке на создание сериализуемого класса исключений - это, вероятно, правильный путь. Это позволяет сериализовать данные исключения, но не трассировку стека.

Опять же, в этот момент вы можете просто использовать json_encode и json_decode для передачи / хранения информации об исключениях.

0 голосов
/ 05 июля 2018

Прежде всего взгляните на класс 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 сериализует трассировку стека.

Когда в трассировке стека присутствует несериализуемый объект, исключение не будет сериализуемым.

И я полагаю, что это причина того, что наш процесс serialize () / unserialize () не работает - потому что исключение не сериализуемо.

Решение:

Напишите сериализуемое исключение, которое расширяет исключение

class SerializableException extends Exception implements Serializable {
// ... go ahead :-)
}
...