Обработка результатов запросов MySQL в PHP

Раздел: Базы данных -> Основы взаимодействия PHP с MySQL

Основы получения ответа от MySQL в PHP

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

Основное решение: PDO и подготовленные запросы

Современный стандарт для работы с MySQL в PHP - расширение PDO (PHP Data Objects). Оно обеспечивает единый интерфейс для разных баз данных, защиту от SQL-инъекций через подготовленные запросы и гибкую обработку ошибок через исключения.

Вопрос: как получить данные из MySQL с помощью PDO и подготовленных запросов?


<?php
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$user = 'root';
$password = '';

try {
    $pdo = new PDO($dsn, $user, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);

    $stmt = $pdo->prepare('SELECT id, name, email FROM users WHERE active = :active');
    $stmt->execute([':active' => 1]);
    $users = $stmt->fetchAll();

    foreach ($users as $user) {
        echo 'ID: ' . $user['id'] . ', Name: ' . $user['name'] . '<br>';
    }
} catch (PDOException $e) {
    echo 'Ошибка: ' . $e->getMessage();
}
?>
  

Возможные проблемы и их решения:

  • Ошибка подключения (неверный DSN, хост, логин). Решение: проверить параметры, убедиться, что сервер MySQL запущен.
  • Исключение PDOException не обработано. Решение: всегда использовать блок try-catch или установить PDO::ATTR_ERRMODE в ERRMODE_WARNING.
  • Неверный параметр в запросе (например, имя плейсхолдера не совпадает). Решение: проверять имена и количество параметров.
  • Данные не возвращаются. Решение: проверить, что запрос выполняется (залогировать SQL), и что в таблице есть подходящие записи.

Варианты решений

Вопрос: как получить ответ от MySQL с помощью MySQLi в объектно-ориентированном стиле?


<?php
$mysqli = new mysqli('localhost', 'root', '', 'test');
if ($mysqli->connect_error) {
    die('Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
}
$result = $mysqli->query('SELECT id, name FROM users WHERE active = 1');
while ($row = $result->fetch_assoc()) {
    echo $row['id'] . ' - ' . $row['name'] . '<br>';
}
$result->free();
$mysqli->close();
?>
  

Особенности: MySQLi имеет как объектный, так и процедурный интерфейс. Для подготовки запросов используйте prepare() и bind_param().

Типичные ошибки: забывают проверить $mysqli->connect_error, используют устаревшие функции (mysql_*), не освобождают результат (free()).

Вопрос: как использовать процедурный MySQLi для получения ответа?


<?php
$link = mysqli_connect('localhost', 'root', '', 'test');
if (!$link) {
    die('Ошибка соединения: ' . mysqli_connect_error());
}
$result = mysqli_query($link, 'SELECT * FROM users LIMIT 5');
while ($row = mysqli_fetch_assoc($result)) {
    echo $row['name'] . '<br>';
}
mysqli_free_result($result);
mysqli_close($link);
?>
  

Процедурный стиль удобен для быстрого прототипирования, но в крупных проектах предпочтительнее объектный.

Вопрос: как получить ответ от устаревшего расширения mysql_*?


<?php
$link = mysql_connect('localhost', 'root', '');
if (!$link) {
    die('Could not connect: ' . mysql_error());
}
mysql_select_db('test');
$result = mysql_query('SELECT name FROM users');
while ($row = mysql_fetch_array($result)) {
    echo $row['name'] . '<br>';
}
mysql_free_result($result);
mysql_close($link);
?>
  

Важно: расширение mysql_* удалено из PHP 7.0. Использовать его не следует. Мигрируйте на PDO или MySQLi.

Вопрос: как получить одно значение из базы данных (например, COUNT)?


// PDO: fetchColumn
$stmt = $pdo->query('SELECT COUNT(*) FROM users');
$count = $stmt->fetchColumn();
echo 'Всего пользователей: ' . $count;

// MySQLi: fetch_row
$result = $mysqli->query('SELECT MAX(age) FROM users');
$row = $result->fetch_row();
$maxAge = $row[0];
  

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

Вопрос: как обработать ошибки выполнения запроса и получить информацию о них?


// PDO с исключениями
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
    $pdo->exec('INSERT INTO users (name) VALUES ("John")');
} catch (PDOException $e) {
    echo 'Ошибка: ' . $e->getMessage();
    // Код ошибки: $e->getCode()
}

// MySQLi
if ($mysqli->errno) {
    echo 'MySQL error: ' . $mysqli->error;
}
  

Совет: всегда проверяйте статус выполнения запроса, особенно для INSERT/UPDATE/DELETE. В PDO можно получить количество затронутых строк через rowCount().

Вопрос: как получить ID последней вставленной записи?


// PDO
$pdo->exec('INSERT INTO users (name) VALUES ("Alice")');
$lastId = $pdo->lastInsertId();

// MySQLi
$mysqli->query('INSERT INTO users (name) VALUES ("Bob")');
$lastId = $mysqli->insert_id;
  

Этот идентификатор полезен для связывания записей.

Расширенные примеры обработки ответа MySQL

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

Пример

<?php
$users = [
    ['name' => 'Anna', 'email' => 'anna@example.com'],
    ['name' => 'Boris', 'email' => 'boris@example.com'],
    ['name' => 'Vera', 'email' => 'vera@example.com']
];

$pdo->beginTransaction();
try {
    $stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
    foreach ($users as $user) {
        $stmt->execute($user);
    }
    $pdo->commit();
    echo 'Вставлено записей: ' . count($users);
} catch (Exception $e) {
    $pdo->rollBack();
    echo 'Ошибка: ' . $e->getMessage();
}
?>
Вставлено записей: 3

Транзакция гарантирует, что все вставки выполнятся атомарно. Если одна из них упадёт, изменения откатятся.

2. Использование fetchAll() с разными режимами

Пример

<?php
$stmt = $pdo->query('SELECT id, name FROM users LIMIT 3');

// Ассоциативный массив (ключи - имена столбцов)
$assoc = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Массив объектов
$stmt->execute();
$objects = $stmt->fetchAll(PDO::FETCH_OBJ);
// Массив, где ключ - первый столбец, значение - второй
$stmt->execute();
$pairs = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);

print_r($assoc);
print_r($objects);
print_r($pairs);
?>
Array
(
    [0] => Array
        (
            [id] => 1
            [name] => Anna
        )
    [1] => Array
        (
            [id] => 2
            [name] => Boris
        )
)
Array
(
    [0] => stdClass Object
        (
            [id] => 1
            [name] => Anna
        )
    [1] => stdClass Object
        (
            [id] => 2
            [name] => Boris
        )
)
Array
(
    [1] => Anna
    [2] => Boris
)

Выбор режима зависит от дальнейшей обработки: FETCH_OBJ удобен для шаблонизаторов, FETCH_KEY_PAIR - для построения карты соответствий.

3. Итерация по большому набору данных без загрузки в память

Пример

<?php
// Использование unbuffered query (только при отключённом буферизации)
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$stmt = $pdo->query('SELECT * FROM large_table');

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // Обработка каждой строки по одной
    echo $row['id'] . '<br>';
}
$stmt->closeCursor();
?>

Осторожно: unbuffered query блокирует таблицу на время итерации и требует полного выбора всех строк до следующего запроса. Используйте только для чтения больших объёмов, когда память ограничена.

4. Обработка NULL значений и типизация

Пример

<?php
// Предположим, что в поле phone может быть NULL
$stmt = $pdo->query('SELECT name, phone FROM contacts');
$stmt->setFetchMode(PDO::FETCH_ASSOC);

while ($row = $stmt->fetch()) {
    $phone = $row['phone'] ?? 'не указан';
    echo $row['name'] . ': ' . $phone . '<br>';
}

// Приведение типов через bindColumn
$stmt = $pdo->prepare('SELECT id, price FROM products WHERE id = ?');
$stmt->execute([$id]);
$stmt->bindColumn('id', $id, PDO::PARAM_INT);
$stmt->bindColumn('price', $price, PDO::PARAM_STR);
$stmt->fetch(PDO::FETCH_BOUND);
echo 'ID: ' . $id . ' (integer), Price: ' . $price . ' (string)';
?>

5. Пример с JOIN и группировкой

Пример

<?php
$sql = 'SELECT c.name AS category, COUNT(p.id) AS product_count
        FROM categories c
        LEFT JOIN products p ON p.category_id = c.id
        GROUP BY c.id';
$stmt = $pdo->query($sql);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo '<pre>';
print_r($results);
echo '</pre>';
?>
Array
(
    [0] => Array
        (
            [category] => Electronics
            [product_count] => 12
        )
    [1] => Array
        (
            [category] => Books
            [product_count] => 5
        )
)

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

6. Обработка ответа с помощью итераторов (интерфейс Traversable)

Пример

<?php
// PDOStatement реализует Traversable, поэтому можно использовать в foreach напрямую
$stmt = $pdo->query('SELECT name FROM users');
foreach ($stmt as $row) {
    echo $row['name'] . '<br>';
}

// Для инкапсуляции можно создать итератор
class UserIterator implements Iterator {
    private $stmt;
    public function __construct(PDOStatement $stmt) {
        $this->stmt = $stmt;
    }
    public function current(): array {
        return $this->stmt->fetch(PDO::FETCH_ASSOC);
    }
    public function key(): int { return 0; }
    public function next(): void {}
    public function rewind(): void { $this->stmt->execute(); }
    public function valid(): bool { return $this->current() !== false; }
}

$stmt = $pdo->prepare('SELECT * FROM users WHERE active = ?');
$stmt->execute([1]);
$iterator = new UserIterator($stmt);
foreach ($iterator as $user) {
    // обработка
}
?>

PHP ответ MySQL - comments

En
Php ответ mysql (php)