Почему стоит пользоваться PDO для работы с базой данных habr.com (Feb 6, 2012)
Модуль PDO
Модуль PHP Data Objects (PDO) определяет простой и согласованный интерфейс для доступа к базам данных:
- PDO_MYSQL ( MySQL 3.x/4.x/5.x/8.x )
- PDO_PGSQL PostgreSQL
- PDO_SQLITE SQLite 3 (SQLite 2* нет в документации)
- PDO_SQLSRV Microsoft SQL Server
- PDO_ODBC ODBC и DB2
- PDO_FIREBIRD Firebird
- и другие
//
Концепция
//www.php.net/manual/ru/book.pdo.php
!!!PDO::exec() не возвращает результат выборки оператором SELECT. Если вам нужно выбрать данные этим оператором единожды в ходе выполнения программы, воспользуйтесь методом PDO::query(). Если требуется запускать один и тот же запрос на выборку множество раз, лучше создать подготовленный запрос PDOStatement методом PDO::prepare(), а затем запускать его методом PDOStatement::execute() столько раз, сколько потребуется.
Класс PDO
Объект класса представляет и реализует соединение с сервером базы данных.
Соединение
Соединение с сервером происходит в момент создания объекта PDO. Повторное использование объекта PDO для подключения к серверу другого типа, другим URL, для другого пользователя, с другими параметрами не предусмотрено. Все параметры подключения необходимо задать в конструкторе:
public PDO::__construct( string $dsn, ?string $username = null,?string $password = null,?array $options = null )
- dsn строка источника данных устанавливает главные параметры подключения к базе данных, разделенный ;
- имя драйвера PDO, за которым следует двоеточие и специфический синтаксис подключения драйвера PDO
- "mysql:host=$hostname;dbname=$db"
- "sqlite:/tmp/foo.db"
- имя драйвера PDO, за которым следует двоеточие и специфический синтаксис подключения драйвера PDO
- username , password логин и пароль передаются для тех случаев, когда они необходимы
- options ассоциативный массив параметров выполнения запросов, которые можно переустановить методом PDO::setAttribute() //www.php.net
- PDO::ATTR_ERRMODE параметр устанавливает способ обработки ошибок в зависимости от значения:
- PDO::ERRMODE_SILENT ошибка в методе возвратит коды ошибки
- PDO::ERRMODE_WARNING ошибка вызовет диагностику уровня E_WARNING
- PDO::ERRMODE_EXCEPTION ошибка вызовет исключение PDOException
- PDO::ATTR_STRINGIFY_FETCHES (bool) значение True устанавливает для выражений SELECT преобразование числовых значений в строковые
- PDO::ATTR_TIMEOUT (int) устанавливает допустимый таймаут в секундах; поддерживается не всеми драйверами и по-разному
- PDO::ATTR_AUTOCOMMIT (bool) только для драйверов OCI, Firebird и MySQL устанавливает автоматический COMMIT после каждого выражения в запросе, по умолчанию установлен
- PDO::ATTR_EMULATE_PREPARES (bool) только для драйверов OCI, Firebird и MySQL, устанавливает эмуляцию подготовленных запросов
- PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (bool) только для драйвера MySQL устанавливает буферизацию запросов, по умолчанию установлена
- PDO::ATTR_DEFAULT_FETCH_MODE устанавливает режим выборки по умолчанию одним из значений или логической сумму нескольких значений:
- PDO::FETCH_ASSOC возвращает массив, индексированный именами столбцов набора
- PDO::FETCH_NAMED возвращает массив такого же вида, как и PDO::FETCH_ASSOC, но, если есть несколько полей с одинаковым именем, то значением с этим ключом будет массив со всеми значениями из рядов, в которых это поле указано
- PDO::FETCH_NUM возвращает массив, индексированный номерами столбцов (от 0)
- PDO::FETCH_BOTH (по умолчанию): возвращает массив, индексированный именами столбцов результирующего набора, а также их номерами (начиная с 0)
- PDO::FETCH_OBJ создаёт анонимный объект со свойствами, соответствующими именам столбцов результирующего набора
- PDO::FETCH_INTO обновляет существующий объект запрошенного класса, присваивая значения столбцов результирующего набора именованным свойствам объекта
- PDO::FETCH_CLASS создаёт и возвращает объект запрошенного класса, присваивая значения столбцов результирующего набора именованным свойствам класса, и следом вызывает конструктор, если не задан PDO::FETCH_PROPS_LATE
- если mode включает в себя атрибут PDO::FETCH_CLASSTYPE (например, PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE), то имя класса, от которого нужно создать объект, будет взято из первого столбца
- PDO::FETCH_PROPS_LATE если используется с PDO::FETCH_CLASS, конструктор класса будет вызван перед назначением свойств из значений столбцов
- PDO::FETCH_BOUND возвращает true и присваивает значения столбцов результирующего набора переменным PHP, которые были привязаны к этим столбцам методом PDOStatement::bindColumn()
- PDO::FETCH_LAZY комбинирует PDO::FETCH_BOTH и PDO::FETCH_OBJ, создавая новый объект со свойствами, соответствующими именам столбцов результирующего набора
- PDO::ATTR_ERRMODE параметр устанавливает способ обработки ошибок в зависимости от значения:
1 2 3 4 5 6 |
$pdh = new PDO( 'mysql:host=reks.biz;dbname=db_demo;charset=utf8mb4', 'user', 'pa55w0rd', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_INIT_COMMAND => 'SET time_zone = \'+00:00\'' ] ); |
Драйверы
Подключение возможно только при наличии драйвера. Проверить доступность драйвер до вызова конструктора можно статическим методом:
public static PDO::getAvailableDrivers(): array
или функцией pdo_drivers(): array
Результатом вызова будет массив:
1 2 3 4 5 6 |
array ( 0 => 'firebird', 1 => 'mysql', 2 => 'pgsql', 3 => 'sqlite', ) |
//
Запрос выборки .query()
Для выполнения запроса выборки данных в PDO предусмотрен метод query(). Существует 4 варианта:
public query(string $query, ?int $fetchMode = null): PDOStatement|false /
public query(string $query, ?int $fetchMode = PDO::FETCH_COLUMN, int $colno): PDOStatement|false /
public query(string $query, ?int $fetchMode = PDO::FETCH_CLASS, string $classname, array $constructorArgs ): PDOStatement|false /
public query(string $query, ?int $fetchMode = PDO::FETCH_INTO, object $object): PDOStatement|false /
!!!
Запрос без выборки .exec()
/
public PDO::exec(string $statement): int|false выполняет SQL-запрос без выборки и возвращает количество записей затронутых запросом
!!!
public PDO::lastInsertId(?string $name = null): string|false
Возвращает ID последней вставленной строки или последнее значение от объекта последовательности, в зависимости от базового драйвера. Например, PDO_PGSQL требует задать имя объекта последовательности для параметра name.
- name Имя объекта последовательности, который должен выдать ID
- Если объект последовательности для name не задан, PDO::lastInsertId() вернёт строку, представляющую ID последней добавленной в базу записи.
- Если же объект последовательности для name задан, PDO::lastInsertId() вернёт строку, представляющую последнее значение, полученное от этого объекта.
- Если PDO драйвер не поддерживает эту возможность, PDO::lastInsertId() запишет IM001 в SQLSTATE.
- В зависимости от драйвера PDO этот метод может вообще не выдать осмысленного результата, так как база данных может не поддерживать автоматического инкремента полей или последовательностей
- !!!
public PDO::prepare(string $query, array $options = []): PDOStatement|false
Подготавливает SQL-запрос к базе данных к запуску посредством метода PDOStatement::execute(). Запрос может содержать именованные (:name) или неименованные (?) псевдопеременные, которые будут заменены реальными значениями во время запуска запроса на выполнение. Использовать одновременно и именованные, и неименованные псевдопеременные в одном запросе нельзя, необходимо выбрать что-то одно. Используйте псевдопеременные, чтобы привязать к запросу пользовательский ввод, не включайте данные, введённые пользователем, напрямую в запрос- query //
- выражение может содержать метки :label для подстановки параметров методом !!!
- для повторного использования параметра в sql-выражении необходимо использовать новую метку, при повторном использовании метки в sql-выражении при вызове метода execute() возникнет ошибка 'SQLSTATE[]: Invalid parameter number'
- выражение может содержать метки :label для подстановки параметров методом !!!
- query //
вспомогательные
//
public PDO::quote(string $string, int $type = PDO::PARAM_STR): string|false готовит строковое значения к использованию в запросе, заключает в кавычки (если требуется) и экранирует специальные символы совместимым способом
- type / PARAM_STR, PDO_PARAM_LOB /
- PDO::PARAM_STR (int) представляет типы данных SQL CHAR, VARCHAR и другие строковые типы для формирования результата в нотации '...'
- PDO::PARAM_STR_CHAR (int) флаг доступен с версии PHP 7.2.0 для обозначения использования в строке string обычного набора символов
- PDO::PARAM_STR_NATL (int) флаг доступен с версии PHP 7.2.0 для обозначения использования в string национальных символов для формирования результата в нотации N'...'
- PDO::PARAM_LOB (int) представляет тип данных больших объектов SQL
- ? PDO::PARAM_INPUT_OUTPUT (int) Указывает, что параметр является параметром INOUT для хранимой процедуры. Для задания типа данных необходимо применить побитовое ИЛИ этой константы с константой типа PDO::PARAM_*
- Метод реализован не во всех драйверах (например PDO_ODBC), поэтому применяется в тех случаях, когда нельзя использовать более надежных PDO::prepare().
//
Класс PDOStatement
//www.php.net/manual/ru/class.pdostatement.php
Амбивалентный объект, который представляет запрос с расширенными методами его подготовки и возможностью эффективного многократного выполнения, после которого представляет еще и результирующий набор данных.
//
Подготовка запроса
//
Выполнение запроса .execute()
public PDOStatement::execute(?array $params = null): bool
Запускает подготовленный запрос. Если запрос содержит маркеры параметров (псевдопеременные), вы должны либо:
public PDOStatement::rowCount(): int
PDOStatement::rowCount() возвращает количество строк, которые были затронуты в ходе выполнения последнего запроса DELETE, INSERT или UPDATE, запущенного соответствующим объектом PDOStatement.
public PDOStatement::columnCount(): int возвращает количество столбцов для выполненного выражения выборки
public PDOStatement::fetchAll(int $mode = PDO::FETCH_DEFAULT): array
public PDOStatement::fetchAll(int $mode = PDO::FETCH_COLUMN, int $column): array
public PDOStatement::fetchAll(int $mode = PDO::FETCH_CLASS, string $class, ?array $constructorArgs): array
public PDOStatement::fetchAll(int $mode = PDO::FETCH_FUNC, callable $callback): array выбирает все оставшиеся в наборе результатов строки!!!
1 2 3 4 |
$dbh = new PDO("mysql:host=$hostname;dbname=$db", $username, $password); foreach( $dbh->query('SELECT * FROM users') as $row) { echo "<tr><td>{$row['id']}</td><td>{$row['user']}</td></tr>"; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
try { $pdo = new PDO( 'mysql:host=reks.biz;dbname=reks_demo;charset=utf8mb4', 'user', 'password', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); $survey_stmt = $pdo->prepare( 'SELECT * FROM myTable WHERE id=:id' ); $survey_stmt->bindParam( ':id', $my_id ); $survey_stmt->execute(); while( $survey_row = $survey_stmt->fetch()) { echo '<pre>',var_export($survey_row,true),'</pre>'; !!!! $kind = 'json'; $insert_stmt = $pdo->prepare( 'INSERT INTO entries (kind,json,gmt) VALUES (:kind,:json,UTC_TIMESTAMP)' ); $insert_stmt->bindParam( ':kind', $kind ); $insert_stmt->bindParam( ':json', json_encode( $_POST, JSON_HEX_QUOT|JSON_UNESCAPED_UNICODE )); $insert_stmt->execute(); $entry_id = $pdo->lastInsertId(); $update_stmt = $pdo->prepare( 'UPDATE dtek202305 SET entry_id=:entry_id, gmt_done=UTC_TIMESTAMP WHERE token=:token' ); $update_stmt->bindParam( ':entry_id', $entry_id ); $update_stmt->bindParam( ':token', $token ); $update_stmt->execute(); // Отправленные данные успешно сохранены. header( 'Location: ' . $redirect . '&msg=' . urlencode( 'The submitted data was saved successfully.' )); exit(); } } catch (PDOException $e) { error_log( $e->getMessage( )); header( 'Location: ' . $redirect . '&msg=' . urlencode( $e->getMessage( ))); exit(); } |
Множинні запити
Способность PDO выполнять множественные запросы зависит от выбранного драйвера.
Драйвер mysql 7.2
Штатный драйвер mysql версии 7.2 позволяет выполнить множественные запросы обработки данных без выборки используя метод PDO::exec(). Методы PDO::query() и PDO::prepare() при множественном запросе вызывает ошибку Syntax error or access violation, таким образом для множественной выборки придется использовать несколько объектов PDOStatement, использую обычный алгоритм.
Параметр подключения PDO::MYSQL_ATTR_MULTI_STATEMENTS не влияет на работу штатного драйвера.
Хотя PDO не позволяет выполнить запрос со множественными выборками, при вызове хранимой процедуры запрос может получить несколько наборов данных, которые можно последовательно обработать используя метод public PDOStatement::nextRowset(): bool. В этом случае алгоритм обработки результат запроса может быть таким:
1 2 3 4 |
$statement = $pdh->query( 'CALL MultiSetProcedure' ); do { $multiset[] = $statement->fetchAll(PDO::FETCH_ASSOC); } while ($statement->nextRowset()); |
Драйвер PDO_MYSQL
Среди альтернативных драйверов часто упоминается PDO_MYSQL, который поддерживает запросы с множественными выборками. Его использование требует отдельной установки.
Библиотека mysqli
Операции реализуются иерархией классов. Класс mysqli представляет связь между PHP и базой данных MySQL, класс mysqli_stmt формирует выражение запроса к серверу, класс mysqli_result представляет полученные данные //www.php.net
Класс mysqli
-
- public mysqli::__construct( string $hostname, string $username, string $password, string $database = "", int $port, string $socket ) коструктор объекта mysqli может установить соединение с сервером MySQL
- int $mysqli->connect_errno свойство содержит код ошибки последней попытки подключения или 0 при успешном подключении
- public mysqli::ssl_set( string $key, string $certificate, string $ca_certificate, string $ca_path, string $cipher_algos ): bool настраивает ключи безопасных SSL соединений, вызывается до вызова mysqli_real_connect(), требует включения поддержки OpenSSL
- public mysqli::connect( string $hostname, string $username, string $password, string $database, int $port, string $socket ): void устанавливает соединение с работающим сервером MySQL
- null|string $mysqli->connect_error; возвращает сообщение об ошибке последней попытки подключения
- public mysqli::select_db(string $database): bool при подключенном сервере устанавливает контестную базу данных для последующих запросов
- public mysqli::set_charset(string $charset): bool задаёт набор символов, который будет использоваться при обмене данными с сервером баз данных
- 'utf8' 'utf8mb3' 'utf8mb4' - типичная современная UTF-8
- 'koi8u' 'koi8r' - устаревшая украинская и русская unix-кодировки
- 'cp1251' - устаревшая кириллическая windows-кодировка
- 'cp866' - архаическая русская dos-кодировка
- 'ascii' -
- 'Latin1'
- public mysqli::query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool выполняет запрос query к базе данных
- public mysqli::multi_query(string $query): bool позволяет выполнить мульти-запрос из нескольких запросов, разделенных точкой с запятой
- public mysqli::store_result(int $mode = 0): mysqli_result|false возвращает текущий результирующий набор запроса или мульти-запроса
- public mysqli::more_results(): bool указывает на доступность следующего результирующего набора мульти-запроса
- public mysqli::next_result(): bool подготавливает следующий результирующий набор мульти-запроса
- int|string $mysqli->affected_rows содержит число строк, затронутых последним запросом INSERT, UPDATE, REPLACE, DELETE и SELECT
- для SELECT affected_rows не соответствует числу записей, для этого следует использовать свойство mysqli_stmt::$num_rows
- int|string $mysqli->insert_id содержит значение поля с атрибутом AUTO_INCREMENT из первой успешной записи запросов INSERT или UPDATE
- public mysqli::close(): bool закрывает открытое соединение с базой данных
- public mysqli::real_escape_string(string $string): string экранирует специальные символы в строке для использования в SQL-выражении, используя текущий набор символов соединения
1 2 3 4 5 6 7 8 9 |
$mydb = new mysqli( DB_HOST, DB_USER, DB_PASS, DB_NAME ); if( $mydb ) { $mydb->set_charset( 'utf8' ); $message = $mydb->real_escape_string( "ATTENTION! \r\n Escape special characters before!" ); $query = "INSERT INTO journal(message) VALUES ('{$message}')"; $result = $mydb->query( $query ); if(( $result === true ) AND ( $mydb->affected_rows === 1 )) return $mydb->insert_id; } |
Обработка мультизапроса:
1 2 3 4 5 6 7 8 9 10 |
... $result = $mydb->multi_query( "SELECT user AS data FROM users;" . "SELECT msg AS data FROM messages;" ); do { $result = $mysqli->store_result(); while ($row = $result->fetch_array( MYSQLI_ASSOC )) { echo $row['data'], "\n"; } } while ($mysqli->more_results() && $mysqli->next_result()); |
Если не "разобрать" результаты мультизапроса после его выполнения, в следующем запросе возникает ошибка "Commands out of sync; you can't run this command now", которую :
1 2 3 4 5 |
... $result = $mydb->multi_query( "INSERT INTO journal(message) VALUES ('{$msg123}')" . "INSERT INTO journal(message) VALUES ('{$msgABC}')" ); while( $mydb->next_result( )); |
Класс mysqli_stmt
-
- public mysqli::stmt_init(): mysqli_stmt|false возвращает указатель на новый объект sql-выражения mysqli_stmt для использования в контексте установленного соединения
- public mysqli::prepare(string $query): mysqli_stmt|false возвращает указатель на новый объект выражения mysqli_stmt c подготавленным запросом $query, который может использоваться для дальнейших операций с выражением, прежде чем оно будет отправлено серверу на выполнение
- public mysqli_stmt::bind_param(string $types, mixed &$var, mixed &...$vars): bool привязывает переменные к меткам параметров в SQL-выражении mysqli_stmt
- $types - строка, содержащая один или более символов, каждый из которых задаёт тип значения привязываемой переменной:
- i соответствующая переменная имеет тип integer
- d соответствующая переменная имеет тип double
- s соответствующая переменная имеет тип string
- b соответствующая переменная является большим двоичным объектом (blob) и будет пересылаться пакетами
- переменная может быть привязана
- $types - строка, содержащая один или более символов, каждый из которых задаёт тип значения привязываемой переменной:
- public mysqli_stmt::execute(?array $params = null): bool подставляет значения параметров из идексированного массива $params и выполняет выражение
- ?!!! НЕПОНЯТНО НУЖНО ЛИ ПЕРЕД get_result() ?!!!
- public mysqli_stmt::get_result(): mysqli_result|false выполняет подготовленное выражение запроса и получает набор данных
- public mysqli_stmt::store_result(): bool функцию следует вызывать для запросов, которые успешно создают набор результатов (например, SELECT, SHOW, DESCRIBE, EXPLAIN), только если необходимо буферизовать в PHP полный набор результатов. Каждый последующий вызов mysqli_stmt_fetch() будет возвращать буферизованные данные
- int|string $mysqli_stmt->affected_rows; возвращает количество строк, изменённых запросом INSERT, UPDATE или DELETE
- int|string $mysqli_stmt->num_rows; public mysqli_stmt::num_rows(): int|string возвращает количество строк, помещённых в буфер после вызова метода store_result()
- int представляет количество буферизованных строк
- 0 в небуферизованном режиме, если с сервера не были получены все строки
- string представляет количество строк, если значение больше PHP_INT_MAX
- int представляет количество буферизованных строк
- public mysqli_stmt::bind_result(mixed &$var, mixed &...$vars): bool привязывает столбцы результирующего набора к переменным
- public mysqli_stmt::fetch(): ?bool связывает результаты подготовленного выражения с переменными
Класс mysqli_result
Представляет результирующий набор, полученный из запроса в базу данных.
-
- public mysqli_result::fetch_assoc(): array|null|false выбирает следующую строку данных из набора результатов в виде ассоциативного массива или null, массив содержит [1] => 1
- public mysqli_result::fetch_row(): array|null|false выбирает следующую строку данных из набора результатов в виде индексированного массива или null, массив содержит [0] => 1
- public PDOStatement::fetchObject(?string $class = "stdClass", array $constructorArgs = []): object|false выбирает следующую строку в виде объекта
- class класс объекта в который будут загружены данные выборки, а затем будет вызван метод конструктора
- constructorArgs параметры метода конструктора
- public mysqli_result::fetch_array(int $mode = MYSQLI_BOTH): array|null|false выбирает следующую строку данных из набора результатов в виде массива, тип массива зависит от $mode
- $mode MYSQLI_ASSOC, MYSQLI_NUM или MYSQLI_BOTH необязательный, по умолчанию MYSQLI_BOTH указывает на тип массива представления данных
- MYSQLI_ASSOC возвратит ассоциативный массив с именами полей идентично mysqli_fetch_assoc()
- MYSQLI_NUM возвратит индексированный массив с индексами полей идентично функции mysqli_fetch_row()
- MYSQLI_BOTH по умолчанию, возвратит массив с двойным набором индексов и имен, массив содержит и [0] => 1, и ['field'] => 1
- PDO::FETCH_CLASS или PDO::FETCH_OBJ
- $mode MYSQLI_ASSOC, MYSQLI_NUM или MYSQLI_BOTH необязательный, по умолчанию MYSQLI_BOTH указывает на тип массива представления данных
- public mysqli_result::fetch_all(int $mode = MYSQLI_NUM): array помещает все строки результирующего набора в общий индексированный массив, при этом каждая строка представляется массивом аналогично fetch_array()
- public mysqli_result::free(): void public mysqli_result::close(): void public mysqli_result::free_result(): void освобождает память, занятую результатами запроса
- Имена полей, возвращаемые этой функцией являются зависимыми от регистра.
- устанавливает NULL-поля в значение null PHP
Пояснения и дополнения
Как правильно использовать mysqli //habr.com (22.06.2022)
"...переводчик считает, что PDO является более продвинутым API для работы с БД, чем mysqli"
MySQLi раскладываем все по полочкам //habr.com
Буфурезированный результат загружает ответ сервера целиком, а небуферизированный по частям, что связано с рядом особенностей:
- Преимущества:
- Результат можно начинать читать раньше, сокращается время ожидания;
- Результат не занимает место в оперативной памяти.
- Недостатки:
- Невозможно узнать, сколько строк получено;
- Невозможно передвигаться к определенному результату, то есть можно читать данные только с начала и по порядку;
- Нельзя выполнять других запросов, пока не закрыт этот результат.
Примеры
Простейший запрос выборки:
1 2 3 4 5 6 7 |
$mydb = new mysqli( $myhost, $myuser, $mypassword, $mydbname ); if( $mydb->connect_errno === 0) { $result = $mydb->query( "SELECT * FROM mytable WHERE id={$myid};" ); $row = $result->fetch_array(); if( !is_null( $row )) { var_dump( $row ); ... |
Запрос с параметрами !!!
$stmt->execute();
$val1 = 'Bordeaux';
$val2 = 'FRA';
$val3 = 'Aquitaine';
/* Выполняем утверждение */
$stmt->execute();
/* Получаем все строки из myCity */
$query = "SELECT Name, CountryCode, District FROM myCity";
$result = $mysqli->query($query);
while ($row = $result->fetch_row()) {
printf("%s (%s,%s)\n", $row[0], $row[1], $row[2]);
}