Статьи

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

Многие программисты PHP научились получать доступ к базам данных, используя расширения MySQL или MySQLi. Начиная с PHP 5.1, есть лучший способ. Объекты данных PHP (PDO) предоставляют методы для подготовленных операторов и работы с объектами, которые сделают вас гораздо более продуктивным!

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

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


«PDO — PHP Data Objects — это уровень доступа к базе данных, обеспечивающий единый метод доступа к нескольким базам данных».

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

PDO - уровень абстракции БД

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

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

  • PDO_DBLIB (FreeTDS / Microsoft SQL Server / Sybase)
  • PDO_FIREBIRD (Firebird / Interbase 6)
  • PDO_IBM (IBM DB2)
  • PDO_INFORMIX (динамический сервер IBM Informix)
  • PDO_MYSQL (MySQL 3.x / 4.x / 5.x)
  • PDO_OCI (интерфейс вызова Oracle)
  • PDO_ODBC (ODBC v3 (IBM DB2, unixODBC и win32 ODBC))
  • PDO_PGSQL (PostgreSQL)
  • PDO_SQLITE (SQLite 3 и SQLite 2)
  • PDO_4D (4D)

Все эти драйверы не обязательно доступны в вашей системе; Вот быстрый способ узнать, какие драйверы у вас есть:

1
print_r(PDO::getAvailableDrivers());

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

Строка подключения
01
02
03
04
05
06
07
08
09
10
11
12
13
14
try {
  # MS SQL Server and Sybase with PDO_DBLIB
  $DBH = new PDO(«mssql:host=$host;dbname=$dbname, $user, $pass»);
  $DBH = new PDO(«sybase:host=$host;dbname=$dbname, $user, $pass»);
 
  # MySQL with PDO_MYSQL
  $DBH = new PDO(«mysql:host=$host;dbname=$dbname», $user, $pass);
 
  # SQLite Database
  $DBH = new PDO(«sqlite:my/database/path/database.db»);
}
catch(PDOException $e) {
    echo $e->getMessage();
}

Обратите внимание на блок try / catch — вы всегда должны заключать операции PDO в try / catch и использовать механизм исключений — подробнее об этом в ближайшее время. Как правило, вы собираетесь установить только одно соединение — есть несколько перечисленных, чтобы показать вам синтаксис. $ DBH означает «дескриптор базы данных» и будет использоваться в этом руководстве.

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

1
2
# close the connection
$DBH = null;

Вы можете получить больше информации о специфических параметрах базы данных и / или строках соединения для других баз данных с PHP.net .


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

1
2
3
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Независимо от того, какой режим ошибок вы установили, ошибка подключения всегда будет вызывать исключение, а создание подключения всегда должно содержаться в блоке try / catch.

Это режим ошибок по умолчанию. Если вы оставите его в этом режиме, вам придется проверять наличие ошибок так, как вы привыкли использовать расширения mysql или mysqli. Два других метода более идеальны для СУХОГО программирования.

Этот режим выдаст стандартное предупреждение PHP и позволит программе продолжить выполнение. Это полезно для отладки.

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

01
02
03
04
05
06
07
08
09
10
11
12
# connect to the database
try {
  $DBH = new PDO(«mysql:host=$host;dbname=$dbname», $user, $pass);
  $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
 
  # UH-OH!
  $DBH->prepare(‘DELECT name FROM people’);
}
catch(PDOException $e) {
    echo «I’m sorry, Dave. I’m afraid I can’t do that.»;
    file_put_contents(‘PDOErrors.txt’, $e->getMessage(), FILE_APPEND);
}

В утверждении select есть преднамеренная ошибка; это вызовет исключение. Исключение отправляет сведения об ошибке в файл журнала и отображает дружественное (или не очень дружелюбное) сообщение для пользователя.


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

От 2 до 3 шагов вставки и обновления

Вот пример самого основного типа вставки:

1
2
3
# STH means «Statement Handle»
$STH = $DBH->prepare(«INSERT INTO folks ( first_name ) values ( ‘Cathy’ )»);
$STH->execute();

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

Использование подготовленных операторов поможет защитить вас от внедрения SQL.

Подготовленный оператор — это предварительно скомпилированный оператор SQL, который можно выполнить несколько раз, отправив только данные на сервер. Он имеет дополнительное преимущество, заключающееся в автоматической защите данных, используемых в заполнителях, от атак SQL-инъекций.

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

1
2
3
4
5
6
7
8
# no placeholders — ripe for SQL Injection!
$STH = $DBH->(«INSERT INTO folks (name, addr, city) values ($name, $addr, $city)»);
 
# unnamed placeholders
$STH = $DBH->(«INSERT INTO folks (name, addr, city) values (?, ?, ?);
 
# named placeholders
$STH = $DBH->(«INSERT INTO folks (name, addr, city) value (:name, :addr, :city)»);

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
# assign variables to each place holder, indexed 1-3
$STH->bindParam(1, $name);
$STH->bindParam(2, $addr);
$STH->bindParam(3, $city);
 
# insert one row
$name = «Daniel»
$addr = «1 Wicked Way»;
$city = «Arlington Heights»;
$STH->execute();
 
# insert another row with different values
$name = «Steve»
$addr = «5 Circle Drive»;
$city = «Schaumburg»;
$STH->execute();

Здесь есть два шага. Сначала мы присваиваем переменные различным заполнителям (строки 2-4). Затем мы присваиваем значения этим заполнителям и выполняем инструкцию. Чтобы отправить другой набор данных, просто измените значения этих переменных и снова выполните инструкцию.

Это кажется немного громоздким для заявлений с большим количеством параметров? Это. Однако, если ваши данные хранятся в массиве, есть простой способ:

1
2
3
4
5
# the data we want to insert
$data = array(‘Cathy’, ‘9 Dark and Twisty Road’, ‘Cardiff’);
 
$STH = $DBH->(«INSERT INTO folks (name, addr, city) values (?, ?, ?);
$STH->execute($data);

Это легко!

Данные в массиве применяются к заполнителям по порядку. $ data [0] входит в первый заполнитель, $ data [1] — второй и т. д. Однако, если ваши индексы массива не в порядке, это не будет работать должным образом, и вам нужно будет переиндексировать массив ,

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

1
2
3
# the first argument is the named placeholder name — notice named
# placeholders always start with a colon.
$STH->bindParam(‘:name’, $name);

Здесь вы также можете использовать ярлык, но он работает с ассоциативными массивами. Вот пример:

1
2
3
4
5
6
# the data we want to insert
$data = array( ‘name’ => ‘Cathy’, ‘addr’ => ‘9 Dark and Twisty’, ‘city’ => ‘Cardiff’ );
 
# the shortcut!
$STH = $DBH->(«INSERT INTO folks (name, addr, city) value (:name, :addr, :city)»);
$STH->execute($data);

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
# a simple object
class person {
    public $name;
    public $addr;
    public $city;
 
    function __construct($n,$a,$c) {
        $this->name = $n;
        $this->addr = $a;
        $this->city = $c;
    }
    # etc …
}
 
$cathy = new person(‘Cathy’,’9 Dark and Twisty’,’Cardiff’);
 
# here’s the fun part:
$STH = $DBH->(«INSERT INTO folks (name, addr, city) value (:name, :addr, :city)»);
$STH->execute((array)$cathy);

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


Извлекать данные в массивы или объекты

Данные получают через -> fetch (), метод вашего оператора. Перед вызовом fetch лучше сообщить PDO, как вы хотите получать данные. У вас есть следующие варианты:

  • PDO :: FETCH_ASSOC: возвращает массив, проиндексированный по имени столбца
  • PDO :: FETCH_BOTH (по умолчанию): возвращает массив, проиндексированный как по имени столбца, так и по номеру
  • PDO :: FETCH_BOUND: присваивает значения ваших столбцов переменным, установленным методом -> bindColumn ()
  • PDO :: FETCH_CLASS: присваивает значения ваших столбцов свойствам именованного класса. Это создаст свойства, если соответствующие свойства не существуют
  • PDO :: FETCH_INTO: обновляет существующий экземпляр указанного класса
  • PDO :: FETCH_LAZY : объединяет PDO :: FETCH_BOTH / PDO :: FETCH_OBJ, создавая имена переменных объекта по мере их использования
  • PDO :: FETCH_NUM: возвращает массив, проиндексированный по номеру столбца
  • PDO :: FETCH_OBJ: возвращает анонимный объект с именами свойств, которые соответствуют именам столбцов

На самом деле есть три, которые охватывают большинство ситуаций: FETCH_ASSOC, FETCH_CLASS и FETCH_OBJ. Чтобы установить метод выборки, используется следующий синтаксис:

1
$STH->setFetchMode(PDO::FETCH_ASSOC);

Вы также можете установить тип выборки непосредственно в вызове метода -> fetch ().

Этот тип выборки создает ассоциативный массив, проиндексированный по имени столбца. Это должно быть знакомо любому, кто использовал расширения mysql / mysqli. Вот пример выбора данных с помощью этого метода:

01
02
03
04
05
06
07
08
09
10
11
12
# using the shortcut ->query() method here since there are no variable
# values in the select statement.
$STH = $DBH->query(‘SELECT name, addr, city from folks’);
 
# setting the fetch mode
$STH->setFetchMode(PDO::FETCH_ASSOC);
 
while($row = $STH->fetch()) {
    echo $row[‘name’] .
    echo $row[‘addr’] .
    echo $row[‘city’] .
}

Цикл while будет продолжать проходить набор результатов по одной строке за раз до завершения.

Этот тип выборки создает объект класса std для каждой строки полученных данных. Вот пример:

01
02
03
04
05
06
07
08
09
10
11
12
# creating the statement
$STH = $DBH->query(‘SELECT name, addr, city from folks’);
 
# setting the fetch mode
$STH->setFetchMode(PDO::FETCH_OBJ);
 
# showing the results
while($row = $STH->fetch()) {
    echo $row->name .
    echo $row->addr .
    echo $row->city .
}

Свойства вашего объекта устанавливаются ДО вызова конструктора. Это важно.

Этот метод извлечения позволяет вам извлекать данные непосредственно в выбранный вами класс. Когда вы используете FETCH_CLASS, свойства вашего объекта устанавливаются ДО вызова конструктора. Прочтите это еще раз, это важно. Если свойства, соответствующие именам столбцов, не существуют, эти свойства будут созданы (как общедоступные) для вас.

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

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

01
02
03
04
05
06
07
08
09
10
11
class secret_person {
    public $name;
    public $addr;
    public $city;
    public $other_data;
 
    function __construct($other = ») {
        $this->address = preg_replace(‘/[az]/’, ‘x’, $this->address);
        $this->other_data = $other;
    }
}

Когда данные извлекаются в этот класс, в адресе все строчные буквы z заменяются буквой x . Теперь использование класса и преобразование данных полностью прозрачно:

1
2
3
4
5
6
$STH = $DBH->query(‘SELECT name, addr, city from folks’);
$STH->setFetchMode(PDO::FETCH_CLASS, ‘secret_person’);
 
while($obj = $STH->fetch()) {
    echo $obj->addr;
}

Если бы адрес был «5 Rosebud», вы бы увидели «5 Rxxxxxx» в качестве вывода. Конечно, могут быть ситуации, когда вы хотите, чтобы конструктор вызывался до назначения данных. ЗОП тоже тебя за это прикрыло.

1
$STH->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, ‘secret_person’);

Теперь, когда вы повторите предыдущий пример с этим режимом выборки (PDO :: FETCH_PROPS_LATE), адрес НЕ будет скрыт, так как был вызван конструктор и назначены свойства.

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

1
$STH->setFetchMode(PDO::FETCH_CLASS, ‘secret_person’, array(‘stuff’));

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

1
2
3
4
5
$i = 0;
while($rowObj = $STH->fetch(PDO::FETCH_CLASS, ‘secret_person’, array($i))) {
    // do stuff
    $i++
}

Хотя это и не предназначено для охвата всего в PDO (это огромное расширение!), Есть еще несколько методов, которые вы хотите знать, чтобы выполнять базовые функции с помощью PDO.

1
$DBH->lastInsertId();

Метод -> lastInsertId () всегда вызывается для дескриптора базы данных, а не для дескриптора оператора, и возвращает автоматически увеличенный идентификатор последней вставленной строки по этому соединению.

1
2
$DBH->exec(‘DELETE FROM folks WHERE 1’);
$DBH->exec(«SET time_zone = ‘-8:00′»);

Метод -> exec () используется для операций, которые не могут возвращать данные, отличные от затронутых строк. Выше приведены два примера использования метода exec.

1
$safe = $DBH->quote($unsafe);

Метод -> quote () заключает в кавычки строки, чтобы их можно было безопасно использовать в запросах. Это ваш запасной вариант, если вы не используете подготовленные заявления.

1
$rows_affected = $STH->rowCount();

Метод -> rowCount () возвращает целое число, указывающее количество строк, затронутых операцией. По крайней мере в одной известной версии PDO, согласно [этому сообщению об ошибке] ( http://bugs.php.net/40822 ), метод не работает с операторами выбора. Если у вас возникла эта проблема, и вы не можете обновить PHP, вы можете получить количество строк следующим образом:

01
02
03
04
05
06
07
08
09
10
11
$sql = «SELECT COUNT(*) FROM folks»;
if ($STH = $DBH->query($sql)) {
    # check the row count
    if ($STH->fetchColumn() > 0) {
 
    # issue a real select here, because there’s data!
    }
    else {
        echo «No rows matched the query.»;
    }
}

Я надеюсь, что это поможет некоторым из вас перейти от расширений mysql и mysqli. Как вы думаете? Есть ли кто-нибудь из вас, кто мог бы переключиться?