среда, 5 июня 2019 г.

Регулярные выражения для самых маленьких

habr.com

Регулярные выражения для самых маленьких


Привет, Хабр.
Меня зовут Виталий Котов и я немного знаю о регулярных выражениях. Под катом я расскажу основы работы с ними. На эту тему написано много теоретических статей. В этой статье я решил сделать упор на количество примеров. Мне кажется, что это лучший способ показать возможности этого инструмента.
Некоторые из них для наглядности будут показаны на примере языков программирования PHP или JavaScript, но в целом они работают независимо от ЯП.
Из названия понятно, что статья ориентирована на самый начальный уровень — тех, кто еще ни разу не использовал регулярные выражения в своих программах или делал это без должного понимания.
В конце статьи я в двух словах расскажу, какие задачи нельзя решить регулярными выражениями и какие инструменты для этого стоит использовать.
Поехали!


Вступление

Регулярные выражения — язык поиска подстроки или подстрок в тексте. Для поиска используется паттерн (шаблон, маска), состоящий из символов и метасимволов (символы, которые обозначают не сами себя, а набор символов).
Это довольно мощный инструмент, который может пригодиться во многих случая — поиск, проверка на корректность строки и т.д. Спектр его возможностей трудно уместить в одну статью.
В PHP работа с регулярными выражениями заключается в наборе функций, из которых я чаще всего использую следующие:
Для работы с ними нужен текст, в котором мы будем искать или заменять подстроки, а также само регулярное выражение, описывающее правило поиска.
Функции на match возвращают число найденных подстрок или false в случае ошибок. Функция на replace возвращает измененную строку/массив или null в случае ошибки. Результат можно привести к bool (false, если не было найдено значений и true, если было) и использовать вместе с if или assertTrue для обработки результата работы.
В JS чаще всего мне приходится использовать:
Все дальнейшие примеры предлагаю смотреть в https://regex101.com/. Это удобный и наглядный интерфейс для работы с регулярными выражениями.

Пример использования функций

В PHP регулярное выражение — это строка, которая начинается и заканчивается символом-разделителем. Все, что находится между разделителями и есть регулярное выражение.
Часто используемыми разделителями являются косые черты “/”, знаки решетки “#” и тильды “~”. Ниже представлены примеры шаблонов с корректными разделителями:
  • /foo bar/
  • #^[^0-9]$#
  • %[a-zA-Z0-9_-]%
Если необходимо использовать разделитель внутри шаблона, его нужно проэкранировать с помощью обратной косой черты. Если разделитель часто используется в шаблоне, в целях удобочитаемости, лучше выбрать другой разделитель для этого шаблона.
  • /http:\/\//
  • #http://#
В JavaScript регулярные выражения реализованы отдельным объектом RegExp и интегрированы в методы строк.
Создать регулярное выражение можно так:
let regexp = new RegExp("шаблон", "флаги");
Или более короткий вариант:
let regexp = /шаблон/; 

let regexp = /шаблон/gmi; 
Пример самого простого регулярного выражения для поиска:
RegExp: /o/
Text: hello world
В этом примере мы просто ищем все символы “o”.
В PHP разница между preg_match и preg_match_all в том, что первая функция найдет только первый match и закончит поиск, в то время как вторая функция вернет все вхождения.
Пример кода на PHP:
<?php
$text = ‘hello world’;
$regexp = ‘/o/’;
$result = preg_match($regexp, $text, $match);
var_dump(
    $result,
    $match
);

int(1)  
array(1) {
  [0]=>
  string(1) "o" 
}
Пробуем то же самое для второй функции:
<?php
$text = ‘hello world’;
$regexp = ‘/o/’;
$result = preg_match_all($regexp, $text, $match);
var_dump(
    $result,
    $match
);

int(2)
array(1) {
  [0]=>
  array(2) {
    [0]=>
    string(1) "o"
    [1]=>
    string(1) "o"
  }
}
В последнем случае функция вернула все вхождения, которые есть в нашем тексте.
Тот же пример на JavaScript:
let str = 'Hello world';
let result = str.match(/o/);
console.log(result);

["o", index: 4, input: "Hello world"]

Модификаторы шаблонов

Для регулярных выражений существует набор модификаторов, которые меняют работу поиска. Они обозначаются одиночной буквой латинского алфавита и ставятся в конце регулярного выражения, после закрывающего “/”.
  • i — символы в шаблоне соответствуют символам как верхнего, так и нижнего регистра.
  • m — по умолчанию текст обрабатывается, как однострочная символьная строка. Метасимвол начала строки '^' соответствует только началу обрабатываемого текста, в то время как метасимвол конца строки '$' соответствует концу текста. Если этот модификатор используется, метасимволы «начало строки» и «конец строки» также соответствуют позициям перед произвольным символом перевода и строки и, соответственно, после, как и в самом начале, и в самом конце строки.
Об остальных модификаторах, используемых в PHP, можно почитать тут.
В JavaScript — тут.
О том, какие вообще бывают модификаторы, можно почитать тут.
Пример предыдущего регулярного выражения с модификатором на JavaScript:
let str = "hello world \
              How is it going?"
let result = str.match(/o/g);
console.log(result);

["o", "o", "o", "o"]

Метасимволы в регулярных выражениях

Примеры по началу будут довольно примитивные, потому что мы знакомимся с самыми основами. Чем больше мы узнаем, тем ближе к реалиям будут примеры.
Чаще всего мы заранее не знаем, какой текст нам придется парсить. Заранее известен только примерный набор правил. Будь то пинкод в смс, email в письме и т.п.
Первый пример, нам надо получить все числа из текста:
Текст: “Привет, твой номер 1528. Запомни его.”
Чтобы выбрать любое число, надо собрать все числа, указав “[0123456789]”. Более коротко можно задать вот так: “[0-9]”. Для всех цифр существует метасимвол “\d”. Он работает идентично.
Но если мы укажем регулярное выражение “/\d/”, то нам вернётся только первая цифра. Мы, конечно, можем использовать модификатор “g”, но в таком случае каждая цифра вернется отдельным элементом массива, поскольку будет считаться новым вхождением.
Для того, чтобы вывести подстроку единым вхождением, существуют символы плюс “+” и звездочка “*”. Первый указывает, что нам подойдет подстрока, где есть как минимум один подходящий под набор символ. Второй — что данный набор символов может быть, а может и не быть, и это нормально. Помимо этого мы можем указать точное значение подходящих символов вот так: “{N}”, где N — нужное количество. Или задать “от” и “до”, указав вот так: “{N, M}”.
Сейчас будет пара примеров, чтобы это уложилось в голове:
Текст: “Я хочу ходить на работу 2 раза в неделю.”
Надо получить цифру из тексте.
RegExp: “/\d/”

Текст: “Ваш пинкод: 24356” или “У вас нет пинкода.”
Надо получить пинкод или ничего, если его нет.
RegExp: “/\d*/”

Текст: “Номер телефона 89091534357”
Надо получить первые 11 символов, или FALSE, если их меньше.
RegExp: “/\d{11}/”
Примерно так же мы работает с буквами, не забывая, что у них бывает регистр. Вот так можно задавать буквы:
  • [a-z]
  • [a-zA-Z]
  • [а-яА-Я]
C кириллицей указанный диапазон работает по-разному для разных кодировок. В юникоде, например, в этот диапазон не входит буква “ё”. Подробнее об этом тут.
Пара примеров:
Текст: “Вот бежит олень” или “Вот ваш индюк”
Надо выбрать либо слово “олень”, либо слово “индюк”.
RegExp: “/[а-яА-Я]+/”
Такое выражение выберет все слова, которые есть в предложении и написаны кириллицей. Нам нужно третье слово.
Помимо букв и цифр у нас могут быть еще важные символы, такие как:
  • \s — пробел
  • ^ — начало строки
  • $ — конец строки
  • | — “или”
Предыдущий пример стал проще:
Текст: “Вот бежит олень” или “Вот бежит индюк”
Надо выбрать либо “олень”, либо “индюк”.
RegExp: “/[а-яА-Я]+$/”
Если мы точно знаем, что искомое слово последнее, мы ставим “$” и результатом работы будет только тот набор символов, после которого идет конец строки.
То же самое с началом строки:
Текст: “Олень вкусный” или “Индюк вкусный”
Надо выбрать либо “олень”, либо “индюк”.
RegExp:  “/^[а-яА-Я]+/”
Прежде, чем знакомиться с метасимволами дальше, надо отдельно обсудить символ “^”, потому что он у нас ходит на две работы сразу (это чтобы было интереснее). В некоторых случаях он обозначает начало строки, но в некоторых — отрицание.
Это нужно для тех случаев, когда проще указать символы, которые нас не устраивают, чем те, которые устраивают.
Допустим, мы собрали набор символов, которые нам подходят: “[a-z0-9]” (нас устроит любая маленькая латинская буква или цифра). А теперь предположим, что нас устроит любой символ, кроме этого. Это будет обозначаться вот так: “[^a-z0-9]”.
Пример:
Текст: “Я люблю кушать суп”
Надо выбрать все слова.
RegExp: “[^\s]+”
Выбираем все “не пробелы”.
Итак, вот список основных метасимволов:
  • \d — соответствует любой цифре; эквивалент [0-9]
  • \D — соответствует любому не числовому символу; эквивалент [^0-9]
  • \s — соответствует любому символу whitespace; эквивалент [ \t\n\r\f\v]
  • \S — соответствует любому не-whitespace символу; эквивалент [^ \t\n\r\f\v]
  • \w — соответствует любой букве или цифре; эквивалент [a-zA-Z0-9_]
  • \W — наоборот; эквивалент [^a-zA-Z0-9_]
  • . — (просто точка) любой символ, кроме перевода “каретки”

Операторы [] и ()

По описанному выше можно было догадаться, что [] используется для группировки нескольких символов вместе. Так мы говорим, что нас устроит любой символ из набора.
Пример:
Текст: “Не могу перевести I dont know, помогите!”
Надо получить весь английский текст.
RegExp: “/[A-Za-z\s]{2,}/”
Тут мы собрали в группу (между символами []) все латинские буквы и пробел. При помощи {} указали, что нас интересуют вхождения, где минимум 2 символа, чтобы исключить вхождения из пустых пробелов.
Аналогично мы могли бы получить все русские слова, сделав инверсию: “[^A-Za-z\s]{2,}”.
В отличие от [], символы () собирают отмеченные выражения. Их иногда называют “захватом”.
Они нужны для того, чтобы передать выбранный кусок (который, возможно, состоит из нескольких вхождений [] в результат выдачи).
Пример:
Текст: ‘Email you sent was ololo@example.com Is it correct?’
Нам надо выбрать email.
Существует много решений. Пример ниже — это приближенный вариант, который просто покажет возможности регулярных выражений. На самом деле есть RFC, который определяет правильность email. И есть “регулярки” по RFC — вот примеры.
Мы выбираем все, что не пробел (потому что первая часть email может содержать любой набор символов), далее должен идти символ @, далее что угодно, кроме точки и пробела, далее точка, далее любой символ латиницы в нижнем регистре…
Итак, поехали:
  • мы выбираем все, что не пробел: “[^\s]+”
  • мы выбираем знак @: “@”
  • мы выбираем что угодно, кроме точки и пробела: “[^\s\.]+”
  • мы выбираем точку: “\.” (обратный слеш нужен для экранирования метасимвола, так как знак точки описывает любой символ — см. выше)
  • мы выбираем любой символ латиницы в нижнем регистре: “[a-z]+”
Оказалось не так сложно. Теперь у нас есть email, собранный по частям. Рассмотрим на примере результата работы preg_match в PHP:
<?php
$text = ‘Email you sent was ololo@example.com. Is it correct?’;
$regexp = ‘/[^\s]+@[^\s\.]+\.[a-z]+/’;
$result = preg_match_all($regexp, $text, $match);
var_dump(
    $result,
    $match
);

int(1)
array(1) {
  [0]=>
  array(1) {
    [0]=>
    string(13) "ololo@example.com"
  }
}
Получилось! Но что, если теперь нам надо по отдельности получить домен и имя по email? И как-то использовать дальше в коде? Вот тут нам поможет “захват”. Мы просто выбираем, что нам нужно, и оборачиваем знаками (), как в примере:
Было:
/[^\s]+@[^\s\.]+\.[a-z]+/
Стало:
/([^\s]+)@([^\s\.]+\.[a-z]+)/
Пробуем:
<?php
$text = ‘Email you sent was ololo@example.com. Is it correct?’;
$regexp = ‘/([^\s]+)@([^\s\.]+\.[a-z]+)/’;
$result = preg_match_all($regexp, $text, $match);
var_dump(
    $result,
    $match
);

int(1)
array(3) {
  [0]=>
  array(1) {
    [0]=>
    string(13) "ololo@example.com"
  }
  [1]=>
  array(1) {
    [0]=>
    string(5) "ololo"
  }
  [2]=>
  array(1) {
    [0]=>
    string(7) "example.com"
  }
}
В массиве match нулевым элементом всегда идет полное вхождение регулярного выражения. А дальше по очереди идут “захваты”.
В PHP можно именовать “захваты”, используя следующий синтаксис:
/(?<mail>[^\s]+)@(?<domain>[^\s\.]+\.[a-z]+)/
Тогда массив матча станет ассоциативным:
<?php
$text = ‘Email you sent was ololo@example.com. Is it correct?’;
$regexp = ‘/(?<mail>[^\s]+)@(?<domain>[^\s\.]+\.[a-z]+)/’;
$result = preg_match_all($regexp, $text, $match);
var_dump(
    $result,
    $match
);

int(1)
array(5) {
  [0]=>
  array(1) {
    [0]=>
    string(13) "ololo@example.com"
  }
  ["mail"]=>
  array(1) {
    [0]=>
    string(5) "ololo"
  }
  ["domain"]=>
  array(1) {
    [0]=>
    string(7) "example.com"
  }
}
Это сразу +100 к читаемости и кода, и регулярки.

Примеры из реальной жизни


Парсим письмо в поисках нового пароля:

Есть письмо с HTML-кодом, надо выдернуть из него новый пароль. Текст может быть либо на английском, либо на русском:
Текст: “пароль: <b>f23f43tgt4</b>” или “password: <b>wh4k38f4</b>”
RegExp: “(password|пароль):\s<b>([^<]+)<\/b>”
Сначала мы говорим, что текст перед паролем может быть двух вариантов, использовав “или”.
Вариантов можно перечислять сколько угодно:
(password|пароль)
Далее у нас знак двоеточия и один пробел:
:\s
Далее знак тега b:
<b>
А дальше нас интересует все, что не символ “<”, поскольку он будет свидетельствовать о том, что тег b закрывается:
([^<]+)
Мы оборачиваем его в захват, потому что именно он нам и нужен.
Далее мы пишем закрывающий тег b, проэкранировав символ “/”, так как это спецсимвол:
<\/b>
Все довольно просто.

Парсим URL:

В PHP есть клевая функция, которая помогает работать с урлом, разбирая его на составные части:
<?php
$URL = "https://hello.world.ru/uri/starts/here?get_params=here#anchor";
$parsed = parse_url($URL);
var_dump($parsed);

array(5) {
  ["scheme"]=>
  string(5) "https"
  ["host"]=>
  string(14) "hello.world.ru"
  ["path"]=>
  string(16) "/uri/starts/here"
  ["query"]=>
  string(15) "get_params=here"
  ["fragment"]=>
  string(6) "anchor"
}
Давай сделаем то же самое, только регуляркой? :)
Любой урл начинается со схемы. Для нас это протокол http/https. Можно было бы сделать логическое “или”:
(http|https)
Но можно схитрить и сделать вот так:
http[s]?
В данном случае символ “?” означает, что “s” может есть, может нет…
Далее у нас идет “://”, но символ “/” нам придется экранировать (см. выше):
“:\/\/”
Далее у нас до знака “/” или до конца строки идет домен. Он может состоять из цифр, букв, знака подчеркивания, тире и точки:
[\w\.-]+
Тут мы собрали в единую группу метасимвол “\w”, точку ”\.” и тире ”-”.
Далее идет URI. Тут все просто, мы берем все до вопросительного знака или конца строки:
[^?$]+
Теперь знак вопроса, который может быть, а может не быть:
[?]?
Далее все до конца строки или начала якоря (символ #) — не забываем о том, что этой части тоже может не быть:
[^
Далее может быть #, а может не быть:
[
Дальше все до конца строки, если есть:
[^$]+
Вся красота в итоге выглядит так (к сожалению, я не придумал, как вставить эту часть так, чтобы Habr не считал часть строки — комментарием):
/(?<scheme>http[s]?):\/\/(?<domain>[\w\.-]+)(?<path>[^?$]+)?(?<query>[^
Главное не моргать! :)
<?php
$URL = "https://hello.world.ru/uri/starts/here?get_params=here#anchor";
$regexp = “/(?<scheme>http[s]?):\/\/(?<domain>[\w\.-]+)(?<path>[^?$]+)?(?<query>[^
$result = preg_match($regexp, $URL, $match);
var_dump(
    $result,
    $match
);

array(11) {
  [0]=>
  string(61) "https://hello.world.ru/uri/starts/here?get_params=here#anchor"
  ["scheme"]=>
  string(5) "https"
  ["domain"]=>
  string(14) "hello.world.ru"
  ["URI"]=>
  string(16) "/uri/starts/here"
  ["params"]=>
  string(15) "get_params=here"
  ["anchor"]=>
  string(6) "anchor"
}
Получилось примерно то же самое, только своими руками.

Какие задачи не решаются регулярными выражениями

На первый взгляд кажется, что регулярными выражениями можно описать и распарсить любой текст. Но, к сожалению, это не так.
Регулярные выражении — это подвид формальных языков, который в иерархии Хомского принадлежат 3-ому типу, самому простому. Об этом тут.
При помощи этого языка мы не можем, например, парсить синтаксис языков программирования с вложенной грамматикой. Или HTML код.

Примеры задач:

У нас есть span, внутри которых много других span и мы не знаем сколько. Надо выбрать все, что находится внутри этого span:
<span>
    <span>ololo1</span>
    <span>ololo2</span>
    <span>ololo3</span>
    <span>ololo4</span>
    <span>ololo5</span>
    <...>
</span>
Само собой, если мы парсим HTML, где есть не только этот span. :)
Суть в том, что мы не можем начать с какого-то момента “считать” символы span и /span, подразумевая, что открывающих и закрывающих символов должно быть равное количество. И “понять”, что закрывающий символ, для которого ранее не было пары — тот самый закрывающий, который обосабливает блок.
То же самое с кодом и символами {}.
Например:
function methodA() {
    function() {<...>}
    if () { if () {<...>} }
}
В такой структуре мы не сможем при помощи только регулярного выражения отличить закрывающую фигурную скобку внутри кода от той, которая завершает начальную функцию (если код состоит не только из этой функции).
Для решение таких задач используются языки более высокого уровня.

Заключение

Я постарался довольно подробно рассказать об азах мира регулярных выражений. Конечно невозможно в одну статью уместить все. Дальнейшая работа с ними — вопрос опыта и умения гуглить.
Спасибо за внимание.

понедельник, 3 июня 2019 г.

PHP 5 Функции Mysqli

w3bai.com

PHP 5 Функции Mysqli


Функции Mysqli позволяет получить доступ к серверам баз данных MySQL.
Примечание: расширение MySQLi предназначен для работы с MySQL версии 4.1.13 или более поздней версии.
Для функций Mysqli быть доступны, вы должны скомпилировать PHP с поддержкой расширения MySQLi.
Расширение MySQLi была введена с PHP версии 5.0.0. Native Driver MySQL был включен в PHP версии 5.3.0.
функция Описание
mysqli_affected_rows() Возвращает число затронутых строк в предыдущей операции MySQL
mysqli_autocommit() Включение или выключение автоматического фиксирующих изменений базы данных
mysqli_change_user() Изменение пользователя указанного соединения с базой данных
mysqli_character_set_name() Возвращает набор символов по умолчанию для подключения к базе данных
mysqli_close() Закрывает предварительно открытое подключение к базе данных
mysqli_commit() Подтверждает текущую транзакцию
mysqli_connect_errno() Возвращает код ошибки из последней ошибки соединения
mysqli_connect_error() Возвращает описание ошибки из последней ошибки соединения
mysqli_connect() Открывает новое соединение с сервером MySQL
mysqli_data_seek() Настройка указателя результата на произвольную строку в множестве результатов
mysqli_debug() Выполняет операцию отладки
mysqli_dump_debug_info() Сплин отладочной информации в журнале
mysqli_errno() Возвращает последний код ошибки для вызова функции последнего
mysqli_error_list() Возвращает список ошибок для вызова функции последнего
mysqli_error() Возвращает описание последней ошибки для вызова функции последнего
mysqli_fetch_all() Выбирает все строки результата в виде ассоциативного массива, числовой массив, или оба
mysqli_fetch_array() Fetches результирующий ряд как ассоциативный, числовой массив, или оба
mysqli_fetch_assoc() Fetches результирующий ряд как ассоциативный массив
mysqli_fetch_field_direct() Возвращает мета-данные для одного поля в наборе результатов, как объект
mysqli_fetch_field() Возвращает следующее поле в наборе результатов, как объект
mysqli_fetch_fields() Возвращает массив объектов, которые представляют поля в наборе результатов
mysqli_fetch_lengths() Возвращает длину столбцов текущей строки в наборе результатов
mysqli_fetch_object() Возвращает текущую строку результирующего набора, как объект
mysqli_fetch_row() Выбирает одну строки из множества результатов и возвращает его в качестве перечислимого массива
mysqli_field_count() Возвращает число столбцов для самого последнего запроса
mysqli_field_seek() Устанавливает поле курсор в заданном поле смещения
mysqli_field_tell() Возвращает позицию курсора поля
mysqli_free_result() Освобождает память, связанную с результатом
mysqli_get_charset() Возвращает набор символов объект
mysqli_get_client_info() Возвращает версию клиентской библиотеки MySQL
mysqli_get_client_stats() Статистика Возвращает о клиенте в процессе
mysqli_get_client_version() Возвращает клиентской библиотеки версии MySQL как целое
mysqli_get_connection_stats() Статистика Возвращает о связи клиента
mysqli_get_host_info() Возвращает имя хоста сервера MySQL и тип соединения
mysqli_get_proto_info() Возвращает версию протокола MySQL
mysqli_get_server_info() Возвращает версию сервера MySQL
mysqli_get_server_version() Возвращает версию сервера MySQL как целое
mysqli_info() Возвращает информацию о последнем выполненном запросе
mysqli_init() Инициализирует MySQLi и возвращает ресурс для использования с mysqli_real_connect()
mysqli_insert_id() Возвращает автоматически сгенерированный идентификатор, используемый в последнем запросе
mysqli_kill() Спрашивает сервер, чтобы убить поток MySQL
mysqli_more_results() Проверяю, есть больше результатов из нескольких запросов
mysqli_multi_query() Выполняет один или несколько запросов к базе данных
mysqli_next_result() Подготавливает следующий результирующий набор из mysqli_multi_query()
mysqli_num_fields() Возвращает количество полей в наборе результатов
mysqli_num_rows() Возвращает количество строк в наборе результатов
mysqli_options() Устанавливает дополнительные подключения опции и влияют на поведение для связи
mysqli_ping() Pings соединения с сервером, или пытается восстановить, если соединение пошел вниз
mysqli_prepare() Готовит инструкцию SQL для выполнения
mysqli_query() Выполняет запрос к базе данных
mysqli_real_connect() Открывает новое соединение с сервером MySQL
mysqli_real_escape_string() Игнорирует специальные символы в строке для использования в операторе SQL
mysqli_real_query() Выполняет запрос SQL
mysqli_reap_async_query() Возвращает результат запроса асинхронного
mysqli_refresh() Обновляет таблицу или тайники, или сбрасывает информацию о сервере репликации
mysqli_rollback() Откатываю текущую транзакцию для базы данных
mysqli_select_db() Изменение базы данных по умолчанию для подключения
mysqli_set_charset() Устанавливает набор символов клиента по умолчанию
mysqli_set_local_infile_default() Сбросы и определенный пользователь обработчик для локальной команды входной_файла нагрузки
mysqli_set_local_infile_handler() Установить функцию обратного вызова для LOAD DATA LOCAL INFILE команды
mysqli_sqlstate() Возвращает код ошибки SQLSTATE для последней операции MySQL
mysqli_ssl_set() Используется для установления защищенного соединения с помощью SSL
mysqli_stat() Возвращает текущее состояние системы
mysqli_stmt_init() Инициализирует заявление и возвращает объект для использования с mysqli_stmt_prepare()
mysqli_store_result() Передает результирующий набор из последнего запроса
mysqli_thread_id() Возвращает идентификатор потока для текущего соединения
mysqli_thread_safe() Возвращает ли клиентская библиотека скомпилирована как поточно-
mysqli_use_result() Запускает извлечение результирующего набора из последнего запроса выполняется с использованием mysqli_real_query()
mysqli_warning_count() Возвращает количество предупреждений от последнего запроса в связи

Базовые примеры расширения MySQLi

Базовые примеры расширения MySQLi

Этот пример показывает как соединяться, выполнять запрос, использовать базовую обработку ошибок, распечатывать результирующую выборку и отключаться от базы данных MySQL.
В этом примере используется свободно доступная база данных Sakila которая может быть загружена с » dev.mysql.com, как описано здесь. Для того, чтобы этот пример работал (a) установите sakila и (b) измените переменные для соединения с БД (host, your_user, your_pass).
Пример #1 Обзорный пример работы с расширением MySQLi
<?php// Давайте начнем с переменной $_GET в нашем примере.
// Она содержит идентификатор aid для поля actor_id в БД Sakila.
// Пусть ее значение по умолчанию будет 1
// и тип этой переменной будет строго целочисленным во избежание SQL-инъекции
// и/или связанных с этим проблем безопасности. Контроль всего перечисленного выше
 // лежит за пределами этого простого примера.
// Пример:
//   http://example.org/script.php?aid=42
if (isset($_GET['aid']) && is_numeric($_GET['aid'])) {
    
$aid = (int) $_GET['aid'];
} else {
    
$aid 1;
}
// Подключаемся к базе MySQL и выбираем базу под названием sakila
// Хост: 127.0.0.1, имя пользователя: your_user, пароль: your_pass, база: sakila
$mysqli = new mysqli('127.0.0.1''your_user''your_pass''sakila');// О нет!! переменная connect_errno существует, а это значит, что соединение не было успешным!if ($mysqli->connect_errno) {
    
// Соединение не удалось. Что нужно делать в этом случае?
    // Можно отправить письмо администратору, отразить ошибку в журнале,
    // информировать пользователя об ошибке на экране и т.п.
    // Вам не нужно при этом раскрывать конфиденциальную информацию, поэтому
    // просто попробуем так:
    
echo "Извините, возникла проблема на сайте";

    
// На реальном сайте этого делать не следует, но в качестве примера мы покажем
    // как распечатывать информацию о подробностях возникшей ошибки MySQL
    
echo "Ошибка: Не удалась создать соединение с базой MySQL и вот почему: \n";
    echo 
"Номер ошибки: " $mysqli->connect_errno "\n";
    echo 
"Ошибка: " $mysqli->connect_error "\n";
  
    
// Вы можете захотеть показать что-то еще, но мы просто выйдем
    
exit;
}
// Выполняем запрос SQL$sql "SELECT actor_id, first_name, last_name FROM actor WHERE actor_id = $aid";
if (!
$result $mysqli->query($sql)) {
    
// О нет! запрос не удался.
    
echo "Извините, возникла проблема в работе сайта.";

    
// И снова: не делайте этого на реальном сайте, но в этом примере мы покажем,
    // как получить информацию об ошибке:
    
echo "Ошибка: Наш запрос не удался и вот почему: \n";
    echo 
"Запрос: " $sql "\n";
    echo 
"Номер ошибки: " $mysqli->errno "\n";
    echo 
"Ошибка: " $mysqli->error "\n";
    exit;
}
// Уфф, мы это сделали. У нас есть соединение с базой данных и успешный запрос.
// Но где же его результат?
if ($result->num_rows === 0) {
    
// Упс! в запросе нет ни одной строки! Иногда это ожидаемо и нормально, иногда нет.
    // Решать вам. В данном случае, может быть actor_id был слишком большим?
    
echo "Мы не смогли найти совпадение для $aid, простите. Пожалуйста, попробуйте еще раз.";
    exit;
}
// Теперь мы знаем только, что результат выполнения запроса существует, поэтому давайте
// перенесем его в ассоциативный массив, в котором ключами массива будут названия
 // столбцов.
$actor $result->fetch_assoc();
echo 
"Иногда я вижу " $actor['first_name'] . " " $actor['last_name'] . " по телевизору.";// Теперь давайте выгрузим пять случайных актеров и выведем их имена в список.
// Здесь мы добавим меньше степеней обработки ошибок, чтобы вы сделали это самостоятельно
$sql "SELECT actor_id, first_name, last_name FROM actor ORDER BY rand() LIMIT 5";
if (!
$result $mysqli->query($sql)) {
    echo 
"Извините, возникла проблема в работе сайта.";
    exit;
}
// Распечатываем список из пяти случайно выбранных актеров и создаем ссылку на id
// каждого из них
echo "<ul>\n";
while (
$actor $result->fetch_assoc()) {
    echo 
"<li><a href='" $_SERVER['SCRIPT_FILENAME'] . "?aid=" $actor['actor_id'] . "'>\n";
    echo 
$actor['first_name'] . ' ' $actor['last_name'];
    echo 
"</a></li>\n";
}
echo 
"</ul>\n";// Скрипт автоматически закрывает соединение MySQL и освобождает память, тем не
// менее давайте сделаем это вручную
$result->free();$mysqli->close();?>

MySQLi раскладываем все по полочкам

habr.com

MySQLi раскладываем все по полочкам


ChAk 31 марта 2012 в 18:44

Для кого это статья? Первоочередной целью написания статьи было именно «разложить все по полочкам» для тех, кто уже работал с mysqli, но не вникал глубоко, а быстренько написал свои обертки и забыл про оригинальный синтаксис. Я постарался разъяснить нюансы, с которым столкнулся сам, при переносе данных из большой и очень старой БД, спроектированной человеком, не знающим про нормализации, в новую, с сильно изменившейся структурой.
Можно ли читать эту статью людям, которые все еще используют старое расширение mysql и только думающие об перехода на PDO или MySqli? Думаю даже нужно.

MySqli или PDO

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

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

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 для последнего запроса вставки.

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

Преимущества подготовленных запросов над «классическими»:
  • При серии запросов, в которых меняются лишь данные, запрос передается на сервер только один раз, в дальнейшем посылаются только изменяющиеся данные;
  • Защита от SQL-инъекций.
За работу с подготовленными запросами в 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_array() – тип массива задается константой,
    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 – количество строк в результате.
Ссылки:
Страница MySQLi в официальной документации на php.net
Больше примеров по MySQLi
Список функций MySQLi на русском языке
Теги:

воскресенье, 2 июня 2019 г.

Улучшенный модуль MySQL (MySQL Improved)

Подробная информация по библиотеке MYSqli  здесь.

PHP.SU - Использование mysqli

php.su

PHP.SU - Использование mysqli


1. Предполагаемая аудитория
Статья предназначена для читателей, имеющих некоторый опыт использования PHP и MySQL. Она предполагает, что читатель понимает основные принципы работы с базами данных и программирования и может использовать сценарий PHP для отправки запроса серверу MySQL.
Обратите внимание на то, что в конце статьи имеются сноски для разъяснения некоторых утверждений и словарь терминов.
2. Введение
Начиная с середины 90х, ext/mysql служило основным мостом между PHP и MySQL. Хотя в нем имелись недостатки и проблемы росли с годами, в общем, ext/mysql делал свое дело неплохо и шел в ногу с изменениями как в PHP, так и в MySQL.
Однако с появлением PHP 5 и MySQL 4.1 все изменилось - начали образовываться несколько достаточно обширных трещин.
В ext/mysql имелись "достоинства, оказавшиеся недостатками": в первую очередь это mysql_pconnect()[1] , подключение по умолчанию и автоматическое подключение[2]. Кроме того, проявились несовместимости между функциями ext/mysql и теми, что поддерживались клиентской библиотекой MySQL, на которой основаны и ext/mysql, и ext/mysqli.
В попытке исправить эти расхождения, Георг Рихтер создал очередное расширение PHP 5, которое поддерживает новые возможности MySQL 4.1+. Это расширение получило название ext/mysqli, где 'i' заменяет одно из слов: improved(улучшенное), interface(интерфейс), ingenious(изобретательное), incompatible(несовместимое) or incomplete(неполное).[3]
Ext/mysqli поддерживает новые возможности, появившиеся в последних версиях MySQL, и предлагает новые функции.
Кроме получения доступа к дополнительному функционалу MySQL 4.1+, зачем же стоит переходить на использование ext/mysqli?
В дополнение к упомянутому функционалу, ext/mysqli имеет несколько существенных преимуществ:
Некоторые аспекты ext/mysqli сильно отличаются от старого расширения. С целью исправления определенных изъянов в дизайне и поведения, склонного к ошибкам, некоторые возможности были убраны:
3. Покажите мне код!
Теперь, когда вы знаете, что изменилось, мы начнем анализировать код, который демонстрирует, как выглядит и работает новое расширение. Весь самостоятельный код, приведенный в этой статье, использует базу данных "world", которая бесплатно доступна на сайте http://www.mysql.com/documentation/index.html.
3.1 Базовое использование
Вот простой скрипт, который соединяется с сервером MySQL, посылает запрос серверу с помощью этого соединения, выводит результаты запроса и затем освобождает результирующее множество запроса и закрывает соединение.
<?php /* Подключение к серверу MySQL */
$link = mysqli_connect(
            'localhost',  /* Хост, к которому мы подключаемся */
            'user',       /* Имя пользователя */
            'password',   /* Используемый пароль */
            'world');     /* База данных для запросов по умолчанию */ if (!$link) {
   printf("Невозможно подключиться к базе данных. Код ошибки: %s\n", mysqli_connect_error());
   exit;
} /* Посылаем запрос серверу */
if ($result = mysqli_query($link, 'SELECT Name, Population FROM City ORDER BY Population DESC LIMIT 5')) {

    print(
"Очень крупные города:\n"); /* Выборка результатов запроса */
    while( $row = mysqli_fetch_assoc($result) ){
        printf("%s (%s)\n", $row['Name'], $row['Population']);
    } /* Освобождаем используемую память */
    mysqli_free_result($result);
} /* Закрываем соединение */
mysqli_close($link);
?>
Приведенный сценарий должен вывести что-то вроде:
Очень крупные города:Mumbai (Bombay) (10500000)
Seoul (9981619)
Sao Paulo (9968485)
Shanghai (9696300)
Jakarta (9604900)

Как видно из кода, ext/mysqli и ext/mysql могут быть очень похожи. Единственным существенным различием является то, что процедурный стиль ext/mysqli несколько более "многословен".
Заметьте, что без проверки на ошибки приведенный скрипт мог бы дать сбой в любом месте и вывести пользователю мерзкое сообщение об ошибке.
3.2 Использование объектно-ориентированного интерфейса
Объектно-ориентированный интерфейс предоставляет немного более лаконичный и менее восприимчивый к ошибкам метод использования ext/mysqli. Код, приведенный ниже, производит те же действия, что и предыдущий, однако, имеются несколько ключевых отличий, на которые стоит обратить внимание:
  • Нам не нужно явно задавать соединение, используемое в наших командах. Информация о подключении содержится в наших объектах $mysqli и $result и доступна при вызове соответствующих методов.
  • Когда производится выборка из результирующего набора данных запроса с использованием fetch_assoc(), не нужно явно задавать идентификатор используемого результирующего набора. Также как и информация о подключении, он содержится в объекте $result.
<?php /* Подключение к серверу MySQL */
$mysqli = new mysqli('localhost', 'user', 'password', 'world');

if (
mysqli_connect_errno()) {
   printf("Подключение к серверу MySQL невозможно. Код ошибки: %s\n", mysqli_connect_error());
   exit;
} /* Посылаем запрос серверу */
if ($result = $mysqli->query('SELECT Name, Population FROM City ORDER BY Population DESC LIMIT 5')) {     print("Очень крупные города:\n"); /* Выбираем результаты запроса: */
    while( $row = $result->fetch_assoc() ){
        printf("%s (%s)\n", $row['Name'], $row['Population']);
    } /* Освобождаем память */
    $result->close();
} /* Закрываем соединение */
$mysqli->close();
?>
4. Подготовленные выражения
Теперь, когда мы разобрали азы использования расширения, рассмотрим несколько новых возможностей.
Подготовленные выражения предоставляют разработчикам возможность создавать запросы, которые являются более безопасными, имеют более высокую производительность и более удобны в написании.
Подготовленные выражения можно использовать двумя способами: с заданными параметрами и с заданными результатами.
4.1 С заданными параметрами
Подготовленные выражения с заданными параметрами позволяют создавать шаблоны запросов и хранить их на сервере MySQL. Когда нужно создать запрос, данные, заполняющие шаблон, отправляются серверу MySQL, где полностью сформированный запрос и выполняется.
Основной процесс создания и использования подготовленных выражений с заданными параметрами прост.
Создается шаблон запроса и посылается серверу MySQL. Сервер его получает, проверяет его корректность, чтобы убедиться, что он имеет смысл, и сохраняет его в специальном буфере. Затем сервер возвращает идентификатор, который может быть в дальнейшем использован для обращения к подготовленному выражению.
Когда нужно создать запрос, данные, заполняющие шаблон, отправляются серверу MySQL и полностью сформированный запрос выполняется.
В этом процессе заключено несколько очень важных деталей.
Тело шаблона отсылается серверу MySQL только один раз. Для выполнения выражения посылаются только данные, необходимые для заполнения шаблона.
Большая часть работы по проверке и обработке запроса проделывается только один раз, вместо того, чтобы делать это каждый раз.
Кроме того, для запросов, которые содержат небольшое количество дынных, расходы сильно уменьшены. Например, если у вас есть запрос типа:
INSERT INTO City (ID, Name) VALUES (NULL, 'Calgary');
то каждый раз при выполнении запроса нужно отослать лишь около 16 байт вместо обычных 60 или более байт. (Эти приближенные числа включают расходы на все данные вроде идентификатора подготовленного выражения, длины данных запроса - для безопасности бинарных данных - и т.д., но не включают расходы на строку запроса.)
Данные запроса не должны проходить через функции вроде mysql_real_escape_string(), чтобы убедиться, что нет угрозы атаки "SQL-впрыска"[4] Вместо этого, клиент и сервер MySQL работают так, чтобы убедиться, что посланные данные безопасно обработаны при их комбинировании с подготовленным выражением.
Шаблон запроса выглядит как-то так:
INSERT INTO City (ID, Name) VALUES (?, ?);
Знак '?' можно использовать в большинстве мест, где используются символьные данные, например запрос может быть переделан из
SELECT Name FROM City WHERE Name = 'Calgary';
в
SELECT Name FROM City WHERE name = ?;
Вот более полный пример, демонстрирующий весь процесс:
<?php
$mysqli = new mysqli('localhost', 'user', 'password', 'world'); /* Проверка соединения */
if (mysqli_connect_errno()) {
    printf("Подключение невозможно: %s\n", mysqli_connect_error());
    exit();
} $stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");
$stmt->bind_param('sssd', $code, $language, $official, $percent); $code = 'DEU';
$language = 'Bavarian';
$official = "F";
$percent = 11.2; /* выполнение подготовленного выражения  */
$stmt->execute(); printf("%d Row inserted.\n", $stmt->affected_rows); /* Закрытие соединения и выражения*/
$stmt->close(); /* Очистить таблицу CountryLanguage */
$mysqli->query("DELETE FROM CountryLanguage WHERE Language='Bavarian'");
printf("%d Row deleted.\n", $mysqli->affected_rows); /* Закрыть подключение */
$mysqli->close();
?>

Обратите внимание на то, что первым параметром bind_param() является короткая строка. Это строка формата, используемая для определения того, как объявленные параметры должны быть интерпретированы.
В случае вышеприведенного сценария 'sssd' означает, что значения первых трех параметров $code, $language и $official будут посланы как строки, а четвертый параметр $percent будет содержать значения типа double с плавающей запятой.
Для каждой заявленной переменной в bind_param(), должна быть своя буква в строке формата, которая означает, как переменная будет отправлена. Например
$stmt->bind_param('s', $foo);
$stmt->bind_param('si', $foo, $bar);
$stmt->bind_param('sid', $foo, $bar, $baz);

Объявление типов обеспечивает то, что расширение mysqli знает, как зашифровать данные для большей эффективности.
Определения типов очень просты: данные в заданных переменных будут обрабатываться как целочисленные, рациональные числа (double) или как строки.
Также имеется специальный тип, позволяющий отправлять блобы (большие бинарные объекты) порциями.
Следующая таблица иллюстрирует типы и возможности использования:
Идентификатор типа Тип столбца
i Все INT типы
d DOUBLE и FLOAT
b BLOB'ы
s Остальные типы
4.2 С заданными результатами
Подготовленные выражения с объявленными результатами позволяют привязывать переменные PHP-скрипта к значениям полей данных в результирующем множестве запроса.
Процесс объявления таков:
  • Создать запрос.
  • Попросить сервер MySQL заготовить запрос.
  • Привязать переменные PHP к столбцам в заготовке запроса.
  • Заставить сервер MySQL выполнить запрос.
  • Запросить добавление нового ряда данных в привязанные переменные.
Вот простой фрагмент кода, иллюстрирующий процесс:
<?php
$mysqli = new mysqli("localhost", "user", "password", "world");

if (
mysqli_connect_errno()) {
    printf("Подключение невозможно: %s\n", mysqli_connect_error());
    exit();
} /* Подготовленное выражение */
if ($stmt = $mysqli->prepare("SELECT Code, Name FROM Country ORDER BY Name LIMIT 5")) {
    $stmt->execute(); /* Привязывание переменных к заготовке */
    $stmt->bind_result($col1, $col2); /* Выборка значений */
    while ($stmt->fetch()) {
        printf("%s %s\n", $col1, $col2);
    } /* Закрытие оператора $stmt->close();
}
/* Закрытие соединения */
$mysqli->close(); ?>
4.3 Использование заданных параметров и результатов вместе
Вот более полный пример, демонстрирующий использование и заданных параметров, и заданных результатов одновременно:
<?php
$mysqli = new mysqli("localhost", "user", "password", "world");

if (
mysqli_connect_errno()) {
    printf("Подключение невозможно: %s\n", mysqli_connect_error());
    exit();
} /* Подготовленное выражение */
if ($stmt = $mysqli->prepare("SELECT Code, Name FROM Country WHERE Code LIKE ? LIMIT 5")) { $stmt->bind_param("s", $code);
    $code = "C%"; $stmt->execute(); /* Объявление переменных для заготовленного выражения*/
    $stmt->bind_result($col1, $col2); /* Выборка значений */
    while ($stmt->fetch()) {
        printf("%s %s\n", $col1, $col2);
    } /* Закрытие выражения */
    $stmt->close();
}
/* Закрытие подключение */
$mysqli->close(); ?>
5. Резюме
В этой статье мы привели обзор возможностей и архитектуры ext/mysqli, а также краткое изложение истории его развития. К этому моменту вы должны понимать, как использовать и получать выгоду от подготовленных выражений MySQL и должны чувствовать удобство использования объектно-ориентированного интерфейса к ext/mysqli.
6. Словарь терминов
ext/mysql - старое расширение PHP для работы с MySQL. Не поддерживает всех возможностей MySQL версий 4.1 и выше.
ext/mysqli - новое расширение PHP 5 для работы с MySQL. Поддерживает возможности MySQL версий от 3.22 до 5.0
Клиентская библиотека MySQL - Компонент MySQL RDBMS (MySQL Relational DataBase Management System - Система управления реляционной базой данных MySQL - прим. переводчика), который позволяет программам общаться с RDBMS.
Сервер MySQL - Компонент MySQL RDBMS, который обрабатывает и отвечает на запросы, управляет файловым представлением данных внутри базы и т.д.
[1] - Функция mysql_pconnect() была создана для предоставления механизма уменьшения затрат на установление и разрыв соединений с сервером MySQL. К сожалению, из-за взаимодействия между архитектурами сервера Apache и PHP, большой трафик на сайте, использующем pconnect, мог быстро загрязнить сервер MySQL большим количеством неиспользуемых соединений, которые мешали активным соединениям получать доступ к базе данных.
[2] - Возможности автоматического соединения позволяли определенным вызовам функций автоматически соединяться с базой данных (если правильная информация о соединении находилась в конфигурационном файле php.ini). Возможность соединения по умолчанию работала по следующему принципу. Последнее открытое соединение с базой MySQL становится используемым соединением, если параметр соединения не был явно указан в аргументах функции.
[3] - Это расширение все еще находится в стадии разработки. В то время как набор возможностей ядра должен быть действительно стабильным, ни MySQL 4.1, ни PHP 5.0 не имеют стабильных релизов (статья появилась до выхода PHP 5.0.0 - прим. переводчика). Также, дополнительный набор возможностей, который не очень аккуратно использует клиентскую библиотеку MySQL, все еще дорабатывается.
[4] - Атаки типа "SQL-впрыск" возможны, когда данные входят в запрос, заставляя его совершать неожиданные и/или злонамеренные действия. Пусть, для примера, дан простой запрос в PHP скрипте типа "DELETE FROM grades WHERE class_name='test_$class'". Атакующий может получить контроль над переменной $class и получить возможность an attacker who can gain control over the value of $class can force unintended deletes to occur by changing the value of $class to something like "oops' or class_name LIKE '%'".
7. Об авторах
Зак Грэнт (Zak Greant) - профессиональный защитник концепции Open Source, писатель и программист. Он работает в MySQL AB пропагандистом Сообщества. Зак поддерживает оба расширения PHP для работы с MySQL и является соавтором PHP Functions Essential Reference.
Георг Рихтер (Georg Richter) - создатель расширения mysqli. Он также поддерживает расширения mysql и ncurses. Он работает в MySQL AB Старшим Разработчиком и является членом Apache Software Foundation.