Небольшой дисклеймер от переводчика: в РНР сложилась парадоксальная ситуация. Сам язык ушёл далеко вперёд, но изучают его по жутко устаревшим материалам. Собственно, постоянный кринж от кода на Тостере (как в вопросах, так и в ответах) и побудил к переводу данной статьи.
Кроме того, переводчик, также как и вы, считает, что PDO является более продвинутым API для работы с БД, чем mysqli. Но поскольку новички в подавляющем большинстве всё равно начинают с mysqli, то нужен хотя бы один нормальный материал по этому расширению. Не можешь противостоять — возглавь!
Не говоря уже о том, что в последнее время mysqli была сильно улучшена, и из совершенно неюзабельной превратилась в довольно сносную библиотеку, в которой из принципиальных отличий от PDO осталось разве что отсутствие именованных плейсхолдеров. Так что даже (особенно) если вы учили РНР 20 лет назад и всё знаете вдоль и поперёк, то всё равно сможете найти для себя что-то новое.
Соединение
Важность кода для соединения с БД часто недооценивают, сводя к его одной-единственной строчке. В то время как правильное соединение поможет заранее решить целую кучу проблем — от кракозябр до непонятных сообщений об ошибках, и даже влияет на безопасность.
Если ваш код является обычным процедурным РНР, то вот простой пример соединения:
$host = '127.0.0.1';
$db = 'test';
$user = 'root';
$password = '';
$port = 3306;
$charset = 'utf8mb4';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new mysqli($host, $user, $password, $db, $port);
$db->set_charset($charset);
$db->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1);
Полное объяснение всех этих команд приводится в отдельной статье, Как правильно соединяться с mysqli (en), а здесь я приведу только ключевые моменты
- устанавливая правильный режим информирования об ошибках, мы избавляемся от непонятных ошибок типа
mysqli_fetch_assoc() expects parameter...
/Call to a member function bind_param()...
, получая вместо них актуальные сообщения об ошибках от MySQL.- кроме того, режим исключений значительно упростит код, в котором не нужно будет писать проверку успешности выполнения каждой функции
- установка правильного значения charset сразу избавит нас от целого класса проблем, связанных с кодировками, таких как крокозябры/вопросики вместо текста, пустой результат json_encode(), проблемы с сохранением эмодзи, и т.д.
- с безопасностью не шутят, и сообщения об ошибках ни в коем случае нельзя вываливать прямо в браузер, как это принято во всех устаревших руководствах
- возвращение из бд любых типов данных в виде строк выглядит в наше время атавизмом. И хотя подготовленные запросы уже возвращают данные типизованными, при использовании функции
query()
по умолчанию все типы данных возвращаются в виде строк. Чтобы это исправить, на помощь приходит настройкаMYSQLI_OPT_INT_AND_FLOAT_NATIVE
Объектный и процедурный интерфейсы
Небольшое, но важное замечание: у mysqli есть одно уникальное свойство: каждая функция может быть вызвана как через объектный, так и через процедурный синтаксис. То есть любое действие может быть выполнено как через вызов функции, так и через обращение к методу объекта:
mysqli_query($mysqli, $query); // процедурный синтаксис
$mysqli->query($query); // объектный синтаксис
Единственное различие заключается в том, что для объектного синтаксиса мы берём параметр функции (например $mysqli
), добавляем объектный оператор (->
) и дальше пишем собственно имя метода, выкидывая избыточное "mysqli_". Отдельно отмечу, что вам не нужно знать ООП чтобы использовать объектный синтаксис: это просто другой способ вызвать ту же самую функцию.
Оба способа полностью взаимозаменяемы, разница только в синтаксисе. Вы можете использовать любой. Их даже можно смешивать в одном и том же коде — это будет работать, хотя и не одобряется с точки зрения стиля.
Учитывая, что объектный синтаксис является более коротким, без постоянных повторений (ср. mysqli_stmt_get_result($stmt)
и $stmt->get_result()
) я настоятельно рекомендую именно его, так что именно объектный синтаксис будет использоваться в этом руководстве.
Выполнение запросов, в которых используются переменные. Подготовленные выражения
Одной из основных причин, по которым старое расширение mysql было удалено из PHP, является отсутствие поддержки подготовленных выражений, то есть переменные PHP без вариантов должны были добавляться напрямую в SQL. Но нет ни малейшей причины продолжать эту опасную практику с mysqli. Другими словами, теперь вы должны использовать подготовленные запросы, что означает полное переписывание каждой операции с БД.
Почему надо использовать подготовленные запросы? По той простой причине, что если мы добавляем данные прямо в запрос, то они могут повредить его. При этом последствия варьируются от синтаксических ошибок до SQL инъекций. В отличие от печально известного "экранирования", которое работает только для строк, и которое легко можно забыть, использовать неправильно или посчитать ненужным, подготовленные запросы позволяют нам сформулировать простую, но стопроцентно безопасную инструкцию из трёх шагов:
- Подготавливаем запрос, добавляя знаки вопроса,
?
, там где раньше была переменная - Привязываем сами переменные к этому подготовленному выражению, указывая для каждой её тип
- Выполняем запрос
Вот простой пример запроса INSERT:
$stmt = $db->prepare("INSERT INTO users (email, password) VALUES (?,?)");
$stmt->bind_param("ss", $email, $password_hash);
$stmt->execute();
Как можно видеть, тут нет ничего сложного, те самые три шага, описанные выше.
Давайте рассмотрим подготовленный запрос подробнее, на примере запроса UPDATE:
$sql = "UPDATE users SET name=?, email=?, password=? WHERE id=?";
$stmt= $conn->prepare($sql);
$stmt->bind_param("sssi", $name, $email, $password, $id);
$stmt->execute();
Что здесь происходит?
$sql = "UPDATE users SET name=?, email=?, password=? WHERE id=?";
Как это было описано выше, сначала мы заменяем все переменные запросе на вопросительные знаки.
ВАЖНО: нельзя добавлять к знакам вопроса кавычки — вы добавляете плейсхолдеры, а не строки.
$stmt= $conn->prepare($sql);
Дальше мы подготавливаем запрос. Идея тут очень остроумная: для того чтобы исключить даже теоретическую возможность инъекции, сам запрос и данные для него едут на сервер по отдельности. Именно это мы здесь и видим: вызов prepare()
отправляет на сервер сначала сам запрос, без данных. При этом создаётся специальная переменная $stmt
, содержащая экземпляр класса mysqli_statement
и дальше мы будем работать именно с ней.
$stmt->bind_param("sssi", $name, $email, $password, $id);
Дальше наши переменные должны быть привязаны к подготовленному выражению. Данный вызов состоит из двух частей: сначала идёт строка, в которой перечислены типы передаваемых переменных, а затем и сами переменные. В mysqli вам необходимо указывать тип для каждой переменной. Это делается с помощью одной буквы для каждой переменной. Количество букв должно всегда соответствовать количеству переменных. Возможных типов четыре:
- i для целых чисел;
- d для чисел с плавающей запятой;
- s для строк;
- b для блобов.
И теперь вы можете сказать, что запись "sssi" означает "у нас будет 3 переменных строкового типа и одна — целочисленного". Затем в функцию передаются все переменные, 4 штуки.
Совет: MySQL с радостью принимает любые данные как строки, так что не нужно сходить с ума пытаясь найти подходящий тип для каждой переменной. Можно просто использовать "s" для всех.
$stmt->execute();
И наконец, запрос выполняется. Переменные отправляются в БД и запрос выполняется.
Важно! Вы не должны проверять результат выполнения запроса вручную. В случае, если соединение было установлено, как описано выше, в случае ошибки mysqli автоматически выбросит исключение.
Кстати, начиная с PHP 8.1, bind_param
можно не использовать, отправив вместо этого все переменные в execute()
в виде массива:
$stmt = $db->prepare("INSERT INTO users (email, password) VALUES (?,?)");
$stmt->execute([$email, $password_hash]);
В этом случае все переменные будут переданы как строки.
Чисто для комплекта, пример запроса DELETE, но я надеюсь, что вы уже уловили идею:
$sql = "DELETE FROM users WHERE id=?";
$stmt= $conn->prepare($sql);
$stmt->bind_param("s", $id);
$stmt->execute();
Выполнение запросов SELECT через подготовленные выражения
Точно так же как это было описано выше, мы подготавливаем запрос с вопросительными знаками, затем привязываем переменные и выполняем запрос. Однако для запросов SELECT нам понадобится один дополнительный шаг, получение переменной типа mysqli_result
, которую можно будет использовать для выборки полученных строк:
$stmt = $db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
здесь функция get_result()
возвращает экземпляр класса mysqli_result
, который может использоваться для того чтобы получать возвращённые запросом строки в виде массивов или объектов.
Примечание: Если вам пишет, что такая функция не определена, то надо отметить чекбокс подписанный php_mysqlnd
в разделе конфигурации PHP в панели управления вашего хостинга.
Получение результатов запроса
Для получения результата запроса используется переменная, являющаяся экземпляром класса mysqli_result
. Все функции, получающие данные из запроса, работают с этой переменной.
Стандартным способом получения как одной, так и нескольких строк, будет обращение к одной из следующих функций:
- fetch_row() которая возвращает нумерованный массив;
- fetch_assoc() которая возвращает ассоциативный массив;
- fetch_object() которая возвращает объект.
Получение одной строки
Если запрос вернул только одну строку, нам нужно обратиться к одной из вышеперечисленных функций. Например:
$stmt = $db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$username = $row['username'];
Получение нескольких строк в цикле
Что интересно, для получения нескольких строк служит та же самая функция, что и для получения одной строки. И всё благодаря одной маленькой детали: когда мы получаем строку с помощью одной из этих функций, внутренний указатель в возвращённом базой наборе строк перемещается на одну позицию, и поэтому следующее обращение к той же самой функции возвращает следующую строку — и так далее, пока строки не кончатся. Если строк больше нет, то функция fetch_* вернёт null
, что позволит нам использовать цикл while
для перебора всех строк:
$users = [];
$sql = "SELECT * FROM users ORDER BY id DESC LIMIT 0, 10";
$result = $db->query($sql);
while ($row = $result->fetch_assoc()) {
$users[] = $row;
}
Здесь мы получили все полученные из БД строки в массив $users.
Подсказка: в mysqli есть удобная функция, которпя сразу возвращает все строки в виде массива:mysqli_fetch_all()
. Вот только по какой-то причине она по умолчанию используетfetch_row()
для наполнения массива, так что если вам нужны строки в виде ассоциативных массивов, то надо использовать параметрMYSQLI_ASSOC
:
$sql = "SELECT * FROM categories";
$result = $db->query($sql);
$data = $result->fetch_all(MYSQLI_ASSOC);
Выполнение запросов без переменных
Если запрос целиком прописан прямо в коде, то есть в нем не используются никакие переменные PHP, то для выполнения запроса можно воспользоваться функцией query()
, которая сразу возвращает объект класса mysqli_result
, и в результате мы получим гораздо более простой код, например
$menu = $db->query("SELECT * FROM menu")->fetch_all(MYSQLI_ASSOC);
$count = $db->query("SELECT count(*) FROM users")->fetch_row()[0];
Простая функция-хелпер
Но не нужно завидовать, выполнение подготовленных запросов может быть таким же простым, благодаря крошечной функции-хелперу (en):
function prepared_query($mysqli, $sql, $params, $types = "")
{
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
}
Эта функция может быть добавлена в файл, в котором прописано соединение с БД и таким образом она будет доступна везде, где требуется mysqli, позволяя писать такие же аккуратные однострочники:
$sql = "SELECT * FROM menu WHERE section=?";
$menu = prepared_query($db, $sql, [$section])->get_result()->fetch_all(MYSQLI_ASSOC);
$sql = "SELECT * FROM users WHERE email=?";
$count = prepared_query($db, $sql, [$email])->get_result()->fetch_assoc();
Обработка ошибок
Обработка ошибок в запросах является очень важной темой, но её реализация вас удивит: в общем случае ошибки mysqli никак обрабатывать не нужно! Несмотря на то, что написано в бесчисленных примерах и руководствах по РНР, как правило, вы не должны писать никакого кода, обрабатывающего ошибки в запросах. Это звучит очень непривычно, но на самом деле именно так и надо делать. Если подумать, то для большинства ошибок надо только сообщить об их возникновении. И mysqli/PHP отлично справляются с этим сами, никакой помощи с вашей стороны им для этого не требуется. Следовательно, вы и не должны писать код, который проверяет результат выполнения запроса — в случае ошибки mysqli сообщит о ней автоматически, благодаря функции mysqli_report()
о которой шла речь выше. Таким образом, все ошибки взаимодействия с БД будут обрабатываться единообразно, точно так же, как и все остальные ошибки РНР, что естественно является очень удобным с точки зрения обработки, которую можно выполнять в одном месте, а не разбрасывать по коду отдельно для каждого запроса. Подробнее почитать про правильный подход к обработке ошибок можно в статье Обработка ошибок в PHP (en).
В тех редких случаях, когда вам действительно надо обработать ошибку, то есть выполнить какое-то определённое действие в случае ошибки, а не просто сообщить о её появлении, запрос(ы) можно обернуть в try..catch
.
Количество строк, которые вернул запрос SELECT
На самом деле нет ни одной причины использовать привычную функцию mysqli_num_rows()
. Если подумать, то вы всегда можете использовать сами полученные данные для ответа на вопрос, были получены какие-то данные, или нет:
$user = $result->fetch_assoc();
if ($user) {
// found!
}
То же самое относится и к получению нескольких строк, благодаря удобной функции mysqli_fetch_all()
, которая сразу вернет все полученные строки в виде массива.
Но конечно же надо помнить, что код ни в коем случае не должен запрашивать из БД больше строк, чем требуется на одной странице. Это относится как к запросам, которые получают определенные строки из БД для последующей обработки, так и — в особенности — к запросам, которые служат только для получения количества строк. Во втором случае вместо запроса самих строк необходимо запросить только их количество, запросом SELECT count(*) ...
.
Количество строк, затронутых при изменении данных
В отличие от предыдущего, количество строк, затронутых запросами INSERT, UPDATE и DELETE может быть довольно полезным. Что интересно, в mysqli есть не одна, целых две возможности получить эту цифру.
Одна из них — это привычное свойство affected_rows
:
$db->query("DELETE FROM users");
echo $db->affected_rows();
Но есть еще одна, уникальная функция mysqli_info(), которая возвращает отдельно количество найденных и затронутых строк. Во всех других драйверах, включая PDO, вы можете получить либо то, либо другое, но не всё вместе. Хотя эта функция возвращает строку, её несложно распарсить, и получить аккуратный массив:
$db->query("update test set i=2");
$pattern = '~Rows matched: (?<matched>\d+) Changed: (?<changed>\d+) Warnings: (?<warnings>\d+)~';
preg_match($pattern, $db->info, $matches);
$info = array_filter($matches, "is_string", ARRAY_FILTER_USE_KEY);
Комментарии (21)
Nnnnoooo
22.06.2022 16:11Или все действительно так плохо со знаниями у джунов, раз нужны такие статьи?
P.S. Ник такой же как на клубе здесь у меня давно забанен :)
P.P.S. Писать могу редко, потмоу ответы по эстонски :)FanatPHP Автор
22.06.2022 17:15Статьи нужны всегда. Дело не в том, у кого какие знания, а в том что их надо сначала откуда-то взять. Тем более — в языке с такой плохой наследственностью, как РНР. Тем более — в таком динамично развивающемся языке, как РНР.
Дело как раз не в джунах. А в том что даже офигевающие от собственной крутости синьор-помидоры на Тостере пишут такую же ересь, "or die()" и вот это вот всё. Потому что "пыха — это же язык для лохов", и кто угодно в нём эксперт. А то что этот "эксперт" студентом видел пару строчек говнокода и с тех пор с языком не сталкивался — это его не волнует.
Nnnnoooo
22.06.2022 20:30Дело как раз не в джунах. А в том что даже офигевающие от собственной крутости синьор-помидоры на Тостере пишут такую же ересь, "or die()" и вот это вот всё
Это да боль и печаль.
отвечу здесь, т.к. лимиты на паузу между сообщениями
Только с появлением семёрки mysqlnd стал доминировать в сборках, а вот в восьмёрке уже практически вытеснил libmysql совсем.
Ну как бы сами разработчики пхп сразу после выпуска mysqlnd говорили что юзайте его а старый вариант будет депрекейтед и неподдерживаемым (и если не ошибаюсь это было еще в 2009 году с выходом 5.3). Т.е. адекватный разраб должен был адаптировать свой код для новых реалий. Плюс в репах всегда был выбор или ставить легаси php-mysql (юзающий мускульную либу) или php-mysqlnd. Так что юзанье старья это проблемы сборщиков таких чудных сборок, а не проблемы системы. Т.к. в RHEL деривативах, учитывая что пхп почти всегда юзается Remi реп, проблем c php-mysqlnd никогда не было. Уверен то же самое и для дебиан/убунту дистров.
Сейчас у большинства пхп разработчиков (даже у большинства топ сеньоров) проблема не в апи работы с базой, а в том что многие относятся к базе как к черному ящику и даже минимально не представляют как она работает — тупо магия и все.
Rsa97
22.06.2022 18:52+2Добавлю, что начиная с PHP 8.1 режим MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT используется по умолчанию.
Были уже вопросы на тостере вида «А чего это вместо моего любимого сообщения из die() какая-то фигня выводится?».
savostin
22.06.2022 20:56+1Следовательно, вы и не должны писать код, который проверяет результат выполнения запроса — в случае ошибки mysqli сообщит о ней автоматически, благодаря функции
mysqli_report()
о которой шла речь выше.Пока вы не начнете писать API.
FanatPHP Автор
22.06.2022 21:02Это кстати интересная тема.
Но для начала надо понять, какое API имеется ввиду — так сказать, для внутреннего или для внешнего потребления. Если для внешнего — то ни одной причины проверять запросы вручную нет, весь форс-мажор отрабатывается единообразно.
А вот какие-то внутренние API, пакеты для использования в чужом коде — вот тут да, интересно. Но я бы тоже скорее обрабатывал возникающие в пакете ошибки глобально, на уровне пакета. И выкидывал какое-нибудь MyPackageException, к котором прицепом идёт уже исходное исключение.savostin
22.06.2022 21:13Я о том, что вывод ошибки может быть не в виде текста/html, а скажем в JSON/XML. Более того, формат вывода может зависеть от входных параметров. Ну и не всегда при ошибке в каком-то SQL запросе нужно валить весь процесс без суда и следствия.
FanatPHP Автор
22.06.2022 21:23+1Помилуйте, но мы же здесь говорим не про вывод, а про выброс. Наше дело сообщить об ошибке, а как она там будет выводиться — дело десятое. Как напишем — так и будет. На домашнем компе пусть полный трейс с расцвеченным куском кода вываливает. А на бою — надо HTML, покажем HTML, надо XML — будет XML. А понадобится в JSON — не проблема переписать на JSON. Причём в одном месте, глобальном обработчике ошибок, который один за вывод и отвечает — а не за каждым запросом потом с лопатой ходить. Это же банальная эффективность труда программиста.
FanatPHP Автор
23.06.2022 10:34+1Чтобы не быть голословным, вот пример, который как раз и предназначен для вывода в HTML, очень легко переделать под JSON:
<?php error_reporting(E_ALL); function myExceptionHandler ($e) { error_log($e); http_response_code(500); if (filter_var(ini_get('display_errors'),FILTER_VALIDATE_BOOLEAN)) { echo $e; } else { echo json_encode(['error' => true, 'code' => 500]); } exit; } set_exception_handler('myExceptionHandler'); set_error_handler(function ($level, $message, $file = '', $line = 0) { throw new ErrorException($message, 0, $level, $file, $line); }); register_shutdown_function(function () { $error = error_get_last(); if ($error !== null) { $e = new ErrorException( $error['message'], 0, $error['type'], $error['file'], $error['line'] ); myExceptionHandler($e); } });
И теперь, после добавления этого кода в проект, любая неперехваченная ошибка будет отдaвать аккуратный JSON!
Тут конечно ещё широкое поле для деятельности, например завести белый список типов исключений, для которых можно отдавать наружу сообщение об ошибке. или добавить переменную, которая будет задавать формат вывода. Или добавить генерацию уникального кода, по которому потом в логах найти конкретную ошибку. Но я в первую очередь хотел продемонстрировать на простом примере сам принцип, когда любые ошибки приложения обрабатываются централизованно, и — следовательно — не нужно проверять результат каждой функции вручную.
NickyX3
23.06.2022 09:26+1$result->fetch_assoc()
while ($row = $result->fetch_assoc()) { $users[] = $row; }
fetch_assoc по мне так уже лишнее, query возвращает mysqli_result типа IteratorAggregate в 8.0+ или Traversable до 8.0, поэтому можно просто
foreach ($result as $row) { $users[] = $row; }
FanatPHP Автор
23.06.2022 10:24Кстати, отличное замечание! Я почему-то думал, что там возвращается MYSQLI_BOTH, как в fetch_array() по умолчанию — но нет, там жёстко зашито MYSQLI_ASSOC. Полезная информация!
Хотя с другой стороны, я только что себя поймал на том, что привыкая в последнее время всё писать явно, без магии, я предпочту написать "лишний" вызов fetch_assoc(), просто для ясности. Но это уже конечно отдельная тема :)
NickyX3
23.06.2022 10:41Оба варианта имеют место быть. Мне кажется, что введение Traversable, а потом и IteratorAggregate это как раз путь к получению ожидаемого поведения вмысле типизации.
У нас щас минимальная версия в проектах это 7.4, а где то уже и 8.1. На fetch_assoc я "забил" и сокращаю до foreach. У - Удобно!
Nnnnoooo
23.06.2022 14:27Мне вот интересно, а как много сейчас в проде напрямую юзают мускуль функции?
Ведь все равно даже при ручных запросах к базе все равно получается много повторяющегося кода. prepare, bind, query, fetch. В 8-ке в некоторых случаях можно от fetch избавиться, но все равно кода дофига. А это значит будет юзаться или стороняя или собственно написанная либа/хелпер в качестве прокладки. И тогда какая разница нужен fetch_assoc в результате или не нужен. Все равно это внутреннее апи хелпера.FanatPHP Автор
23.06.2022 14:57Довольно часто. Во-первых, индусы, у них копипаста — это основной рабочий инструмент. Во-вторых, даже в проектах, интенсивно использующих ORM, сплошь и рядом из него достают объект соединения с БД (
DB::connection()->getPdo()
), и выполняют запрос напрямую, когда запрос слишком сложный или по каким-то другим причинам нежелательно использовать средства ORM. Но это практически всегда будет PDO. ORM-ов на основе mysqli я не видел.Nnnnoooo
23.06.2022 15:17Ну даже когда речь идет об индусском коде и если мне надо будет написать больше 10 запросов в коде, я напишу свой хелпер (если конечно его надо будет писать, а не использовать что-то готовое из симфони или что-то другое живое/популярное)
Даже когда речь об орме, почти всегда он работает поверх какой либо другой абстракции (по крайней мере абсолютно все популярные реализации), где не надо делать prepare, bind, query, fetch, а есть один метод query (или несколько различных методов для разных типов запросов) сразу с биндингом и фетчингом. Вызов одного метода это явно проще чем вызов 4 методов. И это стало стандартом де факто для любого более менее адекватного орма уже давно (еще с первых версий зенда)
SerafimArts
25.06.2022 05:09ORM-ов на основе mysqli я не видел.
Как минимум Doctrine и Eloquent. У них под капотом doctrine/dbal, которому вообще пофигу какой драйвер стоит. Да хоть OCI или ещё что.
Nnnnoooo
Очень полезная статья! Года так для 2000-го…
Просто машина времени какая-то! :)
FanatPHP Ну это же перебор, ты же сам на клубе никому не советуешь юзать напрямую мускуль методы.
FanatPHP Автор
Очень правильный комментарий. Я и сам еле домучал этот перевод, разрываясь между тем что с одной стороны, он давно опоздал, а с другой — в том-то и проблема, что нормального мануала так до сих пор нет, все говнякают по-старинке, по-быстрому переписав код с mysql_query(). В 2022.
И в этом смысле лучше позже, чем никогда.
Я бы сказал так — в ленте этот материал лишний, но на Хабре он должен быть. Из ленты он быстро уедет, а на сайте останется, будет куда гуглу нубов привести.
Ну и надо сказать что даже в 2010-м половины этого туториала бы не было. Про mysqli_report() я узнал в 2013, распаковка аргументов появилась в 2014, а без неё функция prepared_query получилась бы на два экрана отборного говнокода. mysqlnd стала стандартом для сборок РНР буквально пару лет назад, а без неё нет get_result(), а это значит снова пара экранов говнокода, чтобы получить несчастный результат fetch_assoc(). Передача параметров в execute(), как в PDO, и вовсе появилась только в этом году.
Так что если говорить о юзабельной библиотеке mysqli, то в общем примерно как раз.
По поводу методов напрямую. Если говорить о работе, то разумеется, никто и не использует. Но речь-то про обучение. Когда человек учится, то это как раз нормально — применять методы напрямую, чтобы потом, используя ORM, понимать как оно работает под капотом. А то сейчас развелось "специалистов" по Ларавели, которые за всю свою успешную карьеру SQL запрос в глаза не видели.
Nnnnoooo
Ну как бы mysqlnd стандарт де факто уже давно еще с пхп 5.5 если не раньше. Но не будем здесь срач разводить, хабр не клуб и срачи тут не любят :)
FanatPHP Автор
Никак нет, стандартом де-факто mysqlnd стал только недавно. До начала 2020-х топики "Call to undefined function mysqli_get_result()" появлялись очень часто. Только с появлением семёрки mysqlnd стал доминировать в сборках, а вот в восьмёрке уже практически вытеснил libmysql совсем.