Как настроить и использовать PDO (PHP Data Objects) для доступа к базе. Установка расширений OCI8 и PDO_OCI для PHP5 Назначение характеристик после создания объекта

Windows 8

В даанной статье рассматривается процесс установки хорошо всем знакомого 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:


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 .

В настоящее время я работаю в компании, которая очень любит использовать в проектах на 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-инъекциям;
  • поддерживаются транзакции, которые помогают сохранить целостность данных и согласованность запросов при параллельной работе пользователей.

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

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












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

Пример правильного соединения:

$host = "127.0.0.1" ;
$db = "test" ;
$user = "root" ;
$pass = "" ;
$charset = "utf8" ;

$dsn = "mysql:host= $host ;dbname= $db ;charset= $charset " ;
$opt = [
PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION ,
PDO :: ATTR_DEFAULT_FETCH_MODE => PDO :: FETCH_ASSOC ,
PDO :: ATTR_EMULATE_PREPARES => false ,
];
$pdo = new PDO ($dsn , $user , $pass , $opt );

Что здесь происходит?

В $dsn задается тип БД, с которым будем работать (mysql), хост, имя базы данных и чарсет.
- затем идут имя пользователя и пароль
- после которого задается массив опций, про который ни в одном из руководств не пишут.

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

Плюс очень удобно задать FETCH_MODE по умолчанию, чтобы не писать его в КАЖДОМ запросе, как это очень любят делать прилежные хомячки.
Также здесь можно задавать режим pconnect-а, эмуляции подготовленных выражений и много других страшных слов.

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

Для выполнения запросов можно пользоваться двумя методами.
Если в запрос не передаются никакие переменные, то можно воспользоваться функцией query(). Она выполнит запрос и вернёт специальный объект - PDO statement. Очень грубо можно его сравнить с mysql resource, который возвращала mysql_query(). Получить данные из этого объекта можно как традиционным образом, через while, так и через foreach(). Также можно попросить вернуть полученные данные в особом формате, о чем ниже.
$stmt = $pdo -> query ("SELECT name FROM users" );
while ($row = $stmt -> fetch ())
{
}

Если же в запрос передаётся хотя бы одна переменная, то этот запрос в обязательном порядке должен выполняться только через подготовленные выражения . Что это такое? Это обычный SQL запрос, в котором вместо переменной ставится специальный маркер - плейсхолдер. PDO поддерживает позиционные плейсхолдеры (?), для которых важен порядок передаваемых переменных, и именованные (:name), для которых порядок не важен. Примеры:
$sql = ;
$sql = ;

Чтобы выполнить такой запрос, сначала его надо подготовить с помощью функции prepare(). Она также возвращает PDO statement, но ещё без данных. Чтобы их получить, надо исполнить этот запрос, предварительно передав в него переменные. Передать можно двумя способами:
Чаще всего можно просто выполнить метод execute(), передав ему массив с переменными:
$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = ?" );
$stmt -> execute (array($email ));

$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = :email" );
$stmt -> execute (array("email" => $email ));
Как видно, в случае именованных плейсхолдеров в execute() должен передаваться массив, в котором ключи должны совпадать с именами плейсхолдеров.

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

После этого можно использовать PDO statement теми же способами, что и выше. Например, через foreach:
$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = ?" );
$stmt ->
foreach ($stmt as $row )
{
echo $row [ "name" ] . "\n" ;
}

ВАЖНО: Подготовленные выражения - основная причина использовать PDO, поскольку это единственный безопасный способ выполнения SQL запросов, в которых участвуют переменные.

Также prepare() / execute() могут использоваться для многократного выполнения единожды подготовленного запроса с разными наборами данных. На практике это бывает нужно чрезвычайно редко, и особого прироста в скорости не приносит. Но на случай, если понадобится делать много однотипных запросов, то можно писать так:

$data = array(
1 => 1000,
5 => 300,
9 => 200,
);

$stmt = $pdo -> prepare ("UPDATE users SET bonus = bonus + ? WHERE id = ?" );
foreach ($data as $id => $bonus )
{
$stmt -> execute ([ $bonus , $id ]);
}

Здесь мы один раз подготавливаем запрос, а затем много раз выполняем.

Мы уже выше познакомились с методом fetch(), который служит для последовательного получения строк из БД. Этот метод является аналогом функции mysq_fetch_array() и ей подобных, но действует по-другому: вместо множества функций здесь используется одна, но ее поведение задается переданным параметром. В подробностях об этих параметрах будет написано позже, а в качестве краткой рекомендации посоветую применять fetch() в режиме FETCH_LAZY :
$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = ?" );
$stmt -> execute ([ $_GET [ "email" ]]);
while ($row = $stmt -> fetch (PDO :: FETCH_LAZY ))
{
echo $row [ 0 ] . "\n" ;
echo $row [ "name" ] . "\n" ;
echo $row -> name . "\n" ;
}

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

Также у PDO statement есть функция-хелпер для получения значения единственной колонки. Очень удобно, если мы запрашиваем только одно поле - в этом случае значительно сокращается количество писанины:
$stmt = $pdo -> prepare ("SELECT name FROM table WHERE id=?" );
$stmt -> execute (array($id ));
$name = $stmt -> fetchColumn ();

Но самой интересной функцией, с самым большим функционалом, является fetchAll(). Именно она делает PDO высокоуровневой библиотекой для работы с БД, а не просто низкоуровневым драйвером.

FetchAll() возвращает массив, который состоит из всех строк, которые вернул запрос. Из чего можно сделать два вывода:
1. Эту функцию не стоит применять тогда, когда запрос возвращает много данных. В таком случае лучше использовать традиционный цикл с fetch()
2. Поскольку в современных РНР приложениях данные никогда не выводятся сразу по получении, а передаются для этого в шаблон, fetchAll() становится просто незаменимой, позволяя не писать циклы вручную, и тем самым сократить количество кода.

Получение простого массива.
Вызванная без параметров, эта функция возвращает обычный индексированный массив, в котором лежат строки из бд, в формате, который задан в FETCH_MODE по умолчанию. Константы PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_OBJ могут менять формат на лету.

Получение колонки.
Иногда бывает нужно получить простой одномерный массив, запросив единственное поле из кучи строк. Для этого используется режим PDO::FETCH_COLUMN
$data = $pdo -> query ("SELECT name FROM users" )-> fetchAll (PDO :: FETCH_COLUMN );
array (
0 => "John" ,
1 => "Mike" ,
2 => "Mary" ,
3 => "Kathy" ,
)

Получение пар ключ-значение.
Также востребованный формат, когда желательно получить ту же колонку, но индексированную не числами, а одним из полей. За это отвечает константа PDO::FETCH_KEY_PAIR.
$data = $pdo -> query ("SELECT id, name FROM users" )-> fetchAll (PDO :: FETCH_KEY_PAIR );
array (
104 => "John" ,
110 => "Mike" ,
120 => "Mary" ,
121 => "Kathy" ,
)

Получение всех строк, индексированных полем.
Также часто бывает нужно получить все строки из БД, но также индексированные не числами, а уникальным полем. Это делает константа PDO::FETCH_UNIQUE
$data = $pdo -> query ("SELECT * FROM users" )-> fetchAll (PDO :: FETCH_UNIQUE );
array (
104 => array (
"name" => "John" ,
"car" => "Toyota" ,
),
110 => array (
"name" => "Mike" ,
"car" => "Ford" ,
),
120 => array (
"name" => "Mary" ,
"car" => "Mazda" ,
),
121 => array (
"name" => "Kathy" ,
"car" => "Mazda" ,
),
)

Следует помнить, что первой в колонкой надо обязательно выбирать уникальное поле.

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

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

$name = "% $name %" ;
$stm = $pdo -> prepare ("SELECT * FROM table WHERE name LIKE ?" );
$stm -> execute (array($name ));
$data = $stm -> fetchAll ();

Ну, вы поняли. Тут тоже всё плохо. PDO не предоставляет вообще никаких средств для работы с идентификаторами, и их надо форматировать по-старинке, вручную (или посмотреть, все-таки, в сторону SafeMysql , в которой этот, как и многие другие вопросы, решены просто и элегантно).
Следует помнить, что правила форматирования идентификаторов отличаются для разных БД.

В mysql для ручного форматирования идентификатора необходимо выполнить два действия:
- заключить его в обратные одинарные кавычки (backticks, "`").
- проискейпить эти символы внутри идентификатора внутри путём удвоения.

$field = "`" . str_replace ("`" , "``" , $_GET [ "field" ]). "`" ;
$sql = $field " ;

Однако, здесь есть один нюанс. Одного форматирования может быть недостаточно. приведенный выше код гарантирует нас от классической инъекции, но в некоторых случаях враг все равно может записать что-то нежелательное, если мы бездумно подставляем имена полей и таблиц прямиком в запрос. К примеру, есть в таблице users поле admin. Если входящие имена полей не фильтровать, то в это поле, при автоматическом формировании запроса из POST-а, любой дурак запишет любую гадость.

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

Любой код для вставки, который можно увидеть в многочисленных туториалах, навевает тоску и желание убиться апстену. Многокилометровые построения с повторением одних и тех же имен - в идексах $_POST-а, в именах переменных, в именах полей в запросе, в именах плейсхолдеров в запросе, в именах плейсходеров и именах переменных при привязке.
Глядя на этот код, хочется кого-нибудь убить, или, по крайней мере, сделать его немного короче.

Это можно сделать, если принять соглашение, по которому имена полей в форме будут соответствовать именам полей в таблице. Тогда эти имена можно будет перечислить только один раз (в целях защиты от подмены, о которой говорилось выше), и использовать небольшую функцию-хелпер для сборки запроса, которая, в силу особенностей mysql, годится как для INSERT, так и UPDATE запросов:

function pdoSet ($allowed , & $values , $source = array()) {
$set = "" ;
$values = array();
if (! $source ) $source = & $_POST ;
foreach ($allowed as $field ) {
if (isset($source [ $field ])) {
$set .= "`" . str_replace ("`" , "``" , $field ). "`" . "=: $field , " ;
$values [ $field ] = $source [ $field ];
}
}
return substr ($set , 0 , - 2 );
}

Соответственно, для вставки код будет

$allowed = array("name" , "surname" , "email" ); // allowed fields
$sql = "INSERT INTO users SET " . pdoSet ($allowed , $values );
$stm = $dbh -> prepare ($sql );
$stm -> execute ($values );

А для апдейта - такой:

$allowed = array("name" , "surname" , "email" , "password" ); // allowed fields
$_POST [ "password" ] = MD5 ($_POST [ "login" ]. $_POST [ "password" ]);
$sql = "UPDATE users SET " . pdoSet ($allowed , $values ). " WHERE id = :id" ;
$stm = $dbh -> prepare ($sql );
$values [ "id" ] = $_POST [ "id" ];
$stm -> execute ($values );

Не слишком эффектно, но зато очень эффективно. Напомню, кстати, что если использовать Класс для безопасной и удобной работы с MySQL , то это всё делается в две строчки.

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

$dirs = array("ASC" , "DESC" );
$key = array_search ($_GET [ "dir" ], $dirs ));
$dir = $orders [ $key ];
$sql = "SELECT * FROM `table` ORDER BY $field $dir " ;