Объекты доступа к данным (DAO) ============================== Объекты доступа к данным (DAO) предоставляют общий API для доступа к данным, хранящимся в различных СУБД. Это позволяет без проблем поменять используемую СУБД на любую другую без необходимости изменения кода, использующего DAO для доступа к данным. Yii DAO является надстройкой над [PHP Data Objects (PDO)](http://www.php.net/manual/ru/book.pdo.php) - расширением, которое предоставляет унифицированный доступ к данным многих популярных СУБД, таких как MySQL, PostgreSQL. Поэтому для использования Yii DAO необходимо, чтобы были установлены расширение PDO и соответствующий используемой базе данных драйвер PDO (например, `PDO_MYSQL`). Yii DAO состоит из четырёх основных классов: - [CDbConnection]: представляет подключение к базе данных. - [CDbCommand]: представляет запрос к базе данных, который необходимо выполнить. - [CDbDataReader]: представляет однонаправленный поток строк данных, возвращаемых в ответ на запрос. - [CDbTransaction]: представляет транзакцию базы данных. Далее мы проиллюстрируем использование Yii DAO на различных примерах. Соединение с базой данных ------------------------- Для установления соединения с базой необходимо создать экземпляр класса [CDbConnection] и активировать его. Дополнительная информация, необходимая для подключения к БД (хост, порт, имя пользователя, пароль и т.д.), указывается в DSN (Data Source Name). В случае возникновения ошибки в процессе соединения с БД будет выброшено исключение (например, неверный DSN или неправильные имя пользователя/пароль). ~~~ [php] $connection=new CDbConnection($dsn,$username,$password); // устанавливаем соединение // можно использовать конструкцию try…catch для перехвата возможных исключений $connection->active=true; … $connection->active=false; // close connection ~~~ Формат DSN зависит от используемого драйвера PDO. Как правило, DSN состоит из имени драйвера PDO, за которым следует двоеточие, далее указываются параметры подключения, соответствующие синтаксису подключения используемого драйвера. Подробнее с этим можно ознакомиться в [документации PDO](http://www.php.net/manual/ru/pdo.construct.php). Ниже представлены несколько основных форматов DSN: - SQLite: `sqlite:/path/to/dbfile` - MySQL: `mysql:host=localhost;dbname=testdb` - PostgreSQL: `pgsql:host=localhost;port=5432;dbname=testdb` - SQL Server: `mssql:host=localhost;dbname=testdb` - Oracle: `oci:dbname=//localhost:1521/testdb` Поскольку [CDbConnection] наследует класс [CApplicationComponent], мы можем использовать его в качестве [компонента приложения](/doc/guide/basics.application#application-component). Для этого нужно настроить компонент `db` в [конфигурации приложения](/doc/guide/basics.application#application-configuration) следующим образом: ~~~ [php] array( … 'components'=>array( … 'db'=>array( 'class'=>'CDbConnection', 'connectionString'=>'mysql:host=localhost;dbname=testdb', 'username'=>'root', 'password'=>'password', 'emulatePrepare'=>true, // необходимо для некоторых версий инсталляций MySQL ), ), ) ~~~ Теперь мы можем получить доступ к соединению с БД через `Yii::app()->db`. Чтобы соединение не активировалось автоматически, необходимо установить значение [CDbConnection::autoConnect] в false. Этот способ даёт нам возможность использовать одно и то же подключение к БД в любом месте кода. Выполнение SQL-запросов ------------------------ Когда соединение с БД установлено, мы можем выполнять SQL-запросы, используя [CDbCommand]. Для этого необходимо создать экземпляр класса [CDbCommand] путём вызова [CDbConnection::createCommand()], указав SQL-выражение: ~~~ [php] $connection=Yii::app()->db; // так можно делать, если в конфигурации настроен компонент соединения "db" // В противном случае можно создать соединение явно: // $connection=new CDbConnection($dsn,$username,$password); $command=$connection->createCommand($sql); // при необходимости SQL-выражение можно изменить: // $command->text=$newSQL; ~~~ Существуют два способа выполнения SQL-запросов с использованием [CDbCommand]: - [execute()|CDbCommand::execute]: выполняет SQL-запросы `INSERT`, `UPDATE` и `DELETE`. В случае успешного выполнения возвращает количество затронутых строк. - [query()|CDbCommand::query]: выполняет SQL-запросы, возвращающие наборы данных, например, запросы `SELECT`. В случае успешного выполнения возвращает экземпляр класса [CDbDataReader], обеспечивающий доступ к полученным данным. Для удобства также реализованы методы вида `queryXXX()`, возвращающие результаты напрямую. Если в процессе выполнения SQL-запроса возникнет ошибка, то будет выброшено исключение. ~~~ [php] $rowCount=$command->execute(); // выполнение запроса типа `INSERT`, `UPDATE` или `DELETE` $dataReader=$command->query(); // выполнение запроса типа `SELECT` $rows=$command->queryAll(); // возвращает все строки результата запроса $row=$command->queryRow(); // возвращает первую строку результата запроса $column=$command->queryColumn(); // возвращает первый столбец результата запроса $value=$command->queryScalar(); // возвращает значение первого поля первой строки результата запроса ~~~ Обработка результатов запроса ----------------------------- После того как [CDbCommand::query()] создаст экземпляр класса [CDbDataReader], мы можем получить результат запроса построчно путём повторения вызовов метода [CDbDataReader::read()]. Для получения данных строка за строкой можно также использовать [CDbDataReader] в конструкциях `foreach`. ~~~ [php] $dataReader=$command->query(); // многократно вызываем read() до возврата методом значения false while(($row=$dataReader->read())!==false) { … } // используем foreach для построчного обхода данных foreach($dataReader as $row) { … } // получаем все строки разом в виде массива $rows=$dataReader->readAll(); ~~~ > Note|Примечание: Все методы `queryXXX()`, в отличие от [query()|CDbCommand::query], возвращают данные напрямую. Например, метод [queryRow()|CDbCommand::queryRow] возвращает массив, соответствующий первой строке результата запроса. Использование транзакций ------------------------ В случае когда приложение выполняет несколько запросов, каждый из которых что-то пишет или читает из БД, важно удостовериться, что набор запросов выполнен полностью, а не частично. В этой ситуации можно воспользоваться транзакциями, представляющими собой экземпляры класса [CDbTransaction]: - Начало транзакции. - Выполнение запросов по очереди. В этот момент все изменения в базе недоступны извне. - Подтверждение транзакции. Если транзакция проведена успешно, то изменения становятся общедоступны. - При возникновении ошибки в ходе выполнения запросов происходит откат транзакции в начальное состояние. Эту последовательность действий можно реализовать следующим образом: ~~~ [php] $transaction=$connection->beginTransaction(); try { $connection->createCommand($sql1)->execute(); $connection->createCommand($sql2)->execute(); //… прочие SQL-запросы $transaction->commit(); } catch(Exception $e) // в случае возникновения ошибки при выполнении одного из запросов выбрасывается исключение { $transaction->rollback(); } ~~~ Привязка параметров --------------------- Для предотвращения [SQL-инъекций](http://ru.wikipedia.org/wiki/SQL_injection) и повышения производительности при выполнении однотипных SQL-запросов мы можем «подготавливать» SQL-выражения, используя маркеры параметров (placeholders), которые в процессе привязки будут заменяться на реальные значения. Маркеры параметров могут быть именованными (уникальные маркеры) или неименованными (вопросительные знаки). Для замены маркеров на реальные значения нужно вызвать методы [CDbCommand::bindParam()] или [CDbCommand::bindValue()]. Экранировать или заключать в кавычки значения параметров не нужно, используемый драйвер базы данных всё сделает сам. Привязку параметров необходимо осуществить до выполнения SQL-запроса. ~~~ [php] // выражение SQL с двумя именованными маркерами «:username» и «:email» $sql="INSERT INTO tbl_user(username, email) VALUES(:username,:email)"; $command=$connection->createCommand($sql); // заменяем маркер «:username» на соответствующее значение имени пользователя $command->bindParam(":username",$username,PDO::PARAM_STR); // заменяем маркер «:email» на соответствующее значение электронной почты $command->bindParam(":email",$email,PDO::PARAM_STR); $command->execute(); // вставляем следующую строку с новыми параметрами $command->bindParam(":username",$username2,PDO::PARAM_STR); $command->bindParam(":email",$email2,PDO::PARAM_STR); $command->execute(); ~~~ Методы [bindParam()|CDbCommand::bindParam] и [bindValue()|CDbCommand::bindValue] очень похожи. Единственное различие состоит в том, что первый привязывает параметр к ссылке на переменную PHP, а второй — к значению. Для параметров, представляющих большой объем данных, с точки зрения производительности предпочтительнее использовать метод [bindParam()|CDbCommand::bindParam]. Подробнее о привязке параметров можно узнать в [соответствующей документации PHP](http://php.net/manual/en/pdostatement.bindparam.php). Привязка полей ---------------- При получении результатов запроса мы также можем привязать поля таблицы к переменным PHP. Это позволяет автоматически присваивать значения переменным при чтении очередной строки: ~~~ [php] $sql="SELECT username, email FROM tbl_user"; $dataReader=$connection->createCommand($sql)->query(); // привязываем первое поле (username) к переменной $username $dataReader->bindColumn(1,$username); // привязываем второе поле (email) к переменной $email $dataReader->bindColumn(2,$email); while($dataReader->read()!==false) { // переменные $username и $email получают значения полей username и email текущей строки } ~~~ Использование префиксов таблиц ----------------------------- Yii предоставляет встроенную поддержку префиксов таблиц. Префикс таблиц — это строка, предваряющая имена таблиц в текущей подключённой БД. В основном, префиксы используются на виртуальном (shared) хостинге, где к одной БД подключаются несколько приложений, использующих различные префиксы таблиц в целях избежания конфликтов имён. Например, одно приложение использует префикс `tbl_`, а другое — `yii_`. Для использования префикса таблиц установите свойство [CDbConnection::tablePrefix]. Затем в SQL-выражениях используйте `{{TableName}}` для указания имён таблиц, где `TableName` — имя таблицы без префикса. Например, если БД содержит таблицу `tbl_user`, где `tbl_` — это префикс таблиц, то мы можем использовать следующий код для получения списка пользователей: ~~~ [php] $sql='SELECT * FROM {{user}}'; $users=$connection->createCommand($sql)->queryAll(); ~~~