Абстракция Mysqli, извлечение массивов из подготовленных операторов

Недавно я наткнулся на ошибку в библиотеке, которая раньше работала нормально, и будь я проклят, если смогу понять, где она.

Пример кода приведен ниже, и я приношу извинения за отладку внутри него, но я пытаюсь заставить его работать.

Проблема в том, что $temp — это массив с правильным ключом (именем столбцов), но все значения равны NULL.

Я думаю, что проблема кроется в

call_user_func_array(array($query, 'bind_result'), $params);

немного, но я не могу уложить это в голове.

public function fetchRows(){
    error_reporting(E_ALL+E_NOTICE);
    $args = func_get_args();
    $sql = array_shift($args);
    traceVar($sql, "Query");
    $colTypes = array_shift($args);
    if (!$query = $this->prepare($sql, $colTypes)) {
        die('Please check your sql statement : unable to prepare');
    }
    if (count($args)){
        traceVar($args,'Binding params with');
        call_user_func_array(array($query,'bindParam'), $args);
    }

    $query->execute();

    $meta = $query->result_metadata();
    while ($field = $meta->fetch_field()) {
        $params[] = &$row[$field->name];
    }
    traceVar($params,'Binding results with');
    call_user_func_array(array($query, 'bind_result'), $params);

    while ($query->fetch()) {
        traceVar($row,'After fetch');
        $temp = array();
        foreach($row as $key => $val) {
            $temp[$key] = $val;
        } 
        $result[] = $temp;
    }

    $meta->free();
    $query->close(); 
    //self::close_db_conn(); 
    return $result;
}

person Madness    schedule 09.12.2009    source источник


Ответы (2)


Код, который вы предоставили, работает для меня.

Функция call_user_func_array(...) просто вызывает bindParam или bind_result для объекта $query с данным массивом, как если бы вы предоставили каждый элемент массива в качестве аргумента метода.

Вы можете проверить инструкцию SQL, с которой у вас возникла проблема, с помощью приведенного ниже кода. Я немного переписал его, чтобы сделать его полностью тестируемым, поскольку исходный код зависит от класса операторов на уровне абстракции.

<?php

$db_host = 'localhost';
$db_user = 'username';
$db_pass = 'password';
$db_name = 'database';

$mysqli = new mysqli($db_host, $db_user, $db_pass, $db_name);

print_r(fetchRows('SELECT something from some_table WHERE some_id = ?', 'i', 1));

function traceVar($a, $b) {
    print_r(array($b => $a));
}

function fetchRows(){
        error_reporting(E_ALL+E_NOTICE);
        $args = func_get_args();
        $sql = array_shift($args);
        traceVar($sql, "Query");

        // Keep the column types for bind_param.
        // $colTypes = array_shift($args);

        // Column types were originally passed here as a second
        // argument, and stored in the statement object, I suppose.
        if (!$query = $GLOBALS['mysqli']->prepare($sql)){ //, $colTypes)) {
                die('Please check your sql statement : unable to prepare');
        }
        if (count($args)){
                traceVar($args,'Binding params with');

                // Just a quick hack to pass references in order to
                // avoid errors.
                foreach ($args as &$v) {
                    $v = &$v;
                }

                // Replace the bindParam function of the original
                // abstraction layer.
                call_user_func_array(array($query,'bind_param'), $args); //'bindParam'), $args);
        }

        $query->execute();

        $meta = $query->result_metadata();
        while ($field = $meta->fetch_field()) {
                $params[] = &$row[$field->name];
        }
        traceVar($params,'Binding results with');
        call_user_func_array(array($query, 'bind_result'), $params);

        while ($query->fetch()) {
                traceVar($row,'After fetch');
                $temp = array();
                foreach($row as $key => $val) {
                        $temp[$key] = $val;
                } 
                $result[] = $temp;
        }

        $meta->free();
        $query->close(); 
        //self::close_db_conn(); 
        return $result;
}
person Inshallah    schedule 09.12.2009
comment
Я попробовал с paste2.org/p/554177, и это работает как шарм, интересно, где мой оригинал код ломается. - person Madness; 09.12.2009
comment
Должно быть, я делаю какую-то ошибку при реализации ваших изменений, если переписываю функцию как paste2.org/p/554194, а затем запускаю paste2.org/p/554192. Я все равно получаю Array ( [0] = › Массив ( [ID] => [Имя пользователя] => [Пароль] => [Имя] => [Email] => [AccessLevel] => [IsTemp] => ) ) - person Madness; 09.12.2009
comment
Если это будет полезно, я также разместил некоторые другие фрагменты из этого класса здесь paste2.org/p/554201 - person Madness; 09.12.2009
comment
Похоже, проблема связана с другими методами, которые я разместил в своем последнем комментарии. Я установил соединение $db = $this-›; перед if (!$query = $db-›prepare($sql)) { (таким образом, используя исходный объект, а не класс-оболочку), и это работает. Любая подсказка о том, что я могу делать неправильно? - person Madness; 09.12.2009
comment
Глядя на другой код, который вы предоставили, я заметил, что DBStatement::prepare(...) пусто, что объясняет, почему для его работы требуется $db = $this->connection; :-). Можете ли вы проверить, является ли метод DBStatement::prepare(...) в исходном коде пустым? - person Inshallah; 09.12.2009
comment
Да это так, но ведь я им не пользуюсь. Я использую Database::prepare, так что это не имеет значения. - person Madness; 09.12.2009
comment
Ах да, пустой метод был немного запутанным. На самом деле я попробовал это сейчас, и он выдает ошибку о том, что 'bind_param' ожидает ссылку. В DBStatement::bindParam добавьте оператор ссылки к аргументу $params: call_user_func_array(array($this->baseObject, 'bind_param'), &$params);. - person Inshallah; 09.12.2009
comment
Пытался внести некоторые изменения, но я все еще получаю пустой набор значений. paste2.org/p/555802 — это обновленная версия. Интересно, имеет ли это какое-то отношение к использованию func_get_args, нарушающему ссылку. - person Madness; 10.12.2009
comment
Это странно. У меня работает с PHP 5.3.0. Каков результат php -v? Вы можете попробовать добавить оператор ссылки как в DBStatement::__call, так и в Database::__call. - person Inshallah; 10.12.2009

Если бы мы могли выбрать сервер при запуске, мы могли бы использовать модуль php-mysqlnd вместо модуля php-mysql для PHP. (Или некоторые из вас, возможно, уже используют его, запустите «phpinfo();» и найдите «mysqlnd»):

public function fetchRows(){
    ...
    $query->execute();

    $res = $query->get_result();
    while (($row = $res->fetch_assoc()))
        $result[] = $row;
    return $result;
    }
}

Мне это кажется проще.

person Johnny Wong    schedule 04.04.2014
comment
Могу я спросить, на сколько вопросов вы собираетесь ответить с помощью этих новых знаний? Грубая оценка была бы в порядке - person Your Common Sense; 04.04.2014
comment
Два. Просто потому, что в прошлом я искал несколько из этих решений и не заметил, что есть get_result(), который также может работать. Поэтому добавьте его для справки будущих пользователей. - person Johnny Wong; 04.04.2014
comment
Два - это приличная сумма. Хотя ты собирался ответить на все тысячи вопросов, когда-либо заданных по этому поводу. - person Your Common Sense; 04.04.2014