# PDO. Основы работы с MySQL ## Почему стоит использовать PDO Функции `mysql` в PHP для работы с БД устарели, на сегодняшний день желательно использовать `mysqli` или `PDO` (PHP Data Objects). `mysqli` - эта библиотека, которая по большому счёту, не предназначена для использования напрямую в коде. Она может послужить хорошим строительным материалом для создания библиотеки более высокого уровня. При работе с `mysqli` следует также помнить об обеспечении безопасности вашего приложения, в частности о защите от SQL-инъекций. В случае использования PDO (с его подготовленными запросами), такая защита идёт уже "из коробки", главное правильно применить необходимые методы. ## Установка PDO Установка в Linux: ```bash sudo apt update sudo apt install php7.4-mysql sudo apt-get install pdo-mysql ``` В **php.ini** добавить: ``` extension=pdo.so extension=pdo_mysql.so ``` На Windows, как правило драйвер уже установлен, нужно просто проверить включен ли он в **php.ini** ``` extension=php_pdo_mysql.dll ``` ### Проверить доступные драйвера ```php print_r(PDO::getAvailableDrivers()); ``` ## Соединение с базой данных Соединения устанавливаются автоматически при создании объекта `PDO` от его базового класса. **Пример #1**. Простое подключение: ```php $db = new PDO('mysql:host=localhost;dbname=pdo', 'root', 'password'); ``` При ошибке подключения PHP выдаст ошибку: ``` Fatal error: Uncaught PDOException: ... ``` **Пример #2**. Обработка ошибок подключения ```php try { $dbh = new PDO('mysql:host=localhost;dbname=pdo', 'root', 'password'); } catch (PDOException $e) { print "Error!: " . $e->getMessage(); die(); } ``` В этом примере подключения используем конструкцию `try...catch`. ## Подготовленные и прямые запросы В PDO два способа выполнения запросов: - `Прямой` - состоит из одного шага; - `Подготовленный` - состоит из двух шагов. ### Прямые запросы - `query()` используется для операторов, которые не вносят изменения, например `SELECT`. Возвращает объект `PDOStatemnt`, из которого с помощью методов `fetch()` или `fetchAll` извлекаются результаты запроса. Можно его сравнить с `mysql resource`, который возвращала `mysql_query()`. - `exec()` используется для операторов `INSERT, DELETE, UPDATE`. Возвращает число обработанных запросом строк. *Прямые запросы используются только в том случае, если в запросе отсутствуют переменные и есть уверенность, что запрос безопасен и правильно экранирован*. ```php $stmt = $db->query("SELECT * FROM categories"); while ($row = $stmt->fetch()) { echo '
';
  print_r($row);
}

```


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

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

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

PDO поддерживает *позиционные плейсхолдеры* (`?`), для которых важен порядок передаваемых переменных, и *именованные* (`:name`), для которых порядок не важен. Примеры:
```php
$sql = "SELECT name FROM categories WHERE id = ?";
$sql = "SELECT name FROM categories WHERE name = :name";
```

Чтобы выполнить такой запрос, сначала его надо подготовить с помощью метода `prepare()`. Он также возвращает `PDO statement`, но ещё без данных. Чтобы их получить, надо исполнить этот запрос, предварительно передав в него наши переменные. 

Передать можно двумя способами просто выполнив метод `execute()` и передав ему массив с переменными:

вар.1
```php
$stmt = $pdo->prepare("SELECT `name` FROM categories WHERE `id` = ?");
$stmt->execute([$id]);
```

вар.2
```php
$stmt = $pdo->prepare("SELECT `name` FROM categories WHERE `name` = :name");
$stmt->execute(['name' => $name]);
```
в случае именованных плейсхолдеров в `execute()` должен передаваться массив, в котором ключи должны совпадать с именами плейсхолдеров. 

После этого можно извлечь результаты запроса:
```php
$id = 1;
$stmt = $db->prepare("SELECT * FROM categories WHERE `id` = ?");
$stmt->execute([$id]);
$category = $stmt->fetch(PDO::FETCH_LAZY);
echo '
';
print_r($category);
```

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


## Получение данных. 
### Метод fetch()

Мы уже выше познакомились с методом `fetch()`, который служит для *последовательного получения* строк из БД. 

У метода fetch() имеется множество праметров, которые здесь описываться не будут.  
В основном же рекомендуется применять `fetch()` в режиме `FETCH_LAZY`

```php
$id = 1;
$stmt = $db->prepare("SELECT * FROM categories WHERE `id` = ?");
$stmt->execute([$id]);
while ($row = $stmt->fetch(PDO::FETCH_LAZY)) {
    echo 'Category name: '.$row->name;
}
```

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


### Метод fetchColumn()

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

```php
$id = 1;
$stmt = $db->prepare("SELECT `name` FROM categories WHERE `id` = ?");
$stmt->execute([$id]);
$name = $stmt->fetchColumn();
echo 'Category name: '.$name;
```


### Метод fetchAll()

```php
$data = $db->query("SELECT * FROM categories")->fetchAll(PDO::FETCH_ASSOC);
foreach ($data as $k => $v){
  echo 'Category name: '.$v['name'].'
'; } ``` ## PDO и использование операторов Работая с подготовленными запросами, следует понимать, что плейсхолдер может заменять **только** строку или число. *Ни ключевое слово, ни идентификатор, ни часть строки или набор строк* через плейсхолдер **подставить нельзя**. ### PDO и оператор LIKE Поэтому для `LIKE` необходимо сначала подготовить строку поиска целиком, а потом ее подставлять в запрос: ```php $search = 'комп'; $query = "SELECT * FROM categories WHERE `name` LIKE ?"; $params = ["%$search%"]; $stmt = $db->prepare($query); $stmt->execute($params); $data = $stmt->fetchAll(PDO::FETCH_ASSOC); $i = 1; foreach ($data as $category){ echo $i++ . '. ' . $category['name'].'
'; } ``` > Здесь может вознинуть проблема! Поиск может не работать, потому как из базы у вас приходят данные в неправильной кодировке. > Необходимо добавить кодировку в подключение, если она там не указана: `$db = new PDO('mysql:host=localhost;dbname=pdo;charset=utf8mb4', 'root', '');` ### PDO и оператор LIMIT **==Важно!==** Когда PDO работает в режиме эмуляции, все данные, которые были переданы напрямую в `execute()`, форматируются как строки. То есть, эскейпятся и обрамляются кавычками. Поэтому `LIMIT ?,?` превращается в `LIMIT '10', '10'` и очевидным образом вызывает ошибку синтаксиса и, соответственно, пустой массив данных. **Решение #1**: Отключить режим эмуляции: ```php $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); ``` **Решение #2**: Биндить эти цифры через `bindValue()`, принудительно выставляя им тип `PDO::PARAM_INT`: ```php $limit = 3; $stm = $db->prepare('SELECT * FROM categories LIMIT ?'); $stm->bindValue(1, $limit, PDO::PARAM_INT); $stm->execute(); $data = $stm->fetchAll(); echo '
';
print_r($data);
```


### PDO и оператор IN

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

```php
$arr = [1,3,6];
$in  = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM categories WHERE `id` IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($arr);
$data = $stm->fetchAll();
echo '
';
print_r($data);
```


## Примеры

### Добавление записей

```php
$name = 'Новая категория';
$query = "INSERT INTO `categories` (`name`) VALUES (:name)";
$params = [
    ':name' => $name
];
$stmt = $pdo->prepare($query);
$stmt->execute($params);
```


### Изменение записей

```php
$id = 1;
$name = 'Изменённая запись';
$query = "UPDATE `categories` SET `name` = :name WHERE `id` = :id";
$params = [
    ':id' => $id,
    ':name' => $name
];
$stmt = $pdo->prepare($query);
$stmt->execute($params);
```


### Удаление записей

```php
$id = 1;
$query = "DELETE FROM `categories` WHERE `id` = ?";
$params = [$id];
$stmt = $pdo->prepare($query);
$stmt->execute($params);
```