Я публикую это здесь перед 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 сообщает, что возвращенная строка содержит RECORD# 2270, а не ожидаемый 2274.
Просматривая код в gdb, вот что я собрал:
Параметр &$variable 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. Опять же, я хочу быть уверен, что это ожидаемое поведение, и мне действительно нужно использовать назначение по ссылке.