php7、参考文献和 oci_bind_by_name

php7, references and oci_bind_by_name

I'm posting this here before php.net to maybe get a better understanding of the difference in behavior that I'm seeing between PHP 5.x and 7.x.

The following code works in PHP 5.x but not 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() just oci_execute and oci_fetch_array. checkResult() just verifies that the row returned contains the value in the second parameter.

In PHP 7 (7.0.8 and 7.0.10 anyway) the second call to checkResult reports that the row returned contains the RECORD# 2270 not the expected 2274.

Tracing through the code in gdb here's what I've pieced together:

Now the new $cacheRow[0] is pointing to a different string than oci8_statement's bindp->zval so the next oci_execute will pull the old bound value.

I can work around this by ensuring that the assignments involving $cache[0] (both in-to and out-of) are by-reference. This avoids the issue because the $cacheRow array is never separated.

I can also reproduce this in pure PHP code

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';

}

Since I can get different behaviors in the pure PHP tests depending on if I keep a reference to the passed parameter to bbn this makes me think that if oci_bind_by_name increased the refcount on its incoming bind_var parameter my original test would behave identically between PHP 5 and PHP 7. Then again, I'm willing to be convinced that this is expected behavior and I really do need to use assignment-by-ref.

查看 https://bugs.php.net/bug.php?id=71148 中的评论 有 PHP 7 个引用计数更改(针对 PHP 性能)影响了 OCI8。我们尝试在 OCI8 扩展中增加引用计数,但这破坏了其他东西。

试试刚刚上传到 https://bugs.php.net/patch-display.php?bug_id=71148&patch=oci8-php7-bind&revision=latest

的 PHP OCI8 补丁