MySqli или PDO?

mysql-icon[1]Последние годы я писал сайты исключительно на фреймворках, что избавляло меня от работы с БД напрямую. Некоторое время назад начал работу над сайтом на чистом php и задался вопросом, что использовать вместо устаревшего и нерекомендованного к использованию старого расширения PHP MySQL.

Выбирать нужно было между MySqli и PDO. После не очень длительного изучения решил остановиться на MySqli, так как, как мне тогда казалось, он полностью идентичен PDO, за исключением того, что нет возможности отказаться от MySQL в пользу чего-то другого. Как я напишу ниже это не совсем так, минимум одно заметное отличие есть. 

MySqli рекомендован к использованию самими разработчиками PHP.

ООП и процедурный


MySqli позволяет писать как в ООП стиле так и в процедурном. Мне ближе ООП как и большинству из хабр сообщества, поэтому в этом статье будет использован именно он.

Три основные класса


MySqli имеет 3 основные класса, которые будут подробно рассмотрены в этой статье

  1. mysqli — необходим для установки соединения с БД и будет полезен, если мы хотим выполнить так, как мы это делали в старом расширении MySQL;
  2. mysqli_stmt — необходим для использования новой возможности MySqli: выполнять запросы по подготовленным выражениям;
  3. mysqli_result — объединяет функции для получения результатов запросов, сделанных с помощью mysqli или mysqli_stmt.


Рассмотрим каждый из них подробнее ниже.

Соединение с БД


Есть два способа.

Способ первый. Если вам нужно просто создать соединение.

$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'my_db');
if ($mysqli->connect_error) {
die('Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
}



Способ второй. Если вам нужно использовать опции соединения.

$mysqli = mysqli_init();
if (!$mysqli) {
die('mysqli_init failed');
}

if (!$mysqli->options(MYSQLI_INIT_COMMAND, 'SET AUTOCOMMIT = 0')) {
die('Setting MYSQLI_INIT_COMMAND failed');
}

if (!$mysqli->real_connect('localhost', 'my_user', 'my_password', 'my_db')) {
die('Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
}



С помощью $mysqli->connect_errno и $mysqli->connect_error мы получаем описание и код ошибки, возникших при соединении. И new mysqli () и $mysqli->real_connect () при ошибках соединений вызывают ошибку PHP Warning. Поэтому вывод ошибок с помощью выше упомянутых функций имеет смысл, если у вас отключено отображение ошибок PHP, например, на рабочем сервере, либо если вам нужно как-то обработать эти данные. Я упомнил здесь об этом, потому что не все функции MySQLi вызывают PHP Warning в случае ошибки, и для того что бы узнать, что произошла ошибка необходимо обязательно обращаться к специальным функциям, об этом ниже.

Полученный при соединении объект мы присвоили переменной $mysqli, для того чтобы использовать его в дальнейшем. Это очевидно для ООП стиля, но и для процедурного стиля этот объект также необходим, в этом отличие от устаревшего расширения MySQL, где ссылку на соединение необязательно было передавать при каждом использовании mysql функций.

Буферизированные и не буферизированные результаты


Прежде чем рассказывать дальше, хотелось бы объяснить разницу между этими двумя типами результатов. 

Рассмотрим небуферизированный результат. В этом случае вы можете начинать читать результаты, не дожидаясь пока mysql сервер получит результат полностью. 

Преимущества:

  • Результат можно начинать читать раньше, сокращается время ожидания;
  • Результат не занимает место в оперативной памяти.


Недостатки:

  • Невозможно узнать, сколько строк получено;
  • Невозможно передвигаться к определенному результату, то есть можно читать данные только с начала и по порядку;
  • Нельзя выполнять других запросов, пока не закрыт этот результат.



Буферизированный результат лишен этих недостатков и соответственно лишен перечисленных преимуществ.

«Классические» запросы


В MySqli оставили возможность «классических» запросов: когда пользователю предлагается самостоятельно заниматься безопасностью передаваемых запросов так, как это было в устаревшем расширении MySQL. Для этого предлагается использовать функцию $mysqli->real_escape_string (), с помощью которой необходимо обрабатывать все данные перед помещением их в запрос.

Так же как и с соединением есть два способа сделать такой запрос короткий и длинный.

Короткий.

$result = $mysqli->query( запроса’, MYSQLI_USE_RESULT);


Возможные константы:
MYSQLI_STORE_RESULT – вернет буферизированный результат, значение по умолчанию
MYSQLI_USE_RESULT – небуферизированный

Длинный.

$mysqli->real_query('текст запроса');
echo($mysqli->field_count);  // вернет количество столбцов, в результате,
// можно получить до начала получения результата, что дает дополнительную гибкость
// по сравнению c коротким способом, разумеется, вызывать не обязательно
$result = $mysqli->use_result();  // вернет небуферизированный результат
// или
$result = $mysqli->store_result();  // вернет буферизированный результат



Функции $mysqli->use_result () или $mysqli->store_result () так же используются при мульти запросах (запросах состоящих из нескольких запросов). Мульти запросы в этой статье рассмотрены не будут.

И тот и другой синтаксисы вернут результат в виде объекта mysqli_result, который представляет собой удобный интерфейс для работы с результатом, как с буферизированным так и не с небуферизированным.

Как я писал выше, не все функции MySQLi выбрасывают ошибки PHP, описанные выше функции из их числа. В случае если запрос неверный и сервер вернул ошибку, PHP не даст об этом знать. Для проверки используйте функции:

  • $mysqli->error – описание ошибки
  • $mysqli->errno – код ошибки

 

$city = $mysqli->real_escape_string($city);
$mysqli->query(«SELECT * FROM `city` WHERE `id` = '$city);
if ($mysqli->errno) {
die('Select Error (' . $mysqli->errno . ') ' . $mysqli->error);
}



Преимущества «классического» синтаксиса запросов:

  1. Он значительно более компактен, чем подготовленные выражения (класс mysqli_stmt);
  2. Он позволяет получить небуферизированный результат в виде mysqli_result. Подготовленные выражения позволяют получить только буферизированный результат mysqli_result, а с небуферизированным придется работать средствамиmysqli_stmt, который значительно менее гибкий, чем mysqli_result.



Практическое применение «классического» синтаксиса запросов я вижу:

  1. В не буферизированных запросах, если вам хочется использовать преимущества работы с mysqli_result;
  2. В запросах INSERT, UPDATE, REPLACE или DELETE, если для вас предпочтителен их более короткий синтаксис.


Для таких запросов будут полезны свойства:

  • $mysqli->affected_rows – количество затронутых строк предыдущим запросом не на выборку
  • $mysqli->insert_id – автоматически сгенерированный ID для последнего запроса вставки.

 

Подготовленные запросы


Преимущества подготовленных запросов над «классическими»:

  • При серии запросов, в которых меняются лишь данные, запрос передается на сервер только один раз, в дальнейшем посылаются только изменяющиеся данные;
  • от -инъекций.


За работу с подготовленными запросами в MySQLi отвечает класс mysqli_stmt.

Два способа создания подготовленного запроса.

// первый способ - используя объект mysqli
$mysqli->prepare(«SELECT * FROM `sk2_articles` WHERE `id` = ?»);
if ($mysqli->errno) {
die('Select Error (' . $mysqli->errno . ') ' . $mysqli->error);
}

// второй способ - используя объект mysqli_stmt
$stmt = $mysqli->stmt_init();
$stmt->prepare(«SELECT * FROM `sk2_articles` WHERE `id` = ?»);
if ($stmt->errno) {
die('Select Error (' . $stmt->errno . ') ' . $stmt->error);
}



Различия в том, для какого объекта вызываются функции получения информации об ошибке. Мне второй способ кажется удобнее, потому что проверки на ошибки можно объединить в один блок if c другими функциями mysqli_stmt. Как это сделать будет видно в примерах ниже.

$id_min = 81;
$id_max = 88;
$stmt = $mysqli->stmt_init();
if(
// подготовливаем запрос, там куда будут вствлятся данные отмечаем символом ? (плейсхолдоры)
($stmt->prepare(«SELECT title FROM sk2_articles WHERE id > ? and id < ?») ===FALSE)
// привязываем переменные к плейсхолдорам
or ($stmt->bind_param('ii', $id_min, $id_max) === FALSE)
// отрправляем даные, которые на данный момент находятся в привязанных переменных
or ($stmt->execute() === FALSE)
// привязывем переменую для получения в нее результата
or ($stmt->bind_result($title) === FALSE)
// делаем запрос буферизированным, 
// если бы этой строки не было, запрос был бы небуферезированым
or ($stmt->store_result() === FALSE)
// получение результата в привязанную переменную
or ($stmt->fetch() === FALSE)
// закрываем подготовленный запрос
or ($stmt->close() === FALSE)
) {
die('Select Error (' . $stmt->errno . ') ' . $stmt->error);
}
echo $title;



Несколько пояснений к выше написанному коду.

  1. В $stmt->bind_param () первым параметром указываем тип привязываемых данных (i – integer, d — double, s – строка). Вторым и последующим указываются переменные, которые будет привязаны к соответствующим плейсхолдорам, объявленным в $stmt->prepare () символами «?» в том порядке, в каком они указаны в $stmt->prepare (). Это заметное отличие от PDO, где плесхолдоры могут быть именованными, а значит не зависят от порядка объявления.
  2. $stmt->bind_result по-моему самое слабое место класса mysqli_stmt, так как оно обязывает знать, какие именно данные будут получены из запроса и каком порядке они объявлены в запросе. Для случая “SELECT * FROM …” он вряд ли применим. Гораздо более гибкий подход в работе с результатом предлагает класс mysqli_result. Чтобы быть окончательно объективным, стоит упомянуть, что можно воспользоваться функцией $stmt->result_metadata () получить mysqli_resultсодержащий только о метаданные без самих данных, и посмотреть что за столбцы получены, но этот подход мне кажется излишне сложным, и о нем я упомянул только, чтобы избежать обвинений в неточной информации.
  3. $stmt->store_result (), как я написал в комментарии к строке, необязательный и нужен для того, чтобы можно было использовать такие функции как:
    • $mysqli_stmt->num_rows – количество полученных строк к результате,
    • $stmt->data_seek – перемещение внутреннего указателя на заданную строку результата,
    • $stmt->free_result () – и не забудьте вызвать освобождение памяти если вы использовали $stmt->store_result ().
  4. $stmt->fetch () возвращает TRUE/FALSE если данные были получены удачно/неудачно, и NULL если данных больше нет.
  5. Не все функции, объединенные в блок if, в случае ошибки помещают ее в $stmt->error, некоторые вызывают PHP Warning, но все из них в случае неудачи возвращают FALSE, поэтому мне кажется эта конструкция удачной. Если вам нужно выполнить несколько раз $stmt->execute () или $stmt->fetch (), то так сделать не получится.
  6. Не зависимо от того, был ли запрос буферизированный или нет, доступны следующие свойства:
    • $stmt->field_count — количество столбцов в результате,
    • $stmt->affected_rows – количество затронутых строк предыдущим запросом не на выборку,
    • $stmt->insert_id – автоматически сгенерированный id предыдущей вставки.


Изменим код так, чтобы получить результат в виде экземпляра объекта mysqli_result.

$id_min = 81;
$id_max = 88;
$stmt = $mysqli->stmt_init();
if(
($stmt->prepare(«SELECT title FROM sx2_links WHERE id > ? and id < ?») === FALSE)
or ($stmt->bind_param('ii', $id_min, $id_max) === FALSE)
or ($stmt->execute() === FALSE)
// получение буферизированного результата в виде mysqli_result,
// небуферизированный результат получить нельзя, о чем я писал в недостатках
or (($result = $stmt->get_result()) === FALSE)
or ($stmt->close() === FALSE)
) {
die('Select Error (' . $stmt->errno . ') ' . $stmt->error);
}
$row = $result->fetch_row();
echo $row[0];



Класс mysqli_result и работа с результатом с помощью него


Как было показано выше, объект mysqli_result вы могли получить как с помощью «классического» запроса с помощью классаmysqli, тогда он может быть как буферизированный так и небуферизированный, так и с помощью класса mysqli_stmt, тогда он буферизированный. От того какой результат вы получили, зависит работа функций этого класса, поэтому нужно хорошо понимать, что если ваш запрос небуферизированный вы не располагаете всем результатом и соответственно не можете знать сколько строк в результате, и читать его можно только по-порядку строка за строкой. 

Набор функций в этом классе покажется знакомым по-старому расширения:

  • $result->fetch_row () – получает текущий ряд результата в виде нумерованного массива,
  • $result->fetch_assoc () – в виде ассоциативного массива,
  • $result->fetch_ () – тип массива задается константой,
    1. MYSQLI_ASSOC – ассоциативный,
    2. MYSQLI_NUM – нумерованный,
    3. MYSQLI_BOTH – оба,
  • $result->fetch_object () – строка результата в виде объекта.


Про $result->fetch_object () хотелось бы поговорить отдельно. У этой функции есть два параметра, оба необязательные:

  • class_name – имя класса, на основе которого будет создан объект,
  • params – массив параметров, которые будут переданы конструктору при создании объекта.


Как видите, передать конструктору класса предположим ассоциативный массив одной строки результата с помощью этой функции не получится. Она сама за вас присвоит свойствам класса, совпадающие с названиями полей результаты. Если свойства класса не будет найдено, оно создается динамически, с областью видимости public. 

class Book
{
private $some1;
public $some2;
protected $id;

function __construct($param1, $param2) {
$this->some1 = $param1;
$this->some2 = $param2;
}
}
$book = $result->fetch_object('Book', array(1, 2));
var_dump( $book);


Выведет, если в результате было только одно поле id

object(Book)[4]
private 'some1'  => int 1
public 'some2'  => int 2
protected 'id'  => int 382


Другие полезные функции и свойства рассматриваемого класса:

  • $result->fetch_all () — получение всех рядов в виде массива нумерованного / ассоциативного / обоих типов ,
  • $result->data_seek () — установка внутреннего указателя на заданный ряд результата,
  • $result->num_rows – количество строк в результате.

Оригинал: http://habrahabr.ru/post/141127/

Запись навигация

Top