PHP: полный мануал с примерами, функциями и ООП
Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Дерево руководства
Глава 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 отличает записи даже при одинаковых названиях. Таблица похожа на лист с фиксированными колонками; в отличие от рисунка таблицы, СУБД выполняет запросы и проверяет ограничения данных.
- catalog.sqlite хранит таблицу products
- SQL SELECT выбирает id, name, stock
- PDO читает строки в массив $products
- foreach берёт один товар в $product
- 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, обслуживающий сайт, могут читать разные настройки.
<?php
echo in_array('sqlite', PDO::getAvailableDrivers(), true)
? "Можно запускать каталог\n"
: "Нужно включить pdo_sqlite\n";
Ожидаемый результат
Можно запускать каталог
getAvailableDrivers() возвращает имена доступных драйверов. in_array проверяет, есть ли среди них sqlite. Знак ? выбирает сообщение при истине, : — при лжи. Это проверка окружения; сам каталог начинается следующим примером.
Шаг 1. Открыть файл базы и понять, что вернулось
<?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(...). Стрелка означает «вызвать операцию у этого объекта».
<?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],
];- products — все выбранные строки
- [0] — первая строка
- ['name'] — поле внутри этой строки
- Получили строку «Молоток»
Внешний массив — список товаров; каждый внутренний массив — поля одного товара. Поэтому он многомерный. На первом уровне индекс начинается с 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: products | SQL не может найти таблицу в открытой SQLite-базе. | Проверить путь к файлу и выполнение шага создания таблицы. |
| unable to open database file | Соединение не смогло открыть/создать файл. | Проверить папку, путь и права PHP-процесса. |
| could not find driver | PHP не загрузил драйвер из 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: соберите только предусмотренные параметры.
<?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 ветка успешна, потому что проверяем отсутствие строки целиком, а не истинность количества.
<?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 ограничивает, к каким строкам оно относится.
<?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.
<?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. Это не автоматическое исправление базы.
<?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 и сообщение «Каталог временно недоступен».
Готовый проект: таблица из базы становится страницей сайта
Соберём разобранные шаги в маленький сайт. Это каталог с тремя товарами: названия и остатки берутся из файла базы. У Кисти нулевой остаток показан как «Нет в наличии». Файлы базы и установки находятся вне публичной папки, чтобы сервер отдавал только страницу каталога.
Каталог мастерской
| Товар | Наличие |
|---|---|
| Молоток | 8 шт. |
| Перчатки | 15 шт. |
| Кисть | Нет в наличии |
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 на вашем компьютере.
- PHP выбирает pdo_mysql из начала DSN
- Устанавливает соединение с сервером/сокетом
- MySQL проверяет пользователя и права
- Выбирается существующая база catalog
- После успеха 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
$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 для разговора с разными базами через установленный драйвер.
Что уже пригодится. переменная и присваивание, массив, объявление и вызов функции, объект и вызов метода
- PHP: значение артикула
- PDO prepare: SQL с параметром
- execute: отдельно передаём значение
- База выбирает строки
- 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-кода.
- 1SQL с местом для значения↓
SELECT ... FROM orders WHERE id = :id. Название таблицы задано программой.
- 2prepare()↓
Создаёт подготовленный запрос PDO. Этот шаг ещё не возвращает найденный заказ.
- 3execute(['id' => 42])↓
Передаёт значение параметра и выполняет запрос. Ошибка БД обрабатывается отдельно.
- 4fetch() или 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, в который уже вклеен ввод, недостаточно: опасная строка должна находиться именно в параметре.
Подготовка, выполнение и чтение — разные шаги
<?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.
- new PDO('sqlite::memory:')
- Сначала SQLite получает схему из schema.sql, затем SELECT выбирает строку 1. fetch переносит её в $row; ключ name содержит название, stock —8.
- После выполнения: Молоток — 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 и не добавляет отсутствующую поддержку БД.
- DSN sqlite::memory: выбирает драйвер и временную базу
- new PDO создаёт соединение $db
- 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 читает товар. Подготовка сама товар не возвращает.
- $db->prepare('... WHERE id = :id')
- Ключ id в PHP-массиве сопоставлен :id в SQL. Значение 2 выбирает Перчатки; текст и остаток берутся из базы, а не из введённой строки.
- После выполнения: Перчатки — 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 и прочитать полученные строки. Основы: объекты, аргументы и возврат.
| Аргумент | Договор |
|---|---|
| $query | SQL для конкретной БД с :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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- В шаблоне WHERE id = :id ещё нет значения
- execute связывает id=2 как данные
- База возвращает строку Notebook
- 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.
<?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 фиксирует порядок. Возвращается источник строк, а не сам массив товаров.
- $db->query('SELECT name, stock FROM products ORDER BY id')
- Каждый fetch читает следующую строку. while повторяет вывод, пока не получит false — конец. Значение stock=0 не прекращает цикл: проверяется вся строка.
- После выполнения: Молоток — 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- query выполняет SELECT id,name
- FETCH_COLUMN,1 выбирает name, а не id
- 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.
- $db->exec('UPDATE products SET stock = 7 WHERE id = 1')
- После UPDATE читаем ту же строку и наблюдаем 7 вместо 8. Сообщение отражает данные, действительно возвращённые базой.
- После выполнения: Молоток — 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- UPDATE ищет id=99
- Ни одной строки → число 0
- === 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 означает выполнение команды, а не найденный товар.
- $statement->execute(['id' => 3])
- Команда успешно находит Кисть с нулевым остатком. Отсутствующая строка отличалась бы результатом fetch === false.
- После выполнения: Кисть — 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 и прочитать полученные строки. Основы: объекты, аргументы и возврат.
| Аргумент | Договор |
|---|---|
| $params | null использует предварительно связанные значения/переменные. Массив задаёт входные значения: список для ? с индекса 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- prepare сохраняет шаблон id=:id
- execute получает id=1 и исполняет SQL
- true — команда выполнена
- fetchColumn отдельно получает Pen
Как читать этот вариант. Массив execute содержит значения входных параметров. true означает успешное исполнение, а не наличие найденной строки: строку читают отдельно.
<?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 задаёт тип передачи целого числа; это не проверка допустимого диапазона.
- $statement->bindValue(':id', $id, PDO::PARAM_INT)
- Сначала $id равен 1, затем 2. Разный момент чтения значения объясняет различие результатов; схема каталога в обоих архивах одинакова.
- После выполнения: Молоток — 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 | Значение или выражение; сохраняется на момент привязки. |
| $type | PARAM_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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- id=1 → bindValue сохраняет 1
- Исходный $id меняется на 2
- 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.
Как читать этот вариант. Лимит предварительно должен быть в разрешённом диапазоне.
<?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 задаёт тип передачи целого числа; это не проверка допустимого диапазона.
- $statement->bindParam(':id', $id, PDO::PARAM_INT)
- Сначала $id равен 1, затем 2. Разный момент чтения значения объясняет различие результатов; схема каталога в обоих архивах одинакова.
- После выполнения: Перчатки — 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 её можно изменить; драйвер может преобразовать её тип. |
| $type | PARAM_*; для 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- bindParam связывает имя переменной $id
- Код присваивает $id=2
- execute читает её текущее значение 2
- Запрос возвращает Notebook
Как читать этот вариант. Здесь привязана переменная, а не снимок числа. & в сигнатуре требует переменную; bindParam(1, 2) использовать нельзя.
<?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 пакетный запрос может быть быстрее, чем сетевой запрос на каждой итерации.
<?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 означает, что строки нет.
- $statement->fetch(PDO::FETCH_ASSOC)
- Строка существует, хотя остаток равен 0. Проверка $row === false не путает пустой остаток с отсутствующим товаром.
- После выполнения: Кисть — 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 и прочитать полученные строки. Основы: объекты, аргументы и возврат.
| Аргумент | Договор |
|---|---|
| $mode | FETCH_DEFAULT использует выбранный режим (обычно BOTH). Полная таблица ASSOC/NUM/OBJ/CLASS/BOUND/LAZY и модификаторов — ниже. |
| $cursorOrientation | NEXT по умолчанию; 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- Результат содержит три строки
- fetch берёт Pen и сдвигает позицию
- Следующий вызов берёт Notebook
- После 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 затем берёт каждый товар; пустая выборка даёт [].
- $statement->fetchAll(PDO::FETCH_ASSOC)
- Внешний массив хранит три строки, каждая внутренняя строка содержит name и stock. Этот массив можно передать HTML-шаблону; там названия экранируют через htmlspecialchars.
- После выполнения: Молоток — 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- Первый fetchColumn забрал Pen
- fetchAll возвращает Notebook и Box массивом
- Массив остаётся в памяти PHP
- Следующий 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
Как читать этот вариант. Нужны ровно два выбранных столбца; повторные ключи перезаписывают значения.
<?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.
- $statement->fetchColumn()
- SQLite считает строки с остатком больше 0: Молоток и Перчатки. PHP получает одно число 2 вместо массива всего каталога.
- После выполнения: В наличии: 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,Pen]
- fetchColumn(0) выдаёт 1 и потребляет строку
- 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 не даёт опустить остаток ниже нуля.
- $statement->rowCount()
- Вычитание и условие выполняются одной SQL-командой. На странице можно подтвердить резерв и показать остаток 6; для полноценного заказа нужны транзакция и отдельная запись заказа.
- После выполнения: Две штуки зарезервированы · Молоток — 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- UPDATE находит id<=2
- БД меняет две строки
- rowCount сообщает 2
- Для 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-массив от этого не исчезает.
- $statement->closeCursor()
- Первый запрос нам нужен только до первой строки. Завершаем его результат и затем читаем выбранные Перчатки отдельным запросом.
- После выполнения: Молоток — 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). Ошибки аргументов и отдельные ошибки состояния могут возникать независимо от этого режима. В примерах режим задан явно; сообщение драйвера не выводится посетителю.
- Прочитана только Pen
- closeCursor освобождает Notebook и Box
- Statement остаётся доступен
- 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
Как читать этот вариант. Предыдущий набор завершён перед новым выполнением.
<?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 уменьшает ненужную работу.
Источники и обсуждения
- PHP: подключения PDO
- PHP: ошибки PDO
- PHP: DSN SQLite
- PHP: fetchAll
- PHP: DSN MySQL
- SQLite: создание таблицы
- SQLite: открытие и создание файла
- MySQL: создание базы
- Реальная проблема: SQLite открыл не тот путь — обсуждение проблемы
- Реальная проблема: чтение работает, запись не работает — обсуждение проблемы
- Реальная проблема: пустая выборка и ошибка — разные состояния — обсуждение проблемы
- Реальная проблема: строки SQL, массивы и HTML — обсуждение проблемы
- PHP: pdo.prepare
- PHP: pdostatement.fetch
- PHP: pdostatement.rowcount
- Обсуждение: почему имя таблицы нельзя связать параметром PDO — обсуждение проблемы
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


