Перейти к содержимому
Поиск
Php Разработка

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

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

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 81 · После параметризованных запросов и транзакций Firebird

Firebird fbird_*: живое подключение, свежий остаток и события после commit

Реальный HTTP с повторным подключением, свежие остатки, commit/rollback, синхронное ожидание и прекращение callback.

Разделить готовый канал, свежие данные и уведомление

В существующем PHP-проекте встречаются fbird_pconnect и функции событий. Чтобы сопровождать такой код, надо видеть три разных вещи: сколько живёт канал к базе, когда изменение действительно подтверждено и какой запрос покажет новое состояние. В этой главе первый HTTP-ответ содержит Cable 3, второй — Cable 7 при прежнем подключении. Затем отдельные процессы показывают отмену 99, сохранение 7, уведомление и поздний остаток 11 после снятия подписки.

Канал, транзакция и сигнал
  1. PHP worker может сохранить подключение между HTTP-запросами
  2. Каждый запрос выбирает нужные данные в своей завершённой транзакции
  3. Другой процесс: UPDATE Cable 7 + POST_EVENT → commit
  4. Событие сообщает имя, а не переносит товар
  5. Получатель делает новый ограниченный SELECT → Cable 7
  6. Освобождение handler прекращает подписку, но не отменяет commit

Представьте рабочее место с готовой телефонной линией: линию можно использовать снова, но ответы о товаре надо получать заново. Событие похоже на звонок «остаток изменился». Эти сравнения объясняют назначение, а не устройство памяти: PHP-переменные запроса, серверное подключение и ресурс callback — разные объекты владения. Наличие канала само не подписывает на события, а событие само не обновляет страницу браузера.

Что называется такГде живёт и зачем нужно
PHP workerПроцесс, исполняющий запросы; persistent-подключение повторно используется в его области.
Connection / attachmentКанал к одной базе, с которым связано серверное состояние. CURRENT_CONNECTION помогает сравнить его между запросами.
TransactionГраница чтения/изменения. commit подтверждает, rollback отменяет неподтверждённые изменения.
Event handlerРегистрация PHP-callback на точное имя события; отдельный ресурс от connection.
POST_EVENTКоманда PSQL в серверном блоке/триггере. Доставка интересующимся клиентам связана с commit.

Имена fbird_* здесь являются aliases interbase, а не новой библиотекой и не миграцией PDO. Для первого знакомства сначала нужны соединение, параметры и строки запроса, явные commit и rollback и аргументы функции и return. В минимальных фрагментах ниже уже есть открытый $db; его создание и завершение полностью включены в самостоятельные архивы.

Где исполняется PHP и откуда берётся результат

Запуск производится из отдельного TCP-профиля firebird164: PHP 8.2.35 NTS x64, interbase 3.0.1, Firebird 5.0.4. Файлы данных и security принадлежат только этой лаборатории. SQL-порт 127.0.0.1:9764 и вспомогательный порт событий 9765 не требуют открытия входа из интернета. В README есть установка и команда запуска. Основной файл example.php создаёт одну свою базу с Cable 3/Clamp 0 и маркером владельца; после наблюдений дочерние процессы прекращаются, а собственная база и временный FBK удаляются.

Файлы проектаЗадача
example.php, lab.php, README.txt, seed.*Самостоятельная подготовка своего каталога, ограничения владельца, видимые HTML/JSON и очистка.
http-lab.php, http-router.phpТолько в HTTP-проекте:1 worker,2 реальных GET, один UPDATE между ними, private receipt и тела ответов.
events-lab.php, events-child.phpВ трёх проектах событий: listener + sender, готовность/ACK через ограниченные служебные файлы, реальные POST_EVENT и aliases.
result.html, result.jsonПолезные наблюдения: названия товаров, остатки, смысл сравнения подключения и подписки.
metrics.json и process receiptsЛокальные проверки/доказательства остановки; секреты и произвольные URL не используются как вход проекта.

Большой код запуска не надо изучать раньше небольшого вызова. Пройдите нужный сценарий сверху вниз: задача, схема с конкретными значениями, короткая запись, результат, затем полный проект. HTTP-ответы показывают реальное межзапросное поведение, а не два вызова в одном CLI. События принимает другой PHP-процесс, а не напечатанная заранее строка. Внешние сроки ограничивают именно лабораторию, не добавляют несуществующий timeout в API.

Два HTTP-запроса: одно подключение и два разных остатка

Страница склада открывается повторно. Нам хочется использовать готовое соединение, но показать новый остаток. Первый запрос прочитает Cable = 3. Между ответами другой участник сохранит 7. Второй запрос попадёт в тот же PHP worker и должен получить 7 новым SELECT. В отчёте одновременно видны полезные данные и совпадение серверного номера подключения.

Как получится наблюдаемый результат
  1. GET /stock №1 → worker P → fbird_pconnect → attachment A
  2. Новая транзакция чтения → Cable: 3 → commit → HTTP 200
  3. Отдельное подключение parent: UPDATE ID = 1, QTY = 7 → commit
  4. GET /stock №2 → тот же P → pconnect с теми же параметрами → A
  5. Новая транзакция чтения → Cable: 7 → HTTP 200
  6. Worker остановлен; только потом удаляется собственная учебная база

Первый понятный вызов

$db = fbird_pconnect($database, $user, $password, 'UTF8', 0, 3);
if ($db === false) {
    throw new RuntimeException('Подключение не открыто');
}
// Получить текущий остаток нужно отдельным SQL-запросом.

$database, $user и $password содержат настройки, а не сам товар. Строка UTF8 задаёт кодировку обмена; 0 оставляет выбор буферов серверу, 3 выбирает SQL-диалект. Результат $db — ресурс канала к базе. Он не является массивом строк. В полном http-router.php каждый запрос открывает собственную короткую read-only транзакцию, выбирает ID, TITLE, QTY только для ID = 1 и явно завершает её. Переменные первого запроса не продолжают жить во втором.

Что получится после запуска

НаблюдениеРезультат
Первый HTTP-ответCable / остаток 3
Подтверждённая запись между запросамиCable / остаток 7
Второй HTTP-ответCable / остаток 7
Один и тот же PHP workerда
Один CURRENT_CONNECTIONда
Реальных HTTP-запросов2
HTTP worker остановленда
Полный самостоятельный проект и архив
Два HTTP-запроса: одно подключение и два разных остаткаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);
    require __DIR__.'/http-lab.php';
    $database=f164CreateDatabase($service,$owned,'_stock_reuse');

    $session=f164HttpSession($database);$first=$session['responses'][0];$second=$session['responses'][1];
    $before=$first['observed']['item'];$after=$second['observed']['item'];
    $sameWorker=$first['pid']===$second['pid'];$sameConnection=$first['attachment_id']===$second['attachment_id'];
    $rows=[['Первый HTTP-ответ',$before['title'].' / остаток '.$before['qty']],
        ['Подтверждённая запись между запросами','Cable / остаток 7'],['Второй HTTP-ответ',$after['title'].' / остаток '.$after['qty']],
        ['Один и тот же PHP worker',$sameWorker],['Один CURRENT_CONNECTION',$sameConnection],
        ['Реальных HTTP-запросов',$session['http_requests']],['HTTP worker остановлен',$session['stopped']]];
    $checks=['first_item'=>$before===['id'=>1,'title'=>'Cable','qty'=>3],
        'fresh_second_item'=>$after===['id'=>1,'title'=>'Cable','qty'=>7],
        'same_worker'=>$sameWorker,'same_attachment'=>$sameConnection,
        'positive_attachment'=>$first['attachment_id']>0,'one_business_update'=>$session['business_updates']===1,
        'two_real_http_requests'=>$session['http_requests']===2&&$session['proof']['statuses']===[200,200],
        'requests_finished_transactions'=>$first['transaction_finished']===true&&$second['transaction_finished']===true,
        'alias_used'=>$first['persistent_api']==='fbird_pconnect'&&$second['persistent_api']==='fbird_pconnect',
        'http_worker_stopped'=>$session['stopped']===true];
    $observations=['http_session'=>$session,'same_worker'=>$sameWorker,'same_attachment'=>$sameConnection,
        'connection_cache_is_not_result_cache'=>true,'parent_updates_exactly_one_owned_row'=>true];

} finally {
    if(is_resource($db)){f164Close($db);}
    if(is_resource($service)){f164Detach($service);}
    $removed=f164Cleanup($owned);
}

f164Report('fb164-http-stock-reuse','Два HTTP-запроса: одно подключение и два разных остатка',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Первый HTTP-ответ — Cable / остаток 3
Подтверждённая запись между запросами — Cable / остаток 7
Второй HTTP-ответ — Cable / остаток 7
Один и тот же PHP worker — да
Один CURRENT_CONNECTION — да
Реальных HTTP-запросов — 2
HTTP worker остановлен — да

Архив содержит все PHP-файлы и README для отдельной TCP-лаборатории. Выполните example.php из папки проекта: result.html и result.json покажут фактические товары и наблюдения. Создаётся и удаляется одна собственная синтетическая база; чужие БД и учётные записи не затрагиваются. Самостоятельный запуск требует разрешённого PHP proc_open и подготовленного приватного сервера.

Открыть результат программы · Открыть фактические наблюдения JSON.

Границы, ошибки и применение

Два вызова pconnect внутри одного CLI-запуска не доказывают переиспользование между HTTP-запросами. Здесь запросов действительно два, worker один, а CURRENT_CONNECTION читается самой базой. В PHP-FPM запросы могут попасть в разные workers — тогда такое совпадение не обещано. Повторное подключение не сохраняет согласованный снимок данных: устаревшая незавершённая snapshot-транзакция способна помешать увидеть обновление. Поэтому перед следующим запросом заканчивают транзакции и убирают свой USER_SESSION-контекст. Этот пример не измеряет ускорение и не подбирает размер пула.

Измените требование

Сделайте второе сохранённое значение равным 12. Какие наблюдения должны измениться, а какие сравнения останутся прежними?

Вариант решения

В http-lab.php изменяется единственный UPDATE между запросами, в example.php — ожидаемый второй товар и текст таблицы. Два HTTP 200, одинаковые PID/CURRENT_CONNECTION и факт остановки worker остаются проверками того же механизма. SQL не следует заменять выдачей сохранённого первого массива.

Дождаться изменения и прочитать сохранённый остаток

Обработчик должен обновить известную карточку товара после работы другого процесса. Он ждёт FB164_CHANGED, затем получает Cable = 11 обычным запросом. Имя сигнала сообщает, что данные могли поменяться; само число 11 появляется только в результате SELECT. Второй товар Clamp остаётся с нулевым остатком и позволяет увидеть, что запись была адресной.

Как получится наблюдаемый результат
  1. Listener закончил прежнюю транзакцию и вызывает fbird_wait_event
  2. Sender один раз сохраняет Cable: 3 → 11
  3. POST_EVENT FB164_CHANGED + commit → имя доходит listener
  4. Новый SELECT ID IN (1,2) ORDER BY ID → Cable 11, Clamp 0
  5. ACK подтверждает пробуждение; два процесса завершаются

Первый понятный вызов

$event = fbird_wait_event($db, 'FB164_CHANGED');
if ($event === false) {
    throw new RuntimeException('Ожидание не завершилось успешно');
}
$result = fbird_query($db, 'SELECT TITLE, QTY FROM LAB_ITEMS WHERE ID = ?', 1);

Вызов wait останавливает дальнейшее исполнение этого PHP-процесса до события. $db — его открытое соединение, FB164_CHANGED — точное имя подписки. Следующая строка SQL запускается после возврата функции. Параметр 1 выбран самим приложением: событие не принесло ID. Полная программа проверяет SELECT, извлекает строки, освобождает ресурс и завершает транзакцию.

Что получится после запуска

НаблюдениеРезультат
До операции1 / Cable / остаток 3
Полученное имяFB164_CHANGED
Новый SELECT1 / Cable / остаток 11
Предметных UPDATE1
Пробуждение подтвержденода
Оба процесса остановленыда
Полный самостоятельный проект и архив
Дождаться изменения и прочитать сохранённый остатокСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);
    require __DIR__.'/events-lab.php';
    $database=f164CreateDatabase($service,$owned,'event_wait');
    $events=f164EventScenario($database,'wait');
    $listener=$events['listener'];$sender=$events['sender'];

    $items=$listener['items'];
    $rows=[['До операции','1 / Cable / остаток 3'],['Полученное имя',$listener['event']],
        ['Новый SELECT',$items[0]['ID'].' / '.$items[0]['TITLE'].' / остаток '.$items[0]['QTY']],
        ['Предметных UPDATE',$sender['business_updates']],['Пробуждение подтверждено',$sender['ack_received']],
        ['Оба процесса остановлены',$events['all_children_stopped']]];
    $checks=['event_name'=>$listener['event']==='FB164_CHANGED',
        'fresh_selected_rows'=>$items===[['ID'=>1,'TITLE'=>'Cable','QTY'=>11],['ID'=>2,'TITLE'=>'Clamp','QTY'=>0]],
        'single_business_update'=>$sender['business_updates']===1,'wakeup_ack'=>$sender['ack_received']===true,
        'no_native_error'=>$listener['error_code']===false&&$listener['error_message_present']===false,
        'children_stopped'=>$events['all_children_stopped']===true,'two_children'=>$events['process_count']===2];
    $observations=['events'=>$events,'initial_items'=>[['ID'=>1,'TITLE'=>'Cable','QTY'=>3],['ID'=>2,'TITLE'=>'Clamp','QTY'=>0]],
        'event_name_is_not_payload'=>true,'repeated_wakeup_is_not_repeated_business_write'=>true];

} finally {
    if(is_resource($db)){f164Close($db);}
    if(is_resource($service)){f164Detach($service);}
    $removed=f164Cleanup($owned);
}

f164Report('fb164-event-wait','Дождаться изменения и прочитать сохранённый остаток',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До операции — 1 / Cable / остаток 3
Полученное имя — FB164_CHANGED
Новый SELECT — 1 / Cable / остаток 11
Предметных UPDATE — 1
Пробуждение подтверждено — да
Оба процесса остановлены — да

Архив содержит все PHP-файлы и README для отдельной TCP-лаборатории. Выполните example.php из папки проекта: result.html и result.json покажут фактические товары и наблюдения. Создаётся и удаляется одна собственная синтетическая база; чужие БД и учётные записи не затрагиваются. Самостоятельный запуск требует разрешённого PHP proc_open и подготовленного приватного сервера.

Открыть результат программы · Открыть фактические наблюдения JSON.

Границы, ошибки и применение

У wait_event нет параметра timeout. Наш parent ограничивает весь дочерний сценарий 20 секундами, при проблеме завершает только свои процессы, а удаление БД допускает после их остановки. Готовность listener перед блокирующим вызовом не равна подтверждению native-регистрации. Поэтому sender сохраняет остаток ровно один раз, затем ограниченно повторяет лишь уведомление о уже сохранённом состоянии до ACK; это не повтор операции списания. У повторных сигналов есть предел 100 и срок 12 секунд в child. В обычном веб-запросе бесконечно ждать нельзя: для длительного listener нужен отдельный worker с политикой остановки и повторного чтения после соединения.

Клиентам нужен актуальный экран без постоянной выгрузки таблиц на миллионы строк. Обсуждение задачи на Stack Overflow. Сигнал определяет момент перечитывания, а запрос ограничивает поля и товары. Для надёжной фоновой работы отдельно нужны сохранённые задания и обработка перезапуска.

Измените требование

Если нужно показывать только Cable, обязательно ли выгружать весь каталог после каждого сигнала?

Вариант решения

Нет. Получатель уже знает свою задачу. Он выбирает нужные поля WHERE ID = 1; если отображается страница товаров, применяет её фильтр, сортировку и ограничение на сервере. Событие определяет момент обновления, SQL определяет объём полезных данных.

Показывать подтверждённый остаток, а не отменённую попытку

Оператор ошибся: ввёл 99, затем отменил действие. Соседний экран должен оставить подтверждённые 3. После корректного сохранения 7 экран получает уведомление и перечитывает 7. Сравним именно две границы транзакций, чтобы не принимать вызов POST_EVENT за доказательство успешного заказа.

Как получится наблюдаемый результат
  1. Callback зарегистрирован, Cable = 3
  2. UPDATE 99 и POST_EVENT находятся в одной транзакции → rollback
  3. SELECT по-прежнему показывает 3; в согласованном окне callback нет
  4. UPDATE 7 + POST_EVENT → commit
  5. Callback отмечает имя; основной код перечитывает Cable 7
  6. Handler освобождён, оба процесса завершены

Первый понятный вызов

function markCatalogChanged($eventName, $connection) {
    $GLOBALS['catalogNeedsRefresh'] = true;
    return true;
}
$handler = fbird_set_event_handler($db, 'markCatalogChanged', 'FB164_CHANGED');
if ($handler === false) { throw new RuntimeException('Подписка не создана'); }

Callback — обычная PHP-функция, которую вызывает драйвер. Первый параметр получает имя события, второй — ресурс соединения. Внутри мы лишь ставим флаг, чтобы основной код позже сделал запрос. return true здесь оставляет подписку активной; это не commit. $handler — отдельный ресурс регистрации, его сохраняют для последующего free. Код отправителя в архиве делает UPDATE и POST_EVENT в одной транзакции.

Что получится после запуска

НаблюдениеРезультат
Отменённая попытка оператораCable / остаток 99
SELECT после rollbackCable / остаток 3
Callback в окне после rollback0
SELECT после commitCable / остаток 7
Подтверждённое событие полученода
Подписка освобожденада
Полный самостоятельный проект и архив
Показывать подтверждённый остаток, а не отменённую попыткуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);
    require __DIR__.'/events-lab.php';
    $database=f164CreateDatabase($service,$owned,'event_commit');
    $events=f164EventScenario($database,'commit');
    $listener=$events['listener'];$sender=$events['sender'];

    $rolled=$listener['rollback_items'];$items=$listener['items'];$seen=$listener['seen'];
    $rows=[['Отменённая попытка оператора','Cable / остаток 99'],['SELECT после rollback',$rolled[0]['TITLE'].' / остаток '.$rolled[0]['QTY']],
        ['Callback в окне после rollback',$listener['rollback_callbacks']],['SELECT после commit',$items[0]['TITLE'].' / остаток '.$items[0]['QTY']],
        ['Подтверждённое событие получено',$listener['callback_count']>=1],['Подписка освобождена',$listener['freed']]];
    $checks=['rolled_back_stock'=>$rolled===[['ID'=>1,'TITLE'=>'Cable','QTY'=>3],['ID'=>2,'TITLE'=>'Clamp','QTY'=>0]],
        'rollback_window_silent'=>$listener['rollback_callbacks']===0,
        'committed_stock'=>$items===[['ID'=>1,'TITLE'=>'Cable','QTY'=>7],['ID'=>2,'TITLE'=>'Clamp','QTY'=>0]],
        'callback_received'=>$listener['callback_count']>=1,
        'callback_arguments'=>array_unique(array_column($seen,'name'))===['FB164_CHANGED']&&!in_array(false,array_column($seen,'connection_resource'),true),
        'no_payload'=>$listener['payload_exposed']===false&&$listener['counter_argument_exposed']===false,
        'handler_freed'=>$listener['freed']===true,'children_stopped'=>$events['all_children_stopped']===true,'two_children'=>$events['process_count']===2];
    $observations=['events'=>$events,'attempted_stock'=>99,'rollback_observation_window_ms'=>100,
        'event_does_not_store_a_job'=>true,'callback_count_is_not_changed_row_count'=>true];

} finally {
    if(is_resource($db)){f164Close($db);}
    if(is_resource($service)){f164Detach($service);}
    $removed=f164Cleanup($owned);
}

f164Report('fb164-event-commit','Показывать подтверждённый остаток, а не отменённую попытку',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Отменённая попытка оператора — Cable / остаток 99
SELECT после rollback — Cable / остаток 3
Callback в окне после rollback — 0
SELECT после commit — Cable / остаток 7
Подтверждённое событие получено — да
Подписка освобождена — да

Архив содержит все PHP-файлы и README для отдельной TCP-лаборатории. Выполните example.php из папки проекта: result.html и result.json покажут фактические товары и наблюдения. Создаётся и удаляется одна собственная синтетическая база; чужие БД и учётные записи не затрагиваются. Самостоятельный запуск требует разрешённого PHP proc_open и подготовленного приватного сервера.

Открыть результат программы · Открыть фактические наблюдения JSON.

Границы, ошибки и применение

POST_EVENT становится доступен подписавшимся клиентам после commit. Проверка отсутствия callback проводится в ограниченном согласованном окне после rollback; документированный смысл отката не выводится из бесконечного ожидания. Callback этого расширения не передаёт строки или счётчик изменений. Firebird может группировать доставку, поэтому число callback не равно числу UPDATE и не является журналом заказов. Для задания, которое нельзя потерять при выключенном listener, сохраните запись задания/outbox вместе с бизнес-изменением и читайте её при запуске. Событие только ускоряет пробуждение; без durable-данных оно не становится очередью с подтверждением обработки.

Автор длительной процедуры хотел получать сигнал ещё до подтверждения транзакции. Обсуждение задачи на Stack Overflow. Наш сценарий отделяет промежуточную попытку 99 от сохранённого 7. Правило commit проверяется по первичным источникам, а не выводится из вопроса без принятого ответа.

Разработчик связал число callback с числом строк, изменённых триггером. Обсуждение задачи на Stack Overflow. Поясняем разные сущности: выполнение SQL по строкам, внутренние счётчики событий и вызовы PHP callback. В отчёте хранится фактический callback_count, но он не используется как количество заказов.

Измените требование

Рабочий процесс был выключен во время commit заказа. Достаточно ли после запуска ждать следующее событие?

Вариант решения

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

Прекратить подписку и убедиться, что данные продолжают сохраняться

Временная подписка больше не нужна. Сначала она доставит изменение до 7. Затем получатель освободит handler, а отправитель отдельно сохранит 11. Покажем, что отключается конкретный callback, а база и подтверждённые изменения продолжают существовать. Это помогает правильно закончить worker, не перепутав освобождение ресурса с rollback.

Как получится наблюдаемый результат
  1. Commit Cable 7 → callback → SELECT 7
  2. fbird_free_event_handler(handler) → true; handler = null
  3. Sender после ACK освобождения сохраняет Cable 11 и POST_EVENT → commit
  4. Число вызовов этой подписки не растёт
  5. Обычный SELECT видит 11; связь с базой завершается после handler

Первый понятный вызов

if (fbird_free_event_handler($handler) !== true) {
    throw new RuntimeException('Подписка не освобождена');
}
$handler = null;

Передаётся $handler из set_event_handler, а не $db. После успешного вызова ссылка больше не представляет активную регистрацию; присваивание null делает это явным в приложении. Результат true говорит об освобождении ресурса, а не о количестве доставленных сообщений. Полный проект синхронизирует отправителя: поздний commit происходит только после отметки free.

Что получится после запуска

НаблюдениеРезультат
До освобождения handlerCable / остаток 7
fbird_free_event_handler вернул trueда
Поздняя подтверждённая записьCable / остаток 11
Callback после free не прибавилсяда
Освобождение отменило записьнет
Оба процесса остановленыда
Полный самостоятельный проект и архив
Прекратить подписку и убедиться, что данные продолжают сохранятьсяСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);
    require __DIR__.'/events-lab.php';
    $database=f164CreateDatabase($service,$owned,'vent_release');
    $events=f164EventScenario($database,'release');
    $listener=$events['listener'];$sender=$events['sender'];

    $before=$listener['committed_items'];$after=$listener['items'];
    $unchangedCount=$listener['callbacks_after_free']===$listener['callback_count'];
    $rows=[['До освобождения handler',$before[0]['TITLE'].' / остаток '.$before[0]['QTY']],
        ['fbird_free_event_handler вернул true',$listener['freed']],['Поздняя подтверждённая запись',$after[0]['TITLE'].' / остаток '.$after[0]['QTY']],
        ['Callback после free не прибавился',$unchangedCount],['Освобождение отменило запись',False],
        ['Оба процесса остановлены',$events['all_children_stopped']]];
    $checks=['first_committed_stock'=>$before===[['ID'=>1,'TITLE'=>'Cable','QTY'=>7],['ID'=>2,'TITLE'=>'Clamp','QTY'=>0]],
        'handler_freed'=>$listener['freed']===true,'notification_before_free'=>$listener['callback_count']>=1,
        'no_callback_after_free'=>$unchangedCount,
        'write_persisted_after_free'=>$after===[['ID'=>1,'TITLE'=>'Cable','QTY'=>11],['ID'=>2,'TITLE'=>'Clamp','QTY'=>0]],
        'second_commit_after_release'=>$sender['committed_business_updates']===2&&$sender['post_after_release']===true,
        'children_stopped'=>$events['all_children_stopped']===true,'two_children'=>$events['process_count']===2];
    $observations=['events'=>$events,'after_release_observation_window_ms'=>100,'free_is_not_rollback'=>true,
        'handler_released_before_connection'=>true];

} finally {
    if(is_resource($db)){f164Close($db);}
    if(is_resource($service)){f164Detach($service);}
    $removed=f164Cleanup($owned);
}

f164Report('fb164-event-release','Прекратить подписку и убедиться, что данные продолжают сохраняться',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До освобождения handler — Cable / остаток 7
fbird_free_event_handler вернул true — да
Поздняя подтверждённая запись — Cable / остаток 11
Callback после free не прибавился — да
Освобождение отменило запись — нет
Оба процесса остановлены — да

Архив содержит все PHP-файлы и README для отдельной TCP-лаборатории. Выполните example.php из папки проекта: result.html и result.json покажут фактические товары и наблюдения. Создаётся и удаляется одна собственная синтетическая база; чужие БД и учётные записи не затрагиваются. Самостоятельный запуск требует разрешённого PHP proc_open и подготовленного приватного сервера.

Открыть результат программы · Открыть фактические наблюдения JSON.

Границы, ошибки и применение

Освобождайте подписку до закрытия её соединения. Не проверяйте её состояние повторным free: старый native-handle не становится безопасным из-за is_resource. При нескольких PHP-переменных, ссылающихся на один handler, нельзя считать каждую независимым владельцем. Callback с return false прекращает повторную регистрацию, однако сохранённый ресурс всё равно завершают контролируемо. После free не отменяются уже завершённые SQL-транзакции и не исчезают сообщения других подписчиков. Ограниченное окно наблюдения после согласованного позднего commit дополняет документированный контракт отмены.

Измените требование

Если соединение ещё нужно для финального отчёта, обязательно ли закрывать его вместе с подпиской?

Вариант решения

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

Справочник постоянного подключения и трёх операций событий

ФункцияЗадача
fbird_pconnectПовторно использовать подключение в PHP worker, читая свежие данные отдельным запросом.
fbird_wait_eventДождаться имени события и перейти к новому ограниченному SELECT.
fbird_set_event_handlerЗарегистрировать PHP callback и сохранить ресурс подписки.
fbird_free_event_handlerОсвободить свою подписку, сохранив различие между отменой событий и отменой данных.

Выбирать срок жизни ресурса под задачу

Проверьте себя по результатам: тот же attachment может вернуть новый остаток 7; отменённые 99 не становятся сохранёнными данными; событие не содержит строку товара; после free другой процесс по-прежнему может сохранить 11. Если это понятно, решение о следующем SQL и сроке жизни каждого ресурса становится явным.

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

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

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

fbird_pconnectПовторно использовать подключение в PHP worker, читая свежие данные отдельным запросом.

Исторически PHP 5 / PHP 7 до 7.4; затем внешнее interbase. Профиль:3.0.1 / PHP 8.2.35 / Firebird 5.0.4. · interbase

fbird_pconnect(string $database = "", string $username = "", string $password = "", string $charset = "", int $buffers = 0, int $dialect = 0, string $role = "", int $sync = 0): resource|false

Короткие запросы каталога могут обращаться к базе многократно. fbird_pconnect позволяет процессу PHP повторно использовать своё подключение с теми же настройками. При этом остаток товара всё равно надо читать SQL: программа выше показывает переход Cable 3→7 без смены attachment. Польза — возможность сократить повторную установку канала; ускорение конкретного сайта не обещается без измерения.

fbird_pconnect(
    string $database = "", string $username = "", string $password = "",
    string $charset = "", int $buffers = 0, int $dialect = 0,
    string $role = "", int $sync = 0
): resource|false

Сигнатура показывает порядок аргументов реализации 3.0.1; это справочная запись, её не вставляют как готовый вызов. Все параметры допускают пропуск. Пустые database, username, password, charset дополняются соответствующими ini-настройками, если они заданы. Скобки вызова не создают пользовательскую функцию; возвращённый resource присваивается переменной слева.

АргументЗначение, единицы и условия
databaseПуть/алиас базы со стороны сервера. Для TCP, например, host/port:path. Пропуск учитывает ibase.default_db. Это не URL страницы PHP. Старые NetBEUI/IPX варианты из исторического руководства не являются обещанием современной поддержки.
usernameУчётная запись Firebird; при пропуске ibase.default_user. Она отличается от пользователя сайта.
passwordПароль этой учётной записи; при пропуске ibase.default_password. Берите из приватной конфигурации, не из URL и не печатайте в диагностике.
charsetКодировка обмена, например UTF8; при пропуске ibase.default_charset. Не меняет объявление кодировки существующих колонок или базы.
buffersЗапрашиваемое число страниц буфера при подключении;0 оставляет выбор серверу. Это не мегабайты и не гарантированная настройка общего пула. Влияние зависит от сервера и его архитектуры.
dialectSQL-диалект запросов соединения.0 означает выбранный драйвером поддерживаемый вариант; урок задаёт 3 явно. Это не преобразует базу из диалекта 1 в 3. Исторически возможность зависит от клиента InterBase6+.
roleРоль SQL при подключении; пустая строка не выбирает пользовательскую роль. Историческая поддержка InterBase5+. Членство в роли и её выбор обсуждаются отдельно.
syncИсторический аргумент режима записи; PHP Manual оставляет его без пояснения. В 3.0.1 ненулевое значение формирует isc_dpb_force_write; сравнение с IBASE_PRP_WM_SYNC выбирает синхронную запись, другое ненулевое значение — противоположный режим.0 не отправляет эту настройку. В повседневном подключении оставляйте 0: изменение режима записи требует отдельного осознанного решения о сохранности данных.
Возвращаемое значениеРесурс соединения либо false при отказе. Это не bool «пароль верен» и не массив результатов. Возможны PHP warning и диагностический код/текст; их сохраняют сразу до следующей операции расширения.

Применение 1: новый HTTP-запрос получает новый остаток

$db = fbird_pconnect($database, $user, $password, 'UTF8', 0, 3);
if ($db === false) { throw new RuntimeException('База недоступна'); }
$tx = fbird_trans(IBASE_READ | IBASE_COMMITTED, $db);
if ($tx === false) { throw new RuntimeException('Чтение не началось'); }
// SELECT выбранных полей; затем освобождение результата и fbird_commit($tx).

В полном HTTP-проекте есть два настоящих ответа, SQL-чтение attachment ID и изменение между ними. Транзакция чтения заканчивается до ответа. Повторное использование подключения не сохраняет старый PHP-массив и не освобождает от контроля согласованности данных.

Применение 2: разделить роль читателя и редактирование

$reader = fbird_pconnect($database, $user, $password, 'UTF8', 0, 3, 'LAB_READER');
if ($reader === false) { throw new RuntimeException('Читатель не подключён'); }
$result = fbird_query($reader, 'SELECT TITLE, QTY FROM LAB_ITEMS WHERE ID = ?', $id);
// Проверить результат, прочитать одну карточку и закончить транзакцию.

Это контекстный вариант для уже созданной роли, не ещё один исполненный проект. Параметры подключения участвуют в выборе повторно используемого соединения; роль нельзя считать изменяемой меткой посетителя поверх случайно взятого attachment. Разбор выдачи прав и явного выбора роли есть в практике ролей, а создание отдельных учётных записей — в администрировании fbird_*.

Время жизни / настройкаЧто учитывать в приложении
Область повторного использованияПроцесс PHP worker, не браузер и не весь кластер. Другой worker хранит собственные подключения. Resource нельзя переносить через $_SESSION или сериализацию.
Конец запросаВ закреплённом драйвере незаконченная default-транзакция подтверждается, остальные незавершённые транзакции откатываются, события освобождаются. Не поручайте этому бизнес-решение: commit/rollback должны быть явными раньше.
Состояние соединенияUSER_SESSION-контекст может пережить запрос. Убирайте свои метки клиента, завершайте результаты/транзакции; не предполагайте, что следующий запрос принадлежит тому же человеку.
ibase.allow_persistentСистемная настройка разрешения persistent; в новом процессе при 0 новые подключения работают без сохранения в persistent-пуле.
ibase.max_persistentПо документации лимит постоянных соединений процесса, -1 означает отсутствие ограничения. В C-коде 3.0.1 обнаружено сравнение, которое для конечного значения использует порог 1 из-за скобок. Это source-наблюдение, не результат отдельного runtime-теста лимитов. Пример запускает только один persistent-канал с -1.
ibase.max_linksОбщий лимит подключений процесса; -1 без ограничения. Исчерпание может дать false. Лимит одного worker не равен ограничению всех соединений сайта.
fbird_close / завершение процессаОбычный close для persistent-ресурса не обещает отключить физический attachment. При завершении worker его пул прекращает существовать. Не обнуляйте чужие ресурсы и не закрывайте один handle дважды.

Системные INI-настройки задают до запуска worker. Ускорение зависит от стоимости подключения, объёма запросов и числа процессов; один только переход на alias не меняет производительность. Если база недоступна, права недостаточны или кодировка не подходит, сохранённый канал не исправляет причину. Для простой короткой задачи подходит fbird_connect. Диагностика разобрана в fbird_errcode и fbird_errmsg.

fbird_pconnect — alias ibase_pconnect: тот же обработчик расширения, а не второй механизм или более быстрый протокол. Историческая метка PHP 5 / PHP 7 ниже 7.4 описывает прежнюю поставку interbase. Здесь используется внешнее interbase 3.0.1, PHP 8.2.35 NTS x64 и Firebird 5.0.4 через отдельный TCP-сервер. Версии в README записаны явно. Эти функции не появляются автоматически при установке PDO_FIREBIRD и не мигрируют приложение на PDO.

fbird_wait_eventДождаться имени события и перейти к новому ограниченному SELECT.

Исторически PHP 5 / PHP 7 до 7.4; затем внешнее interbase. Профиль:3.0.1 / PHP 8.2.35 / Firebird 5.0.4. · interbase

fbird_wait_event([resource $connection,] string $event_name, string ...$event_names): string|false

Фоновый обработчик знает, какой участок каталога его интересует, и ждёт изменения. fbird_wait_event возвращает имя сигнала; полезные данные получают следующим запросом. Функция проста для одного шага ожидания, но блокирует текущий процесс и сама не задаёт срок остановки.

fbird_wait_event(string $event_name, string ...$event_names): string|false
fbird_wait_event(resource $connection, string $event_name, string ...$event_names): string|false
Параметр / результатКонтракт
connectionНеобязательное действующее SQL-соединение. Без него используется последнее открытое соединение. Передавайте явно при нескольких базах. Это не ресурс транзакции или Service Manager.
event_name, event_namesОдно или несколько точных имён отдельными аргументами, максимум 15 по PHP Manual. Многоточие означает дополнительные строки, а не обязательный массив. POST_EVENT в Firebird 5 допускает имя до 127 байт; регистр и фактическая строка должны совпасть.
ВозвратИмя одного сработавшего события. В 3.0.1 native-ошибка или невозможность определить имя дают false. Это не число сообщений, не строка таблицы и не все события сразу.
ОжиданиеБлокирующее; параметра timeout нет. Срок процесса/внешнего supervisor не является параметром этого API.
Ошибки аргументовНеверное число/тип или неактивный resource могут дать предупреждение/ошибку разбора PHP. Не подменяйте это успешным ожиданием. Native-диагностику сохраняют немедленно.

Применение 1: перечитать известную карточку товара

$name = fbird_wait_event($db, 'FB164_CHANGED');
if ($name === false) { throw new RuntimeException('Нет корректного события'); }
$result = fbird_query($db, 'SELECT TITLE, QTY FROM LAB_ITEMS WHERE ID = ?', 1);

Полный ограниченный запуск listener/sender покажет Cable 11, а не только строку FB164_CHANGED. SELECT не извлекает ID из сигнала: нужный товар уже выбран экраном. Результат SELECT освобождается, транзакция заканчивается; затем можно решить, нужен ли ещё один цикл ожидания.

Применение 2: два повода обновить одну выбранную карточку

$name = fbird_wait_event($db, 'STOCK_CHANGED', 'PRICE_CHANGED');
if ($name === false) { throw new RuntimeException('Ожидание не удалось'); }
$result = fbird_query($db, 'SELECT TITLE, QTY, PRICE_CENTS FROM GOODS WHERE ID = ?', $id);

Это вариант для приложения, у которого уже есть GOODS и нужный ID. Любое из двух имён означает, что представление стоит перечитать. Если вернулось STOCK_CHANGED, нельзя заключать, что PRICE_CHANGED за это время не происходило: функция возвращает одно имя. Драйвер 3.0.1 выбирает первое подходящее имя из своего буфера. Для полного журнала изменений нужна отдельная сохраняемая модель.

Перед долгим ожиданием завершите старую snapshot-транзакцию. После сигнала прочитайте данные в новой подходящей транзакции: уведомление не обновляет прежний снимок автоматически. Сигналы поступают после commit, при rollback эта транзакция не публикует подтверждённое изменение. События отключённому клиенту не превращаются в долговечный почтовый ящик. Для браузера нужен ещё прикладной транспорт HTTP/SSE/WebSocket; wait_event сам не меняет HTML.

В нашем TCP-стенде основной порт и вспомогательное соединение событий ограничены loopback. Если SQL работает, а события не приходят, отдельно проверяют сетевой путь events и настройки сервера; успешный SELECT не проверяет доставку callback. Для нескольких одновременно ожидаемых задач обычно нужен отдельный worker либо подходящий событийный клиент. Альтернатива с сохранённым handle — fbird_set_event_handler, отмена его регистрации — fbird_free_event_handler.

fbird_wait_event — alias ibase_wait_event: тот же обработчик расширения, а не второй механизм или более быстрый протокол. Историческая метка PHP 5 / PHP 7 ниже 7.4 описывает прежнюю поставку interbase. Здесь используется внешнее interbase 3.0.1, PHP 8.2.35 NTS x64 и Firebird 5.0.4 через отдельный TCP-сервер. Версии в README записаны явно. Эти функции не появляются автоматически при установке PDO_FIREBIRD и не мигрируют приложение на PDO.

fbird_set_event_handlerЗарегистрировать PHP callback и сохранить ресурс подписки.

Исторически PHP 5 / PHP 7 до 7.4; затем внешнее interbase. Профиль:3.0.1 / PHP 8.2.35 / Firebird 5.0.4. · interbase

fbird_set_event_handler([resource $connection,] callable $callback, string $event, string ...$events): resource|false

Если основной PHP-код должен знать, что каталог изменился, можно зарегистрировать небольшую функцию callback. Драйвер вызовет её после доставки события. Она отмечает необходимость перечитывания; проверка предметных данных и решение приложения остаются в основном коде.

fbird_set_event_handler(callable $callback, string $event, string ...$events): resource|false
fbird_set_event_handler(resource $connection, callable $callback, string $event, string ...$events): resource|false
Аргумент / результатЧто означает
connectionОткрытое SQL-соединение, связанное с подпиской. Пропуск выбирает последнее соединение. Явное соединение уменьшает неоднозначность нескольких баз.
callbackВызываемая PHP-функция с двумя аргументами: string eventName и resource connection. Имя функции передают строкой; допустимость callable проверяет расширение. В 3.0.1 форма без connection распознаётся по первому строковому аргументу, поэтому для closure/массива callable используйте явный connection.
event и следующие именаТочные строки, по официальному контракту от 1 до 15. Не шаблоны и не SQL-выражения. Передают отдельными аргументами.
Return callbackСтрого false прекращает последующую регистрацию; другое значение не несёт полезного ответа отправителю. В наших примерах true явно оставляет подписку. Это не SQLcommit/rollback.
Возврат set_event_handlerОтдельный ресурс регистрации для free_event_handler, либо false при неудаче. Сохраняйте его, а не только bool is_resource.
ОшибкиНедействующее соединение, неподходящий callable, неверные аргументы или native-ошибка регистрации. Проверять ===false сразу; диагностика следующих вызовов не является исходной ошибкой.

Граница документации и реализации важна: PHP Manual обещает до 15 имён. В C-коде 3.0.1 цикл заполнения использует абсолютный индекс аргумента i<15 вместо количества событий: при явном connection рассматривается не более 13 имён, без него 14. Это обнаружено чтением исходника, а не отдельным runtime-прогоном больших списков. Проекты используют одно имя; для другого числа не полагайтесь на непроверенную границу. Документированный максимум сохранён здесь явно вместе с ограничением конкретной версии.

Применение 1: обновление после commit

function requestRefresh($eventName, $connection) {
    $GLOBALS['refreshNeeded'] = true;
    return true;
}
$handler = fbird_set_event_handler($db, 'requestRefresh', 'FB164_CHANGED');
if ($handler === false) { throw new RuntimeException('Регистрация не удалась'); }

Полный проект с отменой 99 и сохранением 7 выполняет реальную запись во втором процессе. Callback не получает значение 7 и не выполняет тяжёлую работу; основной код позже делает SELECT. Это упрощает владение ресурсами и отделяет механизм доставки от бизнес-логики.

Применение 2: одноразовый сигнал о готовности отчёта

function reportReady($name, $connection) {
    $GLOBALS['reportNeedsRead'] = true;
    return false; // Следующее уведомление этой регистрации уже не нужно.
}
$handler = fbird_set_event_handler($db, 'reportReady', 'REPORT_READY');
if ($handler === false) { throw new RuntimeException('Не удалось ждать отчёт'); }
// Основной код по флагу читает сохранённый отчёт; затем освобождает handler.

Контекстный вариант предполагает, что генератор уже сохраняет отчёт в базе и публикует имя после commit. return false — политика подписки, а не подтверждение успешного чтения отчёта. Сохранённый handle освобождают отдельно; пример такого порядка — отмена регистрации с последующим чтением 11.

В callback передаются только имя и соединение. Внутренний Firebird counter не раскрыт этим PHP API; полезные строки, ID заказа и количество изменённых записей не передаются. Реализация 3.0.1 берёт первое подходящее имя из буфера, а группировка доставки не гарантирует один callback на строку/commit. Поэтому обработчик не должен считать вызовы как проданные товары. Сохраняемый outbox или журнал имеют собственные строки и статусы; уведомление может только разбудить их читателя.

Событие принадлежит соединению и процессу, в котором зарегистрировано. Не передавайте resource через HTTP или сериализацию. Не закрывайте connection раньше handler; не используйте callback как способ бесконечно удержать короткий web request. Для блокирующего одного шага есть fbird_wait_event, для контролируемого завершения — fbird_free_event_handler.

fbird_set_event_handler — alias ibase_set_event_handler: тот же обработчик расширения, а не второй механизм или более быстрый протокол. Историческая метка PHP 5 / PHP 7 ниже 7.4 описывает прежнюю поставку interbase. Здесь используется внешнее interbase 3.0.1, PHP 8.2.35 NTS x64 и Firebird 5.0.4 через отдельный TCP-сервер. Версии в README записаны явно. Эти функции не появляются автоматически при установке PDO_FIREBIRD и не мигрируют приложение на PDO.

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

Исторически PHP 5 / PHP 7 до 7.4; затем внешнее interbase. Профиль:3.0.1 / PHP 8.2.35 / Firebird 5.0.4. · interbase

fbird_free_event_handler(resource $event): bool

Когда экран или worker больше не следит за изменениями, принадлежащую ему подписку надо закончить. fbird_free_event_handler освобождает регистрацию. Данные Firebird, открытое соединение и уже выполненный commit имеют другие сроки жизни: в проекте после освобождения подписки товар всё равно получает остаток 11.

fbird_free_event_handler(resource $event): bool
ЧастьПравило
eventЕдинственный обязательный аргумент: ещё принадлежащий программе ресурс, ранее возвращённый fbird_set_event_handler или его canonical alias. Не ресурс соединения и не результат SELECT.
trueОсвобождение принято. Это не отмена данных и не число обработанных событий.
false / ошибкаНеудача или неверный ресурс; возможны предупреждения/ошибки PHP. Публично показывают безопасную прикладную ошибку, а native-диагностику сохраняют сразу в приватном журнале.
После вызоваПеременную присваивают null, другие копии не используют повторно. Одно владение означает одно освобождение.
ПорядокОсвободить подписку, закончить свои результаты/транзакции, затем закрыть обычное соединение.

Применение 1: завершить временное наблюдение

if (fbird_free_event_handler($handler) !== true) {
    throw new RuntimeException('Подписка не завершена');
}
$handler = null;
$result = fbird_query($db, 'SELECT TITLE, QTY FROM LAB_ITEMS WHERE ID = ?', 1);

В полном двухпроцессном проекте поздний SELECT показывает 11, а счётчик callback не увеличивается после free. Это видимое различие отмены регистрации и отката транзакции. Прочитанный SQL-результат затем освобождают своей функцией, не free_event_handler.

Применение 2: гарантировать один путь освобождения при ошибке приложения

$handler = fbird_set_event_handler($db, 'requestRefresh', 'STOCK_CHANGED');
if ($handler === false) { throw new RuntimeException('Нет подписки'); }
try {
    // Ограниченная работа с флагом requestRefresh и свежими SQL-данными.
} finally {
    if (fbird_free_event_handler($handler) !== true) {
        throw new RuntimeException('Ресурс подписки не освобождён');
    }
    $handler = null;
}

Это каркас владения: requestRefresh объявляется заранее, работа имеет внешний срок, а единственный finally знает, кто отвечает за handler. В более крупном приложении ошибку очистки логируют так, чтобы сохранить и первоначальную причину. Не добавляйте второй free в callback или соседнем участке кода. Если callback вернул false, сохранённая PHP-ссылка всё ещё требует контролируемого завершения.

В закреплённом native-коде нельзя считать is_resource доказательством, что ранее освобождённый handle снова пригоден. Повторный free или закрытие связи в неправильном порядке опаснее обычной логической ошибки. Присваивание null и явный единственный владелец предотвращают такой путь. Для отмены SQL нужен fbird_rollback, для соединения — fbird_close, для регистрации новой подписки — fbird_set_event_handler.

fbird_free_event_handler — alias ibase_free_event_handler: тот же обработчик расширения, а не второй механизм или более быстрый протокол. Историческая метка PHP 5 / PHP 7 ниже 7.4 описывает прежнюю поставку interbase. Здесь используется внешнее interbase 3.0.1, PHP 8.2.35 NTS x64 и Firebird 5.0.4 через отдельный TCP-сервер. Версии в README записаны явно. Эти функции не появляются автоматически при установке PDO_FIREBIRD и не мигрируют приложение на PDO.

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

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

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

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

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