Как настроить смартфоны и ПК. Информационный портал

Установка pdo. Настройка и использование PDO — расширения PHP Data Objects для работы с базами данных


3 июня 2018 Андрей Чернышов Перевод Туториал 1736 0

PDO является акронимом для PHP Data Objects: это PHP расширение для работы с базами данных используя объекты. Одно из его преимуществ лежит в том, что оно не привязано напрямую к определенной базе данных: его интерфейс позволяет получить доступ к нескольким разным средам, включая: MySQL, SQLite, PostgreSQL, Microsoft SQL Server.

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

Создание тестовой базы данных и таблицы

В первую очередь, мы создадим базу данных:

CREATE DATABASE solar_system; GRANT ALL PRIVILEGES ON solar_system.* TO "testuser"@"localhost" IDENTIFIED BY "testpassword";

Мы выдали пользователю testuser все привилегии в базе данных solar_system , используя testpassword для пароля. Теперь давайте создадим таблицу и заполним её какой-нибудь информацией:

USE solar_system; CREATE TABLE planets (id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id), name VARCHAR(10) NOT NULL, color VARCHAR(10) NOT NULL); INSERT INTO planets(name, color) VALUES("earth", "blue"), ("mars", "red"), ("jupiter", "strange");

Описание соединения DSN (Data Source Name)

Теперь, когда у нас есть база данных, мы должны задать DSN . DSN расшифровывается как Data Source Name , и является набором информации, необходимой для подключения к базе данных, DSN имеет форму строки. Синтаксис отличается в зависимости от базы данных, подключение к которой требуется, но так как мы используем MySQL/MariaDB, нам нужно задать следующие:

  • Тип драйвера, используемого для подключения;
  • Имя компьютера-хоста, на котором запущена база данных;
  • Порт для подключения (необязательно);
  • Название базы данных;
  • Кодировка (необязательно).

Формат строки в нашем случае будет таким (мы будем хранить его в переменной $dsn):

$dsn = "mysql:host=localhost;port=3306;dbname=solar_system;charset=utf8";

В первую очередь мы задали database prefix или префикс базы данных. В этом случае, так как мы подключаемся к базе данных типа MySQL/MariaDB, мы используем mysql . Затем мы отделили префикс от остальной строки двоеточием и каждая последующая секция отделена от остальных точкой с запятой.

В следующих двух секциях мы задали hostname , на котором запущена база данных и port используемый для подключения. Если порт не указан, использован будет порт по умолчанию, в данном случае это 3306 . Сразу после database name указывается charset .

Создание PDO объекта

Теперь, когда наш DSN готов, мы приступим к созданию PDO object . Конструктор PDO использует строку DSN как первый параметр, имя пользователя базы данных вторым параметром, пароль – третьим, и необязательный массив настроек – четвертым.

$options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]; $pdo = new PDO($dsn, "testuser", "testpassword", $options);

Настройки так же можно задать и после создания объекта, пользуясь методом SetAttribute() :

$pdo->SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Настройка PDO повидения при ошибках

Давайте взглянем на некоторые опции доступные для PDO::ATTR_ERRMODE . Эти опции крайне важны, потому что они определяют поведение PDO в случае возникновения ошибок. Возможные опции:

PDO::ERRMODE_SILENT

Опция по умолчанию. PDO просто выдаст код ошибки и сообщение об ошибке. Их можно будет получить используя методы errorCode() и errorInfo() .

PDO::ERRMODE_EXCEPTION

Эта опция, на мой взгляд, рекомендуема для использования. С её помощью, помимо выдачи кода ошибки и информации, PDO выдаст исключение PDOException , которое прервет ход выполнения скрипта, а ещё она полезна при PDO transactions (их мы рассмотрим чуть позже).

PDO::ERRMODE_WARNING

С этой опцией, PDO выдаст код ошибки и сообщение о ней, как и при PDO::ERRMODE_SILENT , но еще и покажет предупреждение WARNING , которое не прерывает работу скрипта.

Настройка метода выборки по умолчанию

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

PDO::FETCH_BOTH

При его использовании, полученные результаты будут индексированы и по целым числам, и по названиям столбцов. Использование его в методе для получения ряда из таблицы планет выдаст нам такие результаты:

$stmt = $pdo->query("SELECT * FROM planets"); $results = $stmt->fetch(PDO::FETCH_BOTH); Array ( => 1 => 1 => earth => earth => blue => blue)

PDO::FETCH_ASSOC

С этой константой, результаты будут записаны в ассоциативный массив в котором каждый ключ будет именем столбца, а каждое значение – обозначать определенное значение в ряду:

$stmt = $pdo->query("SELECT * FROM planets"); $results = $stmt->fetch(PDO::FETCH_ASSOC); Array ( => 1 => earth => blue)

PDO::FETCH_NUM

Используя PDO::FETCH_NUM константу мы получим 0-indexed array:

Array ( => 1 => earth => blue)

PDO::FETCH_COLUMN

Эта константа полезна для получения только значений из столбца, а метод вернет все результаты внутри простого одномерного массива. Например, вот такой запрос:

$stmt = $pdo->query("SELECT name FROM planets");

В результате:

Array ( => earth => mars => jupiter)

PDO::FETCH_KEY_PAIR

Эта константа полезна, когда нужно получить значения из двух столбцов. Метод fetchAll() вернет результаты в виде ассоциативного массива. В этом массиве, данные из первого столбца будут указаны в форме ключей, а из второго – в качестве значений:

$stmt = $pdo->query("SELECT name, color FROM planets"); $result = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);

В результате:

Array ( => blue => red => strange)

PDO::FETCH_OBJECT

При использовании константы PDO::FETCH_OBJECT , будет создан anonymous object за каждый полученный ряд. Его (публичные) свойства будут названы также, как и столбцы, а результаты запроса будут использованы в качестве значений. Использование этого метода для того же запроса, что и выше, приведет к следующему результату:

$results = $stmt->fetch(PDO::FETCH_OBJ); stdClass Object ( => earth => blue)

PDO::FETCH_CLASS

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

Class Planet { private $name; private $color; public function setName($planet_name) { $this->name = $planet_name; } public function setColor($planet_color) { $this->color = $planet_color; } public function getName() { return $this->name; } public function getColor() { return $this->color; } }

Не обращайте внимание на простоту кода, лучше займемся осмотром класса Planet, который мы создали: у него в свойствах прописано private и у класса отсутствует конструктор. Теперь же попробуем получить результаты.

Используя fetch() с PDO::FETCH_CLASS необходимо использовать метод setFetchMode() на объект, перед тем как пытаться получить данные, например:

$stmt = $pdo->query("SELECT name, color FROM planets"); $stmt->setFetchMode(PDO::FETCH_CLASS, "Planet");

Мы задаём константу PDO::FETCH_CLASS как первый аргумент метода setFetchMode() и название класса, использовано для создания объекта (в нашем случае «Planet») – вторым аргументом. Теперь запускаем код:

$planet = $stmt->fetch();

Должен получиться объект Planet:

Var_dump($planet); Planet Object ( => earth => blue)

Заметьте, как значения, полученные из запроса, были назначены к соответствующим характеристикам объекта, несмотря на то, что они приватные.

Назначение характеристик после создания объекта

Класс «Planet», не обладал никаким определенным конструктором, так что проблем с назначением характеристик не возникло; но что если у класса есть конструктор, в котором характеристики задаются и изменяются? Так как значения назначены до запуска конструктора, они будут перезаписаны.

PDO помогает предоставить константу FETCH_PROPS_LATE: при её использовании, значения будут назначены после создания объекта. Пример:

Class Planet { private $name; private $color; public function __construct($name = moon, $color = grey) { $this->name = $name; $this->color = $color; } public function setName($planet_name) { $this->name = $planet_name; } public function setColor($planet_color) { $this->color = $planet_color; } public function getName() { return $this->name; } public function getColor() { return $this->color; } }

Мы изменили наш класс Planet, создав конструктор, который возьмет два аргумента: name name и сolor . Эти аргументы имеют базовые значения: moon и gray, что значит, что, если других значений не будет задано, будут установлены эти.

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

$stmt = $pdo->query("SELECT name, color FROM solar_system WHERE name = "earth""); $stmt->setFetchMode(PDO::FETCH_CLASS, "Planet"); $planet = $stmt->fetch();

А теперь рассмотрим объект Planet и проверим, какие значения соответствуют его характеристикам:

Var_dump($planet); object(Planet)#2 (2) { ["name":"Planet":private]=> string(4) "moon" ["color":"Planet":private]=> string(4) "gray" }

Как и ожидалось, полученные из базы данных значения были перезаписаны значениями по умолчанию. Теперь, мы продемонстрируем решение проблем, используя константу FETCH_PROPS_LATE (и тот же запрос, что и предыдущий):

$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Planet"); $planet = $stmt->fetch(); var_dump($planet); object(Planet)#4 (2) { ["name":"Planet":private]=> string(5) "earth" ["color":"Planet":private]=> string(4) "blue" }

Наконец, получен желаемый результат. Но что если у конструктора класса нет базовых значений, и они должны быть заданы? Это уже проще: мы можем задать параметры конструктора в форме массива, как третий аргумент, после имени класса, используя метод setFetchMode() . Например, давайте изменим конструктор:

Class Planet { private $name; private $color; public function __construct($name, $color) { $this->name = $name; $this->color = $color; } [...] }

Аргументы конструктора теперь обязательны, так что мы запускаем:

$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Planet", ["moon", "gray"]);

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

Получение нескольких объектов

Конечно же, возможно получить сразу несколько результатов в форме объектов, или используя метод fetch() , или посредством цикла:

While ($planet = $stmt->fetch()) { // Что-то делам с результатами }

Или получив все результаты сразу. В этом случае, как говорилось ранее, используя метод fetchAll() вам нужно будет указывать fetch режим не перед запуском метода, но в тот момент, когда он запустится:

$stmt->fetchAll(PDO::FETCH_CLASS|PDO_FETCH_PROPS_LATE, "Planet", ["moon", "gray"]);

PDO::FETCH_INTO

При использовании этой константы, PDO не создает новый объект, взамен обновляя характеристики уже существующего, но только если он public или в случае использования метода __set() внутри объекта.

Подготовленные против прямых запросов

У PDO есть два пути работы с запросами: использовать прямые и более надежный - подготовленные.

Прямые запросы

Для использования прямых запросов существует два главных метода: query() и exec() . Первый из них создает объект PDOStatemnt , доступ к которому можно получить через методы fetch() или fetchAll() : если вы используете их в случаях, когда таблица не меняется, таких как SELECT .

Второй метод, взамен, возвращает номер ряда, который был изменен запросом: мы используем его в случаях, которые заменяют ряды, таких как INSERT , DELETE или UPDATE . Прямые запросы должны быть использованы, только в случаях отсутствия переменных в запросах, и в безопасности метода нет никаких сомнений.

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

PDO также поддерживает запросы исполняемые в два шага, подготовленные: они полезны, когда в запросах есть переменные, и более безопасны в целом, поскольку метод prepare() сделает все необходимые действия за нас. Давайте взглянем, как используются переменные. Представьте, что мы хотим вставить характеристики планеты в таблицу Planets . Для начала, подготовим запрос:

$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(?, ?)");

Как и говорилось ранее, мы используем метод prepare() который использует SQL запрос в качестве аргумента, используя временные значения для переменных. Временные значения могут быть двух типов: позиционные и именные.

Позиционные

Используя? позиционные временные значения, код получается более краткий, но мы должны задать данные, которые будут вставлены, в том же порядке, что и имена столбцов, в массиве представленном как аргумент метода execute() :

$stmt->execute([$planet->name, $planet->color]);

Именные

Используя именные временные значения named placeholders , нам не нужен определенный порядок, но мы получим больше кода в результате. При запуске метода execute() , мы должны задать данные в форме ассоциативного массива, в котором каждый ключ – имя использованного временного значения, а ассоциирующееся значения будет тем, что перенесется в запрос. Например, предыдущий запрос станет таким:

$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)"); $stmt->execute(["name" => $planet->name, "color" => $planet->color]);

Методы prepare() и execute() оба могут быть использованы для запросов, которые изменяют или просто получают информацию из базы данных. В первом случае, мы используем fetch методы перечисленные сверху для получения информации, а во втором – используя метод rowCount() .

Методы bindValue() и bindParam()

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

$stmt->bindValue("name", $planet->name, PDO::PARAM_STR);

Мы привязываем значение $planet->name к временному значению:name . Замете, что используя оба метода bindValue() и bindParam() мы можем также задать тип переменной, как третий аргумент, используя подходящую PDO константу, в этом случае PDO::PARAM_STR .

Используя взамен bindParam() мы можем привязать переменную к подходящему временному значению, используемому в подготовке запроса. Заметьте, что в этом случае, переменная связана с reference и её значение будет изменено на временное, только когда запустится метод execute() . Синтаксис такой же, как и в прошлый раз:

$stmt->bindParam("name", $planet->name, PDO::PARAM_STR)

Мы привязали переменную, а не её значение $planet->name к:name ! Как сказано выше, замена произойдет только при запуске метода execute() , так что временное значение будет заменено на значение переменной в тот момент.

PDO Транзакции

Транзакции позволяют сохранить последовательность при запуске множественных запросов. Все запросы выполняются «партиями» и относятся к базе данных, только если они все удачно выполнены. Транзакции не будут работать со всеми базами данных, и не со всеми sql конструкциями, поскольку некоторые из них вызывают проблемы.

В качестве экстремального и странного примера, представьте, что пользователь должен выбрать список планет и каждый раз, когда он делает новый выбор, вам нужно будет удалить предыдущий из базы данных, прежде чем вставлять новый. Что если удаление произойдет, а вставка – нет? Мы получим пользователя без планет! В основном, транзакции применяются так:

$pdo->beginTransaction(); try { $stmt1 = $pdo->exec("DELETE FROM planets"); $stmt2 = $pdo->prepare("INSERT INTO planets(name, color) VALUES (?, ?)"); foreach ($planets as $planet) { $stmt2->execute([$planet->getName(), $planet->getColor()]); } $pdo->commit(); } catch (PDOException $e) { $pdo->rollBack(); }

В первую очередь, метод beginTransaction() в объекте PDO отключает autocommit запроса, затем запросы запускаются в необходимом порядке. В этот момент, если не возникает исключение PDOException запросы автоматически пропускаются через метод commit() , в противном случае – через метод rollBack() транзакции отменяются и autocommit восстанавливается.

Таким образом, при множественных запросах, всегда будет последовательность. Это довольно очевидно, но PDO транзакции могут быть использованы только PDO::ATTR_ERRMODE установлен на PDO::ERRMODE_EXCEPTION .

1. драйвер PDO и PDO_Sqlite включен, по умолчанию, в PHP 5.1.0. Вам может понадобиться выбрать другой драйвер PDO для конкретной базы данных, в этом случае обратитесь к документации по конкретной базе данных драйверов PDO.

Примечание: При построении PDO в качестве общего расширения (shared extension (не рекомендуется)), все PDO драйверы должен быть загружены после PDO самостоятельно.

2. При установке PDO как общего модуля, следует изменить файл php.ini, что бы расширение(extension) PDO загружалось автоматически, работает PHP работает.

Вам также необходимо убедиться, чтобы имелись специфичные драйверы базы данных, и чтоб они были перечислены после pdo.so, так как PDO должен инициализироваться в первую очередь, до загрузки драйверов баз данных PDO.

Если вы запустили PDO для конкретной базы данных расширений статически, вы можете пропустить этот шаг.

3. extension = pdo.so

Установка PDO на системах Windows

1. PDO и все основные драйверы PDO поставляются с PHP как общие расширения(extentions). Для активизации нужно раскомментировать необходимые строки в файле php.ini:

Extension=php_pdo.dll

Примечание: Этот шаг не является необходимым для PHP 5.3 и выше, так как DLL, больше не требуется для PDO.

Extension=php_pdo.dll
extension=php_pdo_firebird.dll
extension=php_pdo_informix.dll
extension=php_pdo_mssql.dll
extension=php_pdo_mysql.dll
extension=php_pdo_oci.dll
extension=php_pdo_oci8.dll
extension=php_pdo_odbc.dll
extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll

Эти библиотеки должны существовать в директории "extension_dir" системы.

Проверяем работу PDO

Просмотрите подключается ли PDO с помощью функции phpinfo().

Phpinfo();

На странице вам нужно найти блок PDO, а также блоки pdo_mysql, pdo_sqlite, и т.д. в зависимости от тех PDO-драйверов которые вы подключали.

Выполните следующий код.

Echo "Доступные драйвера:";
print_r(PDO::getAvailableDrivers());
$pdo = new PDO("sqlite:my.db");
echo "Объект PDO:";
print_r($pdo);

Результатом выполнения должна быть строка:

PDO Object ()

Что означает, что содинение с БД SQLite установлено нормально. Результатом выполнения скрипта будет файл my.db созданный в директории скрипта.

Настройки PDO

Настройки PDO вида pdo.dsn.*

настройки вида pdo.dsn.* - позволяют указать параметры доступа по умолчанию.

Уровень изменения переменных pdo.dsn.* - файл php.ini

В даанной статье рассматривается процесс установки хорошо всем знакомого php extension’а или расширения PHP – pdo_mysql . Без данного расширения, невозможна работа огромного числа программ.
Хотел бы уточнить, что подобная установка доступна только обладателям VDS (Virtual Dedicated Server - Виртуальный выделенный сервер) серверов и требует базовых знаний работы с командной строкой Unix через ssh. Тем же, у кого нет желания/возможности что-либо таким образом устанавливать, следует для начала зайти в панель управления сервером и проверить статус расширения PDO , т.к. оно скорее всего установлено но не включено. Например в панели ISP Manager необходимо зайти в Настройки сервера >> Расширения PHP и отыскать строку pdo_mysql.so . Проверьте статус этого расширения и включите в случае необходимости.

Установку будем проводить на FreeBSD и CentOS.

FreeBSD

Установить pdo_mysql на FreeBSD очень просто, если вы конечно делаете это из портов. Действия по-порядку:

  1. Проверяем не установлено ли уже данное PHP-расширение в системе командой:

    pkg_info | grep php5- pdo_mysql

    Если в ответ пустая строка – переходи к пункту 2.

  2. переходим в директорию /usr/ports/databases/php5-pdo_mysql с помощью команды:

    cd / usr/ ports/ databases/ php5- pdo_mysql

  3. для установки pdo_mysql выполнеям команду:

    make install clean

    дождитесь окончания прочесса и всё – pdo_mysql установлен!

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

    apachectl restart

Linux CentOS

Процесс установки pdo_mysql на CentOS не намного сложнее.

  1. Поочёрёдно выполняем следующие три команды через ssh:

    yum install php- devel php- pear mysql- devel httpd- devel

    pecl install pdo

    PHP_PDO_SHARED= 1 pecl install pdo_mysql

  2. Затем необходимо добавить 2 строки строки в файл php.ini:

В настоящее время я работаю в компании, которая очень любит использовать в проектах на PHP СУБД Oracle, причем иногда версии 11g.

Большая часть разработчиков этой компании работает под ОС Windows. За последний месяц несколько из них решили приобщиться к Linux и поставили себе Ubuntu. По прошествии нескольких дней после установки самой ОС, ребята столкнулись с задачей установки драйверов PHP для работы с СУБД Oracle - OCI8 и PDO_OCI на базе Oracle instant client 11.2, которую не смогли решить самостоятельно.

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

Мануал написан для пользователей Ubuntu Linux, но с некоторыми изменениями подойдет для пользователей большинства Linux"ов.

Подготовка к установке

  1. У вас должна быть возможность выполнять команды под администратором;
  2. У вас должен быть установлен php5 со следующими пакетами (команда установки со списком):
    sudo apt-get install php5 php5-dev php-pear php5-cli
    sudo pecl install pdo
  3. У вас должна быть установлена библиотека libaio1:
    sudo apt-get install libaio1

Установка Oracle instant client

Скачиваем instant client Oracle с официального сайта http://oracle.com для своей архитектуры процессора и ОС.
Для Linux instant client поставляется в двух вариантах:
  • RPM пакет - Linux, CentOS, Fedora, Red Hat Enterprise Linux, Mandriva Linux, SUSE Linux и д.р. у кого есть поддержка RPM;
  • ZIP архив - всем остальным.
Необходимо скачать 2 файла:
  • instantclient-basic - сам Oracle instant client
  • instantclient-sdk - набор библиотек для разработки приложений под Oracle instant client
Также можете скачать:
  • instantclient-sqlplus - SQL*Plus
Создаем директорию, в которой будут лежать файлы Oracle instant client (каталог /opt, зарезервированный для дополнительных пакетов программного обеспечения, хорошо для этого подходит):
sudo mkdir -p /opt/oracle/

Перемещаем скачанные файлы в /opt/oracle и переходим в папку назначения (допустим что вы скачали «zip архивы» в папку «downloads» Вашего пользователя):
sudo mv ~/downloads/instantclient-*.zip /opt/oracle/
cd /opt/oracle/

Разархивируем все скачанные архивы:
sudo unzip instantclient-basic-*-*.zip
sudo unzip instantclient-sdk-*-*.zip
Если вы скачивали SQL*Plus :
sudo unzip instantclient-sqlplus-*-*.zip

В итоге в каталоге /opt/oracle был создан, для Oracle instant client 11.2.0.2.0, каталог instantclient_11_2. Переименуем этот каталог в instantclient (если у вас другая версия/каталог измените команду) и перейдем в него:
sudo mv instantclient_11_2 instantclient
cd instantclient

Далее необходимо создать несколько дополнительных каталогов и символьных ссылок для нормальной работы клиента (обратите внимание на версию и если она у вас другая измените команды):
sudo ln -s /opt/oracle/instantclient/libclntsh.so.* /opt/oracle/instantclient/libclntsh.so
sudo ln -s /opt/oracle/instantclient/libocci.so.* /opt/oracle/instantclient/libocci.so
sudo ln -s /opt/oracle/instantclient/ /opt/oracle/instantclient/lib

Sudo mkdir -p include/oracle/11.2/
cd include/oracle/11.2/
sudo ln -s ../../../sdk/include client
cd -

Sudo mkdir -p lib/oracle/11.2/client
cd lib/oracle/11.2/client
sudo ln -s ../../../ lib
cd -

Создаем конфигурационный файл, в котором будет указан каталог для поиска библиотек Oracle instant client, и подключаем его:
echo /opt/oracle/instantclient/ | sudo tee -a /etc/ld.so.conf.d/oracle.conf
sudo ldconfig

Так как в Ubuntu нет каталога /usr/include/php, а клиент его все равно ищет создадим символьную ссылку на его эквивалент php5:
sudo ln -s /usr/include/php5 /usr/include/php

Устанавливаем OCI8

После всех наших манипуляций расширение oci8 замечательно устанавливается с помощью команды pecl :
sudo pecl install oci8
нас просят ввести путь к Oracle instant client, на что необходимо ответить:
instantclient,/opt/oracle/instantclient

Создаём файл подключения расширения:
echo "; configuration for php oci8 module" | sudo tee /etc/php5/conf.d/oci8.ini
echo extension=oci8.so | sudo tee -a /etc/php5/conf.d/oci8.ini

Устанавливаем PDO_OCI

Для установки PDO_OCI нам сначала необходимо его скачать из репозитория pear .
Обновим список пакетов pear:
sudo pecl channel-update pear.php.net

Скачаем и поместим архив во временную директорию:
sudo mkdir -p /tmp/pear/download/
cd /tmp/pear/download/
sudo pecl download pdo_oci

Извлечем содержимое архива и перейдем к нему:
sudo tar xvf PDO_OCI*.tgz
cd PDO_OCI*

Здесь нам необходимо скорректировать файл config.m4, так как в нем нет данных о нашей версии Oracle instant client, последние изменения датируются 2005 годом. Запускаем любимый редактор и вносим изменения отмеченные "+" (обратите внимание на версию и если она у вас другая измените строчки):
sudo vim config.m4

Далее приведен diff двух файлов:
***************
*** 7,12 ****
--- 7,14 ----
if test -s "$PDO_OCI_DIR/orainst/unix.rgs"; then
PDO_OCI_VERSION=`grep ""ocommon"" $PDO_OCI_DIR/orainst/unix.rgs | sed "s/*/:/g" | cut -d: -f 6 | cut -c 2-4`
test -z "$PDO_OCI_VERSION" && PDO_OCI_VERSION=7.3
+ elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.11.2; then
+ PDO_OCI_VERSION=11.2
elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.10.1; then
PDO_OCI_VERSION=10.1
elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.9.0; then
***************
*** 119,124 ****
--- 121,129 ----
10.2)
PHP_ADD_LIBRARY(clntsh, 1, PDO_OCI_SHARED_LIBADD)
;;
+ 11.2)
+ PHP_ADD_LIBRARY(clntsh, 1, PDO_OCI_SHARED_LIBADD)
+ ;;
*)
AC_MSG_ERROR(Unsupported Oracle version! $PDO_OCI_VERSION)
;;
***************

Подготавливаем окружение для расширения php c помощью команды phpize (обратите внимание на версию, если она у вас другая измените):
sudo phpize

Конфигурируем установщик пакета и устанавливаем пакет (обратите внимание на версию, если она у вас другая измените):
sudo ./configure --with-pdo-oci=instantclient,/opt/oracle/instantclient/,11.2
sudo make
sudo make install

Создаём для него файл подключения:
echo "; configuration for php PDO_OCI module" | sudo tee /etc/php5/conf.d/pdo_oci.ini
echo extension=pdo_oci.so | sudo tee -a /etc/php5/conf.d/pdo_oci.ini

Подводим итоги

Перезапускаем apache и проверяем наличие установленных расширений:
sudo /etc/init.d/apache2 restart
php -m

Заключение

Мануал основан на вот этом посте , который был несколько переработан - исправлены ошибки и внесены дополнения.

Надеюсь, статья будет полезной не только моим коллегам по работе.

Для начала создадим базу данных для этого руководства:

CREATE DATABASE solar_system; GRANT ALL PRIVILEGES ON solar_system.* TO "testuser"@"localhost" IDENTIFIED BY "testpassword";

Пользователю с логином testuser и паролем testpassword предоставили полные права доступа к базе solar_system .

Теперь создадим таблицу и заполним данными, астрономическая точность которых не подразумевается:

USE solar_system; CREATE TABLE planets (id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id), name VARCHAR(10) NOT NULL, color VARCHAR(10) NOT NULL); INSERT INTO planets(name, color) VALUES("earth", "blue"), ("mars", "red"), ("jupiter", "strange");

Описание соединения

Теперь, когда создана база, определим DSN () - сведения для подключения к базе, представленные в виде строки. Синтаксис описания отличается в зависимости от используемой СУБД. В примере работаем с MySQL/MariaDB, поэтому указываем:

  • имя хоста, где расположена СУБД;
  • порт (необязательно, если используется стандартный порт 3306);
  • имя базы данных;
  • кодировку (необязательно).

Строка DSN в этом случае выглядит следующим образом:

$dsn = "mysql:host=localhost;port=3306;dbname=solar_system;charset=utf8";

Первым указывается database prefix . В примере - mysql . Префикс отделяется от остальной части строки двоеточием, а каждый следующий параметр - точкой с запятой.

Создание PDO-объекта

Теперь, когда строка DSN готова, создадим PDO-объект. Конструктор на входе принимает следующие параметры:

  1. Строку DSN.
  2. Имя пользователя, имеющего доступ к базе данных.
  3. Пароль этого пользователя.
  4. Массив с дополнительными параметрами (необязательно).
$options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]; $pdo = new PDO($dsn, "testuser", "testpassword", $options);

Дополнительные параметры можно также определить после создания объекта с помощью метода SetAttribute:

$pdo->SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Определение метода выборки по умолчанию

PDO::DEFAULT_FETCH_MODE - важный параметр, который определяет метод выборки по умолчанию. Указанный метод используется при получении результата выполнения запроса.

PDO::FETCH_BOTH

Режим по умолчанию. Результат выборки индексируется как номерами (начиная с 0), так и именами столбцов:

$stmt = $pdo->query("SELECT * FROM planets"); $results = $stmt->fetch(PDO::FETCH_BOTH);

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

Array ( => 1 => 1 => earth => earth => blue => blue)

PDO::FETCH_ASSOC

Результат сохраняется в ассоциативном массиве, в котором ключ - имя столбца, а значение - соответствующее значение строки:

$stmt = $pdo->query("SELECT * FROM planets"); $results = $stmt->fetch(PDO::FETCH_ASSOC);

В результате получим:

Array ( => 1 => earth => blue)

PDO::FETCH_NUM

При использовании этого режима результат представляется в виде массива, индексированного номерами столбцов (начиная с 0):

Array ( => 1 => earth => blue)

PDO::FETCH_COLUMN

Этот вариант полезен, если нужно получить перечень значений одного поля в виде одномерного массива, нумерация которого начинается с 0. Например:

$stmt = $pdo->query("SELECT name FROM planets");

В результате получим:

Array ( => earth => mars => jupiter)

PDO::FETCH_KEY_PAIR

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

$stmt = $pdo->query("SELECT name, color FROM planets"); $result = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);

В результате получим:

Array ( => blue => red => strange)

PDO::FETCH_OBJECT

При использовании PDO::FETCH_OBJECT для каждой извлеченной строки создаётся анонимный объект. Его общедоступные (public) свойства - имена столбцов выборки, а результаты запроса используются в качестве их значений:

$stmt = $pdo->query("SELECT name, color FROM planets"); $results = $stmt->fetch(PDO::FETCH_OBJ);

В результате получим:

StdClass Object ( => earth => blue)

PDO::FETCH_CLASS

В этом случае, как и в предыдущем, значения столбцов становятся свойствами объекта. Однако требуется указать существующий класс, который будет использоваться для создания объекта. Рассмотрим это на примере. Для начала создадим класс:

Class Planet { private $name; private $color; public function setName($planet_name) { $this->name = $planet_name; } public function setColor($planet_color) { $this->color = $planet_color; } public function getName() { return $this->name; } public function getColor() { return $this->color; } }

Обратите внимание, что у класса Planet закрытые (private) свойства и нет конструктора. Теперь выполним запрос.

Если используется метод fetch с PDO::FETCH_CLASS , перед отправкой запроса на получение данных нужно применить метод setFetchMode:

$stmt = $pdo->query("SELECT name, color FROM planets"); $stmt->setFetchMode(PDO::FETCH_CLASS, "Planet");

Первый параметр, который передаем методу setFetchMode , - константа PDO::FETCH_CLASS . Второй параметр - имя класса, который будет использоваться при создании объекта. Теперь выполним:

$planet = $stmt->fetch(); var_dump($planet);

В результате получим объект Planet:

Planet Object ( => earth => blue)

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

Определение свойств после выполнения конструктора

В классе Planet нет явного конструктора, поэтому проблем при назначении свойств не будет. При наличии у класса конструктора, в котором свойство было назначено или изменено, они будут перезаписаны.

При использовании константы FETCH_PROPS_LATE значения свойств будут присваиваться после выполнения конструктора:

Class Planet { private $name; private $color; public function __construct($name = moon, $color = grey) { $this->name = $name; $this->color = $color; } public function setName($planet_name) { $this->name = $planet_name; } public function setColor($planet_color) { $this->color = $planet_color; } public function getName() { return $this->name; } public function getColor() { return $this->color; } }

Мы изменили класс Planet , добавив конструктор, который принимает на входе два аргумента: name (имя) и color (цвет). Значения этих полей по умолчанию: moon (луна) и gray (серый) соответственно.

Если не использовать FETCH_PROPS_LATE , при создании объекта свойства будут перезаписаны значениями по умолчанию. Проверим это. Сначала выполним запрос:

$stmt = $pdo->query("SELECT name, color FROM solar_system WHERE name = "earth""); $stmt->setFetchMode(PDO::FETCH_CLASS, "Planet"); $planet = $stmt->fetch(); var_dump($planet);

В результате получим:

Object(Planet)#2 (2) { ["name":"Planet":private]=> string(4) "moon" ["color":"Planet":private]=> string(4) "gray" }

Как и ожидалось, извлеченные из базы данных значения перезаписаны. Теперь рассмотрим решение задачи с помощью FETCH_PROPS_LATE (запрос аналогичный):

$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Planet"); $planet = $stmt->fetch(); var_dump($planet);

В результате получим то, что нужно:

Object(Planet)#4 (2) { ["name":"Planet":private]=> string(5) "earth" ["color":"Planet":private]=> string(4) "blue" }

Если у конструктора класса нет значений по умолчанию, а они нужны, параметры конструктора задаются при вызове метода setFetchMode третьим аргументом в виде массива. Например:

Class Planet { private $name; private $color; public function __construct($name, $color) { $this->name = $name; $this->color = $color; } [...] }

Аргументы конструктора обязательны, поэтому выполним:

$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Planet", ["moon", "gray"]);

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

Получение нескольких объектов

Множественные результаты извлекаются в виде объектов с помощью метода fetch внутри цикла while:

While ($planet = $stmt->fetch()) { // обработка результатов }

Или путём выборки всех результатов сразу. Во втором случае используется метод fetchAll , причём режим указывается в момент вызова:

$stmt->fetchAll(PDO::FETCH_CLASS|PDO_FETCH_PROPS_LATE, "Planet", ["moon", "gray"]);

PDO::FETCH_INTO

При выборе этого варианта выборки PDO не создаёт новый объект, а обновляет свойства существующего. Однако это возможно только для общедоступных (public) свойств или при использовании в объекте «магического» метода __set .

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

В PDO два способа выполнения запросов:

  • прямой, который состоит из одного шага;
  • подготовленный, который состоит из двух шагов.

Прямые запросы

Существует два метода выполнения прямых запросов:

  • query используется для операторов, которые не вносят изменения, например SELECT . Возвращает объект PDOStatemnt , из которого с помощью методов fetch или fetchAll извлекаются результаты запроса;
  • exec используется для операторов вроде INSERT , DELETE или UPDATE . Возвращает число обработанных запросом строк.

Прямые операторы используются только в том случае, если в запросе отсутствуют переменные и есть уверенность, что запрос безопасен и правильно экранирован.

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

PDO поддерживает подготовленные запросы (prepared statements), которые полезны для защиты приложения от : метод prepare выполняет необходимые экранирования.

Рассмотрим пример. Требуется вставить свойства объекта Planet в таблицу Planets . Сначала подготовим запрос:

$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(?, ?)");

Используем метод prepare , который принимает как аргумент SQL-запрос с псевдопеременными (placeholders). Псевдопеременные могут быть двух типов: неименнованые и именованные.

Неименованные псевдопеременные

Неименованные псевдопеременные (positional placeholders) отмечаются символом? . Запрос в результате получается компактным, но требуется предоставить значения для подстановки, размещенные в том же порядке. Они передаются в виде массива через метод execute:

$stmt->execute([$planet->name, $planet->color]);

Именованные псевдопеременные

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

$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)"); $stmt->execute(["name" => $planet->name, "color" => $planet->color]);

Методы prepare и execute используются как при выполнении запросов на изменение, так и при выборке.

А информацию о количестве обработанных строк при необходимости предоставит метод rowCount .

Управление поведением PDO при ошибках

Параметр выбора режима ошибок PDO::ATTR_ERRMODE используется для определения поведения PDO в случае ошибок. Доступно три варианта: PDO::ERRMODE_SILENT , PDO::ERRMODE_EXCEPTION и PDO::ERRMODE_WARNING .

PDO::ERRMODE_SILENT

Вариант по умолчанию. PDO просто запишет информацию об ошибке, которую помогут получить методы errorCode и errorInfo .

PDO::ERRMODE_EXCEPTION

Это предпочтительный вариант, при котором в дополнение к информации об ошибке PDO выбрасывает исключение (PDOException). Исключение прерывает выполнение скрипта, что полезно при использовании транзакций PDO. Пример приведён при описании транзакций.

PDO::ERRMODE_WARNING

В этом случае PDO также записывает информацию об ошибке. Поток выполнения скрипта не прерывается, но выдаются предупреждения.

Методы bindValue и bindParam

Для подстановки значений в запросе можно также использовать методы bindValue и bindParam . Первый связывает значение переменной с псевдопеременной, которая использована при подготовке запроса:

$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)"); $stmt->bindValue("name", $planet->name, PDO::PARAM_STR);

Связали значение переменной $planet->name с псевдопеременной:name . Обратите внимание, что при использовании методов bindValue и bindParam как третий аргумент указывается тип переменной, используя соответствующие константы PDO. В примере - PDO::PARAM_STR .

Метод bindParam привязывает переменную к псевдопеременной. В этом случае переменная связана с псевдопеременной ссылкой, а значение будет подставлено в запрос только после вызова метода execute . Рассмотрим на примере:

$stmt->bindParam("name", $planet->name, PDO::PARAM_STR);

Транзакции в PDO

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

$pdo->beginTransaction(); try { $stmt1 = $pdo->exec("DELETE FROM planets"); $stmt2 = $pdo->prepare("INSERT INTO planets(name, color) VALUES (?, ?)"); foreach ($planets as $planet) { $stmt2->execute([$planet->getName(), $planet->getColor()]); } $pdo->commit(); } catch (PDOException $e) { $pdo->rollBack(); }

Метод beginTransaction отключает автоматическое выполнение запросов, а внутри конструкции try-catch запросы выполняются в нужном порядке. Если не возникнет исключений PDOException , запросы выполнятся с помощью метода commit . В противном случае откатятся с помощью метода rollback , а автоматическое выполнение запросов восстановится.

Таким образом появилась согласованность выполнения запросов. Очевидно, что для этого параметру PDO::ATTR_ERRMODE необходимо установить значение PDO::ERRMODE_EXCEPTION .

Заключение

Теперь, когда работа с PDO описана, отметим его основные преимущества:

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

Александр Наливайко , переводчик

Как Яндекс использует ваши данные и машинное обучение для персонализации сервисов - .

Лучшие статьи по теме