Статьи

Доступ к базе данных PHP: вы делаете это правильно?

Мы уже несколько раз освещали PHP PDO API в Nettuts +, но, как правило, эти статьи больше фокусировались на теории, а не на приложении. Эта статья исправит это!

Проще говоря, если вы все еще используете старый PHP-интерфейс mysql для подключения к своим базам данных, читайте дальше!

Если вы работаете с PHP или MySQL и нуждаетесь в быстром исправлении ошибки в вашем коде, вы можете быстро и по доступной цене исправить любую отдельную ошибку разработчиком PHP Araneux в Envato Studio.

PHP developer Araneux on Envato Studio.

Вполне возможно, что на данный момент единственная мысль в вашем уме: «Какого черта PDO?» Ну, это один из трех доступных API PHP для подключения к базе данных MySQL. «Три», говорите вы? Да; многие люди этого не знают, но есть три разных API для подключения:

  • mysql
  • MySQL улучшенный MySQL
  • pdo — объекты данных PHP

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/*
 * Anti-Pattern
 */
 
# Connect
mysql_connect(‘localhost’, ‘username’, ‘password’) or die(‘Could not connect: ‘ . mysql_error());
 
# Choose a database
mysql_select_db(‘someDatabase’) or die(‘Could not select database’);
 
# Perform database query
$query = «SELECT * from someTable»;
$result = mysql_query($query) or die(‘Query failed: ‘ . mysql_error());
 
# Filter through rows and echo desired information
while ($row = mysql_fetch_object($result)) {
    echo $row->name;
}

Да, приведенный выше код довольно прост, но он имеет значительную долю недостатков.

  • Устаревший: Хотя он не был официально объявлен устаревшим — из-за широкого использования — с точки зрения наилучшей практики и образования, он также может быть.
  • Выход из ситуации: процесс избежания пользовательского ввода оставлен на усмотрение разработчика, многие из которых не понимают или не знают, как очистить данные.
  • Гибкость: API не является гибким; приведенный выше код специально создан для работы с базой данных MySQL. Что делать, если вы переключаетесь?

PDO, или PHP Data Objects, предоставляет более мощный API, который не заботится о используемом вами драйвере; это не зависит от базы данных. Кроме того, он предлагает возможность использовать подготовленные операторы, практически исключая любые опасения по поводу внедрения SQL. Ознакомьтесь с ассортиментом сценариев PDO и приложений на Envato Market, чтобы получить представление о том, что возможно.


Когда я впервые узнал об API PDO, я должен признать, что это немного пугало. Это было не потому, что API был слишком сложным (это не так) — просто старый API myqsl был настолько чертовски прост в использовании!

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

Итак, вы уже знаете традиционный способ подключения к базе данных MySQL:

1
2
# Connect
mysql_connect(‘localhost’, ‘username’, ‘password’) or die(‘Could not connect: ‘ . mysql_error());

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

1
$conn = new PDO(‘mysql:host=localhost;dbname=myDatabase’, $username, $password);

Не позволяйте этой длинной нити сбить вас с толку; это действительно очень просто: мы указываем имя драйвера (в данном случае mysql), за которым следуют необходимые данные (строка подключения) для подключения к нему.

Что хорошо в этом подходе, так это то, что если мы вместо этого хотим использовать базу данных sqlite, мы просто соответствующим образом обновляем DSN или «Имя источника данных»; мы не зависим от MySQL, как при использовании функций, таких как mysql_connect .

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

1
2
3
4
5
6
try {
    $conn = new PDO(‘mysql:host=localhost;dbname=myDatabase’, $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
    echo ‘ERROR: ‘ .
}

Так-то лучше! Обратите внимание, что по умолчанию режим ошибок по умолчанию для PDO — PDO::ERRMODE_SILENT . Если оставить этот параметр без изменений, вам нужно будет вручную получать ошибки после выполнения запроса.

1
2
echo $conn->errorCode();
echo $conn->errorInfo();

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

Для справки доступны следующие варианты:

  • PDO::ERRMODE_SILENT
  • PDO::ERRMODE_WARNING
  • PDO::ERRMODE_EXCEPTION

На данный момент мы создали соединение с базой данных; давайте возьмем некоторую информацию из этого. Есть два основных способа решения этой задачи: query и execute . Мы рассмотрим оба.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/*
 * The Query Method
 * Anti-Pattern
 */
 
$name = ‘Joe’;
 
try {
    $conn = new PDO(‘mysql:host=localhost;dbname=myDatabase’, $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
    $data = $conn->query(‘SELECT * FROM myTable WHERE name = ‘ . $conn->quote($name));
 
    foreach($data as $row) {
        print_r($row);
    }
} catch(PDOException $e) {
    echo ‘ERROR: ‘ .
}

Хотя это работает, обратите внимание, что мы по-прежнему вручную PDO::quote данные пользователя с помощью метода PDO::quote . Думайте об этом методе как о более или менее эквивалентном PDO для использования mysql_real_escape_string ; он будет экранировать и указывать строку, которую вы передаете ему. В ситуациях, когда вы связываете предоставленные пользователем данные с запросом SQL, настоятельно рекомендуется вместо этого использовать подготовленные операторы. Тем не менее, если ваши SQL-запросы не зависят от данных формы, метод query является полезным выбором и делает процесс циклического просмотра результатов таким же простым, как оператор foreach .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/*
 * The Prepared Statements Method
 * Best Practice
 */
 
$id = 5;
try {
    $conn = new PDO(‘mysql:host=localhost;dbname=myDatabase’, $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
     
    $stmt = $conn->prepare(‘SELECT * FROM myTable WHERE id = :id’);
    $stmt->execute(array(‘id’ => $id));
 
    while($row = $stmt->fetch()) {
        print_r($row);
    }
} catch(PDOException $e) {
    echo ‘ERROR: ‘ .
}

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

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

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

1
$stmt->execute(array(‘id’ => $id));

Альтернативный, но вполне приемлемый подход заключается в использовании метода bindParam , например, так:

1
2
$stmt->bindParam(‘:id’, $id, PDO::PARAM_INT);
$stmt->execute();

После вызова метода execute существует множество различных способов получения данных: массив (по умолчанию), объект и т. Д. В приведенном выше примере используется ответ по умолчанию: PDO::FETCH_ASSOC ; это может быть легко изменено, хотя, если необходимо:

1
2
3
while($row = $stmt->fetch(PDO::FETCH_OBJ)) {
    print_r($row);
}

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

  • PDO :: FETCH_ASSOC: возвращает массив.
  • PDO :: FETCH_BOTH: возвращает массив, проиндексированный как по столбцу, так и по 0.
  • PDO :: FETCH_BOUND: возвращает TRUE и присваивает значения столбцов в вашем наборе результатов переменным PHP, к которым они были привязаны.
  • PDO :: FETCH_CLASS: возвращает новый экземпляр указанного класса.
  • PDO :: FETCH_OBJ: Возвращает анонимный объект с именами свойств, которые соответствуют столбцам.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
$stmt->execute(array(‘id’ => $id));
 
# Get array containing all of the result rows
$result = $stmt->fetchAll();
 
# If one or more rows were returned…
if ( count($result) ) {
    foreach($result as $row) {
        print_r($row);
    }
} else {
    echo «No rows returned.»;
}

На данный момент наш полный код должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
$id = 5;
try {
  $conn = new PDO(‘mysql:host=localhost;dbname=someDatabase’, $username, $password);
  $stmt = $conn->prepare(‘SELECT * FROM myTable WHERE id = :id’);
  $stmt->execute(array(‘id’ => $id));
 
  $result = $stmt->fetchAll();
 
  if ( count($result) ) {
    foreach($result as $row) {
      print_r($row);
    }
  } else {
    echo «No rows returned.»;
  }
} catch(PDOException $e) {
    echo ‘ERROR: ‘ .
}

Расширение PDO становится особенно мощным при многократном выполнении одного и того же SQL-запроса, но с разными параметрами.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
try {
  $conn = new PDO(‘mysql:host=localhost;dbname=someDatabase’, $username, $password);
  $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
  # Prepare the query ONCE
  $stmt = $conn->prepare(‘INSERT INTO someTable VALUES(:name)’);
  $stmt->bindParam(‘:name’, $name);
 
  # First insertion
  $name = ‘Keith’;
  $stmt->execute();
 
  # Second insertion
  $name = ‘Steven’;
  $stmt->execute();
} catch(PDOException $e) {
  echo $e->getMessage();
}

После того, как запрос подготовлен, он может быть выполнен несколько раз с различными параметрами. Приведенный выше код вставит в базу данных две строки: одну с именем «Кевин», а другую — «Стивен».


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

01
02
03
04
05
06
07
08
09
10
11
12
13
try {
  $pdo = new PDO(‘mysql:host=localhost;dbname=someDatabase’, $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
  $stmt = $pdo->prepare(‘INSERT INTO someTable VALUES(:name)’);
  $stmt->execute(array(
    ‘:name’ => ‘Justin Bieber’
  ));
 
  # Affected Rows?
  echo $stmt->rowCount();
} catch(PDOException $e) {
  echo ‘Error: ‘ .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
$id = 5;
$name = «Joe the Plumber»;
 
try {
  $pdo = new PDO(‘mysql:host=localhost;dbname=someDatabase’, $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
  $stmt = $pdo->prepare(‘UPDATE someTable SET name = :name WHERE id = :id’);
  $stmt->execute(array(
    ‘:id’ => $id,
    ‘:name’ => $name
  ));
   
  echo $stmt->rowCount();
} catch(PDOException $e) {
  echo ‘Error: ‘ .
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
$id = 5;
 
try {
  $pdo = new PDO(‘mysql:host=localhost;dbname=someDatabase’, $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
  $stmt = $pdo->prepare(‘DELETE FROM someTable WHERE id = :id’);
  $stmt->bindParam(‘:id’, $id);
  $stmt->execute();
   
  echo $stmt->rowCount();
} catch(PDOException $e) {
  echo ‘Error: ‘ .
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class User {
  public $first_name;
  public $last_name;
 
  public function full_name()
  {
    return $this->first_name .
  }
}
 
try {
  $pdo = new PDO(‘mysql:host=localhost;dbname=someDatabase’, $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
  $result = $pdo->query(‘SELECT * FROM someTable’);
 
  # Map results to object
  $result->setFetchMode(PDO::FETCH_CLASS, ‘User’);
 
  while($user = $result->fetch()) {
    # Call our custom full_name method
    echo $user->full_name();
  }
} catch(PDOException $e) {
  echo ‘Error: ‘ .
}

Итог: если вы все еще используете этот старый mysql API для подключения к вашим базам данных, остановитесь. Хотя это еще не устарело, с точки зрения образования и документации , оно также может быть. Ваш код будет значительно более безопасным и оптимизированным, если вы примете расширение PDO. Проверьте позиции PDO на Envato Market, чтобы увидеть, что вы можете сделать.