Ошибки, возникающие в коде mysqli и call_user_func_array ()

Я получаю довольно много ошибок при попытке создать предложение dynamic where, использующее mysqli:

Предупреждение. Параметр 2 для mysqli_stmt :: bind_param () должен быть ссылкой, значением, указанным в … по строке 319

Предупреждение: mysqli_stmt :: execute (): (HY000 / 2031): данные не предоставлены для параметров в подготовленном сообщении в … на строке 328

Предупреждение: mysqli_stmt :: bind_result (): (HY000 / 2031): данные не предоставлены для параметров в подготовленном заявлении в … по строке 331

Предупреждение: mysqli_stmt :: store_result (): (HY000 / 2014): команды не синхронизированы; вы не можете запустить эту команду сейчас … в строке 332

Я предполагаю, что есть небольшое изменение, которое необходимо для решения проблем, но происходит то, что если одно из двух выпадающих меню не равно All или если оба не равны All тогда возникают ошибки.

Ниже приведен код, отображающий как выпадающие меню, так и запрос (с предложением dynamic where), который следует за выбором n опций:

HTML:

Выпадающее меню для учащихся:

 <select name="student" id="studentsDrop"> <option value="All">All</option> <option value="11">John May</option> <option value="23">Chris Park</option> </select> 

Выпадающее меню

 <select name="question" id="questionsDrop"> <option value="All">All</option> <option value="123">1</option> <option value="124">2</option> <option value="125">3</option> </select> 

PHP / Mysqli:

  function StudentAnswers() { /*BELOW IS THE QUERY WHERE I AM TRYING TO RETRIEVE DATA DEPENDING ON THE ASSESSMENT CHOSEN AND THEN DEPENDING ON OPTIONS CHOSEN IN STUDENT AND QUESTION NUMBER DROP DOWN MENU */ $selectedstudentanswerqry = " SELECT StudentAlias, StudentForename, StudentSurname, q.SessionId, QuestionNo, QuestionContent, o.OptionType, q.NoofAnswers, GROUP_CONCAT( DISTINCT Answer ORDER BY Answer SEPARATOR ',' ) AS Answer, r.ReplyType, QuestionMarks, GROUP_CONCAT(DISTINCT StudentAnswer ORDER BY StudentAnswer SEPARATOR ',') AS StudentAnswer, ResponseTime, MouseClick, StudentMark FROM Student s INNER JOIN Student_Answer sa ON (s.StudentId = sa.StudentId) INNER JOIN Student_Response sr ON (sa.StudentId = sr.StudentId) INNER JOIN Question q ON (sa.QuestionId = q.QuestionId) INNER JOIN Answer an ON q.QuestionId = an.QuestionId LEFT JOIN Reply r ON q.ReplyId = r.ReplyId LEFT JOIN Option_Table o ON q.OptionId = o.OptionId "; // Initially empty $where = array('q.SessionId = ?'); $parameters = array($_POST["session"]); $parameterTypes = 'i'; // Check whether a specific student was selected if($_POST["student"] !== 'All') { $where[] = 'sa.StudentId = ?'; $parameters[] =& $_POST["student"]; $parameterTypes .= 'i'; } // Check whether a specific question was selected // NB: This is not an else if! if($_POST["question"] !== 'All') { $where[] = 'q.QuestionId = ?'; $parameters[] =& $_POST["question"]; $parameterTypes .= 'i'; } // If we added to $where in any of the conditionals, we need a WHERE clause in // our query if(!empty($where)) { $selectedstudentanswerqry .= ' WHERE ' . implode(' AND ', $where); global $mysqli; $selectedstudentanswerstmt=$mysqli->prepare($selectedstudentanswerqry); // You only need to call bind_param once call_user_func_array(array($selectedstudentanswerstmt, 'bind_param'), array_merge(array($parameterTypes), $parameters)); //LINE 319 ERROR 1 } //Add group by and order by clause to query $selectedstudentanswerqry .= " GROUP BY sa.StudentId, q.QuestionId ORDER BY StudentAlias, q.SessionId, QuestionNo "; // get result and assign variables (prefix with db) $selectedstudentanswerstmt->execute(); //LINE 328 ERROR 2 //bind database fields $selectedstudentanswerstmt->bind_result($detailsStudentAlias,$detailsStudentForename,$detailsStudentSurname,$detailsSessionId,$detailsQuestionNo, $detailsQuestonContent,$detailsOptionType,$detailsNoofAnswers,$detailsAnswer,$detailsReplyType,$detailsQuestionMarks,$detailsStudentAnswer,$detailsResponseTime, $detailsMouseClick,$detailsStudentMark); //LINE 331 ERROR 3 //store results retrieved $selectedstudentanswerstmt->store_result(); //LINE 332 ERROR 4 //count number of rows retrieved $selectedstudentanswernum = $selectedstudentanswerstmt->num_rows(); //output query echo "$selectedstudentanswerqry"; } ?> 

Вот ДЕМО: ДЕМО

В демо выберите оценку из раскрывающегося меню и отправьте. Вы увидите два раскрывающихся меню. Держите их как настроенными как « All и «Отправить», он будет выводить запрос без проблем. Нет в одном из раскрывающихся меню, измените All на конкретного ученика или вопрос, затем отправьте. Теперь вы увидите ошибки

VAR DUMP:

Результат var_dump(array_merge(array($parameterTypes), $parameters))); когда я выбрал сессию (оценку) со значением 31 , значением номера ученика 40 и значением номера вопроса 81 , И WHERE q.SessionId = ? AND sa.StudentId = ? AND q.QuestionId = ? WHERE q.SessionId = ? AND sa.StudentId = ? AND q.QuestionId = ? :

Я получаю этот вывод: array(4) { [0]=> string(3) "iii" [1]=> string(2) "31" [2]=> string(2) "40" [3]=> string(2) "81" }

Это липкая ситуация, вызванная изменением поведения call_user_func_array в PHP 5.4 (я должен предположить): Документация

Как это уродливо, это будет работать, чтобы вызвать bind_param следующим образом:

 $selectedstudentanswerqry .= ' WHERE ' . implode(' AND ', $where); global $mysqli; $stmt =$mysqli->prepare($selectedstudentanswerqry); if (count($where) === 1) { $stmt->bind_param($parameterTypes, $parameters[0]); } else if (count($where) === 2) { $stmt->bind_param($parameterTypes, $parameters[0], $parameters[1]); } else if (count($where) === 3) { $stmt->bind_param($parameterTypes, $parameters[0], $parameters[1], $parameters[2]); } 

Я ненавижу это так, как вы, вероятно, делаете. Я предлагаю переключиться с mysqli на PDO который обрабатывает переменные параметры намного лучше (и, на мой взгляд, имеет превосходный синтаксис):

 $pdo = new PDO('mysql:host=localhost', 'username', 'password'); $stmt = $pdo->prepare($selectedstudentanswerqry); $stmt->execute($parameters); $selectedstudentanswernum = $stmt->rowCount(); 

Предупреждение. Параметр 2 для mysqli_stmt :: bind_param () должен быть ссылкой, значением, указанным в … по строке 319

Это должно быть самоочевидным: аргументы bind_param передаются по ссылке и поэтому должны быть переменными. Что вы могли упустить, так это то, что array_merge возвращает новый массив, который не содержит ссылок на исходные переменные, а просто на значения.

Следующие ошибки просто следуют за этим, потому что параметры не были связаны.

Возможным решением является сохранение ссылок в вашем массиве $parameters , которые даже будут сохранены array_merge:

 $parameters[] =& $_POST["student"]; $parameters[] =& $_POST["question"]; 

Теперь элементы массива $parameters являются ссылками на переменные POST, а также элементы array_merge результата array_merge .

Изменить: похоже, что это невозможно, см. @Explosion Pills

Это по-прежнему не является достаточной причиной для отказа от mysqli для PDO, особенно когда у меня были очень хорошие причины, чтобы не использовать PDO вместо этого в то время. С другой стороны, да, им не нужно было принудительно использовать методы mysqli_stmt :: bind_param и bind_result, чтобы потребовать, чтобы переменные были переданы через Ref, они могли бы вернуть им массив. Но реальная проблема здесь в том, что переадресация времени вызова больше невозможна, но mysqli по-прежнему требует передачи по ссылке, поэтому каждый, кто использует динамические параметры (любой, у кого есть интуитивный SQL-код), оказывается пойманным в этом я сам включил. Потратив часы на создание пользовательского класса оболочки mysqli, который преобразует массивы в динамически подготовленные SQL-инструкции, я не собираюсь переписывать все это для работы с PDO, тем более что PDO имеет требование назвать ваши параметры как часть привязки, Мне нужно было бы написать несколько функций, чтобы создать список того, что было отправлено … для каждого типа запросов. Это противоречит интуиции, и я не буду тратить бесчисленные часы, пытаясь подражать тому, что я сделал с mysqli.

Единственное затруднение в этой точке состоит в том, что мы не можем передавать переменные в call_user_func_array () byRef, а mysqli_stmt :: bind_ * требует, чтобы они были переданы в byRef … поэтому единственными параметрами, которые у нас есть, являются: 1) написать новый call_user_func_array () функция, предназначенная для явной работы для методов bind_ *, где переданные параметры по существу копируются внутри в новый массив или набор переменных с помощью args, а затем передают те из Ref при обратном вызове; или 2) переписать класс mysqli, чтобы сделать это правильно и просто посмотреть, что передано, выполнить SQL, а затем вернуть результат по назначению. Нет никакой причины, что то, что мы передаем методу выполнения SQL, должно быть сбито с результатами, оно должно рассматриваться так же, как обрабатывается консоль SQL.

Поскольку я слишком устал от кодирования, пока мои глаза почти не истекут кровью, я собираюсь взять предложенный вариант сохранения моей версии PHP, застрявшей в 5.3.10-1, до тех пор, пока на самом деле проблема не будет исправлена ​​правильно. Для тех, кто будет аргументировать «за все эти усилия, почему бы просто не потратить его на запись вашей оболочки PDO?» … и мой ответ прост: я использую PHP и MySQL исключительно во многих проектах, где сервер – это маленький компьютер с очень ограниченными помещениями и ресурсами, не говоря уже о возможностях процессора. PDO загружает ВСЕ, что может сделать, а не только то, что вы собираетесь использовать, поэтому в ваших ресурсах есть некоторые оговорки, которые вы даже не будете использовать, которые мои проекты не могут позволить себе потерять.

К счастью, сохранение моей версии PHP не будет вредным в настоящее время. Это то, что на самом деле делают многие фермы серверов уровня предприятия, чтобы избежать необходимости сводить все, чтобы быть «в текущей версии» чего-то, что также безумно дорого обновлять все каждые несколько лет.