简体   繁体   中英

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:

  • The &$variable parameter of oci_bind_by_name ends up be dereferenced by z/ and lives on as a simple string zval in bindp->zval (oci8_statement.c:1250). This is ok, as other simpler tests work as long as all the zvals are pointing at the same string.

  • On return from oci_bind_by_name $cacheRow[0] is now a reference as expected.

  • On the next $cacheRow[0] = '2274' when the copy of $cacheRow is made during the assignment, $cacheRow[0] in the resulting copy is no longer a reference, just a zval pointing to the original string.

  • After the copy when the assignment into the new $cacheRow[0] is made it just changes its str pointer.

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.

See comments in https://bugs.php.net/bug.php?id=71148 There were PHP 7 ref counting changes (for PHP performance) that have impacted OCI8. We experimented with bumping the ref count inside the OCI8 extension but this broke other things.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM