0 follower

Объекты доступа к данным (DAO)

Объекты доступа к данным (DAO) предоставляют общий API для доступа к данным, хранящимся в различных СУБД. Это позволяет без проблем поменять используемую СУБД на любую другую без необходимости изменения кода, использующего DAO для доступа к данным.

Yii DAO является надстройкой над PHP Data Objects (PDO) - расширением, которое предоставляет унифицированный доступ к данным многих популярных СУБД, таких, как MySQL, PostgreSQL. Для использования Yii DAO необходимо, чтобы были установлены расширение PDO и драйвер PDO, соответствующий используемой базе данных (например, PDO_MYSQL).

Yii DAO состоит из четырех основных классов:

  • CDbConnection: представляет подключение к базе данных;
  • CDbCommand: представляет выражение SQL и его исполнение;
  • CDbDataReader: преставляет однонаправленный поток строк из данных, возвращаемых в ответ на SQL-запрос;
  • CDbTransaction: представляет транзакции базы данных.

Ниже мы проиллюстрируем использование Yii DAO.

1. Соединение с базой данных

Для установления соединения с базой необходимо создать экземпляр класса CDbConnection и активировать его. Дополнительную информацию, необходимую для подключения к БД (хост, порт, имя пользователя, пароль и пр.), указываем в DSN. В случае возникновения ошибки в процессе соединения с БД, будет вызвано исключение (например, неверный DSN или неправильные имя пользователя/пароль).

$connection=new CDbConnection($dsn,$username,$password);
// устанавливаем соединение. Можно попробовать try...catch возможных исключений
$connection->active=true;
......
$connection->active=false;  // close connection

Формат DSN зависит от используемого драйвера PDO. Как правило, DSN состоит из имени драйвера PDO, за которым следует двоеточие, а далее указываются параметры подключения, соответствующие синтаксису подключения используемого драйвера. Подробнее с этим можно ознакомиться в документации по PDO. Ниже представлены несколько основных форматов 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, то мы можем использовать его в качестве компонента. Для этого нужно настроить компонент db в конфигурации приложения следующим образом:

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. Этот способ дает нам возможность использования одного подключения к БД в любом месте кода.

2. Исполнение SQL-выражений

Когда соединение с БД установлено, мы можем выполнять SQL-выражения, используя CDbCommand. Для этого создаем экземпляр CDbCommand путем вызова CDbConnection::createCommand() с указанием SQL-выражения:

$command=$connection->createCommand($sql);
// если необходимо, SQL-выражение можно обновить:
// $command->text=$newSQL;

Существует два варианта исполнения SQL-выражения с использованием CDbCommand:

  • execute(): выполняет SQL-выражения типа INSERT, UPDATE и DELETE. В случае успешного исполнения, возвращает количество обработанных строк;

  • query(): выполняет SQL-выражения, возвращающие наборы данных, например, типа SELECT. В случае успешного исполнения, возвращает экземпляр класса CDbDataReader, через который доступны полученные данные. Для удобства также реализованы методы queryXXX(), возвращающие результаты запроса напрямую.

Если в процессе выполнения SQL-выражения возникнет ошибка, будет вызвано исключение.

$rowCount=$command->execute();   // исполнение выражения типа `INSERT`, `UPDATE` и `DELETE`
$dataReader=$command->query();   // исполнение выражения типа `SELECT`
$rows=$command->queryAll();      // запрос и возврат всех строк результата
$row=$command->queryRow();       // запрос и возврат первой строки результата
$column=$command->queryColumn(); // запрос и возврат первого столбца результата
$value=$command->queryScalar();  // запрос и возврат первого поля в первой строке

3. Обработка результатов запроса

После того, как CDbCommand::query() создает экземпляр класса CDbDataReader, мы можем получить данные построчно путем повторного вызова метода CDbDataReader::read(). Для получения данных строка за строкой можно также использовать CDbDataReader в конструкциях foreach.

$dataReader=$command->query();
// многократно вызываем read() до возврата методом значения false
while(($row=$dataReader->read())!==false) { ... }
// используем foreach для построчного обхода данных
foreach($dataReader as $row) { ... }
// получаем все строки разом в одном массиве
$rows=$dataReader->readAll();

Примечание: Все методы queryXXX(), в отличие от query(), возвращают все данные напрямую. Например, метод queryRow() возвращает массив с первой строкой результата запроса.

4. Использование транзакций

В случае, когда приложение выполняет несколько запросов, каждый из которых что-то пишет или читает из БД, важно удостовериться, что набор запросов выполнен полностью, а не наполовину. В этой ситуации можно воспользоваться транзакциями, представляемыми экземпляром класса CDbTransaction:

  • начало транзакции;
  • исполнение запросов по очереди, все изменения данных в БД недоступны вне базы;
  • подтверждение транзакции, если результат транзакции положительный, то изменения становятся доступны;
  • если возникает ошибка при выполнении какого-либо запроса, то вся транзация откатывается назад.

Эту последовательность действий можно реализовать следующим образом:

$transaction=$connection->beginTransaction();
try
{
    $connection->createCommand($sql1)->execute();
    $connection->createCommand($sql2)->execute();
    //.... прочие SQL запросы
    $transaction->commit();
}
catch(Exception $e) // в случае ошибки при выполнении запроса выбрасывается исключение
{
    $transaction->rollBack();
}

5. Связывание параметров

С целью избежания SQL-инъекций и улучшения производительности при выполнении повторно используемых SQL-выражений, мы можем "подготавливать" SQL-выражение с маркерами параметров (placeholder), которые в процессе привязки будут заменяться на реальные значения.

Маркеры параметров могут быть именованными (уникальные маркеры) или неименованными (вопросительные знаки). Для замены маркеров на реальные значения нужно вызвать CDbCommand::bindParam() или CDbCommand::bindValue(). Добавлять кавычки к параметрам нет необходимости, т.к. используемый драйвер базы данных все сделает сам. Связывание параметров должно быть завершено до исполнения SQL-выражения.

// выражение SQL с двумя маркерами ":username" и ":email"
$sql="INSERT INTO users(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() и bindValue() очень похожи, единственное различие состоит в том, что первый привязывает параметр к ссылке на переменную PHP, а второй - к значению. Для параметров, представляющих большой объем данных, с точки зрения производительности предпочтительнее использовать метод bindParam().

Подробнее о связывании параметров можно узнать в соответствующей документации PHP.

6. Связывание полей

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

$sql="SELECT username, email FROM users";
$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 текущей строки
}