# Zend_Db_Adapter

Zend_Db и его родственные классы предоставляют простой интерфейс к базам данных SQL в Zend Framework. Zend_Db_Adapter является базовым классом, который должен использоваться для подключения приложения PHP к СУРБД. Существуют различные классы адаптеров для наиболее часто используемых СУРБД.

Адаптеры Zend_Db создают мост между общим интерфейсом и расширениями PHP от конкретных поставщиков для того, чтобы можно было единовременно писать приложения на PHP и затем переключаться между различными СУРБД с наименьшими усилиями.

Интерфейс класса адаптера подобен интерфейсу расширения PHP Data Objects (PDO). Zend_Db предоставляет классы адаптеров к драйверам PDO для следующих популярных СУРБД:

- IBM DB2 и Informix Dynamic Server (IDS), с использованием расширения pdo_ibm

- MySQL, с использованием расширения pdo_mysql

- Microsoft SQL Server, с использованием расширения pdo_mssql

- Oracle, с использованием расширения pdo_oci

- PostgreSQL, с использованием расширения pdo_pgsql

- SQLite, с использованием расширения pdo_sqlite

Кроме этого, Zend_Db предоставляет классы адаптеров, использующие расширения PHP для следующих распространенных СУРБД:

- MySQL, с использованием расширения mysqli

- Oracle, с использованием расширения oci8

- IBM DB2, с использованием расширения ibm_db2

- Firebird/Interbase, с использованием расширения php_interbase

Все адаптеры Zend_Db используют расширения PHP. Вы должны иметь включенным соответствующее расширение в вашей среде PHP для использования адаптера Zend_Db. Например, если вы используете какой-либо из адаптеров PDO Zend_Db, то нужно включить как расширение PDO, так и драйвер PDO для используемой вами СУРБД.

## Соединение с БД с использованием адаптера

Этот раздел описывает, как создавать экземпляр адаптера БД. Это соответствует созданию соединения с сервером СУРБД из вашего приложения PHP.

### Использование конструктора адаптера Zend_Db

Вы можете создавать экземпляр адаптера с помощью его конструктора. Конструктор адаптера принимает единственный аргумент, который является массивом параметров, использующихся для описания соединения.

#### Использование конструктора адаптера

    $db = new Zend_Db_Adapter_Pdo_Mysql(array(
        'host'     => '127.0.0.1',
        'username' => 'webuser',
        'password' => 'xxxxxxxx',
        'dbname'   => 'test'
    ));

### Использование фабрики Zend_Db

Вместо непосредственного использования конструктора адаптера можно создавать экземпляры адаптера, применяя статический метод `Zend_Db::factory()`. Этот метод динамически загружает файл класса адаптера, используя Zend_Loader::loadClass().

Первым аргументом является строка с базовым именем класса адаптера. Например, строка 'Pdo_Mysql' соответствует классу Zend_Db_Adapter_Pdo_Mysql. Вторым аргументом является тот же массив параметров, который вы должны были бы передать конструктору адаптера.

#### Использование метода-фабрики адаптеров

    // Нам не нужно использовать следующее предложение, поскольку
    // файл Zend_Db_Adapter_Pdo_Mysql будет загружен через
    // метод-фабрику Zend_Db

    // require_once 'Zend/Db/Adapter/Pdo/Mysql.php';

    // Автоматически загружает класс Zend_Db_Adapter_Pdo_Mysql
    // и создает его экземпляр.
    $db = Zend_Db::factory('Pdo_Mysql', array(
        'host'     => '127.0.0.1',
        'username' => 'webuser',
        'password' => 'xxxxxxxx',
        'dbname'   => 'test'
    ));

Если вы создали собственный класс, расширяющий Zend_Db_Adapter_Abstract, но не дали ему имя, начинающееся с префикса "Zend_Db_Adapter", то можете использовать метод `factory()` для загрузки своего адаптера, указав ведущую часть имени класса адаптера с помощью ключа 'adapterNamespace' в массиве параметров.

#### Использование метода-фабрики для пользовательского класса адаптера

    // Нам не нужно загружать файл с классом адаптера,
    // поскольку он будет загружен через метод-фабрику Zend_Db

    // Автоматически загружает класс MyProject_Db_Adapter_Pdo_Mysql
    // и создает его экземпляр.
    $db = Zend_Db::factory('Pdo_Mysql', array(
        'host'             => '127.0.0.1',
        'username'         => 'webuser',
        'password'         => 'xxxxxxxx',
        'dbname'           => 'test',
        'adapterNamespace' => 'MyProject_Db_Adapter'
    ));

### Использование Zend_Config с фабрикой Zend_Db

Опционально вы можете заменить оба аргумента метода `factory()` объектом типа Zend_Config.

Если первым аргументом является объект конфигурации, то ожидается, что он имеет свойство с именем `adapter`, содержащее строку с базовой частью имени класса адаптера. Опционально объект может содержать свойство с именем `params` и "подсвойствами", соответствующими параметрам адаптера.

#### Использование метода-фабрики адаптеров с объектом Zend_Config

В примере ниже объект Zend_Config создан из массива. Вы можете также загружать данные из внешнего файла с помощью Zend_Config_Ini или Zend_Config_Xml.

    $config = new Zend_Config(
        array(
            'database' => array(
                'adapter' => 'Mysqli',
                'params' => array(
                    'dbname' => 'test',
                    'username' => 'webuser',
                    'password' => 'secret',
                )
            )
        )
    );

    $db = Zend_Db::factory($config->database);

Второй аргумент метода `factory()` может быть ассоциативным массивом, содержащим элементы, которые соответствуют параметрам адаптера. Этот аргумент является опциональным. Если первым аргументом является объект типа Zend_Config, то предполагается, что он содержит все необходимые параметры, и второй аргумент игнорируется.

### Параметры адаптера

Список ниже описывает общие параметры, которые распознаются классами адаптеров Zend_Db.

- host: строка, содержащая имя хоста или IP сервера БД. Если база данных размещается на том же хосте, что и приложение PHP, то вы можете использовать 'localhost' или '127.0.0.1'.

- username: идентификатор учетной записи для аутентификации подключения к серверу СУРБД.

- password: пароль учетной записи для аутентификации подключения к серверу СУРБД.

- dbname: имя экземпляра БД на сервере СУРБД.

- port: некоторые сервера СУРБД поддерживают сетевые соединения через указанный администратором порт. Данный параметр дает возможность задать порт, с которым приложение PHP будет устанавливать соединение, он должен соответствовать порту, установленному в сервере СУРБД.

- options: этот параметр является ассоциативным массивом опций, общих для всех классов Zend_Db_Adapter.

- driver_options: этот параметр является ассоциативным массивом дополнительных опций, специфических для данного расширения. Одним из типичных случаев использования этого параметра является установка атрибутов для драйвера PDO.

- adapterNamespace: имя начальной части имени класса для адаптера вместо 'Zend_Db_Adapter'. Используйте его, если нужно использовать метод `factory()` для загрузки "неZend'овского" класса адаптера БД.

#### Передача фабрике опции перевода регистра (case-folding)

Вы можете установить эту опцию посредством константы `Zend_Db::CASE_FOLDING`. Она соответствует атрибуту `ATTR_CASE` в драйверах PDO и IBM DB2, и переводит строковые ключи в результатах запроса в требуемый регистр. Эта опция принимает значения `Zend_Db::CASE_NATURAL` (значение по умолчанию), `Zend_Db::CASE_UPPER` и `Zend_Db::CASE_LOWER`.

    $options = array(
        Zend_Db::CASE_FOLDING => Zend_Db::CASE_UPPER
    );

    $params = array(
        'host'           => '127.0.0.1',
        'username'       => 'webuser',
        'password'       => 'xxxxxxxx',
        'dbname'         => 'test',
        'options'        => $options
    );

    $db = Zend_Db::factory('Db2', $params);

#### Передача фабрике опции автоматического заключения в кавычки

Вы можете задавать эту опцию через константу `Zend_Db::AUTO_QUOTE_IDENTIFIERS`. Если ее значение установлено в `TRUE` (по умолчанию), то идентификаторы, такие, как имена таблиц, имена столбцов и даже псевдонимы, разграничиваются во всем генерируемом объектом адаптера синтаксисе SQL. Это делает возможным использование идентификаторов, содержащих ключевые слова SQL и специальные символы. Если его значение равно `FALSE`, то автоматическое заключение в кавычки не производится. Если требуется заключение идентификаторов в кавычки, то оно должно производиться самостоятельно с использованием метода `quoteIdentifier()`.

    $options = array(
        Zend_Db::AUTO_QUOTE_IDENTIFIERS => false
    );

    $params = array(
        'host'           => '127.0.0.1',
        'username'       => 'webuser',
        'password'       => 'xxxxxxxx',
        'dbname'         => 'test',
        'options'        => $options
    );

    $db = Zend_Db::factory('Pdo_Mysql', $params);

#### Передача фабрике опций драйвера PDO

    $pdoParams = array(
        PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
    );

    $params = array(
        'host'           => '127.0.0.1',
        'username'       => 'webuser',
        'password'       => 'xxxxxxxx',
        'dbname'         => 'test',
        'driver_options' => $pdoParams
    );

    $db = Zend_Db::factory('Pdo_Mysql', $params);

    echo $db->getConnection()
            ->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY);

### Управление отложенными соединениями

Создание экземпляра класса адаптера не приведет к немедленному соединению с сервером СУРБД. Адаптер сохраняет параметры соединения и производит подключение, когда нужно произвести первый запрос к БД. Это значит, что само по себе создание объекта адаптера производится быстро и занимает мало ресурсов. Вы можете создавать экземпляр адаптера даже в том случае, если не уверены в том, что текущий запрос к вашему приложению требует каких-либо действий с БД.

Если нужно принудительно создать соединение с СУРБД, то используйте метод `getConnection()`. Этот метод возвращает объект соединения в представлении соответствующего расширения PHP для баз данных. Например, если вы используете какой-либо класс адаптера для драйверов PDO, то `getConnection()` возвращает объект PDO после того, как он будет инициирован им в качестве "живого" соединения с определенной БД.

Принудительное создание соединения может быть полезным, когда вы хотите отлавливать все исключения, которые бросаются из-за неправильных параметров доступа или других ошибок соединения с сервером СУРБД. Эти исключения не бросаются до тех пор, пока не создается соединение, поэтому можно упростить код приложения, обрабатывая исключения в одном месте вместо того, чтобы делать это каждый раз, когда производится первый запрос к БД.

#### Обработка исключений при соединении

    try {
        $db = Zend_Db::factory('Pdo_Mysql', $parameters);
        $db->getConnection();
    } catch (Zend_Db_Adapter_Exception $e) {
        // возможно, неправильные параметры соединения или СУРБД не запущена
    } catch (Zend_Exception $e) {
        // возможно, попытка загрузки требуемого класса адаптера потерпела неудачу
    }

## Пример базы данных

В документации к классам Zend_Db мы использовали набор простых таблиц для того, чтобы проиллюстрировать использование классов и их методов. Эти таблицы должны были хранить информацию для отслеживания ошибок в проекте разработки ПО. База данных содержит четыре таблицы:

- accounts (учетные записи) хранит информацию о всех пользователях системы отслеживания ошибок.

- products (продукты) хранит информацию обо всех программных продуктах, для которых могут отслеживаться ошибки.

- bugs (ошибки) хранит информацию об ошибках, включая текущее состояние ошибки, лицо, сообщившее об ошибке, лицо, которому назначено устранение ошибки и лицо, которому назначена проверка устранения ошибки.

- bugs_products хранит связи между ошибками и продуктами. Она реализует связь "многие-ко-многим", потому что одна ошибка может относиться к нескольким продуктам, и один продукт может иметь множество ошибок.

Следующий псевдокод для определения данных SQL описывает таблицы в этой базе данных. Это таблицы интенсивно используются в unit-тестах для Zend_Db.

    CREATE TABLE accounts (
      account_name      VARCHAR(100) NOT NULL PRIMARY KEY
    );

    CREATE TABLE products (
      product_id        INTEGER NOT NULL PRIMARY KEY,
      product_name      VARCHAR(100)
    );

    CREATE TABLE bugs (
      bug_id            INTEGER NOT NULL PRIMARY KEY,
      bug_description   VARCHAR(100),
      bug_status        VARCHAR(20),
      reported_by       VARCHAR(100) REFERENCES accounts(account_name),
      assigned_to       VARCHAR(100) REFERENCES accounts(account_name),
      verified_by       VARCHAR(100) REFERENCES accounts(account_name)
    );

    CREATE TABLE bugs_products (
      bug_id            INTEGER NOT NULL REFERENCES bugs,
      product_id        INTEGER NOT NULL REFERENCES products,
      PRIMARY KEY       (bug_id, product_id)
    );

Также обратите внимание, что таблица `bugs` содержит несколько внешних ключей, ссылающихся на таблицу `accounts`. Для одной ошибки эти внешние ключи могут ссылаться на разные строки в таблице `accounts`.

Диаграмма ниже иллюстрирует физическую модель данных для этой базы данных.

## Чтение результатов запроса

Этот раздел описывает методы класса адаптера, с помощью которых вы можете производить запросы SELECT и извлекать их результаты.

### Извлечение полного набора результатов

Вы можете запустить запрос SELECT и извлечь его результаты за один шаг, используя метод `fetchAll()`.

Первым аргументом этого метода должна быть строка, содержащая оператор SELECT. Также первым аргументом может быть объект класса Zend_Db_Select. Адаптер автоматически преобразует этот объект в строковое представление оператора SELECT.

Вторым аргументом `fetchAll()` должен быть массив значений для подстановки вместо меток заполнения (placeholders) в операторе SQL.

#### Использование fetchAll()

    $sql = 'SELECT * FROM bugs WHERE bug_id = ?';

    $result = $db->fetchAll($sql, 2);

### Изменение режима извлечения

По умолчанию `fetchAll()` возвращает массив строк, каждая из которых представлена ассоциативным массивом. Ключами ассоциативных массивов являются имена столбцов или псевдонимы столбцов, определенные в данном запросе на выборку.

Вы можете задать другой стиль извлечения результатов, используя метод `setFetchMode()`. Поддерживаемые режимы идентифицируются константами:

- Zend_Db::FETCH_ASSOC: возвращает данные в массиве ассоциативных массивов. Ключами массива являются имена столбцов в виде строк. Это режим извлечения, используемый по умолчанию в классах Zend_Db_Adapter.

  Обратите внимание, что если ваш список выборки содержит столбцы с одинаковыми именами, например, если они из разных таблиц в JOIN-е, то в ассоциативном массиве может быть только одна запись для этого имени. Если вы используете режим FETCH_ASSOC, то должны задавать псевдонимы столбцов в своем запросе SELECT для того, чтобы для всех столбцов были свои уникальные ключи.

  По умолчанию эти строки возвращаются так же, как если бы они были возвращены драйвером БД. Как правило, это синтаксис столбцов для данного сервера СУРБД. Вы можете задать регистр для этих строк, используя опцию. `Zend_Db::CASE_FOLDING`. Задавайте его во время инстанцирования адаптера. См. .

- Zend_Db::FETCH_NUM: возвращает данные в массиве массивов. Массив индексируется целочисленными значениями в соответствии с позицией данного поля в списке выборки запроса.

- Zend_Db::FETCH_BOTH: возвращает данные в массиве массивов. Ключами массива являются как строки, так и целочисленные значения. Число элементов в массиве получается в два раза больше, чем если бы использовались FETCH_ASSOC или FETCH_NUM.

- Zend_Db::FETCH_COLUMN: возвращает данные в массиве значений. Значение в каждом массиве является значением, возвращенным из одного столбца результата выборки. По умолчанию это первый столбец, индексированный нулем.

- Zend_Db::FETCH_OBJ: возвращает данные в массиве объектов. По умолчанию используется встроенный в PHP класс stdClass. Столбцы результата выборки доступны в качестве открытых свойств этого объекта.

#### Использование setFetchMode()

    $db->setFetchMode(Zend_Db::FETCH_OBJ);

    $result = $db->fetchAll('SELECT * FROM bugs WHERE bug_id = ?', 2);

    // $result является массивом объектов
    echo $result[0]->bug_description;

### Извлечение результатов выборки в виде ассоциативного массива

Метод `fetchAssoc()` возвращает данные в массиве ассоциативных массивов безотносительно того, какое значение вы установили для режима извлечения.

#### Использование fetchAssoc()

    $db->setFetchMode(Zend_Db::FETCH_OBJ);

    $result = $db->fetchAssoc('SELECT * FROM bugs WHERE bug_id = ?', 2);

    // $result является массивом ассоциативных массивов, независимо
    // от установленного режима извлечения
    echo $result[0]['bug_description'];

### Извлечение единственного столбца из результатов выборки

Метод `fetchCol()` возвращает данные в массиве значений безотносительно того, какое значение вы установили для режима извлечения. Он возвращает только первый столбец из возвращенных запросом. Все остальные столбцы, возвращенные запросом, не учитываются. Если вам нужно извлечь столбец, отличный от первого, то см. .

#### Использование fetchCol()

    $db->setFetchMode(Zend_Db::FETCH_OBJ);

    $result = $db->fetchCol(
        'SELECT bug_description, bug_id FROM bugs WHERE bug_id = ?', 2);

    // содержит bug_description; bug_id не возвращается
    echo $result[0];

### Извлечение пар ключ-значение из результатов выборки

Метод `fetchPairs()` возвращает данные в массиве пар ключ-значение, Ключ ассоциативного массива берется из первого столбца, возвращенного запросом SELECT. Значение берется из второго столбца, возвращенного запросом SELECT. Все остальные столбцы, возвращенные запросом, не учитываются.

Вы должны строить запрос SELECT так, чтобы первый из возвращенных столбцов имел уникальные значения. Если в нем имеются повторяющиеся значения, то записи в ассоциативном массиве будут перезаписываться.

#### Использование fetchPairs()

    $db->setFetchMode(Zend_Db::FETCH_OBJ);

    $result = $db->fetchPairs('SELECT bug_id, bug_status FROM bugs');

    echo $result[2];

### Извлечение единственной строки из результатов выборки

Метод `fetchRow()` возвращает данные с использованием текущего режима извлечения, но возвращает только первую строку из результатов выборки.

#### Использование fetchRow()

    $db->setFetchMode(Zend_Db::FETCH_OBJ);

    $result = $db->fetchRow('SELECT * FROM bugs WHERE bug_id = 2');

    // обратите внимание, что $result - единственный объект, а не массив объектов
    echo $result->bug_description;

### Извлечение единственного скалярного значения из результатов выборки

Метод `fetchOne()` является как бы комбинацией методов `fetchRow()` и `fetchCol()` - он возвращает значение первого столбца в первой строке из результатов выборки. Таким образом, он возвращает одно скалярное значение, а не массив или объект.

#### Использование fetchOne()

    $result = $db->fetchOne('SELECT bug_status FROM bugs WHERE bug_id = 2');

    // это единственное строковое значение
    echo $result;

## Изменение данных в БД

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

### Добавление данных

Вы можете добавлять новые строки в таблицы в своей базе данных, используя метод `insert()`. Первым аргументом этого метода является строка с именем таблицы, а вторым аргументом - ассоциативный массив с именами столбцов и соответствующими им значениями.

#### Добавление в таблицу

    $data = array(
        'created_on'      => '2007-03-22',
        'bug_description' => 'Something wrong',
        'bug_status'      => 'NEW'
    );

    $db->insert('bugs', $data);

Те столбцы, которые не были включены в массив данных, не передаются базе данных. Таким образом, они следуют тем же правилам, что и SQL-оператор INSERT: если столбец имеет предложение DEFAULT, то он принимает это значение в созданной строке, иначе остается в состоянии NULL.

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

Иногда бывает необходимо, чтобы часть значений в массиве данных трактовалась как SQL-выражения, в этом случае они не должны заключаться в кавычки. По умолчанию все данные, переданные в виде строк, трактуются как строковые литералы. Для того, чтобы указать, что данное значение является SQL-выражением (а значит, не должно заключаться в кавычки), передавайте его в массиве данных в виде объекта типа Zend_Db_Expr вместо простой строки.

#### Добавление выражений в таблицу

    $data = array(
        'created_on'      => new Zend_Db_Expr('CURDATE()'),
        'bug_description' => 'Something wrong',
        'bug_status'      => 'NEW'
    );

    $db->insert('bugs', $data);

### Получение сгенерированного значения

Некоторые СУРБД поддерживают автоинкремент первичных ключей. Таблица, описанная определенным образом, автоматически генерирует значение первичного ключа во время добавления новой строки. Возвращаемое методом `insert()` значение не является последним добавленным идентификатором, потому что таблица может не иметь автоинкрементных столбцов. Вместо этого возвращаемое значение является количеством затронутых строк (обычно 1).

Если ваша таблица определена с автоинкрементным первичным ключом, то вы можете вызывать метод `lastInsertId()` после добавления. Этот метод возвращает последнее значение, сгенерированное в области видимости текущего соединения с БД.

#### Использование lastInsertId() для автоинкрементного ключа

    $db->insert('bugs', $data);

    // возвращает последнее значение, сгенерированное автоинкрементным столбцом
    $id = $db->lastInsertId();

Некоторые СУРБД поддерживают объекты последовательностей (sequence object), которые генерируют уникальные значения для использования в качестве значений первичных ключей. Для поддержки последовательностей `lastInsertId()` принимает два необязательных строковых аргумента. Эти аргументы служат для передачи имен таблицы и столбца, при этом предполагается, что вы следуете соглашению, по которому имя последовательности состоит из имен таблицы и столбца, для которых эта последовательность генерирует значения, и суффикса "_seq". Это соглашение основано на используемом системой PostgreSQL при именовании последовательностей для столбцов SERIAL. Например, таблица "bugs" с первичным ключом "bug_id" должна использовать последовательность с именем "bugs_bug_id_seq".

#### Использование lastInsertId() для последовательности

    $db->insert('bugs', $data);

    // возвращает последнее значение, сгенерированное
    // последовательностью 'bugs_bug_id_seq'
    $id = $db->lastInsertId('bugs', 'bug_id');

    // альтернативно, возвращает последнее значение, сгенерированное
    // последовательностью 'bugs_seq'.
    $id = $db->lastInsertId('bugs');

Если имя вашего объекта последовательности не следует этому соглашению по именованию, то используйте метод `lastSequenceId()`. Этот метод принимает один строковой аргумент, через который передается точное имя последовательности

#### Использование lastSequenceId()

    $db->insert('bugs', $data);

    // возвращает последнее значение, сгенерированное
    // последовательностью 'bugs_id_gen'.
    $id = $db->lastSequenceId('bugs_id_gen');

Для тех СУРБД, которые не поддерживают последовательности, включая MySQL, Microsoft SQL Server и SQLite, аргументы метода lastInsertId() игнорируются, и возвращается самое последнее значение, сгенерированное для любой таблицы через оператор INSERT в течение данного соединения. Для этих типов СУРБД метод lastSequenceId() всегда будет возвращать `NULL`.

#### Почему не используется "SELECT MAX(id) FROM table"?

Иногда этот запрос возвращает последнее значение первичного ключа, добавленное в таблицу. Однако этот способ небезопасен в условиях, когда несколько клиентов добавляют записи в базу данных. Может случиться (и должно происходить в конечном итоге) так, что другой клиент добавляет другую строку в короткий промежуток времени между добавлением строки, производимым вашим приложением-клиентом БД, и вашим запросом для получения значения MAX(id). Таким образом, это возвращаемое значение не будет соответствовать добавленной вами строке, вместо этого оно будет соответствовать строке, добавленной другим клиентом. Нет способа определить, когда это происходит.

Использование высокого уровня изоляции транзакций, такого, как "repeatable read", может уменьшить этот риск, но некоторые СУРБД не поддерживают требуемую для этого изоляцию транзакций, либо намеренно используется более низкий уровень изоляции транзакций в приложении.

Использование выражения наподобие "MAX(id)+1" для генерации нового значения первичного ключа тоже небезопасно, так как два клиента могут сделать этот запрос одновременно, и оба будут использовать одно и то же полученное значение для своей последующей операции INSERT.

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

### Обновление данных

Вы можете обновлять строки в таблице БД, используя метод `update()` адаптера. Этот метод принимает три аргумента: первый является имением таблицы, второй - ассоциативным массивом столбцов, которые требуется изменить, и значений, которые требуется присвоить этим столбцам.

Значения в массиве данных интерпретируются как строковые константы. Информацию об использовании выражений SQL в массиве данных см. в разделе .

Третий аргумент является строкой, содержащей выражение SQL, которое используется в качестве условия, при выполнении которого строка должна изменяться. Значения и идентификаторы в этом аргументе не заключаются в кавычки и не экранируются. Вы ответственны за то, чтобы все динамическое содержимое было безопасным образом включено в эту строку. Информацию о методах, которые помогут вам в этом, см. в разделе .

Возвращаемое значение является числом строк, затронутых в операции обновления.

#### Обновление строк

    $data = array(
        'updated_on'      => '2007-03-23',
        'bug_status'      => 'FIXED'
    );

    $n = $db->update('bugs', $data, 'bug_id = 2');

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

Если вы передадите массив строк в качестве третьего аргумента, то эти строки будут объединены как термы выражения, разделенные операторами `AND`.

#### Обновление строк с использованием массива выражений

    $data = array(
        'updated_on'      => '2007-03-23',
        'bug_status'      => 'FIXED'
    );

    $where[] = "reported_by = 'goofy'";
    $where[] = "bug_status = 'OPEN'";

    $n = $db->update('bugs', $data, $where);

    // Результирующий SQL:
    //  UPDATE "bugs" SET "update_on" = '2007-03-23', "bug_status" = 'FIXED'
    //  WHERE ("reported_by" = 'goofy') AND ("bug_status" = 'OPEN')

### Удаление данных

Вы можете удалять строки из таблицы БД, используя метод `delete()`. Этот метод принимает два аргумента, первый из них является строкой с именем таблицы.

Второй аргумент является строкой, содержащей выражение SQL, который используется в качестве условия, при выполнении которого строка удаляется. Значения и идентификаторы в этом аргументе не заключаются в кавычки и не экранируются. Вы ответственны за то, чтобы весь динамический контент был безопасным образом включен в эту строку. Информацию о методах, которые помогут вам в этом, см. в разделе .

Возвращаемое значение является числом строк, задействованных в операции удаления.

#### Удаление строк

    $n = $db->delete('bugs', 'bug_id = 3');

Если вы опустите второй аргумент, то в результате все строки в таблице БД будут удалены.

Если вы передадите массив строк в качестве второго аргумента, то эти строки будут объединены как термы выражения, разделенные операторами `AND`.

## Заключение в кавычки значений и идентификаторов

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

    $name = "O'Reilly";
    $sql = "SELECT * FROM bugs WHERE reported_by = '$name'";

    echo $sql;
    // SELECT * FROM bugs WHERE reported_by = 'O'Reilly'

Еще серьезнее риск того, что такие ошибки в коде могут быть целенаправленно использованы тем, кто пытается получить управление вашим веб-приложением. Если он может указать значение переменной PHP, используя параметры HTTP или другой механизм, то может заставить ваши SQL-запросы выполнять действия, для которых они не предназначены - например, возвращение данных, на чтение которых лицо не имеет прав. Это серьезное и широко распространенное нарушение безопасности приложения, известное под названием "SQL-инъекции" (см. http://ru.wikipedia.org/wiki/Инъекция_SQL).

Класс адаптера Zend_Db предоставляет удобные функции для того, чтобы уменьшить уязвимость приложения к SQL-инъекциям. Решение состоит в том, чтобы экранировать специальные символы, такие, как кавычки в значениях PHP, до того, как они будут включены в строки запросов SQL. Это защищает как от случайных, так и от целенаправленных манипуляций строками SQL через переменные PHP, содержащие специальные символы.

### Использование `quote()`

Метод `quote()` принимает единственный аргумент - скалярное строковое значение. Он возвращает значение с специальными символами, экранированными соответствующим образом для используемой вами СУРБД, и окруженным ограничителями строковых значений. Стандартным ограничителем строковых значений в SQL является одинарная кавычка (`'`).

#### Использование quote()

    $name = $db->quote("O'Reilly");
    echo $name;
    // 'O\'Reilly'

    $sql = "SELECT * FROM bugs WHERE reported_by = $name";

    echo $sql;
    // SELECT * FROM bugs WHERE reported_by = 'O\'Reilly'

Обратите внимание, что возвращаемое методом `quote()` значение включает в себя окружающие кавычки. Этим метод отличается от некоторых функций, которые экранируют специальные символы, но не добавляют кавычки, например, mysql_real_escape_string().

Данные могут требовать или не требовать заключения в кавычки в зависимости от того, в каком контексте типа данных SQL они используются. Например, в некоторых СУРБД целочисленное значение не должно заключаться в кавычки, если оно сравнивается со столбцом или выражением целочисленного типа. Другими словами, следующий запрос является ошибочным в некоторых реализациях SQL, если столбец `intColumn` имеет целочисленный тип данных `INTEGER`.

    SELECT * FROM atable WHERE intColumn = '123'

Вы можете использовать необязательный второй аргумент метода `quote()` для избирательного заключения в кавычки тех типов данных SQL, которые вы укажете.

#### Использование quote() с указанием типа SQL

    $value = '1234';
    $sql = 'SELECT * FROM atable WHERE intColumn = '
         . $db->quote($value, 'INTEGER');

Каждый класс Zend_Db_Adapter имеет закодированные имена типов данных SQL для соответствующих СУРБД. Вы можете также использовать константы `Zend_Db::INT_TYPE`, `Zend_Db::BIGINT_TYPE` и `Zend_Db::FLOAT_TYPE` для написания еще более независимого от типа используемой СУРБД кода.

Zend_Db_Table автоматически указывает типы SQL для метода `quote()` при генерации SQL-запросов, ссылающихся на ключевые столбцы таблицы.

### Использование `quoteInto()`

Наиболее типичным случаем использования операции заключения в кавычки является добавление переменной PHP в выражение или оператор SQL. Вы можете использовать метод `quoteInto()` для того, чтобы выполнить это за один шаг. Этот метод принимает два аргумента: первый аргумент является строкой, содержащей символ метки заполнения (`?`), а второй аргумент - значением или переменной PHP, которая должна быть подставлена вместо этой метки заполнения.

Символ метки заполнения одинаковый в многих СУРБД для позиционных параметров, но метод `quoteInto()` только эмулирует параметры запроса. Этот метод просто добавляет значение в строку, экранируя специальные символы и заключая его в кавычки. В случае настоящих параметров запроса сохраняется разделение между строкой SQL и параметрами, поскольку строка запроса анализируется сервером СУРБД.

#### Использование quoteInto()

    $sql = $db->quoteInto("SELECT * FROM bugs WHERE reported_by = ?", "O'Reilly");

    echo $sql;
    // SELECT * FROM bugs WHERE reported_by = 'O\'Reilly'

Вы можете использовать опциональный третий параметр метода `quoteInto()` для указания типа данных SQL. Числовые типы данных не заключаются в кавычки, остальные заключаются.

#### Использование quoteInto() с указанием типа SQL

    $sql = $db->quoteInto("SELECT * FROM bugs WHERE bug_id = ?", '1234', 'INTEGER');

    echo $sql;
    // SELECT * FROM bugs WHERE reported_by = 1234

### Использование `quoteIdentifier()`

Значения являются не единственной частью синтаксиса SQL, которая может изменяться. Если вы используете переменные PHP для имен таблиц, столбцов и других идентификаторов в своих операторах SQL, то эти строки тоже следует заключать в кавычки. По умолчанию идентификаторы в SQL следуют тем же правилам синтаксиса, что есть в PHP и других языках программирования. Например, идентификаторы не должны содержать пробелы, определенные знаки препинания, специальные символы или международные символы. Также в синтаксисе SQL зарезервированы некоторые слова, и они не должны использоваться в качестве идентификаторов.

Тем не менее, в SQL есть возможность, которая называется идентификаторы с ограничителями (delimited identifiers), она дает большие возможности выбора идентификаторов. Если вы заключите идентификатор SQL в кавычки требуемого типа, то можете использовать те идентификаторы, которые были бы недопустимыми без кавычек. Идентификаторы с ограничителями могут содержать пробелы, знаки препинания и международные символы. Вы можете также использовать зарезервированные слова SQL, если заключите их в ограничители идентификаторов.

`quoteIdentifier()` работает так же, как `quote()`, но он применяет символы ограничителей идентификаторов к строке в соответствии с типом используемой СУРБД. Например, стандартный SQL использует двойные кавычки (`"`) в качестве ограничителей идентификаторов и большинство типов СУРБД использует именно их. MySQL по умолчанию использует обратные кавычки (```). Метод `quoteIdentifier()` также экранирует специальные символы в строковом аргументе.

#### Использование quoteIdentifier()

    // мы можем иметь имя таблицы,
    // которое является зарезервированным в SQL словом
    $tableName = $db->quoteIdentifier("order");

    $sql = "SELECT * FROM $tableName";

    echo $sql
    // SELECT * FROM "order"

Идентификаторы с ограничителями в SQL являются чувствительными к регистру, в отличие от не заключенных в кавычки. Поэтому, если вы используете идентификаторы с ограничителями, то должны использовать в точности то же написание идентификаторов, как и в схеме БД, включая регистр букв.

В большинстве случаев, когда SQL генерируется в классах Zend_Db, все идентификаторы по умолчанию автоматически заключаются в ограничители. Вы можете изменить это поведение с помощью опции `Zend_Db::AUTO_QUOTE_IDENTIFIERS`. Указывайте ее при инстанцировании объекта адаптера. См. .

## Управление транзакциями

Базы данных описывают транзакции как логические единицы работы, которые могут фиксироваться или откатываться как одно изменение, даже если они затрагивают несколько таблиц. Все запросы к БД выполняются в контексте транзакций, даже если драйвер баз данных работает с ними неявным образом. Это называется режимом автоматической фиксации, в котором драйвера БД создают транзакции для каждого выполняемого SQL-оператора. По умолчанию все классы адаптеров Zend_Db функционируют в режиме автоматической фиксации.

Вы можете также задавать начало и конец транзакции, и таким образом контролировать число SQL-запросов в группе, которая фиксируется (или откатывается) как одна операция. Используйте метод `beginTransaction()` для инициирования транзакции. Последующие SQL-операторы будут выполняться в контексте этой транзакции до тех пор, пока вы не завершите ее явным образом.

Для завершения транзакции используйте методы `commit()` или `rollBack()`. Метод `commit()` помечает изменения, произведенные в течение данной транзакции, как зафиксированные, это означает, что результаты этих изменений будут видны в запросах, выполняемых в других транзакциях.

Метод `rollBack()` делает обратное - он не учитывает изменения, произведенные в течение транзакции. Изменения будут эффективно отменены, и состояние данных вернется к тому, в котором они были до того, как была начата транзакция. Тем не менее, откат транзакции не повлияет на изменения, произведенные другими транзакциями, запущенными в это же время.

После того, как вы завершите транзакцию, `Zend_Db_Adapter` вернется в режим автоматической фиксации до того, как вы не вызовете `beginTransaction()` снова.

### Управление транзакциями для обеспечения согласованности данных

    // Старт транзакции явным образом
    $db->beginTransaction();

    try {
        // Попытка произвести один или несколько запросов
        $db->query(...);
        $db->query(...);
        $db->query(...);

        // Если все запросы были произведены успешно, то транзакция фиксируется,
        // и все изменения фиксируются одновременно
        $db->commit();

    } catch (Exception $e) {
        // Если какой-либо из этих запросов прошел неудачно, то вся транзакция
        // откатывается, при этом все изменения отменяются, даже те, которые были
        // произведены успешно.
        // Таким образом, все изменения либо фиксируются, либо не фиксируется вместе.
        $db->rollBack();
        echo $e->getMessage();
    }

## Листинг и описание таблиц

Метод `listTables()` возвращает массив имен всех таблиц в текущей базе данных.

Метод `describeTable()` возвращает ассоциативный массив метаданных таблицы. Указывайте имя таблицы в качестве первого аргумента этого метода. Второй аргумент является опциональным, и обозначает схему, в которой существует эта таблица.

Ключами возвращаемого ассоциативного массива являются имена столбцов таблицы. Значения, соответствующие этим столбцам, также являются ассоциативными массивами со следующими ключами и значениями:

### Поля метаданных, возвращаемых методом describeTable()

Ключ | Тип | Описание
--- | --- | ---
SCHEMA_NAME | (string) | Имя схемы БД, в которой находится эта таблица.
TABLE_NAME | (string) | Имя таблицы, которой принадлежит данный столбец.
COLUMN_NAME | (string) | Имя столбца
COLUMN_POSITION | (integer) | Порядковый номер столбца в таблице.
DATA_TYPE | (string) | Имя типа данных столбца, используемое в данной СУРБД
DEFAULT | (string) | Значение по умолчанию, если есть.
NULLABLE | (boolean) | TRUE, если столбец допускает значение NULL, иначе FALSE.
LENGTH | (integer) | Длина или значение столбца, сообщаемое СУРБД.
SCALE | (integer) | Масштаб для типа данных NUMERIC или DECIMAL.
PRECISION | (integer) | Точность для типа данных NUMERIC или DECIMAL.
UNSIGNED | (boolean) | TRUE, если целочисленный тип объявлен как UNSIGNED (беззнаковое число).
PRIMARY | (boolean) | TRUE, если столбец является частью первичного ключа этой таблицы.
PRIMARY_POSITION | (integer) | Порядковый номер (начинается с 1) данного столбца в первичном ключе.
IDENTITY | (boolean) | TRUE, если данный столбец использует автоматически генерируемые значения.

Если таблица, соответствующая заданным имени таблицы и имени схемы (опционально), не существует, то `describeTable()` возвращает пустой массив.

### Как поле метаданных IDENTITY соотносится с типом СУРБД

Поле метаданных IDENTITY было выбрано в качестве "идиоматического" термина для представления связи с суррогатными ключами. Это поле обычно знакомо под следующими именами:

- `IDENTITY` - DB2, MSSQL

- `AUTO_INCREMENT` - MySQL

- `SERIAL` - PostgreSQL

- `SEQUENCE` - Oracle

## Закрытие соединения

Обычно нет необходимости в том, чтобы закрывать соединение с БД. PHP автоматически очищает все ресурсы в конце запроса. Расширения PHP для баз данных спроектированы таким образом, чтобы они закрывали соединение, когда удаляется ссылка на объект ресурса.

Тем не менее, если у вас есть скрипт PHP длительного времени выполнения, который инициирует множество соединений с БД, то может потребоваться закрывать соединения, чтобы избежать снижения производительности сервера СУРБД. Вы можете использовать метод адаптера `closeConnection()` для явного закрытия лежащего в основе соединения с БД.

### Закрытие соединения с БД

    $db->closeConnection();

### Поддерживает ли Zend_Db постоянные соединения?

Использование постоянных соединений не поддерживается или рекомендуется в Zend_Db.

Использование постоянных соединений может привести к избытку неиспользуемых соединений на сервере СУРБД, что приносит больше проблем, чем дает выигрыша в производительности, достигаемого путем уменьшения накладных расходов на установку соединений.

Соединения с БД имеют свое состояние, т.е. некоторые объекты на сервере СУРБД существуют в области видимости сессии. Примером являются блокировки, пользовательские переменные, временные таблицы и информация о последних выполненных запросах, такая, как количество затронутых строк и последнее сгенерированное значение. Если вы используете постоянные соединения, то ваше приложение может получать неверные или привилегированные данные, созданные в предыдущем PHP-запросе.

## Запуск других операторов БД

Может потребоваться получить прямой доступ к объекту соединения в том виде, в котором он предоставляется расширением PHP для баз данных. Некоторые из этих расширений могут предоставлять функционал, который не поддерживается методами Zend_Db_Adapter_Abstract.

Например, все операторы SQL, запускаемые через Zend_Db, подготавливаются перед выполнением. Однако некоторый функционал баз данных несовместим с подготовленными операторами. Операторы DDL, такие, как CREATE и ALTER, не могут подготавливаться в MySQL. Также операторы SQL не дают выигрыша от кэширования запросов MySQL в версиях MySQL до 5.1.17.

Большинство расширений PHP для баз данных предоставляет метод для выполнения операторов SQL без их подготовки. Например, в PDO таким методом является `exec()`. Вы можете обратиться напрямую к объекту соединения в расширении PHP, используя getConnection().

### Запуск неподготовленного оператора в адаптере PDO

    $result = $db->getConnection()->exec('DROP TABLE bugs');

Так же вы можете получить доступ к другим методам или свойствам, специфическим для данного расширения. Тем не менее, следует учитывать, что, делая это, вы можете ограничить ваше приложение интерфейсом, предоставляемым расширением для определенной СУРБД.

В будущих версиях Zend_Db будет возможность добавить точки входа методов для функционала, который является общим для поддерживаемых расширений PHP. Это не нарушит обратную совместимость.

## Примечания к отдельным адаптерам

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

### IBM DB2

- Для установки этого адаптера через метод factory() используйте строку 'Db2'.

- Этот адаптер использует PHP-расширение ibm_db2.

- IBM DB2 поддерживает как последовательности, так и автоинкрементные ключи. Поэтому аргументы для `lastInsertId()` являются опциональными. Если вы не передадите аргументы, то адаптер вернет последнее значение, сгенерированное для автоинкрементного ключа. Если вы передадите аргументы, то адаптер вернет последнее значение, сгенерированное последовательностью, имя которой удовлетворяет соглашению 'таблица_имя_seq'.

### MySQLi

- Для установки этого адаптера через метод factory() используйте строку 'Mysqli'.

- Этот адаптер использует PHP-расширение mysqli.

- MySQL не поддерживает последовательности, поэтому `lastInsertId()` игнорирует переданные аргументы и всегда возвращает последнее значение, сгенерированное для автоинкрементного ключа. Метод `lastSequenceId()` возвращает `NULL`.

### Oracle

- Для установки этого адаптера через метод factory() используйте строку 'Oracle'.

- Этот адаптер использует PHP-расширение oci8.

- Oracle не поддерживает автоинкрементные ключи, поэтому вы должны указывать имя последовательности для `lastInsertId()` или `lastSequenceId()`.

- Расширение Oracle не поддерживает позиционные параметры. Вы должны использовать именованные параметры.

- На данный момент опция `Zend_Db::CASE_FOLDING` не поддерживается адаптером Oracle. Для того, чтобы применять эту опцию с Oracle, вам нужно использовать адаптер PDO OCI.

### PDO для IBM DB2 и Informix Dynamic Server (IDS)

- Для установки этого адаптера через метод factory() используйте строку 'Pdo_Ibm'.

- Этот адаптер использует PHP-расширения pdo и pdo_ibm.

- Вы должны использовать расширение PDO_IBM версии не ниже 1.2.2. Если вы используете более раннюю версию этого расширения, то должны обновить расширение PDO_IBM из PECL.

### PDO Microsoft SQL Server

- Для установки этого адаптера через метод factory() используйте строку 'Pdo_Mssql'.

- Этот адаптер использует PHP-расширения pdo и pdo_mssql.

- Microsoft SQL Server не поддерживает последовательности, поэтому `lastInsertId()` игнорирует переданные аргументы и всегда возвращает последнее значение, сгенерированное для автоинкрементного ключа. Метод `lastSequenceId()` возвращает `NULL`.

- Zend_Db_Adapter_Pdo_Mssql устанавливает `QUOTED_IDENTIFIER ON` сразу после соединения с сервером баз данных. Это заставляет драйвер использовать стандартные символы-ограничители идентификаторов (`"`) вместо квадратных скобок, которые SQL Server использует в качестве ограничителей идентификаторов.

- Вы можете указывать `pdoType` в качестве ключа в массиве опций. Возможными значениями могут быть "mssql" (по умолчанию), "dblib", "freetds" или "sybase". Эта опция влияет на префикс DSN, который используется адаптером, когда строится строка DSN. "Freetds" и "sybase" подразумевают префикс "sybase:", который используется для набора библиотек FreeTDS. Более подробную информацию о префиксах, используемых в этих драйверах, читайте на http://www.php.net/manual/en/ref.pdo-dblib.connection.php.

### PDO MySQL

- Для установки этого адаптера через метод factory() используйте строку 'Pdo_Mysql'.

- Этот адаптер использует PHP-расширения pdo и pdo_mysql.

- MySQL не поддерживает последовательности, поэтому `lastInsertId()` игнорирует переданные аргументы и всегда возвращает последнее значение, сгенерированное для автоинкрементного ключа. Метод `lastSequenceId()` возвращает `NULL`.

### PDO Oracle

- Для установки этого адаптера через метод factory() используйте строку 'Pdo_Oci'.

- Этот адаптер использует PHP-расширения pdo и pdo_oci.

- Oracle не поддерживает автоинкрементные ключи, поэтому вы должны указывать имя последовательности для `lastInsertId()` или `lastSequenceId()`.

### PDO PostgreSQL

- Для установки этого адаптера через метод factory() используйте строку 'Pdo_Pgsql'.

- Этот адаптер использует PHP-расширения pdo и pdo_pgsql.

- PostgreSQL поддерживает как последовательности, так и автоинкрементные ключи. Поэтому аргументы для `lastInsertId()` являются опциональными. Если вы не передадите аргументы, то адаптер вернет последнее значение, сгенерированное для автоинкрементного ключа. Если вы передадите аргументы, то адаптер вернет последнее значение, сгенерированное последовательностью, имя которой удовлетворяет соглашению 'таблица_имя_seq'.

### PDO SQLite

- Для установки этого адаптера через метод factory() используйте строку 'Pdo_Sqlite'.

- Этот адаптер использует PHP-расширения pdo и pdo_sqlite.

- SQLite не поддерживает последовательности, поэтому `lastInsertId()` игнорирует переданные аргументы и всегда возвращает последнее значение, сгенерированное для автоинкрементного ключа. Метод `lastSequenceId()` возвращает `NULL`.

- Для того, чтобы соединится с базой данных SQLite2, указывайте `'sqlite2'=>true` в массиве параметров при создании экземпляра адаптера Pdo_Sqlite.

- Для соединения с базой данных SQLite в памяти указывайте `'dbname'=>':memory:'` в массиве параметров при создании экземпляра адаптера Pdo_Sqlite.

- Старые версии драйвера SQLite для PHP могут не поддерживать команды PRAGMA, необходимые для обеспечения использования коротких имен столбцов в результатах. Если имеются проблемы с тем, что результаты возвращаются с ключами в виде "tablename.columnname", когда производится запрос с объединением таблиц, то следует обновить PHP до текущей версии.

### Firebird/Interbase

- Этот адаптер использует PHP-расширение php_interbase.

- Firebird/interbase не поддерживает автоинкрементные ключи, поэтому вы должны указывать имя последовательности для `lastInsertId()` или `lastSequenceId()`.

- На данный момент опция `Zend_Db::CASE_FOLDING` не поддерживается адаптером Firebird/interbase. Не заключенные в кавычки идентификаторы автоматически возвращаются в верхнем регистре..