Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. SQL и PDO с нуля: база, таблица, данные и каталог на странице
  2. Задача: хранить каталог и показывать его на странице
  3. Подготовка: где писать код и чем его запускать
  4. Шаг 1. Открыть файл базы и понять, что вернулось
  5. Шаг 2. Создать таблицу: что означает каждая строка SQL
  6. Шаг 3. Записать товары: INSERT не равен SELECT
  7. Шаг 4. Прочитать таблицу: от SQL к многомерному массиву
  8. Шаг 5. Вывести все строки через foreach
  9. Пустая таблица, отсутствующая таблица и ошибка — три разных результата
  10. Шаг 6. Передать данные пользователя через параметры
  11. Изменить запись и удалить выбранную: UPDATE, DELETE и WHERE
  12. Что происходит, когда подключение или запрос не удались
  13. Готовый проект: таблица из базы становится страницей сайта
  14. MySQL: сначала база и права, затем подключение из PHP
  15. Куда двигаться после первого каталога
  16. Что такое база данных, запрос SQL и результат PDO
  17. База хранит факты, PDO передаёт команды
  18. Подготовка, выполнение и чтение — разные шаги
  19. Сортировка и LIKE требуют отдельных решений
  20. Главная стоимость часто находится за пределами PHP
  21. NULL, деньги и поиск больших страниц
  22. Практика: проверьте решение, а не только успешный путь
  23. Функции и методы этой темы
  24. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 32 · От основ к практике

SQL и PDO с нуля: база, таблица, данные и каталог на странице

От файла SQLite и CREATE TABLE до INSERT, SELECT, массива строк, foreach и HTML-каталога. Подключение к MySQL, ошибки, безопасные параметры и дальнейшие приёмы PDO.

Задача: хранить каталог и показывать его на странице

У небольшого магазина есть три товара. Сегодня посетитель открывает страницу, завтра открывает её снова — названия и остатки должны сохраниться. Обычный PHP-массив живёт во время выполнения скрипта. База данных хранит записи между запусками и умеет искать, изменять и связывать их.

Сначала создадим настоящий файл базы SQLite, таблицу products, добавим три товара и выведем их. Затем получим страницу каталога и разберём отказ подключения, неправильный файл, отсутствующую таблицу и пустой результат. Для первого запуска отдельный сервер базы не нужен; переход к MySQL разобран дальше.

id — номер товараname — названиеstock — остаток
1Молоток8
2Перчатки15
3Кисть0

Это будущие строки таблицы. Столбец задаёт одно свойство: например, stock хранит число штук. id отличает записи даже при одинаковых названиях. Таблица похожа на лист с фиксированными колонками; в отличие от рисунка таблицы, СУБД выполняет запросы и проверяет ограничения данных.

Один каталог: от постоянного хранения до экрана
  1. catalog.sqlite хранит таблицу products
  2. SQL SELECT выбирает id, name, stock
  3. PDO читает строки в массив $products
  4. foreach берёт один товар в $product
  5. HTML показывает название и остаток
НазваниеЧто это означает здесь
База данныхХранилище таблиц. В первом проекте — файл catalog.sqlite.
СУБДПрограмма, которая управляет хранением и выполняет SQL. SQLite работает внутри процесса PHP; MySQL обычно работает отдельным сервером.
SQLЯзык команд для базы: CREATE TABLE создаёт таблицу, INSERT добавляет строки, SELECT читает их.
PDOИнтерфейс PHP для обращения к базе через драйвер. PDO не является самой базой и не переводит автоматически все диалекты SQL.
Драйвер pdo_sqliteСвязь PDO с SQLite. Для MySQL нужен другой драйвер — pdo_mysql.

Пригодятся переменные, массивы и foreach. Новые команды SQL и операции PDO ниже объясняются на месте. Если вы ищете конкретный метод, после учебного пути находится полный раздел PDO с категориями методов.

Подготовка: где писать код и чем его запускать

Создайте отдельную учебную папку catalog-lesson. Не проводите первые опыты в рабочей базе магазина. Каждый шаг ниже имеет самостоятельный скачиваемый пример; дополнительные PHP-файлы из его архива должны лежать рядом с example.php. В следующих шагах они лишь воспроизводят уже объяснённую таблицу, чтобы пример можно было запустить отдельно.

Нужны PHP и расширение pdo_sqlite. В терминале, открытом в учебной папке, команда php example.php выполняет файл. В редакторе файл сохраняют как UTF-8 без BOM. PHP-код начинается с <?php; для файла только с PHP закрывающий тег не требуется.

php --ini
php -m

Первая команда показывает, какой php.ini использует командная строка. В списке второй должны быть PDO и pdo_sqlite. В Windows подходящее расширение обычно включают строкой extension=pdo_sqlite в ini установленной сборки; в Linux способ установки зависит от пакетов дистрибутива. Затем перезапускают PHP/веб-службу. CLI и PHP, обслуживающий сайт, могут читать разные настройки.

Проверить доступность SQLiteСамостоятельный пример
<?php
echo in_array('sqlite', PDO::getAvailableDrivers(), true)
    ? "Можно запускать каталог\n"
    : "Нужно включить pdo_sqlite\n";
Ожидаемый результат
Можно запускать каталог

getAvailableDrivers() возвращает имена доступных драйверов. in_array проверяет, есть ли среди них sqlite. Знак ? выбирает сообщение при истине, : — при лжи. Это проверка окружения; сам каталог начинается следующим примером.

Шаг 1. Открыть файл базы и понять, что вернулось

Первое подключение: три строки PHPСамостоятельный пример
<?php
$path = __DIR__ . '/catalog.sqlite';
$db = new PDO('sqlite:' . $path);
echo "Соединение с каталогом открыто\n";
Ожидаемый результат
Соединение с каталогом открыто

После запуска в папке примера появляется catalog.sqlite. Пока это новая база без таблицы товаров. В переменной $db находится объект соединения, а не массив товаров и не содержимое файла.

Элемент записиЗачем он здесь
$path = ...;Сохраняет путь в переменную. Точка склеивает две строки.
__DIR__Папка именно этого PHP-файла. Поэтому путь не зависит от того, из какой папки запущен терминал.
sqlite:Начало строки подключения, называемой DSN. Выбирает драйвер SQLite; после двоеточия идёт путь.
new PDO(...)Создаёт объект и пытается открыть базу. Если это невозможно, выбрасывает PDOException; обычное значение false при отказе подключения не возвращается.
$dbСсылка на созданное соединение. Через него следующий код отдаёт команды базе.

SQLite обычно создаёт отсутствующий файл, если существует папка и разрешена запись. Родительские папки автоматически не создаются. У MySQL другое поведение: запись dbname=catalog выбирает уже существующую базу на сервере, а не создаёт её. Подключение и создание таблицы — два разных действия.

После завершения скрипта обычное соединение освобождается, а файл остаётся. При следующем запуске тот же путь откроет те же данные. Строка sqlite::memory: создаёт другую, временную базу: она исчезает вместе с соединением. Здесь используем файл, чтобы увидеть сохранение между запусками.

Шаг 2. Создать таблицу: что означает каждая строка SQL

Открытая база пока не знает, что такое товар. Зададим структуру: номер, название, количество. SQL — строка внутри PHP; её выполняет SQLite после вызова $db->exec(...). Стрелка означает «вызвать операцию у этого объекта».

Создать таблицу products в новой учебной базеСамостоятельный пример
<?php
$db = new PDO('sqlite:' . __DIR__ . '/catalog.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    stock INTEGER NOT NULL CHECK (stock >= 0)
)");
echo "Таблица products создана\n";
Ожидаемый результат
Таблица products создана

exec подходит команде, от которой не требуется набор строк. После успешного выполнения появляется таблица с тремя столбцами, но без товаров. Возвращённый 0 для CREATE TABLE не означает ошибку: число изменённых строк здесь не является проверкой существования таблицы.

SQLСмысл
CREATE TABLE productsСоздать таблицу с именем products.
id INTEGER PRIMARY KEYЦелый идентификатор и первичный ключ. В SQLite эта запись связана с rowid; при пропуске id база может выбрать значение сама.
name TEXT NOT NULLТекстовое название; SQL NULL запрещён. Пустая строка при этом отдельно не запрещена.
stock INTEGER NOT NULLЦелое количество, которое обязательно указать.
CHECK (stock >= 0)Запретить отрицательный остаток на уровне базы.
Запятые и скобкиВ скобках перечисляются столбцы и правила; запятые разделяют элементы, после последнего запятой нет.

В этом первом примере предполагается новая база. Повторный запуск в той же папке даст ошибку «table products already exists». Это сигнал, что таблица уже есть. Для повторяемой установки используют CREATE TABLE IF NOT EXISTS, но эта команда не исправляет старую структуру: добавление столбца позже требует отдельного изменения схемы, называемого миграцией. Не удаляйте рабочую таблицу ради устранения такого сообщения.

SQLite имеет собственные правила типов; объявление INTEGER само по себе не делает всякую обычную таблицу строго типизированной. Для строгого режима предусмотрены таблицы STRICT. Здесь передаём целые числа; данные формы всё равно потребуется проверить до записи. Правила SQLite нельзя автоматически переносить на MySQL.

Шаг 3. Записать товары: INSERT не равен SELECT

Сначала внесём три известные записи. Здесь строки заданы автором, а не пользователем сайта. Переменные из формы этим способом в SQL не вставляют — подготовленный запрос разобран после чтения.

Добавить Молоток, Перчатки и КистьСамостоятельный пример
<?php
require __DIR__ . '/create.php';
$db->exec("INSERT INTO products (id, name, stock) VALUES
    (1, 'Молоток', 8),
    (2, 'Перчатки', 15),
    (3, 'Кисть', 0)");
echo "В каталог записаны три товара\n";
Ожидаемый результат
В каталог записаны три товара

create.php в архиве содержит предыдущий шаг подключения и создания таблицы. require выполняет его; после этого доступна переменная $db. INSERT перечисляет столбцы, затем значения каждой строки в том же порядке.

$db->exec("INSERT INTO products (id, name, stock) VALUES
    (1, 'Молоток', 8),
    (2, 'Перчатки', 15),
    (3, 'Кисть', 0)");

(1, 'Молоток', 8) значит: id равен 1, name — Молоток, stock — 8. Несколько групп после VALUES добавляют несколько записей. Пока мы ничего не выводили из базы: сообщение echo только подтверждает, что код дошёл до следующей строки. Следующий шаг действительно прочитает сохранённые товары.

Повторная вставка id=1 вызовет конфликт первичного ключа. Нельзя скрыть ошибку и считать импорт выполненным. Для повторяемого импорта сначала решают, должна ли существующая запись обновиться, пропуститься или считаться конфликтом. Это отдельное требование, а не повод автоматически стирать таблицу.

Шаг 4. Прочитать таблицу: от SQL к многомерному массиву

Теперь базе отдаётся команда SELECT. Она выбирает столбцы id, name и stock из products; ORDER BY id задаёт порядок. Без ORDER BY порядок строк не обещан. query выполняет запрос и возвращает объект результата PDOStatement. Из него ещё нужно прочитать строки.

Получить массив и прочитать одно полеСамостоятельный пример
<?php
require __DIR__ . '/prepare.php';
$statement = $db->query('SELECT id, name, stock FROM products ORDER BY id');
$products = $statement->fetchAll(PDO::FETCH_ASSOC);
echo $products[0]['name'], "\n";
echo $products[0]['stock'], "\n";
Ожидаемый результат
Молоток
8

В архиве prepare.php создаёт учебную базу и три ранее показанные записи. fetchAll читает оставшиеся строки результата в массив. PDO::FETCH_ASSOC просит ключи по названиям столбцов, поэтому доступно ['name'], а не только номер столбца.

После fetchAll переменная $products имеет такую форму. Это уже обычные PHP-массивы; JSON в этом процессе не участвовал:

$products = [
    ['id' => 1, 'name' => 'Молоток', 'stock' => 8],
    ['id' => 2, 'name' => 'Перчатки', 'stock' => 15],
    ['id' => 3, 'name' => 'Кисть', 'stock' => 0],
];
Как найти название первого товара
  1. products — все выбранные строки
  2. [0] — первая строка
  3. ['name'] — поле внутри этой строки
  4. Получили строку «Молоток»

Внешний массив — список товаров; каждый внутренний массив — поля одного товара. Поэтому он многомерный. На первом уровне индекс начинается с 0. Это индекс PHP-массива, не значение столбца id: после фильтрации первый элемент [0] может содержать товар id=37. Не путайте адрес элемента массива с идентификатором записи.

В примере выборка гарантированно содержит три строки. В приложении она может оказаться пустой: обращение к [0] без проверки тогда неверно. Ниже разберём пустой каталог и поиск одного товара.

Шаг 5. Вывести все строки через foreach

Показать каждый товар и его остатокСамостоятельный пример
<?php
require __DIR__ . '/prepare.php';
$statement = $db->query('SELECT id, name, stock FROM products ORDER BY id');
$products = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($products as $product) {
    echo $product['name'], ': ', $product['stock'], " шт.\n";
}
Ожидаемый результат
Молоток: 8 шт.
Перчатки: 15 шт.
Кисть: 0 шт.

Первый проход присваивает $product массив Молотка, второй — Перчаток, третий — Кисти. Тело цикла выполняется для каждой строки. На экран попадают значения из базы, а не заранее написанный список.

Проход$product['name']$product['stock']
1Молоток8
2Перчатки15
3Кисть0

Для вывода известных полей нужен один foreach. Второй вложенный цикл понадобился бы, если бы вы намеренно перебирали все столбцы каждой строки. Здесь названия полей известны — читаем их по ключам. В варианте foreach ($products as $index => $product) переменная $index содержит 0, 1, 2; номер товара по-прежнему находится в $product['id'].

После fetchAll данные можно перебирать снова: они уже в массиве. Напротив, результат запроса читается последовательно; повторный fetchAll того же полностью прочитанного statement обычно даст пустой массив. Для новой выборки выполняют запрос заново.

Пустая таблица, отсутствующая таблица и ошибка — три разных результата

Каталог создан, но товаров ещё нетСамостоятельный пример
<?php
require __DIR__ . '/create.php';
$statement = $db->query('SELECT id, name, stock FROM products ORDER BY id');
$products = $statement->fetchAll(PDO::FETCH_ASSOC);
if ($products === []) {
    echo "Товаров пока нет. Добавьте первый товар.\n";
}
foreach ($products as $product) {
    echo $product['name'], "\n";
}
Ожидаемый результат
Товаров пока нет. Добавьте первый товар.

SELECT выполнен успешно. fetchAll вернул пустой массив [], а foreach не выполнил ни одного прохода. Это нормальное состояние нового каталога, не отказ базы.

Нельзя проверять пустой fetchAll через === false: в актуальном PHP результат этого метода — массив, при отсутствии строк он пуст. Для одного fetch договор другой: отсутствие следующей строки обозначается false. Подробности — fetchAll и fetch.

СитуацияЧто произошлоЧто делать
[] после SELECTБаза и таблица доступны, подходящих строк нет.Показать пустое состояние, а не ошибку сервера.
no such table: productsSQL не может найти таблицу в открытой SQLite-базе.Проверить путь к файлу и выполнение шага создания таблицы.
unable to open database fileСоединение не смогло открыть/создать файл.Проверить папку, путь и права PHP-процесса.
could not find driverPHP не загрузил драйвер из DSN.Проверить расширение и ini того процесса, который выполняет запрос.

На Stack Overflow встречается типичная ошибка: пустую выборку принимают за false и не показывают сообщение «ничего не найдено». Здесь договор результата проверяется явно. Неопределённая переменная, неверное имя столбца и пустой набор — тоже разные причины; не маскируйте их одним @.

Шаг 6. Передать данные пользователя через параметры

Из формы или интеграции приходит название: Набор O'Brien. Апостроф — обычная часть данных, но внутри склеенного SQL он может изменить смысл команды. Разделим шаблон SQL и значения. Сначала prepare готовит команду с именованными местами :name и :stock, потом execute передаёт массив значений.

Сохранить название с апострофом и прочитать его обратноСамостоятельный пример
<?php
require __DIR__ . '/create.php';
$name = "Набор O'Brien";
$stock = 4;
$insert = $db->prepare('INSERT INTO products (name, stock) VALUES (:name, :stock)');
$insert->execute(['name' => $name, 'stock' => $stock]);
$product = $db->query('SELECT name, stock FROM products')->fetch(PDO::FETCH_ASSOC);
echo $product['name'], ': ', $product['stock'], " шт.\n";
Ожидаемый результат
Набор O'Brien: 4 шт.

id не передан: в этой SQLite-схеме база назначает его сама. Ключ name в массиве execute соответствует :name в SQL, stock — :stock. Вокруг :name не нужны кавычки. Результат показывает, что апостроф сохранился как часть названия.

Подготовленный запрос защищает границу между SQL и значениями. Он не проверяет, разрешено ли пользователю менять каталог, и не запрещает отрицательное число вместо остатка. Приём формы требует проверки метода, полей, прав и CSRF; подробный путь находится в обработке HTML-форм. Не передавайте весь $_POST в execute: соберите только предусмотренные параметры.

Найти один товар по id и обработать отсутствиеСамостоятельный пример
<?php
require __DIR__ . '/prepare.php';
$find = $db->prepare('SELECT name, stock FROM products WHERE id = :id');
$find->execute(['id' => 2]);
$product = $find->fetch(PDO::FETCH_ASSOC);
if ($product === false) {
    echo "Товар не найден\n";
} else {
    echo $product['name'], ': ', $product['stock'], " шт.\n";
}
Ожидаемый результат
Перчатки: 15 шт.

WHERE отбирает строку с заданным id. Замените 2 на 99: попадёте в ветку «Товар не найден». При найденном товаре stock=0 ветка успешна, потому что проверяем отсутствие строки целиком, а не истинность количества.

Тот же поиск: id=99 отсутствуетСамостоятельный пример
<?php
require __DIR__ . '/prepare.php';
$find = $db->prepare('SELECT name FROM products WHERE id = :id');
$find->execute(['id' => 99]);
$product = $find->fetch(PDO::FETCH_ASSOC);
echo $product === false ? "Товар не найден\n" : $product['name'];
Ожидаемый результат
Товар не найден

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

Изменить запись и удалить выбранную: UPDATE, DELETE и WHERE

Магазин получил ещё четыре молотка. Создавать второй товар с тем же названием не нужно: меняется остаток существующей записи. UPDATE выбирает таблицу, SET задаёт изменение, WHERE ограничивает, к каким строкам оно относится.

Было 8 молотков, поставка добавила 4: стало 12Самостоятельный пример
<?php
require __DIR__ . '/prepare.php';
$update = $db->prepare('UPDATE products SET stock = stock + :added WHERE id = :id');
$update->execute(['added' => 4, 'id' => 1]);
$product = $db->query('SELECT name, stock FROM products WHERE id = 1')->fetch(PDO::FETCH_ASSOC);
echo $product['name'], ': ', $product['stock'], " шт.\n";
Ожидаемый результат
Молоток: 12 шт.

Выражение stock + :added вычисляет база относительно текущего stock. Последующий SELECT показывает реально сохранённое количество. Это полезно для поступления товара; внешние added/id прежде проверяют как допустимые значения.

Без WHERE команда обновит все строки таблицы. Параметр защищает SQL от подмены текста, но не защищает от ошибочного бизнес-решения «изменить все товары». Сначала сформулируйте, какие записи разрешено менять, и проверьте это условие на SELECT. Для списания нельзя слепо делать stock - quantity: нужно не уйти ниже нуля и учесть одновременно выполняемые покупки; это разобрано в транзакциях и условном UPDATE.

Убрать Кисть из учебного каталога по её idСамостоятельный пример
<?php
require __DIR__ . '/prepare.php';
$delete = $db->prepare('DELETE FROM products WHERE id = :id');
$delete->execute(['id' => 3]);
$products = $db->query('SELECT name FROM products ORDER BY id')->fetchAll(PDO::FETCH_ASSOC);
foreach ($products as $product) {
    echo $product['name'], "\n";
}
Ожидаемый результат
Молоток
Перчатки

DELETE удаляет строку, а не PHP-переменную и не саму таблицу. Здесь удаление выполняется только в созданной самим примером временной учебной папке. Обновлённый список подтверждает, что товар исчез из хранилища.

Удаление товара в настоящем магазине требует проверки прав и связей с заказами. Часто товар лучше пометить архивным, чтобы история покупок продолжала ссылаться на него. DELETE FROM products без WHERE очистит всю таблицу; DROP TABLE products удалит ещё и её структуру. Это три разных операции. Вывод confirm в браузере не заменяет проверку прав на сервере.

ЗадачаSQLЧто читаем после выполнения
Добавить новую записьINSERT INTO ... VALUES ...Назначенный id через lastInsertId, либо свежую запись отдельным SELECT.
Изменить существующуюUPDATE ... SET ... WHERE ...Новое состояние; для некоторых задач число изменённых/совпавших строк с учётом драйвера.
Удалить выбраннуюDELETE FROM ... WHERE ...Состояние списка и допустимость результата по требованиям.
Получить данные без измененияSELECT ... FROM ... WHERE ...Строки через fetch/fetchAll, одно поле через fetchColumn.

Для SELECT не используйте rowCount() как универсальное количество результатов. Для MySQL UPDATE число может описывать изменённые строки, а при другом режиме — совпавшие: запись того же значения и отсутствие товара не всегда различаются одной такой цифрой. Нужна ли вам идемпотентность, конфликт версии или сообщение «товар не найден», определяет договор операции.

Что происходит, когда подключение или запрос не удались

try отмечает действия, при которых ожидаем отказ; catch (PDOException $error) принимает ошибку PDO. При исключении выполнение внутри try прерывается в месте отказа: код ниже проблемной строки не исполняется, управление переходит в catch. Затем, если мы не завершили скрипт, выполнение продолжается после catch. Это не автоматическое исправление базы.

Папки нет: управление переходит в catchСамостоятельный пример
<?php
try {
    $db = new PDO('sqlite:' . __DIR__ . '/missing/catalog.sqlite');
    echo "Подключились\n";
} catch (PDOException $error) {
    echo "Не удалось открыть базу. Проверьте путь и папку.\n";
}
echo "Проверка подключения завершена\n";
Ожидаемый результат
Не удалось открыть базу. Проверьте путь и папку.
Проверка подключения завершена

В самостоятельном примере папка missing отсутствует. Сообщение «Подключились» не появится. Исключение конструктора возникает независимо от выбранного режима обработки SQL-ошибок. Переменную $db после такого отказа нельзя использовать как успешное соединение.

Опечатка в имени: открылась новая пустая базаСамостоятельный пример
<?php
require __DIR__ . '/prepare.php';
$wrongDb = new PDO('sqlite:' . __DIR__ . '/catlaog.sqlite');
$wrongDb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Подключение открылось\n";
try {
    $wrongDb->query('SELECT name FROM products');
} catch (PDOException $error) {
    echo "В открытом файле нет таблицы products\n";
}
Ожидаемый результат
Подключение открылось
В открытом файле нет таблицы products

Подготовлена база catalog.sqlite, а открыта catlaog.sqlite. SQLite создала другой файл без таблиц. Поэтому важно проверить не только факт соединения, но и адрес хранилища. Этот catch демонстрирует известный сбой своей фикстуры; в приложении не всякая PDOException означает отсутствующую таблицу.

Файл существует, но внутри обычный текстСамостоятельный пример
<?php
$db = new PDO('sqlite:' . __DIR__ . '/notes.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
    $db->query('SELECT name FROM products');
} catch (PDOException $error) {
    echo "Не удалось прочитать каталог: файл не является базой SQLite\n";
}
Ожидаемый результат
Не удалось прочитать каталог: файл не является базой SQLite

Расширение .sqlite не превращает текст в базу. Открытие может пройти, а чтение структуры затем обнаружит неправильное содержимое. is_file проверяет наличие обычного файла, но не его формат.

Для рабочего приложения разделяют установку и обычное чтение. Установщик сознательно создаёт новый файл. Страница каталога ожидает существующий файл и сообщает об ошибке настройки, если его нет. Проверка is_file до PDO помогает обнаружить опечатку, но не является атомарной гарантией: файл может исчезнуть между проверкой и открытием. Для строгого запрета создания используйте режим открытия драйвера, если он доступен, или SQLite3 с SQLITE3_OPEN_READWRITE без SQLITE3_OPEN_CREATE.

ОшибкаВозможная причинаОсмысленная проверка
Не удаётся открыть файлНеверная папка, права, ограничения PHP, другой рабочий каталог.Сравнить полный путь; выяснить пользователя PHP-службы и права на папку.
Чтение есть, запись не проходитНет права создать журнал рядом с SQLite-файлом либо база открыта только для чтения.Проверить права на каталог и режим открытия, а не только права самого файла. Не делать chmod 777 всем подряд.
Таблица не найденаНе выполнено создание схемы либо открыт другой файл/другая база.Проверить конфигурацию и историю миграций.
Дублирование idПовторная вставка первичного ключа.Определить поведение повторной операции: отказ, пропуск, обновление.
database is lockedДругая операция удерживает несовместимую блокировку.Сократить транзакцию, проверить владельца блокировки; ограниченный busy_timeout помогает ждать, но не исправляет зависшую архитектуру.

Локально разработчику нужен код и контекст ошибки. Посетителю — понятный ответ без пароля, DSN, трассировки и SQL с личными данными. На боевом сайте display_errors выключен, журнал закрыт от скачивания. Не продолжайте обработку так, будто данные записались, если execute завершился исключением. В проекте ниже это даёт HTTP 503 и сообщение «Каталог временно недоступен».

Готовый проект: таблица из базы становится страницей сайта

Соберём разобранные шаги в маленький сайт. Это каталог с тремя товарами: названия и остатки берутся из файла базы. У Кисти нулевой остаток показан как «Нет в наличии». Файлы базы и установки находятся вне публичной папки, чтобы сервер отдавал только страницу каталога.

Вот страница, которую выводит этот проект

Каталог мастерской

Остатки из базы SQLite
ТоварНаличие
Молоток 8 шт.
Перчатки 15 шт.
Кисть Нет в наличии
Строки прочитаны из products и показаны через foreach.
catalog-project/
  bin/init.php        — однократное создание учебных данных
  data/               — здесь появится catalog.sqlite
  src/database.php    — подключение к существующей базе
  public/index.php    — запрос и HTML-страница

В полном архиве примера есть все перечисленные файлы. В терминале из корня проекта выполните:

php bin/init.php
php -S 127.0.0.1:8090 -t public

Первая команда создаёт базу; повторный запуск отказывается перезаписывать существующий каталог. Вторая запускает учебный локальный сервер. Откройте http://127.0.0.1:8090/. Встроенный сервер PHP предназначен для разработки, а не для публикации магазина в интернете.

Здесь показан сохранённый HTML, полученный реальным запуском данного PHP-проекта. Это не изображение таблицы и не макет с подставленными вручную числами. Чтобы менять данные и получать новое состояние, запустите проект из архива.

Все файлы: создать каталог для последующего открытия в браузереСамостоятельный пример
<?php
require __DIR__ . '/bin/init.php';
Ожидаемый результат
Созданы catalog.sqlite и три товара.

Скачиваемый архив включает bin, src и public. В файле example.php только запуск установщика; устройство каждого настоящего файла разобрано ниже.

bin/init.php — установка отдельно от просмотра страницы

<?php
$root = dirname(__DIR__);
$directory = $root . '/data';
if (!is_dir($directory) && !mkdir($directory, 0700, true)) {
    exit("Не удалось создать папку data\n");
}
$path = $directory . '/catalog.sqlite';
if (is_file($path)) {
    exit("База уже существует. Учебные данные не перезаписаны.\n");
}
$db = new PDO('sqlite:' . $path);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    stock INTEGER NOT NULL CHECK (stock >= 0)
)');
$insert = $db->prepare('INSERT INTO products (name, stock) VALUES (:name, :stock)');
$insert->execute(['name' => 'Молоток', 'stock' => 8]);
$insert->execute(['name' => 'Перчатки', 'stock' => 15]);
$insert->execute(['name' => 'Кисть', 'stock' => 0]);
echo "Созданы catalog.sqlite и три товара.\n";

Этот файл запускается из командной строки. dirname(__DIR__) поднимается из bin в корень проекта. mkdir создаёт приватную папку data, а is_file не даёт затереть уже существующую базу. Веб-посетитель не вызывает установщик при каждом открытии страницы. CREATE TABLE и INSERT выполняются только на этапе установки.

src/database.php — одно подключение к известному файлу

<?php
$path = dirname(__DIR__) . '/data/catalog.sqlite';
if (!is_file($path)) {
    throw new RuntimeException('Файл каталога отсутствует: сначала запустите bin/init.php');
}
return new PDO('sqlite:' . $path, null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

В этом файле return возвращает объект в то место, где выполнен require. Поэтому выражение $db = require ... получает готовое соединение. Это подключение файла, а не глобальная переменная. Проверка отсутствующего файла превращает ошибку настройки в явный отказ вместо случайного нового пустого каталога. Папка data должна принадлежать приложению, а не произвольным пользователям.

public/index.php — прочитать строки и вывести HTML

<?php
$requestPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($requestPath !== '/' && $requestPath !== '/index.php') {
    http_response_code(404);
    exit('Страница не найдена');
}
try {
    $db = require dirname(__DIR__) . '/src/database.php';
    $statement = $db->query('SELECT id, name, stock FROM products ORDER BY id');
    $products = $statement->fetchAll();
} catch (PDOException | RuntimeException $error) {
    error_log('catalog_unavailable type=' . get_class($error) . ' code=' . $error->getCode());
    http_response_code(503);
    header('Content-Type: text/plain; charset=UTF-8');
    exit('Каталог временно недоступен. Попробуйте позже.');
}
header('Content-Type: text/html; charset=UTF-8');
?>
<!doctype html>
<html lang="ru">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Каталог мастерской</title>
<style>
body { margin: 0; padding: 24px; background: #f3f7f5; color: #193d40; font: 17px/1.5 system-ui, sans-serif; }
main { max-width: 680px; margin: auto; padding: 24px; border: 1px solid #c7d8d3; border-radius: 16px; background: white; }
h1 { font-size: 26px; line-height: 1.2; margin-top: 0; }
table { width: 100%; border-collapse: collapse; }
caption { text-align: left; margin-bottom: 12px; }
th, td { padding: 12px 6px; text-align: left; border-bottom: 1px solid #dbe5e1; }
th:last-child, td:last-child { text-align: right; }
small { display: block; margin-top: 20px; color: #526b6e; }
@media (max-width: 420px) { body { padding: 12px; } main { padding: 16px; } }
</style>
<main>
<h1>Каталог мастерской</h1>
<?php if ($products === []): ?>
    <p>Товаров пока нет. Добавьте первый товар.</p>
<?php else: ?>
    <table>
        <caption>Остатки из базы SQLite</caption>
        <thead><tr><th scope="col">Товар</th><th scope="col">Наличие</th></tr></thead>
        <tbody>
        <?php foreach ($products as $product): ?>
            <tr>
                <td><?= htmlspecialchars($product['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td>
                <td><?= $product['stock'] > 0 ? (int) $product['stock'] . ' шт.' : 'Нет в наличии' ?></td>
            </tr>
        <?php endforeach; ?>
        </tbody>
    </table>
<?php endif; ?>
<small>Строки прочитаны из products и показаны через foreach.</small>
</main>
</html>

PHP сначала выполняет запрос, затем формирует HTML. В шаблоне foreach (...): и endforeach; — альтернативная запись того же цикла; она удобна среди тегов. <?= ... ?> выводит значение, как echo. htmlspecialchars превращает специальные символы названия в безопасный текст HTML: товар с именем <script> будет текстом, а не выполняемым скриптом. Эта функция не защищает SQL — это другая граница данных.

Обработка ошибки расположена до HTML. Поэтому status 503 отправляется до вывода страницы; exit прекращает дальнейшее выполнение. Пустой массив, напротив, оставляет ответ 200 и показывает «Товаров пока нет». При ошибке не выводится пустая таблица под видом исправного каталога.

Что попробовать самостоятельно

  • В init.php перед первым запуском поменяйте запас Молотка с 8 на 3. После установки на странице появится 3 шт.
  • В отдельной копии проекта переименуйте data/catalog.sqlite. Страница покажет отказ вместо создания новой базы.
  • После установки очистите таблицу в своей учебной базе: при следующем запросе увидите пустое состояние. Не выполняйте это действие на рабочей базе.
  • Добавьте товар с названием «Кисть & валик»: на странице амперсанд будет обычным текстом.

MySQL: сначала база и права, затем подключение из PHP

MySQL обычно работает отдельной службой. Здесь уже нужны адрес сервера, база, пользователь и пароль. PDO отправляет команды этой службе; данные не хранятся в PHP-файле. Успешный пример SQLite не проверяет наличие MySQL на вашем компьютере.

Соединение с сервером
  1. PHP выбирает pdo_mysql из начала DSN
  2. Устанавливает соединение с сервером/сокетом
  3. MySQL проверяет пользователя и права
  4. Выбирается существующая база catalog
  5. После успеха PHP получает $db и выполняет SQL

Установите или запустите учебный MySQL и включите pdo_mysql в используемом PHP. SQL ниже выполняет администратор базы в клиенте MySQL или панели хостинга. Пароль-заглушку надо заменить. Это отдельная подготовка окружения, не код посетительской страницы. На хостинге создание базы и пользователя часто доступно только в панели, с выданным провайдером именем базы.

CREATE DATABASE catalog CHARACTER SET utf8mb4;
CREATE USER 'catalog_app'@'localhost' IDENTIFIED BY 'REPLACE_WITH_LONG_RANDOM_PASSWORD';
GRANT SELECT, INSERT, UPDATE, DELETE ON catalog.* TO 'catalog_app'@'localhost';
USE catalog;
CREATE TABLE products (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    stock INT UNSIGNED NOT NULL
);
INSERT INTO products (name, stock) VALUES ('Молоток', 8), ('Перчатки', 15), ('Кисть', 0);

Пользователю приложения выданы чтение и изменение записей, но не создание/удаление таблиц. Схему создаёт установщик с отдельными правами. Имя host в учётной записи MySQL — часть правил допуска: пользователь для localhost не автоматически разрешён с другого компьютера. Дистанционное подключение и TLS настраиваются отдельно; не открывайте административный доступ всему интернету.

PHP: подключиться к подготовленному MySQL и вывести товарыПример для указанного окружения
<?php
$dsn = 'mysql:host=localhost;dbname=catalog;charset=utf8mb4';
$user = getenv('CATALOG_DB_USER');
$password = getenv('CATALOG_DB_PASSWORD');
if ($user === false || $password === false) {
    exit("Задайте CATALOG_DB_USER и CATALOG_DB_PASSWORD\n");
}
try {
    $db = new PDO($dsn, $user, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);
    $statement = $db->query('SELECT name, stock FROM products ORDER BY id');
    foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $product) {
        echo $product['name'], ': ', $product['stock'], " шт.\n";
    }
} catch (PDOException $error) {
    error_log('catalog_database_failure SQLSTATE=' . $error->getCode());
    exit("Каталог недоступен. Проверьте журнал приложения.\n");
}
Ожидаемый результат
Молоток: 8 шт.
Перчатки: 15 шт.
Кисть: 0 шт.

Это контекстный пример для подготовленного MySQL: синтаксис PHP проверяется отдельно от реального сервера. Перед запуском задайте CATALOG_DB_USER=catalog_app и пароль в окружении процесса PHP. getenv читает настройку, а не данные формы. Показанный результат ожидается после SQL-установки выше; SQLite-тест не является проверкой этого MySQL-соединения.

Часть DSN/настройкиЧто выбирает
mysql:Драйвер pdo_mysql.
host=localhostМестный MySQL. На Unix localhost часто использует сокет; 127.0.0.1 обычно выбирает TCP. Порт и сокет сверяют с конфигурацией сервера.
dbname=catalogУже созданную базу catalog.
charset=utf8mb4Кодировку соединения; для правильного хранения нужна совместимая схема таблицы.
Имя и парольУчётную запись MySQL, не логин посетителя сайта.
ATTR_EMULATE_PREPARES=falseПросьбу использовать нативную подготовку для поддерживаемых запросов. Семантику конкретного драйвера всё равно учитывают.
СбойЧто он обычно означаетПервое действие
could not find driverОтсутствует pdo_mysql именно в этом PHP.Проверить php.ini и список драйверов.
Connection refused / timeoutСервер не слушает указанный адрес/порт либо соединение блокируется.Проверить службу и настройки сети; это ещё не ошибка SQL.
Access deniedНе приняты реквизиты или правило доступа пользователя.Сверить имя, пароль, host и права. Не исправлять запуском сайта от root.
Unknown databaseНа этом сервере нет выбранной базы.Создать её при установке или исправить dbname; PDO не создаёт базу по имени.
Table ... doesn't existПодключение есть, но не найдена таблица.Выполнить миграцию и проверить выбранную базу/имя таблицы.

Код примера не разбирает текст ошибки по словам: сообщения зависят от сервера и языка. Для автоматических решений используют SQLSTATE и код конкретного драйвера. Подробности в обработке ошибок PDO. MySQLi — другой PHP-интерфейс для MySQL; его методы нельзя вызывать на объекте PDO. Сравнение и справочник находятся в разделе MySQLi.

Куда двигаться после первого каталога

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

Новое требованиеСледующий инструмент
Нужно изменить только выбранный товарUPDATE с WHERE и параметром id; не выполнять UPDATE без условия по случайности.
Заказ должен одновременно списать остаток и записать оплатуТранзакции: фиксация и откат связанной работы.
В каталоге тысячи строкLIMIT, предсказуемая сортировка и пагинация; не загружать весь каталог ради одной страницы.
Для каждого заказа нужно имя покупателяСвязь через customer_id и JOIN вместо отдельного сетевого запроса на каждой итерации.
Нужно получить одно число, например количество товаровfetchColumn после SELECT COUNT(*), а не загрузка всех строк.
Нужно выбрать конкретный режим чтенияСправочник PDOStatement и режимов выборки.

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

Что такое база данных, запрос SQL и результат PDO

Массив в одном запросе исчезает после его завершения. База данных хранит товары между запросами и помогает искать их по условиям. PHP отправляет ей SQL-команду; PDO — интерфейс PHP для разговора с разными базами через установленный драйвер.

Что уже пригодится. переменная и присваивание, массив, объявление и вызов функции, объект и вызов метода

Одна выборка
  1. PHP: значение артикула
  2. PDO prepare: SQL с параметром
  3. execute: отдельно передаём значение
  4. База выбирает строки
  5. fetch: PHP получает строку-массив или false

new PDO(...) создаёт объект соединения. -> вызывает его метод. :sku в SQL — место для значения, которое передадим отдельно. fetch(PDO::FETCH_ASSOC) возвращает строку в виде массива с именами столбцов.

Первая таблица и одна параметризованная выборкаСамостоятельный пример
<?php
declare(strict_types=1);

$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('CREATE TABLE products (sku TEXT PRIMARY KEY, title TEXT NOT NULL)');
$insert = $db->prepare('INSERT INTO products (sku, title) VALUES (:sku, :title)');
$insert->execute(['sku' => 'BOLT-1', 'title' => 'Болт']);
$query = $db->prepare('SELECT title FROM products WHERE sku = :sku');
$query->execute(['sku' => 'BOLT-1']);
$product = $query->fetch(PDO::FETCH_ASSOC);
echo $product === false ? 'Не найдено' : $product['title'];
Ожидаемый результат
Болт

Здесь SQLite создаёт учебную базу в памяти текущего процесса, поэтому пример не меняет вашу рабочую базу. CREATE TABLE описывает столбцы, INSERT добавляет запись, SELECT читает её. В реальном приложении соединение ведёт к постоянному хранилищу, а SQL-структуру создают миграцией. Нужны расширения PDO и pdo_sqlite.

Следующая ступень. Ниже отделяем SELECT от INSERT/UPDATE/DELETE, обрабатываем несколько строк и понимаем, что параметром можно передать значение, но нельзя произвольное имя столбца.

База хранит факты, PDO передаёт команды

Наглядная модель

В PDO команда и её значения передаются отдельно

Нужно найти заказ №42. Число 42 — значение для запроса, а не кусок SQL-кода.

  1. SQL с местом для значения

    SELECT ... FROM orders WHERE id = :id. Название таблицы задано программой.

  2. prepare()

    Создаёт подготовленный запрос PDO. Этот шаг ещё не возвращает найденный заказ.

  3. execute(['id' => 42])

    Передаёт значение параметра и выполняет запрос. Ошибка БД обрабатывается отдельно.

  4. fetch() или fetchAll()

    PHP получает строку или список строк и решает, что показать пользователю.

Параметры заменяют значения. Имя колонки и направление сортировки выбирают из разрешённого списка, а не связывают как :id.

Каталог магазина хранит товары, цены и остатки. PDO даёт единый PHP-интерфейс, но не превращает SQL разных баз в один диалект. Пагинация, блокировки, типы JSON и возврат вставленного ID различаются между SQLite, MySQL и PostgreSQL. Поэтому называйте СУБД в проекте и тестах. SQLite удобна для самостоятельных примеров, но её успешный тест не доказывает одинаковую конкуренцию на MySQL.

Соединение создают в точке сборки приложения и передают сервисам. Не открывайте новое соединение внутри каждой функции и не используйте административного пользователя базы. У приложения должны быть только необходимые права. DSN и пароль берутся из конфигурации, ошибка подключения пишется в закрытый журнал без секрета. Для MySQL явно задайте charset=utf8mb4, PDO::ERRMODE_EXCEPTION и осознанный режим подготовленных запросов.

Параметр запроса обозначает значение целиком, а не произвольный кусок SQL. Поэтому WHERE id=:id корректно, а ORDER BY :column не выбирает столбец так, как ожидает новичок. Имя таблицы, направление сортировки и список колонок выбираются кодом из разрешённого набора. Подготовить SQL, в который уже вклеен ввод, недостаточно: опасная строка должна находиться именно в параметре.

Подготовка, выполнение и чтение — разные шаги

Ищем товар: строка с SQL остаётся даннымиСамостоятельный пример
<?php
declare(strict_types=1);

$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)');
$insert = $db->prepare('INSERT INTO products VALUES (?, ?, ?)');
$insert->execute([1,'Notebook',12000]);
$insert->execute([2,'Pen',150]);
$find = $db->prepare('SELECT name FROM products WHERE name = :name');
foreach (['Pen', "x' OR 1=1 --"] as $name) {
    $find->execute(['name'=>$name]);
    $row = $find->fetch(PDO::FETCH_ASSOC);
    echo $row === false ? "not-found
" : $row['name']."
";
}
Ожидаемый результат
Pen
not-found

База существует только в памяти этого процесса. Цены указаны в минимальных денежных единицах. Вредоносная строка не меняет SQL, потому что передана как значение. Отсутствие строки проверяется через === false.

fetch возвращает одну строку либо false, fetchAll загружает весь оставшийся результат. Для пяти строк это удобно; для миллиона заказов может исчерпать память. Выбирайте только нужные столбцы, ограничивайте результат и используйте ключевую пагинацию для больших экспортов. Цикл fetch экономит PHP-массив, но драйвер может буферизовать результат: режим буферизации и повторное использование соединения нужно изучать отдельно.

Не используйте rowCount для подсчёта SELECT: поведение зависит от драйвера. Для количества нужен SELECT COUNT(*), а для факта наличия — подходящий ограниченный запрос. fetchColumn возвращает false при отсутствии строки, поэтому значение 0 нельзя проверять через if (!$count). Типы чисел из драйвера тоже не стоит угадывать: контракт репозитория может явно преобразовать уже проверенные значения в нужные типы.

Сортировка и LIKE требуют отдельных решений

Разрешённые столбцы и буквальный поиск процентаСамостоятельный пример
<?php
declare(strict_types=1);

$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)');
$insert = $db->prepare('INSERT INTO products VALUES (?, ?, ?)');
$insert->execute([1,'50% cotton',200]);
$insert->execute([2,'50 cotton',100]);
$sortInput = 'price';
$sort = ['name'=>'name','price'=>'price'][$sortInput] ?? 'name';
$term = '50%';
$pattern = '%'.strtr($term,['!'=>'!!','%'=>'!%','_'=>'!_']).'%';
$statement = $db->prepare("SELECT name FROM products WHERE name LIKE ? ESCAPE '!' ORDER BY $sort");
$statement->execute([$pattern]);
echo implode(',', $statement->fetchAll(PDO::FETCH_COLUMN))."
";
Ожидаемый результат
50% cotton

Параметры останавливают SQL-инъекцию, но % и _ остаются операторами LIKE. Здесь пользователь ищет буквальный процент, поэтому экранируется именно грамматика LIKE. SQL-пример проверен на SQLite; правила сравнения, регистр и индексы зависят от СУБД.

Для IN нужен отдельный placeholder на каждый элемент. Пустой массив обработайте до SQL: «нет разрешённых ID» обычно означает пустой результат, а не все записи. Список предварительно ограничивают по длине и типам. Для сотен тысяч ID лучше временная таблица или пакетный подход. Никогда не вставляйте implode входных строк в SQL под предлогом того, что они «должны быть числами».

Именованные placeholders не надо окружать кавычками. Не смешивайте ? и :name в одном запросе и не полагайтесь на повтор одного имени при нативной подготовке: используйте разные имена. bindValue фиксирует значение, bindParam связывает переменную по ссылке и читает её позже при execute. Для большинства запросов массив execute проще; для LIMIT и иных чувствительных типов бывает удобнее явный PDO::PARAM_INT.

Главная стоимость часто находится за пределами PHP

Шаблон N+1 возникает, когда сначала читают 100 заказов, а затем для каждого отдельным запросом получают покупателя. Сто циклов PHP могут быть дешёвыми, а сто сетевых обменов — дорогими. Сначала посчитайте запросы и посмотрите планы выполнения, затем решайте, нужны JOIN, пакетная выборка или кеш. Индекс проектируют под реальные фильтры и сортировку; индекс на каждый столбец увеличивает стоимость записи.

Не ловите PDOException, чтобы молча вернуть пустой каталог: пользователь увидит «товаров нет», а мониторинг пропустит недоступную базу. Граница HTTP превращает инфраструктурную ошибку в контролируемый 500/503, а журнал получает идентификатор запроса. Ожидаемый конфликт уникальности, например занятый email, можно преобразовать в доменную ошибку после проверки кода конкретной СУБД. Текст исключения не является стабильным протоколом.

NULL, деньги и поиск больших страниц

SQL NULL обозначает отсутствие значения, поэтому условие column = NULL не работает как обычное равенство: используются IS NULL и IS NOT NULL. На PHP-стороне null, пустая строка и ноль тоже имеют разный смысл. Определите контракт поля при записи, иначе поиск «товары без описания» станет зависеть от того, какая форма когда-то сохранила пустое значение.

Деньги часто хранят целыми минимальными единицами либо точным DECIMAL с согласованным масштабом. Float способен вносить двоичные погрешности, поэтому не превращайте без необходимости decimal-строку базы в float и обратно. Валюта и правило округления относятся к данным операции. Итог заказа вычисляют из серверных цен, а не из суммы, присланной браузером.

Глубокий OFFSET заставляет базу пройти пропущенные строки. Для больших экспортов подходит keyset-пагинация по устойчивому ключу: WHERE id > :last_id ORDER BY id LIMIT :limit. При сортировке по неуникальной дате нужен дополнительный ID, иначе записи с одинаковым временем могут повториться или потеряться. Выбор стратегии зависит от требований к стабильности снимка и конкурентным изменениям.

Практика: проверьте решение, а не только успешный путь

Добавьте фильтр id IN и сортировку по убыванию цены. Что делать с пустым списком и sort=price;DROP TABLE products?

Разбор решения

Пустой список вернуть как пустой результат без запроса. Для каждого проверенного ID создать placeholder; направление ASC/DESC и имя столбца взять из разрешённого отображения. Вредоносная строка не должна попадать в SQL ни на одном этапе.

Функции и методы этой темы

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

PDO::__constructСоздать соединение, через которое сайт будет читать и менять данные.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

new PDO(string $dsn, ?string $username = null, #[SensitiveParameter] ?string $password = null, ?array $options = null)

Открыть каталог и прочитать первый товар. DSN — строка выбора драйвера и базы. new создаёт соединение; таблица появляется только после отдельного SQL CREATE TABLE.

Данные и действие: PDO::__construct
  1. new PDO('sqlite::memory:')
  2. Сначала SQLite получает схему из schema.sql, затем SELECT выбирает строку 1. fetch переносит её в $row; ключ name содержит название, stock —8.
  3. После выполнения: Молоток — 8 шт.

В этом самостоятельном примере подготовка указана непосредственно в коде и дополнительных файлах архива. Запуск: php example.php; требования и внешние зависимости отмечены перед кодом.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Открыть каталог и прочитать первый товарСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec(file_get_contents(__DIR__ . '/schema.sql'));
$statement = $db->query('SELECT name, stock FROM products WHERE id = 1');
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Молоток — 8 шт.

Сначала SQLite получает схему из schema.sql, затем SELECT выбирает строку 1. fetch переносит её в $row; ключ name содержит название, stock —8.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Молоток8 шт.

Если требование изменится. Для сохранения между запросами замените DSN на sqlite: с абсолютным путём к файлу. Отсутствующий родительский каталог и недоступный драйвер вызывают отказ подключения; обработка показана в базовом уроке.

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

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

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$dsnИмя драйвера, двоеточие и параметры конкретной СУБД. Например sqlite::memory: либо mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4.
$username / $passwordИмя и пароль либо null; отдельные драйверы не требуют их. Пароль имеет SensitiveParameter в сигнатуре, но это не защита от вашего собственного журнала/вывода.
$optionsМассив атрибут → значение, применяемых при соединении, либо null. Часть атрибутов задаётся только здесь.

new возвращает объект соединения. Любая ошибка подключения вызывает PDOException независимо от ERRMODE. Три формы DSN: полный driver:..., доверенный alias из pdo.dsn.NAME в php.ini (не .htaccess/httpd.conf), исторический uri: с файлом/URL DSN. uri: устарел с PHP 8.5 из-за риска внешнего источника: для нового кода используйте собственную конфигурацию и полный DSN. Передавать пользовательский DSN нельзя. PDO не переводит диалекты SQL и не добавляет отсутствующую поддержку БД.

Путь данных
  1. DSN sqlite::memory: выбирает драйвер и временную базу
  2. new PDO создаёт соединение $db
  3. query выполняет SELECT 42, fetchColumn возвращает 42

Как читать этот вариант. sqlite::memory: создаёт пустую базу только в памяти этого соединения. SELECT 42 возвращает одну строку с одним столбцом. query выполняет SQL, fetchColumn читает его значение; это разные вызовы.

Открыть учебную базу и получить значениеСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
echo $db->query('SELECT 42')->fetchColumn(), "\n";
Ожидаемый результат
42

Как читать этот вариант. Учебный файл появляется внутри временной папки примера. В отличие от :memory: его данные доступны другому соединению с тем же путём. Это не проверка конкурентного доступа: две операции идут последовательно.

Два соединения видят одну локальную базуСамостоятельный пример
<?php
$dsn = 'sqlite:' . __DIR__ . '/shop.sqlite';
$first = new PDO($dsn);
$first->exec('CREATE TABLE orders (id INTEGER PRIMARY KEY)');
$first->exec('INSERT INTO orders VALUES (42)');
$second = new PDO($dsn);
echo $second->query('SELECT id FROM orders')->fetchColumn(), "\n";
Ожидаемый результат
42

Рядом: PDO::connect(), PDO::getAvailableDrivers(), PDO::prepare().

Дополнительные ранее разобранные сценарии.

DSN выбирает драйвер и адрес. Создаёт PDO; при ошибке соединения выбрасывает PDOException. Нужны PDO и соответствующий драйвер.

Как читать этот вариант. Ни один внешний сервер не используется.

Временная учебная базаСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:');
echo $db->getAttribute(PDO::ATTR_DRIVER_NAME)."\n";
Ожидаемый результат
sqlite

Как читать этот вариант. Ошибка запроса становится исключением; технические подробности не выводятся пользователю.

Явный режим исключенийСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
try { $db->query('SELECT * FROM missing'); } catch (PDOException) { echo "query failed safely\n"; }
Ожидаемый результат
query failed safely

Рядом по задаче. prepare для параметров, query для статического SQL; соединение передают зависимостью.

PDO::prepareПодготовить SQL-шаблон и отделить данные пользователя от грамматики SQL.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDO::prepare(string $query, array $options = []): PDOStatement|false

Найти выбранный товар по номеру. prepare сохраняет SQL-шаблон; :id — место для одного значения. execute передаёт 2, после чего fetch читает товар. Подготовка сама товар не возвращает.

Данные и действие: PDO::prepare
  1. $db->prepare('... WHERE id = :id')
  2. Ключ id в PHP-массиве сопоставлен :id в SQL. Значение 2 выбирает Перчатки; текст и остаток берутся из базы, а не из введённой строки.
  3. После выполнения: Перчатки — 15 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Найти выбранный товар по номеруСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->prepare('SELECT name, stock FROM products WHERE id = :id');
$statement->execute(['id' => 2]);
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Перчатки — 15 шт.

Ключ id в PHP-массиве сопоставлен :id в SQL. Значение 2 выбирает Перчатки; текст и остаток берутся из базы, а не из введённой строки.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Перчатки15 шт.

Если требование изменится. Если номер пришёл из формы, сначала проверьте целое положительное значение и право читать товар. Имя колонки и направление сортировки параметрами не становятся: выбирайте их из собственного разрешённого списка.

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

Подготовить SQL-шаблон и отделить данные пользователя от грамматики SQL.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$querySQL для конкретной БД с :name либо ?, без смешения стилей. Один маркер на одно полное значение.
$optionsАтрибуты создаваемого statement, например ATTR_CURSOR => CURSOR_SCROLL, если драйвер это поддерживает.

Возвращает PDOStatement либо false/ошибку по ERRMODE. Подготовка не выполняет запрос. При эмуляции prepare вообще не посылает SQL серверу и не подтверждает его синтаксис. Маркер не заменяет имя таблицы, ORDER BY, часть строки, список IN или целый SQL-фрагмент; не заключайте его в кавычки. Имена не повторяют при нативной подготовке: для двух мест два маркера. Эмуляция/переписывание может преобразовать стиль маркеров. С PHP 7.4 ?? обозначает буквальный ? при переписывании, например оператор PostgreSQL. До 8.4 общий разборщик учитывал нестандартное backslash-экранирование кавычек и мог ошибиться; с 8.4 применяются драйверные SQL-разборщики. Общий распознаёт одиночные/двойные кавычки, -- и невложенные /* */ комментарии; драйвер может дополнять правила.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. В шаблоне WHERE id = :id ещё нет значения
  2. execute связывает id=2 как данные
  3. База возвращает строку Notebook
  4. fetchColumn передаёт значение в PHP

Как читать этот вариант. SQL содержит место для целого значения, а не строковую вставку. Ключ id соответствует :id; двоеточие в ключе массива допустимо, но не обязательно. execute запускает поиск, fetchColumn читает найденное название.

Отдельно подготовить поиск и передать значениеСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products WHERE id = :id');
$statement->execute(['id' => 2]);
echo $statement->fetchColumn(), "\n";
Ожидаемый результат
Notebook

Как читать этот вариант. Подготовка вынесена перед циклом. Повторно используются SQL и объект команды; возможность кеширования плана зависит от драйвера. closeCursor явно завершает предыдущий результат.

Повторить один поиск для двух товаровСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products WHERE id = ?');
foreach ([1, 3] as $id) {
    $statement->execute([$id]);
    echo $statement->fetchColumn(), "\n";
    $statement->closeCursor();
}
Ожидаемый результат
Pen
Box

Рядом: PDOStatement::execute(), PDOStatement::bindValue(), PDO::query().

Дополнительные ранее разобранные сценарии.

Возвращает statement либо false/исключение в зависимости от режима ошибок. При эмуляции некоторые SQL-ошибки обнаруживаются только на execute.

Как читать этот вариант. ID приходит отдельным значением, не склеивается с SQL.

Поиск товара по IDСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('SELECT name FROM products WHERE id=?');
$s->execute([1]);
echo $s->fetchColumn()."\n";
Ожидаемый результат
Tea

Как читать этот вариант. Один шаблон выполняется с разными значениями. Для связанной пачки нужна транзакция.

Повторное использование шаблонаСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('UPDATE products SET stock=? WHERE id=?');
$s->execute([5,1]);
$s->execute([2,2]);
echo $db->query('SELECT SUM(stock) FROM products')->fetchColumn()."\n";
Ожидаемый результат
7

Рядом по задаче. execute передаёт значения, bindValue задаёт тип; динамический SQL через allowlist.

PDO::queryВыполнить SQL без маркеров и получить объект результата.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDO::query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false

Стоимость зависит от внешней работы. Измеряйте время ожидания, объём ответа и число вызовов. Короткая строка PHP может скрывать медленный SQL или сеть.

Показать известный список товаров. query выполняет готовый SQL без внешних значений. ORDER BY id фиксирует порядок. Возвращается источник строк, а не сам массив товаров.

Данные и действие: PDO::query
  1. $db->query('SELECT name, stock FROM products ORDER BY id')
  2. Каждый fetch читает следующую строку. while повторяет вывод, пока не получит false — конец. Значение stock=0 не прекращает цикл: проверяется вся строка.
  3. После выполнения: Молоток — 8 шт. · Перчатки — 15 шт. · Кисть — 0 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
while (...)Повторяет блок, пока условие истинно. В чтении результата проверка конца связана с конкретным методом: false или null указаны в его контракте.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Показать известный список товаровСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->query('SELECT name, stock FROM products ORDER BY id');
while (($row = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
    echo $row['name'], ' — ', $row['stock'], " шт.\n";
}
Ожидаемый результат
Молоток — 8 шт.
Перчатки — 15 шт.
Кисть — 0 шт.

Каждый fetch читает следующую строку. while повторяет вывод, пока не получит false — конец. Значение stock=0 не прекращает цикл: проверяется вся строка.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Молоток8 шт.
Перчатки15 шт.
Кисть0 шт.

Если требование изменится. При внешнем фильтре используйте prepare/execute. Для неограниченной выгрузки не добавляйте fetchAll автоматически: размер PHP-массива растёт вместе с выборкой.

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

Выполнить SQL без маркеров и получить объект результата.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$queryГотовый статический SQL. Для placeholders нужны отдельные prepare и execute.
$fetchModeНеобязательный режим FETCH_* для результата; null сохраняет режим соединения.
$fetchModeArgsАргументы как у setFetchMode: для COLUMN номер, CLASS имя/constructorArgs, INTO объект.

Возвращает statement либо false/ошибку. Несчитанные результаты могут мешать следующему query на том же соединении: дочитайте или вызовите closeCursor. Для повторяемого запроса удобнее prepare один раз; ускорение зависит от драйвера/объёма, не от названия метода.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. query выполняет SELECT id,name
  2. FETCH_COLUMN,1 выбирает name, а не id
  3. foreach выдаёт по названию в порядке ORDER BY id

Как читать этот вариант. query совмещает prepare и execute. foreach читает текущий результат; в SQL нет пользовательских значений. Без ORDER BY порядок строк не гарантируется.

Статический список названийСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->query('SELECT name FROM products ORDER BY id');
foreach ($statement as $row) { echo $row['name'], "\n"; }
Ожидаемый результат
Pen
Notebook
Box

Как читать этот вариант. Режим FETCH_COLUMN требует номера столбца с нуля. При FETCH_CLASS дополнительные аргументы — класс и аргументы конструктора, при FETCH_INTO — объект; эти настройки описаны у setFetchMode.

Сразу выбрать один столбец для каждого шагаСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->query('SELECT id, name FROM products ORDER BY id', PDO::FETCH_COLUMN, 1);
foreach ($statement as $name) { echo $name, "\n"; }
Ожидаемый результат
Pen
Notebook
Box

Рядом: PDO::prepare(), PDOStatement::setFetchMode(), PDOStatement::closeCursor().

Дополнительные ранее разобранные сценарии.

Подготавливает и выполняет SQL без отдельного массива параметров. Возвращает statement; ошибки зависят от ERRMODE.

Как читать этот вариант. В SQL нет внешних значений.

Статический счётчик каталогаСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
echo $db->query('SELECT COUNT(*) FROM products')->fetchColumn()."\n";
Ожидаемый результат
2

Как читать этот вариант. Результат обходится построчно; буферизация зависит от драйвера.

Фиксированный отчёт остатковСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
foreach ($db->query('SELECT name FROM products WHERE stock>0 ORDER BY id') as $row) echo $row['name']."\n";
Ожидаемый результат
Tea

Рядом по задаче. prepare + execute нужны для входных значений; exec подходит командам без набора строк.

PDO::execВыполнить команду изменения без чтения набора строк.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDO::exec(string $statement): int|false

Стоимость зависит от внешней работы. Измеряйте время ожидания, объём ответа и число вызовов. Короткая строка PHP может скрывать медленный SQL или сеть.

Изменить остаток и увидеть сохранённое значение. exec подходит SQL-команде изменения. Результат — счётчик затронутых строк; чтобы показать товар, нужен отдельный SELECT.

Данные и действие: PDO::exec
  1. $db->exec('UPDATE products SET stock = 7 WHERE id = 1')
  2. После UPDATE читаем ту же строку и наблюдаем 7 вместо 8. Сообщение отражает данные, действительно возвращённые базой.
  3. После выполнения: Молоток — 7 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Изменить остаток и увидеть сохранённое значениеСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$changed = $db->exec('UPDATE products SET stock = 7 WHERE id = 1');
if ($changed === false) { throw new RuntimeException('Остаток не записан'); }
$statement = $db->query('SELECT name, stock FROM products WHERE id = 1');
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Молоток — 7 шт.

После UPDATE читаем ту же строку и наблюдаем 7 вместо 8. Сообщение отражает данные, действительно возвращённые базой.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Молоток7 шт.

Если требование изменится. Ноль изменений может быть успешным исходом. Для количества из формы подготовьте параметризованный UPDATE. Глобальная exec запускает процессы; PDO::exec выполняет SQL в соединении.

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

Выполнить команду изменения без чтения набора строк.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$statementГотовый SQL без маркеров; переменные данные передают через prepare/execute.

Возвращает число затронутых строк, включая 0, либо false/ошибку. Используйте === false. exec не возвращает данные SELECT. Не путайте PDO::exec с глобальной exec запуска внешней программы: здесь работает только SQL в соединении.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. UPDATE ищет id=99
  2. Ни одной строки → число 0
  3. === false не принимает успешный ноль за ошибку

Как читать этот вариант. Проверка if (!$changed) ошибочно посчитала бы ноль неудачей. exec не выдаёт строки SELECT; он возвращает счётчик изменений.

Ноль изменений — допустимый успешный результатСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$changed = $db->exec('UPDATE products SET price = 500 WHERE id = 99');
if ($changed === false) { throw new RuntimeException('Ошибка SQL'); }
echo 'Изменено:', $changed, "\n";
Ожидаемый результат
Изменено:0

Как читать этот вариант. Команда затронула одну запись. Для цены из формы нужен параметризованный UPDATE, а не конкатенация входа. Различие matched/changed rows зависит от СУБД и её параметров.

Обновить статическое правило и проверить данныеСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$changed = $db->exec('UPDATE products SET price = price + 10 WHERE id = 1');
echo $changed, ':', $db->query('SELECT price FROM products WHERE id = 1')->fetchColumn(), "\n";
Ожидаемый результат
1:130

Рядом: PDO::prepare(), PDO::query(), PDOStatement::rowCount().

Дополнительные ранее разобранные сценарии.

Возвращает число затронутых строк, включая 0, либо false/исключение. Подходит статическим DDL и простым служебным командам.

Как читать этот вариант. Демонстрируется число затронутых строк.

Изменить известный товарСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$changed=$db->exec('UPDATE products SET stock=stock+1 WHERE id=1');
echo $changed." changed\n";
Ожидаемый результат
1 changed

Как читать этот вариант. Строгое сравнение не путает отсутствие записи с ошибкой.

Ноль изменений — допустимый результатСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$changed=$db->exec('UPDATE products SET stock=1 WHERE id=99');
echo $changed === 0 ? "no matching row\n" : "other\n";
Ожидаемый результат
no matching row

Рядом по задаче. query возвращает строки; PDOStatement::execute выполняет параметризованную команду.

PDOStatement::executeЗапустить подготовленную команду с текущими значениями.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::execute(?array $params = null): bool

Стоимость зависит от внешней работы. Измеряйте время ожидания, объём ответа и число вызовов. Короткая строка PHP может скрывать медленный SQL или сеть.

Запустить сохранённый поиск с другим номером. Объект $statement хранит шаблон. execute запускает его с текущим массивом значений; true означает выполнение команды, а не найденный товар.

Данные и действие: PDOStatement::execute
  1. $statement->execute(['id' => 3])
  2. Команда успешно находит Кисть с нулевым остатком. Отсутствующая строка отличалась бы результатом fetch === false.
  3. После выполнения: Кисть — 0 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Запустить сохранённый поиск с другим номеромСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->prepare('SELECT name, stock FROM products WHERE id = :id');
$statement->execute(['id' => 3]);
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Кисть — 0 шт.

Команда успешно находит Кисть с нулевым остатком. Отсутствующая строка отличалась бы результатом fetch === false.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Кисть0 шт.

Если требование изменится. Для позиционных ? передают список [3], для :id — массив с ключом id. Не передавайте весь POST: выберите и проверьте только нужные поля. execute([]) меняет привязки; execute() использует уже связанные значения.

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

Запустить подготовленную команду с текущими значениями.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$paramsnull использует предварительно связанные значения/переменные. Массив задаёт входные значения: список для ? с индекса 0 либо ключи имён для :name.

true при успешном выполнении, false/ошибка по ERRMODE. Значения массива имеют тип привязки PARAM_STR; для точного PARAM_INT/BOOL/LOB используйте bindValue/bindParam и execute() без массива. Массив заменяет текущие привязки: execute([]) не равно execute(). Лишний параметр — ошибка; не полагайтесь на допускаемые отдельным драйвером пропуски. Одно значение не раскрывается автоматически в список IN, имя таблицы или фрагмент SQL.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. prepare сохраняет шаблон id=:id
  2. execute получает id=1 и исполняет SQL
  3. true — команда выполнена
  4. fetchColumn отдельно получает Pen

Как читать этот вариант. Массив execute содержит значения входных параметров. true означает успешное исполнение, а не наличие найденной строки: строку читают отдельно.

Передать идентификатор без ручной склейки SQLСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products WHERE id = :id');
$ok = $statement->execute(['id' => 1]);
var_dump($ok);
echo $statement->fetchColumn(), "\n";
Ожидаемый результат
bool(true)
Pen

Как читать этот вариант. Грамматику из знаков ? создаёт программа, сами ID остаются параметрами. Перед генерацией реального поиска обработайте пустой список и ограничьте его размер; лимит числа маркеров зависит от сервера/драйвера.

Найти два товара: два значения требуют двух местСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$ids = [1, 3];
$marks = implode(', ', array_fill(0, count($ids), '?'));
$statement = $db->prepare('SELECT name FROM products WHERE id IN (' . $marks . ') ORDER BY id');
$statement->execute($ids);
echo implode(', ', $statement->fetchAll(PDO::FETCH_COLUMN)), "\n";
Ожидаемый результат
Pen, Box

Рядом: PDO::prepare(), PDOStatement::bindValue(), PDOStatement::bindParam().

Дополнительные ранее разобранные сценарии.

Возвращает bool или бросает PDOException в режиме исключений. Массив должен соответствовать placeholders; значения не заменяют структуру SQL.

Как читать этот вариант. Имена в массиве сопоставляются placeholders без ручных кавычек.

Именованный параметрСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('SELECT name FROM products WHERE id=:id');
$s->execute(['id'=>2]);
echo $s->fetchColumn()."\n";
Ожидаемый результат
Coffee

Как читать этот вариант. Отсутствие товара не является транспортной ошибкой базы.

Успешный запрос без результатаСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('SELECT name FROM products WHERE id=?');
$ok=$s->execute([99]);
echo $ok && $s->fetch()===false ? "executed but absent\n" : "other\n";
Ожидаемый результат
executed but absent

Рядом по задаче. fetch читает результат; rowCount применим к изменениям с учётом драйвера.

PDOStatement::bindValueЗакрепить текущее значение за одним маркером SQL.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::bindValue(string|int $param, mixed $value, int $type = PDO::PARAM_STR): bool

Понять, какой номер попадёт в поиск. Запоминается значение на момент вызова: поздняя смена переменной не меняет параметр. PDO::PARAM_INT задаёт тип передачи целого числа; это не проверка допустимого диапазона.

Данные и действие: PDOStatement::bindValue
  1. $statement->bindValue(':id', $id, PDO::PARAM_INT)
  2. Сначала $id равен 1, затем 2. Разный момент чтения значения объясняет различие результатов; схема каталога в обоих архивах одинакова.
  3. После выполнения: Молоток — 8 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Понять, какой номер попадёт в поискСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->prepare('SELECT name, stock FROM products WHERE id = :id');
$id = 1;
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$id = 2;
$statement->execute();
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Молоток — 8 шт.

Сначала $id равен 1, затем 2. Разный момент чтения значения объясняет различие результатов; схема каталога в обоих архивах одинакова.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Молоток8 шт.

Если требование изменится. Для одного готового значения удобен bindValue; bindParam нужен при осознанной работе с изменяемой переменной и повторными execute. Не связывайте одну и ту же временную переменную с разными маркерами, ожидая копирования значений.

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

Закрепить текущее значение за одним маркером SQL.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$param:name для имени либо позиция ? с 1.
$valueЗначение или выражение; сохраняется на момент привязки.
$typePARAM_STR по умолчанию; INT, BOOL, NULL, LOB и поддерживаемые драйверные модификаторы.

true/false по успешности привязки; исполнение происходит позже. PARAM_INT не доказывает, что первоначальная строка была допустимым ID: проверка входа остаётся отдельным шагом. Не добавляйте кавычки вокруг маркера в SQL.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. id=1 → bindValue сохраняет 1
  2. Исходный $id меняется на 2
  3. execute использует сохранённое 1

Как читать этот вариант. bindValue запомнил число 1 в момент вызова. Изменение $id позже не меняет сохранённое значение. В отличие от bindParam сюда допустимо передать выражение.

Связать значение, затем изменить исходную переменнуюСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products WHERE id = :id');
$id = 1;
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$id = 2;
$statement->execute();
echo $statement->fetchColumn(), "\n";
Ожидаемый результат
Pen

Как читать этот вариант. Позиция при bindValue начинается с 1, а не с 0 как ключ execute([...]). Тип привязки задаёт передачу, но не проверяет бизнес-правило: входной limit предварительно проверяют и ограничивают.

Передать целочисленный предел выборкиСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products ORDER BY id LIMIT ?');
$statement->bindValue(1, 2, PDO::PARAM_INT);
$statement->execute();
echo implode(', ', $statement->fetchAll(PDO::FETCH_COLUMN)), "\n";
Ожидаемый результат
Pen, Notebook

Рядом: PDOStatement::bindParam(), PDOStatement::execute().

Дополнительные ранее разобранные сценарии.

Фиксирует значение в момент вызова и возвращает bool. Позиционные параметры нумеруются с 1; именованные используют :name.

Как читать этот вариант. Лимит предварительно должен быть в разрешённом диапазоне.

Целочисленный LIMITСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('SELECT name FROM products ORDER BY id LIMIT :limit');
$s->bindValue(':limit',1,PDO::PARAM_INT);
$s->execute();
echo $s->fetchColumn()."\n";
Ожидаемый результат
Tea

Как читать этот вариант. Показано отличие от bindParam на жизненном примере выбранного товара.

Значение фиксируется сразуСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$id=1;
$s=$db->prepare('SELECT name FROM products WHERE id=?');
$s->bindValue(1,$id,PDO::PARAM_INT);
$id=2;
$s->execute();
echo $s->fetchColumn()."\n";
Ожидаемый результат
Tea

Рядом по задаче. bindParam связывает переменную по ссылке; execute(array) проще для обычных значений.

PDOStatement::bindParamСвязать переменную, актуальное значение которой прочитается при исполнении.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::bindParam(string|int $param, mixed &$var, int $type = PDO::PARAM_STR, int $maxLength = 0, mixed $driverOptions = null): bool

Понять, какой номер попадёт в поиск. Привязывается переменная: её текущее значение читается при execute. PDO::PARAM_INT задаёт тип передачи целого числа; это не проверка допустимого диапазона.

Данные и действие: PDOStatement::bindParam
  1. $statement->bindParam(':id', $id, PDO::PARAM_INT)
  2. Сначала $id равен 1, затем 2. Разный момент чтения значения объясняет различие результатов; схема каталога в обоих архивах одинакова.
  3. После выполнения: Перчатки — 15 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Понять, какой номер попадёт в поискСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->prepare('SELECT name, stock FROM products WHERE id = :id');
$id = 1;
$statement->bindParam(':id', $id, PDO::PARAM_INT);
$id = 2;
$statement->execute();
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Перчатки — 15 шт.

Сначала $id равен 1, затем 2. Разный момент чтения значения объясняет различие результатов; схема каталога в обоих архивах одинакова.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Перчатки15 шт.

Если требование изменится. Для одного готового значения удобен bindValue; bindParam нужен при осознанной работе с изменяемой переменной и повторными execute. Не связывайте одну и ту же временную переменную с разными маркерами, ожидая копирования значений.

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

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

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$paramИмя с : либо позиция ? с 1.
&$varИменно переменная. До execute её можно изменить; драйвер может преобразовать её тип.
$typePARAM_*; для INOUT процедуры добавляют | PDO::PARAM_INPUT_OUTPUT, если драйвер поддерживает.
$maxLengthДлина для выходного/INOUT параметра процедуры; для обозначения OUT требуется явно заданная длина.
$driverOptionsДрайверные дополнительные параметры.

true/false. IN, OUT и INOUT — направление передачи: в БД, из БД, в обе стороны. SQLite не имеет серверных процедур с OUT, поэтому такие примеры нельзя выдавать за универсальные. Для одноразового входного значения чаще яснее bindValue или массив execute.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. bindParam связывает имя переменной $id
  2. Код присваивает $id=2
  3. execute читает её текущее значение 2
  4. Запрос возвращает Notebook

Как читать этот вариант. Здесь привязана переменная, а не снимок числа. & в сигнатуре требует переменную; bindParam(1, 2) использовать нельзя.

Использовать новое значение при executeСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products WHERE id = ?');
$id = 1;
$statement->bindParam(1, $id, PDO::PARAM_INT);
$id = 2;
$statement->execute();
echo $statement->fetchColumn(), "\n";
Ожидаемый результат
Notebook

Как читать этот вариант. На каждом шаге цикл присваивает той же переменной новый ID. Ссылку создают на стабильную переменную вне цикла; попытка связать в цикле множество параметров с одной переменной приводит к одинаковым последним значениям.

Однократно привязать переменную для повторных командСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products WHERE id = :id');
$id = 0;
$statement->bindParam(':id', $id, PDO::PARAM_INT);
foreach ([3, 1] as $id) {
    $statement->execute();
    echo $statement->fetchColumn(), "\n";
    $statement->closeCursor();
}
Ожидаемый результат
Box
Pen

Рядом: PDOStatement::bindValue(), PDOStatement::bindColumn(), PDOStatement::execute().

Дополнительные ранее разобранные сценарии.

Второй аргумент передаётся по ссылке, поэтому нужен изменяемый PHP-variable, а не литерал или результат выражения. Возвращает bool.

Как читать этот вариант. Тот же выбор товара даёт другой результат, чем bindValue.

Значение читается позднееСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$id=1;
$s=$db->prepare('SELECT name FROM products WHERE id=?');
$s->bindParam(1,$id,PDO::PARAM_INT);
$id=2;
$s->execute();
echo $s->fetchColumn()."\n";
Ожидаемый результат
Coffee

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

Один binding для циклаСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$id=0;
$s=$db->prepare('SELECT name FROM products WHERE id=?');
$s->bindParam(1,$id,PDO::PARAM_INT);
foreach ([1,2] as $id) { $s->execute(); echo $s->fetchColumn()."\n"; }
Ожидаемый результат
Tea
Coffee

Рядом по задаче. bindValue фиксирует значение; массив execute не требует отдельного связывания.

PDOStatement::fetchВзять очередную строку результата в подходящем для PHP виде.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::fetch(int $mode = PDO::FETCH_DEFAULT, int $cursorOrientation = PDO::FETCH_ORI_NEXT, int $cursorOffset = 0): mixed

Прочитать один товар и отдельно проверить отсутствие. FETCH_ASSOC делает имена колонок ключами массива. Один вызов берёт одну следующую строку; false означает, что строки нет.

Данные и действие: PDOStatement::fetch
  1. $statement->fetch(PDO::FETCH_ASSOC)
  2. Строка существует, хотя остаток равен 0. Проверка $row === false не путает пустой остаток с отсутствующим товаром.
  3. После выполнения: Кисть — 0 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Прочитать один товар и отдельно проверить отсутствиеСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->query('SELECT name, stock FROM products WHERE id = 3');
$row = $statement->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
    echo "Товар не найден\n";
} else {
    echo $row['name'], ' — ', $row['stock'], " шт.\n";
}
Ожидаемый результат
Кисть — 0 шт.

Строка существует, хотя остаток равен 0. Проверка $row === false не путает пустой остаток с отсутствующим товаром.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Кисть0 шт.

Если требование изменится. Поменяйте id=3 на 99: получится «Товар не найден». Если SQL обращается к несуществующей таблице, это уже исключение, а не пустой результат.

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

Взять очередную строку результата в подходящем для PHP виде.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$modeFETCH_DEFAULT использует выбранный режим (обычно BOTH). Полная таблица ASSOC/NUM/OBJ/CLASS/BOUND/LAZY и модификаторов — ниже.
$cursorOrientationNEXT по умолчанию; PRIOR/FIRST/LAST/ABS/REL только для поддерживаемого прокручиваемого курсора, заказанного ATTR_CURSOR => CURSOR_SCROLL при prepare.
$cursorOffsetНомер при ABS либо сдвиг при REL; в обычном последовательном чтении не нужен.

Результат зависит от режима: массив, объект или true при BOUND. false означает конец либо ошибку; exception-режим помогает не спутать отказ SQL с нормальным завершением. FETCH_CLASS присваивает свойства до конструктора, включая доступные PDO private/protected/неинициализированные readonly; PROPS_LATE меняет порядок. Колонки не становятся аргументами конструктора. Отсутствующее свойство вызывает __set или создаёт dynamic property (нежелательно для обычных классов PHP 8.2+). Для JOIN используйте AS с разными именами: ASSOC иначе теряет коллидирующее значение, NAMED собирает совпадения.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. Результат содержит три строки
  2. fetch берёт Pen и сдвигает позицию
  3. Следующий вызов берёт Notebook
  4. После Box возвращается false

Как читать этот вариант. Строка БД превращается в массив: имя столбца — ключ, содержимое ячейки — значение. Один fetch продвинул результат только на одну строку, остальные ещё доступны.

Прочитать одну строку как подписанный массивСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->query('SELECT name, price FROM products ORDER BY id');
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ':', $row['price'], "\n";
Ожидаемый результат
Pen:120

Как читать этот вариант. Внешние скобки позволяют сначала присвоить очередную строку, затем строго сравнить с false. После конца новый fetch не запускает SQL заново; для нового прохода нужно execute или новый query.

Прочитать строки до окончания результатаСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->query('SELECT name FROM products ORDER BY id');
while (($row = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
    echo $row['name'], "\n";
}
var_dump($statement->fetch(PDO::FETCH_ASSOC));
Ожидаемый результат
Pen
Notebook
Box
bool(false)

Рядом: PDOStatement::fetchAll(), PDOStatement::fetchColumn(), PDOStatement::setFetchMode().

Дополнительные ранее разобранные сценарии.

Возвращает строку в выбранном режиме либо false при окончании/ошибке. FETCH_ASSOC удобен для именованных полей.

Как читать этот вариант. В реальном поиске сначала проверяют отсутствие строки.

Одна строка как словарьСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->query('SELECT name,stock FROM products WHERE id=1');
$row=$s->fetch(PDO::FETCH_ASSOC);
echo $row['name'].':'.$row['stock']."\n";
Ожидаемый результат
Tea:3

Как читать этот вариант. Читается по одной строке до явного окончания.

Обход результатаСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->query('SELECT name FROM products ORDER BY id');
while (($row=$s->fetch(PDO::FETCH_ASSOC))!==false) echo $row['name']."\n";
Ожидаемый результат
Tea
Coffee

Рядом по задаче. fetchAll для ограниченного списка; fetchColumn для одного столбца.

PDOStatement::fetchAllПолучить все оставшиеся строки небольшого результата одним массивом.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::fetchAll(int $mode = PDO::FETCH_DEFAULT, mixed ...$args): array

Подготовить небольшой каталог к выводу. fetchAll собирает оставшиеся строки в обычный PHP-массив. foreach затем берёт каждый товар; пустая выборка даёт [].

Данные и действие: PDOStatement::fetchAll
  1. $statement->fetchAll(PDO::FETCH_ASSOC)
  2. Внешний массив хранит три строки, каждая внутренняя строка содержит name и stock. Этот массив можно передать HTML-шаблону; там названия экранируют через htmlspecialchars.
  3. После выполнения: Молоток — 8 шт. · Перчатки — 15 шт. · Кисть — 0 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
foreach ($rows as $row)Берёт по одной строке из набора. Блок между { и } повторяется для каждой строки; при пустом наборе не выполняется.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Подготовить небольшой каталог к выводуСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->query('SELECT name, stock FROM products ORDER BY id');
$products = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($products as $product) {
    echo $product['name'], ' — ', $product['stock'], " шт.\n";
}
Ожидаемый результат
Молоток — 8 шт.
Перчатки — 15 шт.
Кисть — 0 шт.

Внешний массив хранит три строки, каждая внутренняя строка содержит name и stock. Этот массив можно передать HTML-шаблону; там названия экранируют через htmlspecialchars.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Молоток8 шт.
Перчатки15 шт.
Кисть0 шт.

Если требование изменится. Для счётчика товаров используйте SELECT COUNT(*), а не fetchAll+count. Для большого экспорта читайте строки постепенно и учитывайте буфер конкретного драйвера; fetch сам по себе не отключает буфер MySQL.

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

Получить все оставшиеся строки небольшого результата одним массивом.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$modeРежим FETCH_*; COLUMN выбирает столбец, CLASS объекты, FUNC результат callback, GROUP/UNIQUE перестраивают ключи.
$argsДля COLUMN номер с 0; CLASS имя и constructorArgs; FUNC callable. Динамические аргументы передают позиционно, не по именам.

В PHP 8 всегда массив, пустой при отсутствии остатка; SQL-ошибка регулируется ERRMODE. UNIQUE использует первый столбец как ключ и перезаписывает повторения; GROUP сохраняет группу всех строк. fetchAll расходует память пропорционально всем возвращённым значениям плюс массивам/объектам, а драйвер может отдельно буферизовать данные. WHERE, LIMIT, агрегирование в SQL и последовательный fetch часто сокращают затраты. LAZY запрещён; с PHP 8.5 INTO тоже ValueError, а PROPS_LATE допустим только с CLASS.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. Первый fetchColumn забрал Pen
  2. fetchAll возвращает Notebook и Box массивом
  3. Массив остаётся в памяти PHP
  4. Следующий fetchAll уже пуст

Как читать этот вариант. fetchAll не перематывает результат: первая строка уже прочитана. После чтения остатка повторный fetchAll возвращает пустой массив.

Получить именно оставшиеся строкиСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->query('SELECT name FROM products ORDER BY id');
echo $statement->fetchColumn(), "\n";
$remaining = $statement->fetchAll(PDO::FETCH_COLUMN);
echo implode(', ', $remaining), "\n";
var_dump($statement->fetchAll(PDO::FETCH_ASSOC));
Ожидаемый результат
Pen
Notebook, Box
array(0) {
}

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

Собрать многомерный массив вариантов доставкиСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$statement = $db->query("SELECT 'pickup' AS kind, 'Office' AS point UNION ALL SELECT 'pickup', 'Locker' UNION ALL SELECT 'courier', 'Door'");
$groups = $statement->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_COLUMN);
echo implode(', ', $groups['pickup']), "\n";
echo $groups['courier'][0], "\n";
Ожидаемый результат
Office, Locker
Door

Рядом: PDOStatement::fetch(), PDOStatement::fetchColumn(), PDOStatement::setFetchMode().

Дополнительные ранее разобранные сценарии.

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

Как читать этот вариант. Два результата удобно получить сразу.

Список названийСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$names=$db->query('SELECT name FROM products ORDER BY id')->fetchAll(PDO::FETCH_COLUMN);
echo implode(',',$names)."\n";
Ожидаемый результат
Tea,Coffee

Как читать этот вариант. Нужны ровно два выбранных столбца; повторные ключи перезаписывают значения.

Карта ID → названиеСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$map=$db->query('SELECT id,name FROM products ORDER BY id')->fetchAll(PDO::FETCH_KEY_PAIR);
echo $map[2]."\n";
Ожидаемый результат
Coffee

Рядом по задаче. fetch читает по строке; FETCH_COLUMN и FETCH_KEY_PAIR дают специальные структуры.

PDOStatement::fetchColumnПрочитать одну ячейку очередной строки, например COUNT или найденное имя.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::fetchColumn(int $column = 0): mixed

Показать количество позиций без загрузки всего каталога. SELECT COUNT(*) возвращает одну строку с одним числом. fetchColumn читает первую колонку этой строки; результат не является счётчиком строк объекта PDOStatement.

Данные и действие: PDOStatement::fetchColumn
  1. $statement->fetchColumn()
  2. SQLite считает строки с остатком больше 0: Молоток и Перчатки. PHP получает одно число 2 вместо массива всего каталога.
  3. После выполнения: В наличии: 2 наименования

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Показать количество позиций без загрузки всего каталогаСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->query('SELECT COUNT(*) FROM products WHERE stock > 0');
$count = $statement->fetchColumn();
echo 'В наличии: ', $count, " наименования\n";
Ожидаемый результат
В наличии: 2 наименования

SQLite считает строки с остатком больше 0: Молоток и Перчатки. PHP получает одно число 2 вместо массива всего каталога.

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

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

Прочитать одну ячейку очередной строки, например COUNT или найденное имя.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
$columnНомер столбца с 0; отсутствие аргумента выбирает первый.

Возвращает значение либо false при конце/ошибке. Для настоящего boolean false в БД результат неотличим от конца: используйте fetch и поле массива. Пустая строка, 0 и null не равны false при строгом сравнении. Другой столбец уже потреблённой строки этим методом не получить; неверный индекс не является запросом новой колонки.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. Строка 1 содержит [1,Pen]
  2. fetchColumn(0) выдаёт 1 и потребляет строку
  3. fetchColumn(1) из следующей строки даёт Notebook

Как читать этот вариант. COUNT(*) — SQL-вычисление одной ячейки. Это переносимый способ получить число строк условия, в отличие от rowCount после SELECT.

Получить счётчик без массива строкиСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$count = $db->query('SELECT COUNT(*) FROM products')->fetchColumn();
echo $count, "\n";
Ожидаемый результат
3

Как читать этот вариант. Второй вызов не получает name первой строки: он уже читает name второй. Если нужны два поля одной записи, вызовите fetch один раз и возьмите два ключа массива.

Каждый вызов берёт следующую строкуСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->query('SELECT id, name FROM products ORDER BY id');
echo $statement->fetchColumn(0), "\n";
echo $statement->fetchColumn(1), "\n";
Ожидаемый результат
1
Notebook

Рядом: PDOStatement::fetch(), PDOStatement::rowCount().

Дополнительные ранее разобранные сценарии.

Возвращает значение столбца с индексом от 0 либо false при отсутствии строки. Сам вызов продвигает курсор.

Как читать этот вариант. COUNT возвращает строку результата даже для пустой таблицы.

Количество товаровСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$count=$db->query('SELECT COUNT(*) FROM products')->fetchColumn();
echo $count."\n";
Ожидаемый результат
2

Как читать этот вариант. Строгое сравнение сохраняет важное нулевое значение.

Нулевой остаток существуетСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$stock=$db->query('SELECT stock FROM products WHERE id=2')->fetchColumn();
echo $stock === false ? "missing\n" : 'stock:'.$stock."\n";
Ожидаемый результат
stock:0

Рядом по задаче. fetch возвращает всю строку, SELECT COUNT(*) даёт число записей.

PDOStatement::rowCountУзнать число строк последнего INSERT, UPDATE или DELETE.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::rowCount(): int

Отличить успешное резервирование от нехватки товара. После условного UPDATE счётчик показывает, была ли изменена строка. Здесь условие stock >= quantity не даёт опустить остаток ниже нуля.

Данные и действие: PDOStatement::rowCount
  1. $statement->rowCount()
  2. Вычитание и условие выполняются одной SQL-командой. На странице можно подтвердить резерв и показать остаток 6; для полноценного заказа нужны транзакция и отдельная запись заказа.
  3. После выполнения: Две штуки зарезервированы · Молоток — 6 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Отличить успешное резервирование от нехватки товараСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->prepare('UPDATE products SET stock = stock - :quantity WHERE id = :id AND stock >= :required');
$statement->execute(['quantity'=>2, 'id'=>1, 'required'=>2]);
echo $statement->rowCount() === 1 ? "Две штуки зарезервированы\n" : "Недостаточно товара\n";
$statement = $db->query('SELECT name, stock FROM products WHERE id = 1');
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Две штуки зарезервированы
Молоток — 6 шт.

Вычитание и условие выполняются одной SQL-командой. На странице можно подтвердить резерв и показать остаток 6; для полноценного заказа нужны транзакция и отдельная запись заказа.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Молоток6 шт.

Если требование изменится. rowCount у SELECT не переносим между драйверами. MySQL для UPDATE может считать изменённые либо найденные строки в зависимости от настройки; запись прежнего значения не равна ошибке.

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

Узнать число строк последнего INSERT, UPDATE или DELETE.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
АргументыНет.

Целое число затронутых строк. Для SELECT поведение не определено единообразно; MySQL с буфером может вернуть число, другой драйвер — 0. Для подсчёта используйте SELECT COUNT(*) с тем же условием. PostgreSQL CURSOR_SCROLL возвращает 0. Счётчик не содержит сами данные и не доказывает, что commit уже выполнен.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. UPDATE находит id<=2
  2. БД меняет две строки
  3. rowCount сообщает 2
  4. Для SELECT число считают SQL COUNT

Как читать этот вариант. Это количество строк последнего изменения statement. Различие «совпали условию» и «реально изменили значение» зависит от сервера и его флагов.

Посчитать изменённые строки командыСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('UPDATE products SET price = price + ? WHERE id <= ?');
$statement->execute([10, 2]);
echo $statement->rowCount(), "\n";
Ожидаемый результат
2

Как читать этот вариант. Условие по неизвестному ID не затронуло строк. Для конкурентного списания запасов это развивают в UPDATE stock=stock-quantity WHERE stock>=quantity и проверку количества; весь заказ всё равно требует согласованной транзакции.

Условное обновление сообщает, найден ли доступный товарСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('UPDATE products SET price = ? WHERE id = ?');
$statement->execute([500, 99]);
echo $statement->rowCount() === 0 ? "Товар не найден\n" : "Изменено\n";
Ожидаемый результат
Товар не найден

Рядом: PDO::exec(), PDOStatement::fetchColumn().

Дополнительные ранее разобранные сценарии.

Чаще используется после INSERT/UPDATE/DELETE. Поведение для SELECT и детали changed/matched зависят от драйвера и настроек.

Как читать этот вариант. Здесь значение реально меняется, а условие атомарно проверяет наличие остатка.

Успешное резервированиеСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('UPDATE products SET stock=stock-1 WHERE id=? AND stock>0');
$s->execute([1]);
echo $s->rowCount()." reserved\n";
Ожидаемый результат
1 reserved

Как читать этот вариант. Ноль изменений — ожидаемая ветка бизнеса.

Нет доступного остаткаСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('UPDATE products SET stock=stock-1 WHERE id=? AND stock>0');
$s->execute([2]);
echo $s->rowCount()===0 ? "sold out\n" : "reserved\n";
Ожидаемый результат
sold out

Рядом по задаче. SELECT COUNT(*) для количества; условный UPDATE + rowCount для резервирования.

PDOStatement::closeCursorОсвободить текущий результат, сохранив возможность выполнить команду снова.

PHP 5.1+; примеры PHP 8.2+ · pdo_sqlite

PDOStatement::closeCursor(): bool

Прекратить ненужный просмотр и выполнить следующую команду. Курсор — текущая позиция чтения результата. closeCursor освобождает оставшийся ответ для дальнейшей работы соединения; сохранённый PHP-массив от этого не исчезает.

Данные и действие: PDOStatement::closeCursor
  1. $statement->closeCursor()
  2. Первый запрос нам нужен только до первой строки. Завершаем его результат и затем читаем выбранные Перчатки отдельным запросом.
  3. После выполнения: Молоток — 8 шт. · Перчатки — 15 шт.

В архиве catalog.php открывает отдельную учебную базу и выполняет schema.sql: 1 — Молоток, 8 шт.; 2 — Перчатки, 15 шт.; 3 — Кисть, 0 шт. База находится в памяти и начинает работу с этих данных при каждом запуске. Распакуйте все файлы вместе и запустите php example.php. Полная подготовка и схема.

Запись в этом примереКак читать
$db->method(...)Стрелка вызывает действие у конкретного соединения, команды или результата. Скобки содержат входные значения; результат можно сохранить через =.
Класс::ИМЯДвойное двоеточие обращается к константе режима либо статическому методу класса; это не выбор колонки SQL.
[...]Квадратные скобки задают массив либо читают его элемент. Ключ имени колонки и номер позиции — разные способы адресации.
Прекратить ненужный просмотр и выполнить следующую командуСамостоятельный пример
<?php
require __DIR__ . '/catalog.php';
$statement = $db->query('SELECT name, stock FROM products ORDER BY id');
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
$statement->closeCursor();
$statement = $db->query('SELECT name, stock FROM products WHERE id = 2');
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo $row['name'], ' — ', $row['stock'], " шт.\n";
Ожидаемый результат
Молоток — 8 шт.
Перчатки — 15 шт.

Первый запрос нам нужен только до первой строки. Завершаем его результат и затем читаем выбранные Перчатки отдельным запросом.

Эти же строки в табличном виде. CLI-пример выше печатает текст; таблица помогает увидеть соответствие названия и остатка. Как получить такую таблицу настоящим HTML-обработчиком.

Товар / строкаНаличие
Молоток8 шт.
Перчатки15 шт.

Если требование изменится. В потоке MySQL несчитанный ответ способен блокировать новые команды. closeCursor не подтверждает транзакцию и не отменяет уже выполненный UPDATE; для отмены нужен rollBack.

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

Освободить текущий результат, сохранив возможность выполнить команду снова.

$db обозначает соединение, $statement — подготовленную команду и её текущий результат. -> вызывает метод конкретного объекта, :: — статический метод или константу класса. Круглые скобки содержат аргументы; результат справа от = сохраняется в переменной. Это отдельные шаги: выполнить SQL и прочитать полученные строки. Основы: объекты, аргументы и возврат.

АргументДоговор
АргументыНет.

true/false. Реализация драйвера может освободить быстро, общий fallback дочитывает оставшиеся строки и наборы — не обещайте постоянное время на огромном ответе. Это не закрытие самого PDO и не rollback. Нужно драйверам, запрещающим новый запрос при непрочитанном предыдущем результате.

При неудаче SQL-операции режим ERRMODE_SILENT оставляет false и диагностику, ERRMODE_WARNING добавляет E_WARNING, ERRMODE_EXCEPTION выбрасывает PDOException (стандарт с PHP 8.0). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.

Путь данных
  1. Прочитана только Pen
  2. closeCursor освобождает Notebook и Box
  3. Statement остаётся доступен
  4. execute задаёт новый параметр и новый результат

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

Прервать чтение и повторить командуСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->prepare('SELECT name FROM products WHERE id >= ? ORDER BY id');
$statement->execute([1]);
echo $statement->fetchColumn(), "\n";
$statement->closeCursor();
$statement->execute([3]);
echo $statement->fetchColumn(), "\n";
Ожидаемый результат
Pen
Box

Как читать этот вариант. finally срабатывает и при исключении в обработке строки. На драйверах с небуферизованными результатами несчитанный набор может блокировать следующий запрос соединения.

Освободить результат при досрочном выходеСамостоятельный пример
<?php
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)');
$db->exec("INSERT INTO products VALUES (1, 'Pen', 120), (2, 'Notebook', 350), (3, 'Box', 200)");
$statement = $db->query('SELECT name FROM products ORDER BY id');
try {
    foreach ($statement as $row) { echo $row['name'], "\n"; break; }
} finally { $statement->closeCursor(); }
echo $db->query('SELECT COUNT(*) FROM products')->fetchColumn(), "\n";
Ожидаемый результат
Pen
3

Рядом: PDOStatement::execute(), PDOStatement::nextRowset().

Дополнительные ранее разобранные сценарии.

Завершает работу с текущим набором строк и возвращает bool. Некоторые драйверы требуют освободить непрочитанный результат перед новым запросом.

Как читать этот вариант. В SQLite это не обязательный барьер, но пример показывает явный жизненный цикл.

Прочитать одну строку и освободить наборСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->query('SELECT name FROM products ORDER BY id');
echo $s->fetchColumn()."\n";
$s->closeCursor();
echo $db->query('SELECT COUNT(*) FROM products')->fetchColumn()."\n";
Ожидаемый результат
Tea
2

Как читать этот вариант. Предыдущий набор завершён перед новым выполнением.

Повторно выполнить statementСамостоятельный пример
<?php
declare(strict_types=1);

$db=new PDO('sqlite::memory:',null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)');
$db->exec("INSERT INTO products VALUES (1,'Tea',3),(2,'Coffee',0)");
$s=$db->prepare('SELECT name FROM products WHERE id>? ORDER BY id');
$s->execute([0]);
$s->closeCursor();
$s->execute([1]);
echo $s->fetchColumn()."\n";
Ожидаемый результат
Coffee

Рядом по задаче. fetch/fetchAll потребляют результат; ограничение SELECT уменьшает ненужную работу.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.