php7, ссылки и oci привязываются по имени


Я публикую это здесь раньше php.net чтобы, возможно, лучше понять разницу в поведении, которую я вижу между PHP 5.x и 7.x.

Следующий код работает в PHP 5.x, но не 7.x

$conn = oci_connect('****', '****', '****', '****');
$stmt = oci_parse($conn, 'select record# from company where record#=:1');

$cache = [];

$cacheRow[0] = '2270';

oci_bind_by_name($stmt, ":1", $cacheRow[0], 2*strlen($cacheRow[0])+32);

$cache[0] = $cacheRow;

$result = runStmt($stmt);
checkResult($result, '2270');

$cacheRow = $cache[0];
$cacheRow[0] = '2274';
$cache[0] = $cacheRow;

$result = runStmt($stmt);
checkResult($result, '2274');

runstmt() просто oci_execute и oci_fetch_array. Функция checkResult() просто проверяет, что возвращаемая строка содержит значение второго параметра.

В PHP 7 (7.0.8 и 7.0.10 в любом случае) второй вызов checkResult сообщает, что возвращенная строка содержит ЗАПИСЬ № 2270 не соответствует ожидаемому 2274.

Отслеживание кода в gdb вот что я собрал воедино:

  • Параметр &$ переменной oci_bind_by_name в конечном итоге разыменовывается на z/ и продолжает существовать как простая строка zval в bindp->zval (oci8_statement.c:1250). Это нормально, так как другие более простые тесты работают до тех пор, пока все zvals указывают на одну и ту же строку.

  • При возврате из oci_bind_by_name $cacherow[0] теперь является ссылкой как ожидаемый.

  • На следующем $cacherow[0] = '2274', когда копия $cacherow создается во время задания, $cacherow[0] в результирующей копии больше не является ссылкой, а просто zval, указывающий на исходную строку.

  • После копирования, когда выполняется назначение в новый $cacherow[0], он просто меняет свой указатель str.

Теперь новый $cacherow[0] указывает на строку, отличную от bindp->zval в oci8_statement, поэтому следующий oci_execute вытянет старое привязанное значение.

Я могу обойти это, убедившись, что назначения, включающие $cache[0] (как входящие, так и исходящие), являются ссылочными. Это позволяет избежать проблемы, поскольку массив $cacherow никогда не разделяется.

Я также могу воспроизвести это в чистом PHP-коде

function bbn1(&$var)
{
}

function test1()
{
    $cache = [];

    $cacheRow[0] = '2270';

    bbn1($cacheRow[0]);
    $x = $cacheRow[0];

    $cache[0] = $cacheRow;

    $cacheRow = $cache[0];
    // Copy-on-write of $cacheRow does not preserve the reference in 
    // $cacheRow[0] because $cacheRow[0]'s refcount == 1
    // zend_array_dup_element in zend_hash.c
    $cacheRow[0] = '2274';

}

function bbn2(&$var)
{
    static $cache = [];
    $cache[] =& $var;
}

function test2()
{
    $cache = [];

    $cacheRow[0] = '2270';

    bbn2($cacheRow[0]);
    $x = $cacheRow[0];

    $cache[0] = $cacheRow;

    $cacheRow = $cache[0];

    // Copy-on-write of $cacheRow preserves the reference in 
    // $cacheRow[0] because $cacheRow[0]'s refcount != 1
    // zend_array_dup_element in zend_hash.c
    $cacheRow[0] = '2274';

}

Поскольку я могу получить различное поведение в тестах чистого PHP в зависимости от того, сохраняю ли я ссылку на переданный параметр в bbn, это заставляет меня думать, что если oci_bind_by_name увеличил количество ссылок на его входящий параметр bind_var, мой исходный тест, будет вести себя одинаково между PHP 5 и PHP 7. С другой стороны, я готов убедиться, что это ожидаемое поведение, и мне действительно нужно использовать присваивание по ссылке.

Author: Machavity, 2016-08-10

2 answers

Попробуйте патч PHP OCI8, только что загруженный в https://bugs.php.net/patch-display.php?bug_id=71148&patch=oci8-php7-bind&revision=latest

 1
Author: Christopher Jones, 2016-10-13 04:54:44

Смотрите комментарии в https://bugs.php.net/bug.php?id=71148 Были изменения в подсчете ссылок PHP 7 (для производительности PHP), которые повлияли на OCI8. Мы экспериментировали с увеличением количества ссылок внутри расширения OCI8, но это нарушило другие вещи.

 0
Author: Christopher Jones, 2016-09-01 20:51:03