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

Дерево руководства
Глава 294 · После подключения и параметров PDO
PDO_FIREBIRD: заказы, видимость изменений и отчёт без записи
Сохранить заказ с остатком, сравнить два режима чтения и защитить агрегированный отчёт от случайного UPDATE.
Заказ, остаток и согласованный отчёт
В заявке две связанные записи: сколько деталей осталось на складе и сколько принято в заказ. Пользователю нужен согласованный результат. Транзакция объединяет изменения в одну работу: либо они подтверждаются вместе, либо отменяются вместе. Отдельная задача — определить, какие изменения других пользователей видит отчёт.
- stock: остаток 5 → зарезервировать 2
- orders: сохранить заявку на 2
- COMMIT: остаток 3 и заявка существуют вместе
- ROLLBACK: незавершённая заявка не меняет склад
PDO соединяет PHP-приложение с базой: объект соединения создаёт подготовленные запросы, а запрос возвращает строки или сохраняет изменение. PDO_FIREBIRD реализует этот интерфейс для Firebird; SQL, типы и транзакции сохраняют особенности базы.
Примеры рассчитаны на отдельную учебную базу. Проверяемая связка: PHP 8.5.11 с PDO_FIREBIRD и Firebird 5.0.4. Pdo\Firebird появился в PHP 8.4; старый new PDO с префиксом firebird: остаётся доступным. Введение в PDO объясняет соединение и подготовленный запрос.
Начните с подключения и первого запроса. Здесь $db — объект соединения, $db->beginTransaction() — вызов его метода без аргументов, а точка с запятой завершает PHP-команду. Метод исполняется именно на этом соединении: другое PDO-соединение имеет собственную транзакцию. Общие основы транзакций объясняют атомарность подробнее.
База хранит версии строк; это модель MVCC. Удобная аналогия — несколько подписанных редакций складского листа. Читатель получает доступ к редакциям по правилам своей транзакции. Это не физическая копия всей базы при каждом beginTransaction: аналогия объясняет видимость, а не устройство файлов и памяти.
Начать, подтвердить или отменить работу
Три действия нужно различать. beginTransaction начинает явную транзакцию. commit подтверждает её изменения и завершает её. rollBack отменяет ещё не подтверждённые изменения и завершает её. После commit нельзя вернуть прежнее состояние вызовом rollBack: нужна новая компенсирующая операция с бизнес-правилами.
$db->beginTransaction();
// Здесь выполняются изменения одной задачи.
$db->rollBack();Это минимальная форма отмены. Комментарий после // ничего не выполняет; без изменений в середине отменять нечего. В следующем проекте между границами стоят реальные UPDATE и INSERT. try/catch позволяет отменить транзакцию при исключении; finally освобождает ресурсы независимо от результата. Не перехватывайте ошибку только ради сообщения «успешно».
| Вопрос | Механизм | Пример |
|---|---|---|
| Сохранить несколько изменений вместе | Транзакция и проверка результата каждого действия | Остаток и заказ |
| Какие чужие изменения видны | Уровень изоляции | Отчёт видит 10 или новые 20 |
| Разрешены ли изменения в этой транзакции | READ ONLY / READ WRITE | Защитить отчёт от UPDATE |
| Что пользователю вообще разрешено | SQL-права пользователя и роли | Роль читателя имеет только SELECT |
PDO может выполнять отдельные команды в autocommit. Это не означает отсутствие внутренних транзакций Firebird. Явный beginTransaction нужен, когда несколько действий составляют одну задачу. Не смешивайте произвольный SET TRANSACTION, ручной COMMIT через exec и методы PDO без понимания состояния драйвера: PDO должен правильно учитывать начало и конец. В этих программах прикладные границы задаются методами PDO, параметры — до начала.
Pdo\Firebird: класс, атрибуты и границы транзакции
Pdo\Firebird — специализированный подкласс PDO начиная с PHP 8.4. Его экземпляр представляет соединение Firebird, а не таблицу и не результат SELECT. Он наследует общий интерфейс PDO: prepare/query/exec, атрибуты, управление транзакцией и диагностику. Запрос возвращает PDOStatement; конкретные строки читаются уже из него. Справочник PDO содержит общие методы; здесь разобраны особенности драйвера.
$db = new Pdo\Firebird($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);new создаёт объект, $dsn описывает подключение, $user и $password — реквизиты, массив в четвёртом аргументе задаёт опции. Запись с => связывает ключ атрибута со значением. Двоеточия :: обращаются к константе класса, а -> — к методу экземпляра. На PHP до 8.4 используют new PDO с firebird:; наличие драйвера проверяют отдельно. Не подставляйте путь к базе из пользовательского запроса. Подробности DSN и ошибок есть в первой главе.
| Константа Pdo\Firebird | Значение для приложения | Когда выбирать |
|---|---|---|
| ATTR_DATE_FORMAT | Формат получаемого DATE; форматная строка семейства strftime | Экспорт календарной даты |
| ATTR_TIME_FORMAT | Формат получаемого TIME | Время приёма заявки |
| ATTR_TIMESTAMP_FORMAT | Формат получаемого TIMESTAMP | Временная отметка события |
| TRANSACTION_ISOLATION_LEVEL | Атрибут выбора одного из трёх режимов ниже | Перед beginTransaction |
| READ_COMMITTED | Чтение подтверждённых данных; граница зависит от варианта режима Firebird | Экран, которому нужны свежие подтверждённые данные |
| REPEATABLE_READ | Соответствует Firebird SNAPSHOT | Согласованные связанные чтения одного отчёта |
| SERIALIZABLE | Соответствует SNAPSHOT TABLE STABILITY | Особый сценарий со строгими ограничениями конкурентного доступа |
| WRITABLE_TRANSACTION | Булев атрибут: true — READ WRITE, false — READ ONLY; исходный режим записи true | Защита транзакции отчёта от записи |
Три формата и их реальные CSV разобраны в главе результатов. Это формат вывода значений драйвером, а не изменение типа столбца и не правило ввода пользовательской даты. Имена исторических PDO::FB_* и современного класса нельзя механически переносить между версиями; используйте константы доступной версии и проверяйте загрузку расширения.
Официальная страница класса называет READ_COMMITTED режимом по умолчанию. В закреплённом исходнике PHP 8.5.11 для явных транзакций поле isolation_level инициализируется REPEATABLE_READ; autocommit использует отдельную ветвь READ_COMMITTED. Поэтому здесь все бизнес-примеры задают нужную изоляцию явно и проверяют её наблюдаемое действие двумя соединениями. Значение по умолчанию из описания не подменяет проверку конкретного драйвера.
SERIALIZABLE не означает «самый быстрый и всегда правильный режим». Его более строгие правила могут существенно влиять на параллельную работу. Выбор начинается с инварианта задачи; не держите блокировки во время сетевых вызовов или ожидания пользователя. Примеры ниже не измеряют производительность и не объявляют все режимы одинаковыми.
Метод getApiVersion() возвращает номер API из заголовка, использованного при сборке, без аргументов. Это не версия работающего сервера. Общие getAttribute/setAttribute могут поддерживать разные наборы атрибутов у разных драйверов; не обещайте произвольный атрибут лишь потому, что он есть в базовом классе PDO.
Сохранить заказ и остаток одной транзакцией
Сервис принимает заявку на две детали. Нужно одновременно уменьшить остаток и записать заказ. Если второе действие не закончено, первое тоже отменяется: иначе склад и журнал заявок расходятся.
- Остаток 5 → UPDATE с qty>=2 → внутри транзакции 3
- INSERT заказа 101 → COMMIT → остаток 3 и заказ сохранены
- Недостаток или ошибка → ROLLBACK → обе таблицы согласованы
Минимальный вызов
$db->beginTransaction();
$statement = $db->prepare('UPDATE stock SET qty=qty-? WHERE id=? AND qty>=?');
$statement->execute([2, 1, 2]);
$db->rollBack(); // учебная отмена: остаток вернётсяbeginTransaction начинает общую границу изменений. Три ? — три отдельно переданных значения: вычесть 2, найти id=1, проверить минимум 2. rollBack отменяет изменение этой транзакции. Сам SELECT перед UPDATE не резервирует товар: условие наличия должно оставаться в UPDATE. В полном проекте rowCount проверяет изменённую строку, затем добавляется заказ и выполняется commit.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Заказ 101 | 2 единицы сохранены |
| Заказ 102 | 7 единиц: недостаточно |
| Заказ 103 | отменён вместе с уменьшением остатка |
| Осталось | 3 |
| Сохранённых заказов | 1 |
Самостоятельный проект, исходник и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=p176Config();$db=p176Connect();$runtime=p176Runtime($db);
$stockName=p176OwnName($db,$owned,'TABLE','stock');$stock=p176Qualified($stockName);
p176Ddl($db,'CREATE TABLE '.$stock.' (id INTEGER PRIMARY KEY, qty INTEGER NOT NULL CHECK(qty>=0))');
$logName=p176OwnName($db,$owned,'TABLE','orders');$log=p176Qualified($logName);
p176Ddl($db,'CREATE TABLE '.$log.' (id INTEGER PRIMARY KEY, item_id INTEGER NOT NULL REFERENCES '.$stock.'(id), qty INTEGER NOT NULL)');
p176Command($db,'INSERT INTO '.$stock.' (id,qty) VALUES (1,5)');
$db->setAttribute(Pdo\Firebird::TRANSACTION_ISOLATION_LEVEL,Pdo\Firebird::READ_COMMITTED);
$reserve=static function(int $order,int $qty,bool $simulateFailure=false) use ($db,$stock,$log):string {
if($qty<=0) {throw new InvalidArgumentException('Количество должно быть положительным.');}
p176Begin($db);
try {
$changed=p176Command($db,'UPDATE '.$stock.' SET qty=qty-? WHERE id=? AND qty>=?',[$qty,1,$qty]);
if($changed!==1) {p176Rollback($db);return 'недостаточно';}
p176Command($db,'INSERT INTO '.$log.' (id,item_id,qty) VALUES (?,?,?)',[$order,1,$qty]);
if($simulateFailure) {throw new RuntimeException('Учебная отмена перед подтверждением');}
p176Commit($db);return 'сохранено';
} catch(Throwable $e) {p176Rollback($db);throw $e;}
};
$saved=$reserve(101,2);
$shortage=$reserve(102,7);
$rolledBack=false;
try {$reserve(103,1,true);}catch(RuntimeException $e) {$rolledBack=$e->getMessage()==='Учебная отмена перед подтверждением';}
$remaining=p176Read($db,'SELECT qty AS "qty" FROM '.$stock.' WHERE id=1')[0]['qty'];
$orders=p176Read($db,'SELECT id AS "id",qty AS "qty" FROM '.$log.' ORDER BY id');
$checks=['reservation_committed'=>$saved==='сохранено','shortage_refused'=>$shortage==='недостаточно',
'failure_rolled_back'=>$rolledBack,'stock_consistent'=>$remaining===3,'one_order'=>$orders===[['id'=>101,'qty'=>2]],'transaction_finished'=>!$db->inTransaction()];
$observations=['orders'=>$orders,'remaining'=>$remaining,'shortage'=>$shortage,'cancelled_order_present'=>false];
$rows=[['Заказ 101','2 единицы сохранены'],['Заказ 102','7 единиц: недостаточно'],['Заказ 103','отменён вместе с уменьшением остатка'],['Осталось',3],['Сохранённых заказов',1]];
file_put_contents(__DIR__.'/reservation.csv',"order_id,quantity\n101,2\n");
} finally {
if ($db instanceof PDO) {try {$removed=p176Cleanup($db,$owned);} finally {p176Close($db);}}
}
p176Report('pdo176-reservation','Сохранить заказ и остаток одной транзакцией',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Заказ 101 — 2 единицы сохранены
Заказ 102 — 7 единиц: недостаточно
Заказ 103 — отменён вместе с уменьшением остатка
Осталось — 3
Сохранённых заказов — 1
Распакуйте весь ZIP и прочитайте README.txt. example.php содержит прикладную логику, lab.php — работу с отдельной базой и собственным реестром объектов. После выполнения откройте result.html; JSON и полезный файл доступны ниже.
Открыть результат выполнения · Посмотреть данные JSON · Скачать журнал заказов CSV
Границы решения
Это атомарность базы, а не откат отправленного письма или HTTP-запроса. Внешние действия выполняют после успешного commit либо через outbox. Повтор операции требует уникального ключа заявки; слепой повтор при потерянном ответе может создать дубль. Конкурентный отказ SQL обрабатывают отдельно и не превращают любой сбой в «нет товара». Таблицы и связи создаются до прикладной транзакции.
Попробуйте изменить условие
Попросите 3 единицы вместо 7 во второй заявке. Какой остаток получится и сколько заказов сохранится?
Разбор
После первого заказа остаётся 3. Второй успешно заберёт все 3: остаток 0, заказов 2. Третья попытка уже не пройдёт условие наличия. Измените также ожидаемый CSV и проверки; отказ и принудительная отмена — разные ветви.
Увидеть изменение другой сессии: READ COMMITTED и SNAPSHOT
Диспетчер открыл отчёт, а кладовщик изменил остаток. Должен ли следующий запрос отчёта увидеть новое число? Это решает изоляция. Один PHP-процесс ниже управляет двумя настоящими соединениями и точным порядком шагов; случайных задержек нет.
- Читатель: SELECT → 10
- Писатель: UPDATE 20 → COMMIT
- Читатель снова: READ_COMMITTED → 20, SNAPSHOT → 10
- Читатель завершил транзакцию → новое чтение → 20
Минимальный вызов
$db->setAttribute(Pdo\Firebird::TRANSACTION_ISOLATION_LEVEL, Pdo\Firebird::REPEATABLE_READ);
$db->beginTransaction();
// Здесь выполняются связанные SELECT одного отчёта.
$db->commit();setAttribute задаёт параметр соединения для следующей явной транзакции. REPEATABLE_READ у этого драйвера соответствует Firebird SNAPSHOT. До beginTransaction режим уже выбран. commit закрывает снимок; новая транзакция получает новую границу видимости. В полном сценарии SELECT расположен до и после commit другого соединения.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| READ_COMMITTED в той же транзакции | 10 → 20 |
| REPEATABLE_READ в той же транзакции | 10 → 10 |
| После завершения и нового чтения | 20 в обоих режимах |
Самостоятельный проект, исходник и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=p176Config();$db=p176Connect();$runtime=p176Runtime($db);
$name=p176OwnName($db,$owned,'TABLE','visibility');$table=p176Qualified($name);
p176Ddl($db,'CREATE TABLE '.$table.' (id INTEGER PRIMARY KEY, qty INTEGER NOT NULL)');
p176Command($db,'INSERT INTO '.$table.' (id,qty) VALUES (1,10)');
$writer=p176Connect();$samples=[];
try {
foreach(['READ_COMMITTED'=>Pdo\Firebird::READ_COMMITTED,'REPEATABLE_READ'=>Pdo\Firebird::REPEATABLE_READ] as $label=>$mode) {
p176Command($writer,'UPDATE '.$table.' SET qty=10 WHERE id=1');
$db->setAttribute(Pdo\Firebird::TRANSACTION_ISOLATION_LEVEL,$mode);p176Begin($db);
try {
$before=p176Read($db,'SELECT qty AS "qty" FROM '.$table.' WHERE id=1')[0]['qty'];
p176Begin($writer);p176Command($writer,'UPDATE '.$table.' SET qty=20 WHERE id=1');p176Commit($writer);
$after=p176Read($db,'SELECT qty AS "qty" FROM '.$table.' WHERE id=1')[0]['qty'];
p176Commit($db);
} catch(Throwable $e) {p176Rollback($db);p176Rollback($writer);throw $e;}
$fresh=p176Read($db,'SELECT qty AS "qty" FROM '.$table.' WHERE id=1')[0]['qty'];
$samples[]=['mode'=>$label,'before'=>$before,'same_transaction'=>$after,'new_read'=>$fresh];
}
} finally {p176Close($writer);}
$checks=['read_committed_refresh'=>$samples[0]===['mode'=>'READ_COMMITTED','before'=>10,'same_transaction'=>20,'new_read'=>20],
'snapshot_stable'=>$samples[1]===['mode'=>'REPEATABLE_READ','before'=>10,'same_transaction'=>10,'new_read'=>20],
'transaction_finished'=>!$db->inTransaction()];
$observations=['sequence'=>'reader SELECT 10; writer UPDATE 20 + COMMIT; reader SELECT; reader COMMIT; new SELECT','samples'=>$samples];
$rows=[['READ_COMMITTED в той же транзакции','10 → 20'],['REPEATABLE_READ в той же транзакции','10 → 10'],['После завершения и нового чтения','20 в обоих режимах']];
file_put_contents(__DIR__.'/visibility.csv',"mode,before,same_transaction,new_read\nREAD_COMMITTED,10,20,20\nREPEATABLE_READ,10,10,20\n");
} finally {
if ($db instanceof PDO) {try {$removed=p176Cleanup($db,$owned);} finally {p176Close($db);}}
}
p176Report('pdo176-visibility','Увидеть изменение другой сессии: READ COMMITTED и SNAPSHOT',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
READ_COMMITTED в той же транзакции — 10 → 20
REPEATABLE_READ в той же транзакции — 10 → 10
После завершения и нового чтения — 20 в обоих режимах
Распакуйте весь ZIP и прочитайте README.txt. example.php содержит прикладную логику, lab.php — работу с отдельной базой и собственным реестром объектов. После выполнения откройте result.html; JSON и полезный файл доступны ниже.
Открыть результат выполнения · Посмотреть данные JSON · Скачать наблюдения двух сессий CSV
Границы решения
Не держите снимок открытым, пока человек заполняет форму: длительные транзакции мешают освобождению старых версий. В Firebird 4/5 настройка ReadConsistency влияет на варианты READ COMMITTED; этот проект сравнивает разные завершённые SQL-запросы, а не изменение строки посреди одного SELECT. Это тест видимости, не нагрузочный тест и не измерение скорости.
Попробуйте изменить условие
Перенесите commit читателя перед вторым SELECT. Будет ли режим SNAPSHOT всё ещё показывать 10?
Разбор
Нет: прежний снимок закончился. Новый запрос начинает новую транзакционную область и видит уже подтверждённые 20. Кеш на стороне PHP или интерфейса остаётся отдельной причиной устаревшего отображения.
Собрать отчёт в SQL и запретить случайную запись
Отчёту нужны суммы по категориям, а не все складские позиции. GROUP BY и SUM вычисляют две итоговые строки на сервере. Режим READ ONLY дополнительно защищает эту транзакцию от случайного UPDATE в коде отчёта.
- Строки cable: 4 и 6; tools: 2
- SQL GROUP BY + SUM → cable:10, tools:2
- READ ONLY → случайный UPDATE отклонён
- Закрыть транзакцию → сохранить report.csv
Минимальный вызов
$db->setAttribute(Pdo\Firebird::WRITABLE_TRANSACTION, false);
$db->beginTransaction();
$statement = $db->query('SELECT category, SUM(qty) FROM stock GROUP BY category');
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
$statement->closeCursor();
$db->commit();false здесь означает режим транзакции READ ONLY. Это не отключение подключения и не выбор изоляции. GROUP BY объединяет строки по category; SUM суммирует qty внутри группы. fetchAll уместен для заранее ограниченного количества категорий, а не для загрузки всей таблицы. Полный проект отдельно задаёт SNAPSHOT, проверяет отказ UPDATE и возвращает режим записи после завершения.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Кабель: сумма остатка | 10 |
| Инструмент: сумма остатка | 2 |
| UPDATE внутри READ ONLY | отклонён |
| Исходный остаток первой позиции | 4 |
Самостоятельный проект, исходник и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=p176Config();$db=p176Connect();$runtime=p176Runtime($db);
$name=p176OwnName($db,$owned,'TABLE','report');$table=p176Qualified($name);
p176Ddl($db,'CREATE TABLE '.$table.' (id INTEGER PRIMARY KEY, category VARCHAR(20), qty INTEGER NOT NULL)');
foreach([[1,'cable',4],[2,'cable',6],[3,'tools',2]] as $row) {p176Command($db,'INSERT INTO '.$table.' (id,category,qty) VALUES (?,?,?)',$row);}
$db->setAttribute(Pdo\Firebird::TRANSACTION_ISOLATION_LEVEL,Pdo\Firebird::REPEATABLE_READ);
$db->setAttribute(Pdo\Firebird::WRITABLE_TRANSACTION,false);p176Begin($db);
try {
$summary=p176Read($db,'SELECT category AS "category",SUM(qty) AS "quantity" FROM '.$table.' GROUP BY category ORDER BY category');
$denied=p176Capture(static fn()=>p176Command($db,'UPDATE '.$table.' SET qty=0 WHERE id=1'));
p176Rollback($db);
} finally {p176Rollback($db);$db->setAttribute(Pdo\Firebird::WRITABLE_TRANSACTION,true);}
$after=p176Read($db,'SELECT qty AS "qty" FROM '.$table.' WHERE id=1')[0]['qty'];
$checks=['aggregated_in_sql'=>$summary===[['category'=>'cable','quantity'=>10],['category'=>'tools','quantity'=>2]],
'write_rejected'=>$denied['exception'] && $denied['sqlstate']!=='00000','unchanged'=>$after===4,
'transaction_finished'=>!$db->inTransaction(),'writable_restored'=>$db->getAttribute(Pdo\Firebird::WRITABLE_TRANSACTION)===true];
unset($denied['value']);$observations=['summary'=>$summary,'rejected_update'=>$denied,'quantity_after'=>$after];
$rows=[['Кабель: сумма остатка',10],['Инструмент: сумма остатка',2],['UPDATE внутри READ ONLY','отклонён'],['Исходный остаток первой позиции',4]];
file_put_contents(__DIR__.'/report.csv',"category,quantity\ncable,10\ntools,2\n");
} finally {
if ($db instanceof PDO) {try {$removed=p176Cleanup($db,$owned);} finally {p176Close($db);}}
}
p176Report('pdo176-read-only-report','Собрать отчёт в SQL и запретить случайную запись',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Кабель: сумма остатка — 10
Инструмент: сумма остатка — 2
UPDATE внутри READ ONLY — отклонён
Исходный остаток первой позиции — 4
Распакуйте весь ZIP и прочитайте README.txt. example.php содержит прикладную логику, lab.php — работу с отдельной базой и собственным реестром объектов. После выполнения откройте result.html; JSON и полезный файл доступны ниже.
Открыть результат выполнения · Посмотреть данные JSON · Скачать итоговый отчёт CSV
Границы решения
READ ONLY не заменяет права отдельного пользователя: после переключения режима привилегированное соединение снова может писать. Не меняйте атрибут посреди активной транзакции. Особые временные таблицы имеют отдельные правила; пример работает с обычной таблицей. Количество категорий тоже нужно ограничивать, если оно растёт до размеров исходного каталога.
Попробуйте изменить условие
Добавьте фильтр, чтобы показать только cable. Где разместить WHERE: до GROUP BY или после?
Разбор
WHERE category=? размещают перед GROUP BY и передают cable параметром. WHERE фильтрует исходные строки; HAVING проверяет уже агрегированные группы, например SUM(qty)>5. Не выгружайте все позиции в PHP ради этого отбора.
Встроить транзакцию в приложение
В реальном обработчике сначала проверьте входные данные и права. Затем откройте короткую транзакцию, выполните только нужные запросы, проверьте результат и подтвердите изменения. При исключении отмените незавершённую работу и отделите безопасное сообщение пользователю от закрытого технического журнала. Полный текст ошибки соединения может содержать внутренний путь — не показывайте его в HTML.
Вопрос «Транзакция не откатывается» относится к старой связке PHP/Firebird. Он объясняет, зачем доказывать откат повторным чтением, а не только печатью true от метода. Новый проект проверяет и остаток, и отсутствие отменённого заказа; старый совет про атрибуты не переносится без проверки.
Обсуждение READ ONLY показывает смешение запрета записи с видимостью. Это независимые настройки: read-only отчёт всё равно нуждается в выбранной изоляции. Случай временной таблицы и PDO напоминает также о времени жизни временных данных и состоянии драйвера. Проекты этой главы используют обычные собственные таблицы; отдельный контракт GTT нельзя выводить из их поведения.
Следующий шаг: параметры и роль чтения, даты, точные числа и вложения, транзакции в приложении. Для большой таблицы отбирайте WHERE, сортируйте ORDER BY, ограничивайте строки и агрегируйте в SQL. SELECT * не является ошибкой синтаксиса, но выгрузка ненужных столбцов и сотен тысяч строк ради небольшого экрана расходует передачу, память и время. Транзакция сама по себе эту ошибку проектирования не исправляет.
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


