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

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

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

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

Глава 78 · После SQL и базовых служебных функций Firebird

Firebird fbird_*: отчёт для поддержки, ёмкость таблицы и правильный маршрут базы

Заявка поддержки с реальными товарами, физическая статистика рядом с ограниченным SELECT, изоляция EAST/WEST и чтение после исправления маршрута.

От сообщения пользователя к конкретному выводу

Старый PHP-код вызывает fbird_*, а поддержке нужно понять, где именно проблема: не отвечает экземпляр, выбран чужой файл или у товара просто нет остатка. В этой главе получатся четыре законченных результата: заявка с предметными данными, экран из трёх SKU рядом со статистикой, правильная выдача из EAST и восстановленное чтение после ошибки маршрута.

Одна заявка — несколько осмысленных проверок
  1. Выбрать понятный маршрут приложения: main-catalog или EAST
  2. service_attach → экземпляр; server_info → его версия
  3. db_info с отдельным путём → свойства выбранного файла
  4. Обычный SQL по этому маршруту → нужные товары
  5. Сохранить различимые результаты и завершить принадлежащие ресурсы
ОбластьЧто проверяетЧего не доказывает
Экземпляр FirebirdОтвет service и версия движкаЧто маршрут указывает на правильный филиал.
Файл базыODS, диалект, физическая статистикаЧто товар существует или его остаток положителен.
Предметная выборкаКонкретные строки и правила приложенияПолную целостность файла и пригодность резервной копии.

Сервис можно представить справочной службой здания: сначала выбирают здание, затем нужное помещение и конкретный вопрос. Это модель адресации, а не физического устройства страниц. Слово alias здесь означает альтернативное имя PHP-функции; код EAST в карте маршрутов — другое понятие, логическое имя базы внутри приложения. Смена ibase_* на fbird_* не создаёт новый движок или отдельную модель доступа.

До этой главы полезны запросы fbird_*, базовые service-ресурсы и немедленная диагностика. Каждый текущий вызов снова объясняется ниже: ссылки помогают перейти глубже и не заменяют разбор его параметров.

Как запустить и что окажется в результате

ZIP каждого примера содержит example.php, lab.php, README.txt, seed.sql и seed.fbk.base64.txt. Программа создаёт собственную базу из синтетического шаблона; пример EAST/WEST создаёт две. Никакие рабочие файлы не подменяются. После получения результата собственные базы и промежуточные FBK удаляются по точным меткам владения; HTML/JSON остаются. Формат входного шаблона и SHA256 описаны в README.

Используется частный embedded-профиль Firebird 5.0.4 с PHP 8.2.35/interbase 3.0.1. Здесь нет проверки сетевого сервера, чужих прав или большого production-каталога. f163Service, f163Runtime и f163Detach действительно вызывают fbird_service_attach, fbird_server_info и fbird_service_detach; reportText163 вызывает fbird_db_info. f163Read обслуживает обычный параметризованный SQL. Вспомогательные функции не подменяют реальный ответ заранее записанным результатом.

Текст gstat может содержать пути и служебные идентификаторы. Основная таблица показывает только нужные значения; раскрываемый HTML/JSON получает выбранные поля либо очищенный f163PublicText отчёт. Готовые примеры не отправляют сообщения поддержке и не публикуют реквизиты подключения. Минимальный фрагмент показывает один шаг, а самостоятельный файл — завершённую работу и обработку ресурсов.

Собрать понятный отчёт для поддержки

Пользователь пишет: «Предохранителя нет в каталоге, наверное, база не работает». Подготовим отчёт SUPPORT-163, в котором отдельно видны ответ экземпляра Firebird, формат выбранной базы и две нужные товарные строки. У Fuse 10A действительно остаток 0, а Cable USB имеет 12 единиц. Нулевой остаток становится понятным предметным результатом, а не ошибкой связи.

Путь данных и действий
  1. SUPPORT-163 → разрешённый маршрут main-catalog
  2. service_attach → server_info: версия и платформа экземпляра
  3. db_info(HDR_PAGES) → ODS 13.1 и диалект 3
  4. SELECT двух ID → Cable USB/12 и Fuse 10A/0
  5. Отдельные выводы о сервисе, файле и данных → закрыть ресурсы

Сначала один вызов

$version = fbird_server_info($service, IBASE_SVC_SERVER_VERSION);
if (!is_string($version)) { throw new RuntimeException("Версия не получена"); }
echo $version;

service — ресурс менеджера сервисов, а SERVER_VERSION выбирает одно поле его паспорта. Присваивание сохраняет ответ в version; echo показывает строку. Полный файл сначала вызывает реальный fbird_service_attach и server_info через проверяемые f163-обёртки, затем создаёт отдельную учебную базу. Обычный SQL использует другой ресурс db. Эти два канала отвечают на разные вопросы.

Видимый результат

НаблюдениеРезультат
ЗаявкаSUPPORT-163
Маршрут приложенияmain-catalog
Версия экземпляраWI-V5.0.4.1812 Firebird 5.0
Формат файла ODS13.1
SQL-диалект3
Проверено товарных строк2
Остаток Cable USB12
Остаток Fuse 10A0
Вывод для поддержкиСервис отвечает; Fuse 10A отсутствует на складе
Сервисное соединение освобожденода
Собрать понятный отчёт для поддержкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

    function reportText163(&$service,string $database,int $action,int $argument=0): string {
        $call=f163Capture(static fn()=>fbird_db_info($service,$database,$action,$argument));
        if(!is_string($call['value']) || $call['warning'] || $call['code']!==0) {
            if($call['value']===false) {$service=null;}
            f163Failure('Статистический текст не получен',$call);
        }
        return $call['value'];
    }
    function reportField163(string $report,string $pattern): string {
        if(preg_match($pattern,$report,$match)!==1) {
            throw new RuntimeException('Ожидаемое поле отсутствует в служебном отчёте.');
        }
        return $match[1];
    }
    function header163(&$service,string $database): array {
        $report=reportText163($service,$database,IBASE_STS_HDR_PAGES);
        return ['page_size'=>(int)reportField163($report,'/\bPage size\s+(\d+)\b/'),
            'ods'=>reportField163($report,'/\bODS version\s+(\d+\.\d+)\b/'),
            'dialect'=>(int)reportField163($report,'/\bDatabase dialect\s+(\d+)\b/')];
    }

    $database=f163CreateDatabase($service,$owned,'support');
    $db=f163Connect($database);
    f163Command($db,'UPDATE LAB_ITEMS SET TITLE=?,QTY=? WHERE ID=?',['Cable USB',12,1]);
    f163Command($db,'UPDATE LAB_ITEMS SET TITLE=?,QTY=? WHERE ID=?',['Fuse 10A',0,2]);
    f163Commit($db);
    $items=f163Read($db,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE ID IN (?,?) ORDER BY ID ROWS 2',[1,2]);
    f163Close($db);
    $header=header163($service,$database);
    $serviceClosed=f163Detach($service);
    $summary='Сервис отвечает; Fuse 10A отсутствует на складе';
    $rows=[['Заявка','SUPPORT-163'],['Маршрут приложения','main-catalog'],['Версия экземпляра',$runtime['server_version']],
        ['Формат файла ODS',$header['ods']],['SQL-диалект',$header['dialect']],['Проверено товарных строк',count($items)],
        ['Остаток Cable USB',$items[0]['qty']],['Остаток Fuse 10A',$items[1]['qty']],['Вывод для поддержки',$summary],
        ['Сервисное соединение освобождено',$serviceClosed && $service===null]];
    $checks=['engine_received'=>str_contains($runtime['server_version'],'V5.0.4.'),
        'header_received'=>$header===['page_size'=>8192,'ods'=>'13.1','dialect'=>3],
        'business_rows_received'=>$items===[['id'=>1,'title'=>'Cable USB','qty'=>12],['id'=>2,'title'=>'Fuse 10A','qty'=>0]],
        'zero_is_data'=>$items[1]['qty']===0,'sql_closed'=>$db===null,'service_closed'=>$serviceClosed===true && $service===null];
    $observations=['ticket'=>'SUPPORT-163','route'=>'main-catalog','engine'=>$runtime['server_version'],
        'implementation'=>$runtime['implementation'],'storage'=>$header,'selected_items'=>$items,
        'conclusions'=>['service_replied'=>true,'configured_database_read'=>true,'zero_stock_is_not_connection_failure'=>true],
        'limits'=>['not_full_integrity_validation'=>true,'not_an_inventory_of_all_tables'=>true],
        'password_and_physical_path_in_report'=>false];

} finally {
    if(is_resource($db)){f163Close($db);}
    if(is_resource($service)){f163Detach($service);}
    $removed=f163Cleanup($owned);
}

f163Report('fb163-support-report','Собрать понятный отчёт для поддержки',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Заявка — SUPPORT-163
Маршрут приложения — main-catalog
Версия экземпляра — WI-V5.0.4.1812 Firebird 5.0
Формат файла ODS — 13.1
SQL-диалект — 3
Проверено товарных строк — 2
Остаток Cable USB — 12
Остаток Fuse 10A — 0
Вывод для поддержки — Сервис отвечает; Fuse 10A отсутствует на складе
Сервисное соединение освобождено — да

Самостоятельный CLI-файл: настоящие service-алиасы, собственная база/базы из приложенного шаблона, осмысленные SQL-результаты, result.html/result.json и очистка своего окружения.

Фактический HTML-результат · Данные JSON.

Границы и типичные ошибки

Читаются только два заранее выбранных товара, а не все строки и столбцы рабочей базы. Отчёт не доказывает исправность всех таблиц, отсутствие повреждений или наличие резервной копии. ODS — версия формата файла, а interbase 3.0.1 — версия PHP-расширения; ни одно число не заменяет ответ server_info. Пароль и физический путь в поддержку автоматически не отправляются: пример лишь создаёт локальные HTML/JSON, которые можно просмотреть.

Автор реального вопроса получил чужой файл .fdb и не знает, какой Firebird нужен для его формата. Исходный вопрос Stack Overflow. В нашем отчёте версия отвечающего движка и ODS файла — отдельные поля. Мы не определяем совместимость всех старых файлов по расширению .fdb и не редактируем заголовок hex-редактором: для переноса нужен подходящий инструмент и проверяемое восстановление.

Измените условие

Если SQL вернул пустой массив для ID=2, можно ли написать тот же вывод «остаток 0»?

Ожидаемое решение

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

Показать три товара и отдельно оценить хранение

Сотруднику нужны первые три товара с малым положительным остатком, а администратору — сведения о хранении таблицы. Это разные задачи. Создадим 120 SKU: 48 соответствуют остатку 1–2, но PHP получит только SKU-001, SKU-006 и SKU-011. В раскрываемом отчёте отдельно появятся фактические страницы данных и структура индекса.

Путь данных и действий
  1. 120 SKU; qty=id%5 → остатки 0,1,2,3,4
  2. WHERE qty BETWEEN 1 AND 2 → 48 подходящих строк в базе
  3. ORDER BY qty,id ROWS 3 → 3 строки для экрана
  4. db_info(DATA_PAGES, IDX_PAGES) → физические показатели
  5. Сохранить обе группы отдельно; не объявлять статистику планом запроса

Сначала один вызов

$report = fbird_db_info($service, $database, IBASE_STS_DATA_PAGES, IBASE_STS_IDX_PAGES);
if (!is_string($report)) { throw new RuntimeException("Статистика не получена"); }
// Текст относится к хранению, а не к строкам текущего SELECT.

Третий аргумент выбирает действие, четвёртый добавляет флаг индексов в проверенном драйвере. Для экрана SQL сам фильтрует, сортирует и ограничивает результат. Два COUNT относятся к нашей маленькой фиксированной таблице и возвращают по одному числу; они не загружают 120 строк в PHP. Физические значения находятся в observations.physical_statistics и в раскрываемом блоке HTML: их не подставляют заранее.

Видимый результат

НаблюдениеРезультат
Всего синтетических SKU120
Остаток от 1 до 2 включительно48
Строк передано экрану3
На экранеSKU-001 / 1; SKU-006 / 1; SKU-011 / 1
Таблица физического отчётаLAB_ITEMS
Индекс физического отчётаIX_FB163_QTY
Статистика страниц и узлов полученада
Все 120 строк выгружены в PHPнет
Использование индекса запросом доказанонет
Показать три товара и отдельно оценить хранениеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

    function reportText163(&$service,string $database,int $action,int $argument=0): string {
        $call=f163Capture(static fn()=>fbird_db_info($service,$database,$action,$argument));
        if(!is_string($call['value']) || $call['warning'] || $call['code']!==0) {
            if($call['value']===false) {$service=null;}
            f163Failure('Статистический текст не получен',$call);
        }
        return $call['value'];
    }
    function reportField163(string $report,string $pattern): string {
        if(preg_match($pattern,$report,$match)!==1) {
            throw new RuntimeException('Ожидаемое поле отсутствует в служебном отчёте.');
        }
        return $match[1];
    }
    function header163(&$service,string $database): array {
        $report=reportText163($service,$database,IBASE_STS_HDR_PAGES);
        return ['page_size'=>(int)reportField163($report,'/\bPage size\s+(\d+)\b/'),
            'ods'=>reportField163($report,'/\bODS version\s+(\d+\.\d+)\b/'),
            'dialect'=>(int)reportField163($report,'/\bDatabase dialect\s+(\d+)\b/')];
    }

    $database=f163CreateDatabase($service,$owned,'capacity');
    $db=f163Connect($database);
    f163Command($db,'DELETE FROM LAB_ITEMS');f163Commit($db);
    for($id=1;$id<=120;$id++) {
        f163Command($db,'INSERT INTO LAB_ITEMS(ID,TITLE,QTY) VALUES (?,?,?)',[$id,sprintf('SKU-%03d',$id),$id%5]);
    }
    f163Commit($db);
    f163Command($db,'CREATE INDEX IX_FB163_QTY ON LAB_ITEMS(QTY)');f163Commit($db);
    $total=(int)f163Read($db,'SELECT COUNT(*) AS "n" FROM LAB_ITEMS')[0]['n'];
    $eligible=(int)f163Read($db,'SELECT COUNT(*) AS "n" FROM LAB_ITEMS WHERE QTY BETWEEN ? AND ?',[1,2])[0]['n'];
    $selected=f163Read($db,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE QTY BETWEEN ? AND ? ORDER BY QTY,ID ROWS 3',[1,2]);
    f163Close($db);
    $report=reportText163($service,$database,IBASE_STS_DATA_PAGES,IBASE_STS_IDX_PAGES);
    $pages=(int)reportField163($report,'/\bLAB_ITEMS\s+\(\d+\).*?\bData pages:\s*(\d+)/s');
    if(preg_match('/\bIndex IX_FB163_QTY\s+\(\d+\).*?\bdepth:\s*(\d+),\s*leaf buckets:\s*(\d+),\s*nodes:\s*(\d+)/s',$report,$index)!==1) {
        throw new RuntimeException('Статистика собственного индекса отсутствует.');
    }
    $stats=['data_pages'=>$pages,'index'=>'IX_FB163_QTY','depth'=>(int)$index[1],
        'leaf_pages'=>(int)$index[2],'nodes'=>(int)$index[3]];
    $statisticsPresent=$pages>0 && $stats['depth']>0 && $stats['leaf_pages']>0 && $stats['nodes']>0;
    $screen=implode('; ',array_map(static fn($item)=>$item['title'].' / '.$item['qty'],$selected));
    $rows=[['Всего синтетических SKU',$total],['Остаток от 1 до 2 включительно',$eligible],['Строк передано экрану',count($selected)],
        ['На экране',$screen],['Таблица физического отчёта','LAB_ITEMS'],['Индекс физического отчёта','IX_FB163_QTY'],
        ['Статистика страниц и узлов получена',$statisticsPresent],['Все 120 строк выгружены в PHP',false],
        ['Использование индекса запросом доказано',false]];
    $checks=['fixture_count'=>$total===120,'sql_filter_count'=>$eligible===48,
        'bounded_screen_rows'=>$selected===[['id'=>1,'title'=>'SKU-001','qty'=>1],['id'=>6,'title'=>'SKU-006','qty'=>1],['id'=>11,'title'=>'SKU-011','qty'=>1]],
        'physical_statistics_present'=>$statisticsPresent,'only_three_rows_transferred'=>count($selected)===3,
        'sql_link_closed'=>$db===null];
    $observations=['physical_statistics'=>$stats,'selected_items'=>$selected,'total_fixture_rows'=>$total,'matching_rows'=>$eligible,
        'screen_sql'=>'SELECT ID,TITLE,QTY FROM LAB_ITEMS WHERE QTY BETWEEN 1 AND 2 ORDER BY QTY,ID ROWS 3',
        'statistics_request'=>['action'=>'IBASE_STS_DATA_PAGES','argument'=>'IBASE_STS_IDX_PAGES'],
        'report_excerpt'=>f163PublicText($report),'gstat_is_not_a_transaction_snapshot'=>true,
        'index_presence_is_not_query_plan'=>true,'physical_numbers_not_guessed_before_run'=>true];

} finally {
    if(is_resource($db)){f163Close($db);}
    if(is_resource($service)){f163Detach($service);}
    $removed=f163Cleanup($owned);
}

f163Report('fb163-table-capacity','Показать три товара и отдельно оценить хранение',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Всего синтетических SKU — 120
Остаток от 1 до 2 включительно — 48
Строк передано экрану — 3
На экране — SKU-001 / 1; SKU-006 / 1; SKU-011 / 1
Таблица физического отчёта — LAB_ITEMS
Индекс физического отчёта — IX_FB163_QTY
Статистика страниц и узлов получена — да
Все 120 строк выгружены в PHP — нет
Использование индекса запросом доказано — нет

Самостоятельный CLI-файл: настоящие service-алиасы, собственная база/базы из приложенного шаблона, осмысленные SQL-результаты, result.html/result.json и очистка своего окружения.

Фактический HTML-результат · Данные JSON.

Границы и типичные ошибки

Наличие IX_FB163_QTY не доказывает выбор индекса оптимизатором. gstat не даёт транзакционный снимок: физические версии и текущий SELECT нельзя считать одинаковым набором строк. Число страниц не равно размеру JSON и не позволяет само по себе установить причину медленного сайта. Большая база требует отдельного согласованного сбора статистики; этот учебный пример не выполняет нагрузочный тест. Парсер рассчитан на подписи выбранного Firebird 5.0.4 и явно отказывает при другом формате.

После переноса записей из плоского файла автор увидел гораздо больший файл базы и попытался понять, чем занят объём. Исходный вопрос Stack Overflow. Разделяем строки предметных данных, страницы таблиц и индекс. Отчёт показывает измеренные физические показатели рядом с ограниченной выборкой; он не назначает автоматически ремонт, сжатие или восстановление по одному соотношению размеров.

Измените условие

Замените ROWS 3 на ROWS 5. Должно ли измениться число страниц в статистике только из-за этой замены?

Ожидаемое решение

Нет: изменился размер результата чтения, а данные таблицы не менялись. Пять первых строк при прежнем порядке имеют ID 1,6,11,16,21. Измените ожидаемый экранный результат, но не пытайтесь получить физическое число страниц из LIMIT/ROWS.

Выдать товар из нужного филиала

У двух филиалов есть товар Cable USB с ID=1. В EAST лежит 7 единиц, в WEST — 2. Общий сервер и одинаковый формат файла не говорят, к какому филиалу относится SQL-соединение. Выдадим две единицы только из EAST, проверив разрешённый маршрут и метку филиала внутри выбранной базы. Получим остатки 5 и 2.

Путь данных и действий
  1. EAST → собственный файл A; WEST → собственный файл B
  2. db_info обоих файлов → одинаковые ODS 13.1 и диалект 3
  3. Запрос EAST → разрешённая карта routes → BRANCH_INFO=EAST
  4. UPDATE qty=qty-2 WHERE id=1 AND qty>=2 → commit
  5. Свежие SELECT обеих баз → EAST/5, WEST/2

Сначала один вызов

$branch = "EAST";
$database = $routes[$branch]; // Карта задаётся приложением.
$header = fbird_db_info($service, $database, IBASE_STS_HDR_PAGES);
if (!is_string($header)) { throw new RuntimeException("Маршрут не проверен"); }

routes — массив сопоставления понятного кода филиала с разрешённым именем базы. service остаётся подключением к одному экземпляру, а аргумент database меняется для каждой базы. В полной программе неизвестный ключ отклоняется до открытия файла, затем SQL читает метку филиала. Только после совпадения выполняется UPDATE; service header сам по себе не проверяет бизнес-принадлежность базы.

Видимый результат

НаблюдениеРезультат
Выбран филиалEAST
ID товара в обеих базах1
ТоварCable USB
EAST до выдачи7
WEST до выдачи2
Выдано из EAST2
EAST после выдачи5
WEST после выдачи2
Формат ODS обеих баз13.1
Одинаковый формат означает одну базунет
Выдать товар из нужного филиалаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

    function reportText163(&$service,string $database,int $action,int $argument=0): string {
        $call=f163Capture(static fn()=>fbird_db_info($service,$database,$action,$argument));
        if(!is_string($call['value']) || $call['warning'] || $call['code']!==0) {
            if($call['value']===false) {$service=null;}
            f163Failure('Статистический текст не получен',$call);
        }
        return $call['value'];
    }
    function reportField163(string $report,string $pattern): string {
        if(preg_match($pattern,$report,$match)!==1) {
            throw new RuntimeException('Ожидаемое поле отсутствует в служебном отчёте.');
        }
        return $match[1];
    }
    function header163(&$service,string $database): array {
        $report=reportText163($service,$database,IBASE_STS_HDR_PAGES);
        return ['page_size'=>(int)reportField163($report,'/\bPage size\s+(\d+)\b/'),
            'ods'=>reportField163($report,'/\bODS version\s+(\d+\.\d+)\b/'),
            'dialect'=>(int)reportField163($report,'/\bDatabase dialect\s+(\d+)\b/')];
    }

    $routes=['EAST'=>f163CreateDatabase($service,$owned,'east'),'WEST'=>f163CreateDatabase($service,$owned,'west')];
    $before=[];$headers=[];
    foreach(['EAST'=>7,'WEST'=>2] as $branch=>$quantity) {
        $db=f163Connect($routes[$branch]);
        f163Command($db,'DELETE FROM LAB_ITEMS WHERE ID<>?',[1]);
        f163Command($db,'UPDATE LAB_ITEMS SET TITLE=?,QTY=? WHERE ID=?',['Cable USB',$quantity,1]);
        f163Command($db,'CREATE TABLE BRANCH_INFO(BRANCH_CODE VARCHAR(4) NOT NULL)');f163Commit($db);
        f163Command($db,'INSERT INTO BRANCH_INFO(BRANCH_CODE) VALUES (?)',[$branch]);f163Commit($db);
        $before[$branch]=f163Read($db,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE ID=?',[1])[0];
        f163Close($db);
        $headers[$branch]=header163($service,$routes[$branch]);
    }
    $requestedBranch='EAST';
    if(!array_key_exists($requestedBranch,$routes)) {throw new DomainException('Неизвестный филиал.');}
    $db=f163Connect($routes[$requestedBranch]);
    $actualBranch=f163Read($db,'SELECT BRANCH_CODE AS "branch" FROM BRANCH_INFO ROWS 1')[0]['branch'];
    if($actualBranch!==$requestedBranch) {throw new DomainException('Файл не соответствует выбранному филиалу.');}
    $changed=f163Query($db,'UPDATE LAB_ITEMS SET QTY=QTY-? WHERE ID=? AND QTY>=?',[2,1,2]);
    if($changed!==1) {throw new DomainException('Выдача не выполнена: товара нет или остатка недостаточно.');}
    f163Commit($db);f163Close($db);
    $after=[];
    // interbase 3.0.1 хранит default link: последнее чтение EAST согласовано
    // с порядком удаления учебных баз WEST → EAST в завершающей очистке.
    foreach(array_reverse($routes,true) as $branch=>$database) {
        $db=f163Connect($database);
        $after[$branch]=f163Read($db,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE ID=?',[1])[0];
        f163Close($db);
    }
    $sameFormat=$headers['EAST']===$headers['WEST'];
    $rows=[['Выбран филиал',$requestedBranch],['ID товара в обеих базах',1],['Товар','Cable USB'],
        ['EAST до выдачи',$before['EAST']['qty']],['WEST до выдачи',$before['WEST']['qty']],['Выдано из EAST',2],
        ['EAST после выдачи',$after['EAST']['qty']],['WEST после выдачи',$after['WEST']['qty']],
        ['Формат ODS обеих баз',$headers['EAST']['ods']],['Одинаковый формат означает одну базу',false]];
    $checks=['two_distinct_owned_routes'=>count($routes)===2 && $routes['EAST']!==$routes['WEST'],
        'initial_stock_per_branch'=>$before['EAST']['qty']===7 && $before['WEST']['qty']===2,
        'route_identity_checked'=>$actualBranch==='EAST','single_row_changed'=>$changed===1,
        'east_stock_changed'=>$after['EAST']['qty']===5,'west_stock_unchanged'=>$after['WEST']===$before['WEST'],
        'same_storage_format'=>$sameFormat && $headers['EAST']['ods']==='13.1','sql_handles_closed'=>$db===null];
    $observations=['logical_routes'=>array_keys($routes),'before'=>$before,'after'=>$after,'database_headers'=>$headers,
        'requested_branch'=>$requestedBranch,'database_branch_marker'=>$actualBranch,
        'same_id_in_two_independent_databases'=>true,'path_taken_only_from_allowlist'=>true,
        'cross_database_transaction_claimed'=>false,'raw_database_paths_published'=>false];

} finally {
    if(is_resource($db)){f163Close($db);}
    if(is_resource($service)){f163Detach($service);}
    $removed=f163Cleanup($owned);
}

f163Report('fb163-database-routing','Выдать товар из нужного филиала',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Выбран филиал — EAST
ID товара в обеих базах — 1
Товар — Cable USB
EAST до выдачи — 7
WEST до выдачи — 2
Выдано из EAST — 2
EAST после выдачи — 5
WEST после выдачи — 2
Формат ODS обеих баз — 13.1
Одинаковый формат означает одну базу — нет

Самостоятельный CLI-файл: настоящие service-алиасы, собственная база/базы из приложенного шаблона, осмысленные SQL-результаты, result.html/result.json и очистка своего окружения.

Фактический HTML-результат · Данные JSON.

Границы и типичные ошибки

Карта не заменяет авторизацию пользователя на конкретный филиал; здесь показаны два синтетических разрешённых маршрута. Не принимайте путь к файлу прямо из HTTP-параметра. Мы изменяем одну базу: программа не обещает атомарного перевода между двумя файлами. Таблица BRANCH_INFO — учебная метка принадлежности, а не криптографическое доказательство. Модель одинакового ID в разных базах отличается от двух строк филиалов внутри одной реляционной базы.

Измените условие

Пользователь запросил WEST, но в routes по ошибке стоит файл EAST. Как остановить выдачу до изменения остатков?

Ожидаемое решение

После открытия сравните BRANCH_INFO с запрошенным кодом и при несовпадении завершите операцию без UPDATE. Проверка только версии сервера, ODS или существования ID=1 эту ошибку не выявит.

Восстановить диагностику после неверного маршрута

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

Путь данных и действий
  1. Заранее отсутствующее имя → db_info отказывает
  2. Сохранить false, код, warning и наличие сообщения сразу
  3. Нативная ветка уже инвалидировала service → убрать ссылку
  4. Новый service_attach → версия отвечает
  5. Разрешённый main-catalog → заголовок и SELECT Fuse 10A/9; исходная ошибка остаётся в отчёте

Сначала один вызов

$report = fbird_db_info($service, $configuredPath, IBASE_STS_HDR_PAGES);
if ($report === false) {
    $code = fbird_errcode();
    $message = fbird_errmsg();
    // Сохранить причину до следующего вызова драйвера.
}

В полном файле f163Capture дополнительно сохраняет ожидаемое предупреждение PHP. Затем выбирается уже известный правильный маршрут, а не случайная перестановка параметров и не автоматические повторные попытки. Для этой native-ветки 3.0.1 убирается ссылка на уже инвалидированный ресурс без второго detach. Новый успешный ответ не стирает сохранённый массив initial_failure.

Видимый результат

НаблюдениеРезультат
Отказ первой операции сохранёнда
Код первоначального отказа-901
Предупреждение и сообщение сохраненыда
Доступность нового service handleда
Исправленный маршрутmain-catalog
Товар после исправленияFuse 10A
Остаток после исправления9
Первоначальная причинаОшибочное имя отсутствующего учебного файла
Пустая база вместо отсутствующей создананет
Новый сервис освобождёнда
Восстановить диагностику после неверного маршрутаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

    function reportText163(&$service,string $database,int $action,int $argument=0): string {
        $call=f163Capture(static fn()=>fbird_db_info($service,$database,$action,$argument));
        if(!is_string($call['value']) || $call['warning'] || $call['code']!==0) {
            if($call['value']===false) {$service=null;}
            f163Failure('Статистический текст не получен',$call);
        }
        return $call['value'];
    }
    function reportField163(string $report,string $pattern): string {
        if(preg_match($pattern,$report,$match)!==1) {
            throw new RuntimeException('Ожидаемое поле отсутствует в служебном отчёте.');
        }
        return $match[1];
    }
    function header163(&$service,string $database): array {
        $report=reportText163($service,$database,IBASE_STS_HDR_PAGES);
        return ['page_size'=>(int)reportField163($report,'/\bPage size\s+(\d+)\b/'),
            'ods'=>reportField163($report,'/\bODS version\s+(\d+\.\d+)\b/'),
            'dialect'=>(int)reportField163($report,'/\bDatabase dialect\s+(\d+)\b/')];
    }

    $database=f163CreateDatabase($service,$owned,'recovery');
    $db=f163Connect($database);
    f163Command($db,'UPDATE LAB_ITEMS SET TITLE=?,QTY=? WHERE ID=?',['Fuse 10A',9,1]);f163Commit($db);f163Close($db);
    $routes=['main-catalog'=>$database];
    $missing='fb163_missing_0000000000.fdb';$missingPath=f163Path($missing);
    clearstatcache(true,$missingPath);
    if(file_exists($missingPath)) {throw new RuntimeException('Отсутствующий учебный путь уже существует.');}
    $failed=f163Capture(static fn()=>fbird_db_info($service,$missing,IBASE_STS_HDR_PAGES));
    $failure=['returned_false'=>$failed['value']===false,'code'=>$failed['code'],
        'warning'=>$failed['warning'],'message_present'=>$failed['message_present']];
    // This native error path in interbase3.0.1 already invalidates the service resource.
    // Keep the diagnostics; do not call detach a second time on that handle.
    $service=null;
    if(!$failure['returned_false']) {throw new RuntimeException('Ожидаемый отказ пути не получен.');}
    $service=f163Service();
    $version=f163Capture(static fn()=>fbird_server_info($service,IBASE_SVC_SERVER_VERSION));
    if(!is_string($version['value']) || $version['warning']) {
        if($version['value']===false) {$service=null;}
        f163Failure('Новый сервис не ответил',$version);
    }
    $header=header163($service,$routes['main-catalog']);
    $db=f163Connect($routes['main-catalog']);
    $item=f163Read($db,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE ID=?',[1])[0];
    f163Close($db);$closed=f163Detach($service);
    clearstatcache(true,$missingPath);$missingStillAbsent=!file_exists($missingPath);
    $rows=[['Отказ первой операции сохранён',$failure['returned_false']],['Код первоначального отказа',$failure['code']],
        ['Предупреждение и сообщение сохранены',$failure['warning'] && $failure['message_present']],
        ['Доступность нового service handle',is_string($version['value'])],['Исправленный маршрут','main-catalog'],
        ['Товар после исправления',$item['title']],['Остаток после исправления',$item['qty']],
        ['Первоначальная причина','Ошибочное имя отсутствующего учебного файла'],['Пустая база вместо отсутствующей создана',!$missingStillAbsent],
        ['Новый сервис освобождён',$closed && $service===null]];
    $checks=['initial_task_rejected'=>$failure['returned_false'],
        'diagnostics_preserved'=>$failure['code']===-901 && $failure['warning'] && $failure['message_present'],
        'fresh_service_available'=>str_contains($version['value'],'V5.0.4.'),
        'correct_header_received'=>$header['ods']==='13.1',
        'business_read_recovered'=>$item===['id'=>1,'title'=>'Fuse 10A','qty'=>9],
        'wrong_path_not_created'=>$missingStillAbsent,'all_owned_handles_closed'=>$db===null && $service===null && $closed===true];
    $observations=['initial_failure'=>$failure,'known_fixture_cause'=>'Отсутствие файла проверено до вызова.',
        'recovery'=>['fresh_service_version'=>$version['value'],'logical_route'=>'main-catalog','header'=>$header,'item'=>$item],
        'initial_diagnostics_not_overwritten'=>true,'automatic_retry_of_bad_path'=>false,
        'changed_only_the_confirmed_route'=>true,'server_restart_performed'=>false,'raw_error_message_published'=>false];

} finally {
    if(is_resource($db)){f163Close($db);}
    if(is_resource($service)){f163Detach($service);}
    $removed=f163Cleanup($owned);
}

f163Report('fb163-service-recovery','Восстановить диагностику после неверного маршрута',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Отказ первой операции сохранён — да
Код первоначального отказа — -901
Предупреждение и сообщение сохранены — да
Доступность нового service handle — да
Исправленный маршрут — main-catalog
Товар после исправления — Fuse 10A
Остаток после исправления — 9
Первоначальная причина — Ошибочное имя отсутствующего учебного файла
Пустая база вместо отсутствующей создана — нет
Новый сервис освобождён — да

Самостоятельный CLI-файл: настоящие service-алиасы, собственная база/базы из приложенного шаблона, осмысленные SQL-результаты, result.html/result.json и очистка своего окружения.

Фактический HTML-результат · Данные JSON.

Границы и типичные ошибки

Число -901 само по себе не переводится в «файла нет» для любой ситуации. Здесь причина известна, потому что путь заранее проверен как отсутствующий; в реальной заявке нужен частный текст ошибки, серверный путь и права процесса. Успех нового service handle доказывает доступность в этой попытке, а успешный SELECT — чтение конкретного товара. Это не тест аварийного перезапуска сервера. Не вызывайте detach повторно на ресурсе, уже потреблённом нативной ошибкой; все false нельзя без разбора объявлять одинаковой веткой.

PHP-приложение использует подключённый сетевой диск как адрес Firebird и получает ошибку поиска хоста, хотя сервер запущен. Исходный вопрос Stack Overflow. Объясняем, кто интерпретирует host и путь: сервис выбранного экземпляра, а не браузер и не произвольный диск клиента. В собственной лаборатории намеренно неверное имя заменяется известным разрешённым маршрутом; права Everyone, отключение защиты и повторные слепые попытки не выдаются за решение.

Измените условие

После ошибки новый вызов server_info вернул версию. Нужно ли заменить сохранённый код -901 на 0 в отчёте первой операции?

Ожидаемое решение

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

Карточки четырёх служебных функций

ЗадачаВызов
Открыть служебное соединение с выбранным экземпляром.fbird_service_attach
Получить нужные сведения отвечающего экземпляра.fbird_server_info
Получить статистику конкретного разрешённого файла базы.fbird_db_info
Освободить принадлежащее программе сервисное соединение.fbird_service_detach

От установленной причины к следующему действию

После диагностики важно менять только то, что связано с установленной причиной. Отчёт по месту хранения не является поводом автоматически выполнять ремонт или заменять базу. Следующий раздел — обслуживание, резервная копия и проверяемое восстановление через fbird_*. Для SQL-производительности отдельно нужны подходящие запросы, индексы и анализ плана; карта маршрутов и служебная статистика этого не заменяют.

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

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

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

fbird_service_attachОткрыть служебное соединение с выбранным экземпляром.

Исторические PHP 5 / PHP 7 < 7.4; отдельно interbase 3.0.1 · interbase

fbird_service_attach(string $host, string $dba_username, string $dba_password): resource|false

Для служебной проверки сначала нужно найти нужный экземпляр Firebird. service_attach создаёт канал к менеджеру сервисов; SQL-подключение к определённой базе создаётся отдельно. ibase_service_attach — каноническое имя той же функции. В interbase 3.0.1 fbird_service_attach зарегистрирована как alias того же обработчика; переименование не меняет сервер, права, формат файлов или скорость SQL.

PHP Manual отмечает исторические PHP 5 / PHP 7 до 7.4. Старое встроенное расширение убрали из поставки PHP 7.4; здесь используется отдельно установленный interbase 3.0.1 с PHP 8.2.35 и Firebird 5.0.4. Этот профиль указан для воспроизведения, а не как заявление о самой новой версии. Параметры приводятся позиционно; новые возможности другого клиента нельзя добавлять к старому API по аналогии.

Аргумент / результатЗначение
host: stringИмя или IP экземпляра. Необязательный порт указывают через /: например db-host/3050; без порта — стандартный 3050. Имя .fdb сюда не входит. Драйвер сам добавляет :service_mgr.
dba_username: stringИмя допустимого пользователя. Несмотря на историческое название аргумента, это не обязательно SYSDBA: права зависят от операции и конфигурации сервера.
dba_password: stringПароль пользователя из конфигурации приложения. В коде главы не публикуется действующий пароль и не создаётся открытая HTTP-консоль администратора.
resource | falseУспех — service handle для server_info/db_info и других служебных задач. При отказе — false и диагностика. Это не ресурс SQL, не идентификатор файла и не результат SELECT. Неверный тип аргумента может дать ошибку PHP.
Отказ и ограниченияОшибочный адрес, аутентификация, недоступный менеджер или ограничения драйвера требуют немедленного сохранения причины. Не вызывайте следующую функцию с false. У этой сигнатуры нет аргумента роли, имени базы или тайм-аута подключения.
Локальный профиль уроковf163Service вызывает настоящий fbird_service_attach с пустым host в частном embedded-профиле README. Такой запуск не доказывает открытый TCP-порт или проверку сетевого пароля. Для удалённого экземпляра нужна его собственная конфигурация.

Начать заявку поддержки

$service = fbird_service_attach($host, $user, $password);
if ($service === false) {
    $code = fbird_errcode();
    $message = fbird_errmsg();
    throw new RuntimeException("Менеджер сервисов недоступен");
}
// Теперь получить версию, затем проверить выбранную базу.

В отчёте SUPPORT-163 это первый этап. Самостоятельный файл берет локальные реквизиты из окружения, а результат содержит безопасные сведения о движке и товарах. Успешный attach ещё не подтверждает наличие Fuse 10A или правильный путь каталога.

Один экземпляр — два разрешённых файла

$service = fbird_service_attach($host, $user, $password);
if ($service === false) { throw new RuntimeException("Сервис недоступен"); }
$eastPath = $routes["EAST"];
$westPath = $routes["WEST"];
// Один service; отдельный db_info для каждого выбранного пути.

В маршрутах EAST/WEST не требуется придумывать второй сервер только потому, что базы разные. После выбора файла обычный SQL дополнительно проверяет BRANCH_INFO. Несколько задач на одном service выполняются последовательно; одно соединение не является универсальной очередью параллельных административных команд.

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

fbird_server_infoПолучить нужные сведения отвечающего экземпляра.

Исторические PHP 5 / PHP 7 < 7.4; отдельно interbase 3.0.1 · interbase

fbird_server_info(resource $service_handle, int $action): string; фактически string|array|false по action и ответу

Отчёту нужны сведения о том экземпляре, который действительно ответил. Выберите конкретное поле: версию, платформу, служебный путь или структурированные сведения о подключениях и пользователях. ibase_server_info — каноническое имя той же функции. В interbase 3.0.1 fbird_server_info зарегистрирована как alias того же обработчика; переименование не меняет сервер, права, формат файлов или скорость SQL.

PHP Manual отмечает исторические PHP 5 / PHP 7 до 7.4. Старое встроенное расширение убрали из поставки PHP 7.4; здесь используется отдельно установленный interbase 3.0.1 с PHP 8.2.35 и Firebird 5.0.4. Этот профиль указан для воспроизведения, а не как заявление о самой новой версии. Параметры приводятся позиционно; новые возможности другого клиента нельзя добавлять к старому API по аналогии.

Параметр / результатКонтракт
service_handle: resourceРесурс менеджера из service_attach, не $db обычного SQL-подключения.
action: intОдна информационная константа. Здесь не соединяют несколько IBASE_SVC_* оператором |: нужны отдельные запросы и отдельная проверка каждого результата.
Тип ответаИсторическая сигнатура показывает string, а её описание уточняет mixed по контексту. В 3.0.1 текстовые поля возвращают строки, структурированные сведения — массивы; отказ может дать false. Проверяйте ожидаемый тип, а не только истинность.
ДиагностикаНемедленно сохраняйте предупреждение, код и сообщение. Нативные ветки ошибки в 3.0.1 инвалидируют сервисный ресурс; неизвестный формат ответа и отказ нативного запроса не следует без разбора считать одной причиной. Отдельный пример показывает подтверждённую ветку и новый attach.
Все информационные константы этой группыЧто возвращает / где применять
IBASE_SVC_SERVER_VERSIONСтрока версии экземпляра, например WI-V5.0.4.1812 Firebird 5.0 в данной лаборатории. Не версия PHP-расширения и не ODS файла.
IBASE_SVC_IMPLEMENTATIONСтрока платформы/реализации экземпляра. Помогает сопоставить окружение заявки, но не доказывает совместимость произвольного старого файла.
IBASE_SVC_GET_ENVСтрока каталога установки Firebird на стороне экземпляра. Не путь сайта или текущий каталог PHP.
IBASE_SVC_GET_ENV_LOCKСтрока расположения служебных lock-файлов. Не признак отсутствия конфликтов пользовательских транзакций.
IBASE_SVC_GET_ENV_MSGСтрока каталога сообщений/служебных библиотечных ресурсов, возвращаемая экземпляром. Для локальной диагностики окружения, не для публичного вывода.
IBASE_SVC_USER_DBPATHСтрока пути к базе безопасности, насколько эту информацию предоставляет используемый сервер. Не каталог прикладных пользователей сайта.
IBASE_SVC_SVR_DB_INFOМассив с attachments, databases и числовыми элементами имён баз. Область сведений зависит от архитектуры экземпляра/процесса; это не поиск всех .fdb на компьютере и не список товарных строк.
IBASE_SVC_GET_USERSМассив доступных сведений о серверных пользователях; обработчик разбирает user_name, first_name, middle_name, last_name, user_id, group_id. Наличие полей зависит от ответа. Не выгружайте этот список посетителям.

Разделить три версии в заявке

$engine = fbird_server_info($service, IBASE_SVC_SERVER_VERSION);
if (!is_string($engine)) { throw new RuntimeException("Версия движка не получена"); }
$phpDriver = phpversion("interbase");
// ODS выбранного файла получается отдельно через db_info.

В заявке поддержки версии не перемешиваются: движок отвечает сам, PHP сообщает версию расширения, а заголовок базы — ODS. Это полезнее одной надписи «Firebird установлен», когда на машине несколько компонентов.

Проверить доступность после отказа пути

$service = fbird_service_attach($host, $user, $password);
if ($service === false) { throw new RuntimeException("Новый сервис не подключён"); }
$engine = fbird_server_info($service, IBASE_SVC_SERVER_VERSION);
if (!is_string($engine)) { throw new RuntimeException("Новый экземпляр не ответил"); }
// Сохранить этот успех отдельно от первоначальной ошибки пути.

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

Остальные действия перечислены для полноты контракта; программы этой главы реально используют версию и реализацию, а не проверяют все режимы списка пользователей и соединений. В Classic счётчик одного сервисного процесса не обязан совпадать с MON$ATTACHMENTS выбранной базы. Данные о пользователях и внутренних путях нужны защищённой административной задаче. Для выбранного файла переходите к fbird_db_info, для серверных учётных записей — к администрированию пользователей.

fbird_db_infoПолучить статистику конкретного разрешённого файла базы.

Исторические PHP 5 / PHP 7 < 7.4; отдельно interbase 3.0.1 · interbase

fbird_db_info(resource $service_handle, string $db, int $action, int $argument = 0): string; драйвер также возвращает true/false в отдельных ветках

Чтобы разобрать формат файла или объём хранения, сервису передают отдельный путь базы и вид статистики. db_info возвращает служебный текст; строки товаров получают SELECT-запросом. ibase_db_info — каноническое имя той же функции. В interbase 3.0.1 fbird_db_info зарегистрирована как alias того же обработчика; переименование не меняет сервер, права, формат файлов или скорость SQL.

PHP Manual отмечает исторические PHP 5 / PHP 7 до 7.4. Старое встроенное расширение убрали из поставки PHP 7.4; здесь используется отдельно установленный interbase 3.0.1 с PHP 8.2.35 и Firebird 5.0.4. Этот профиль указан для воспроизведения, а не как заявление о самой новой версии. Параметры приводятся позиционно; новые возможности другого клиента нельзя добавлять к старому API по аналогии.

Аргумент / результатКонтракт
service_handle: resourceОткрытое сервисное соединение. Оно не выбирает прикладную базу при attach и не подменяет SQL-подключение.
db: stringПуть или поддерживаемое сервером имя базы с точки зрения выбранного экземпляра. Путь на компьютере браузера либо сетевой диск PHP-клиента не становится локальным серверным путём автоматически.
action: intОдно из пяти распознаваемых значений IBASE_STS_* ниже. В 3.0.1 сначала проверяется именно отдельное значение action.
argument: int = 0Дополнительные параметры статистики. В этой реализации выбранная action объединяется с argument через |; DATA_PAGES третьим и IDX_PAGES четвёртым аргументами дают оба вида статистики.
Возврат и документацияКаноническая страница PHP Manual пока содержит только аргументы и string, без полного описания. Исходник 3.0.1 запускает задачу и читает строки отчёта: возвращает текст, true при завершении без строк, false при отказе. Наш парсер требует именно текст и известные поля.
ОшибкиНераспознанная action, недоступный файл, неподходящий формат, права или отказ сервисной операции. Диагностика сохраняется сразу. Ошибка файла не доказывает остановку сервера; путь/сообщение могут быть частными данными.
Полный набор IBASE_STS_* в выбранном драйвереНазначение
IBASE_STS_HDR_PAGESЗаголовок: страница хранения, ODS, диалект и служебные сведения выбранного файла.
IBASE_STS_DATA_PAGESСтатистика страниц прикладных таблиц; не количество строк отфильтрованного SELECT.
IBASE_STS_IDX_PAGESСтатистика страниц/структуры индексов. Присутствие индекса не доказывает его использование запросом.
IBASE_STS_SYS_RELATIONSВключение системных отношений. Для отчёта по LAB_ITEMS не требуется.
IBASE_STS_DB_LOGИсторическая опция страниц журнала InterBase; не чтение firebird.log. Наличие константы в расширении не означает поддержки старой операции современным Firebird; её успешное выполнение здесь не заявлено.

Проверить формат разрешённого маршрута

$header = fbird_db_info($service, $routes["EAST"], IBASE_STS_HDR_PAGES);
if (!is_string($header)) { throw new RuntimeException("Заголовок не получен"); }
// Разобрать ODS/диалект; затем отдельно проверить метку филиала SQL-запросом.

В двух филиалах оба заголовка имеют одинаковый ODS. Поэтому программа не выбирает склад по номеру формата: она использует карту маршрутов и проверяет BRANCH_INFO перед выдачей.

Собрать физические показатели рядом с выборкой

$stats = fbird_db_info($service, $database, IBASE_STS_DATA_PAGES, IBASE_STS_IDX_PAGES);
if (!is_string($stats)) { throw new RuntimeException("Статистический текст не получен"); }
// Отдельно: SELECT нужных полей WHERE ... ORDER BY ... ROWS 3.

В оценке хранения PHP получает три товарные строки и отдельный текст, из которого выбираются LAB_ITEMS и IX_FB163_QTY. Реальные числа страниц и узлов сохраняются в JSON/раскрываемом HTML; ожидаемые значения заранее не выдумываются.

Сбор подробной статистики читает структуру базы и может быть дорогой операцией; не запускайте его при каждом открытии карточки товара. Служебный отчёт не является транзакционным снимком, планом запроса или проверкой всей целостности. Подписи gstat и варианты вывода зависят от версии; при несовпадении формата парсер должен остановиться, а не подставить нули. Встроенный SPB-буфер старого драйвера ограничен: длинные/неподходящие параметры могут дать собственный отказ ещё до сервера. Полный отчёт очищается от частных путей, а основные поля выбираются явно.

Ошибочное имя разбирает восстановление диагностики. В известной нативной ветке interbase 3.0.1 service уже инвалидирован: сохраните причину, уберите свою ссылку, создайте новый service. Это не разрешение автоматически повторять разрушительные команды или объявлять любое false одинаковым отказом. Административные изменения и проверка копии вынесены в связанный раздел.

fbird_service_detachОсвободить принадлежащее программе сервисное соединение.

Исторические PHP 5 / PHP 7 < 7.4; отдельно interbase 3.0.1 · interbase

fbird_service_detach(resource $service_handle): bool

Когда служебная работа закончена, её ресурс нужно освободить. Это завершение подключения к менеджеру; оно не удаляет базу и не подтверждает SQL-операцию. ibase_service_detach — каноническое имя той же функции. В interbase 3.0.1 fbird_service_detach зарегистрирована как alias того же обработчика; переименование не меняет сервер, права, формат файлов или скорость SQL.

PHP Manual отмечает исторические PHP 5 / PHP 7 до 7.4. Старое встроенное расширение убрали из поставки PHP 7.4; здесь используется отдельно установленный interbase 3.0.1 с PHP 8.2.35 и Firebird 5.0.4. Этот профиль указан для воспроизведения, а не как заявление о самой новой версии. Параметры приводятся позиционно; новые возможности другого клиента нельзя добавлять к старому API по аналогии.

Параметр / результатКонтракт
service_handle: resourceДействующий ресурс из fbird_service_attach или ibase_service_attach. Не SQL-ресурс, не путь и не уже потреблённый ошибкой handle.
boolPHP Manual обещает true при успехе, false при отказе. В выбранном обработчике 3.0.1 после уменьшения ссылки возвращается true; предупреждение деструктора всё равно возможно. Полный пример проверяет значение вместе с немедленной диагностикой.
ВладениеПосле завершения присвойте переменной null, не вызывайте service-функции через неё снова и не создавайте лишние копии ресурса. Сохранённые строки/массивы отчёта остаются обычными данными PHP.
Чего функция не делаетНе завершает SQL commit/rollback, не закрывает независимый $db, не проверяет пригодность backup и не заменяет ожидание окончания административной задачи.
Ошибочное состояниеВ подтверждённой native-ветке service error 3.0.1 уже уменьшает ссылку; второй detach опасен. Этот случай отличается от штатного завершения успешно выполненного отчёта.

Освободить сервис, сохранив отчёт

$closed = fbird_service_detach($service);
$service = null;
if ($closed !== true) { throw new RuntimeException("Сервис не освобождён"); }
// Ранее полученные $version, $header и $items остаются в памяти PHP.

В заявке поддержки после освобождения ресурса формируется HTML/JSON из уже полученных значений. Самостоятельный helper f163Detach вызывает alias прямо, снимает предупреждения и код сразу, не помещая потребляемый ресурс в дополнительную замыкающую обёртку.

Закончить уже исправленную операцию

// До этого: первоначальный ошибочный handle убран без повторного detach.
// Новый service успешно проверил версию и правильную базу.
$closed = fbird_service_detach($service);
$service = null;
if ($closed !== true) { throw new RuntimeException("Новый сервис не освобождён"); }

В восстановлении штатный detach относится только к новому исправному ресурсу. Исходная диагностика хранится отдельно. Порядок объясняет, почему нельзя просто добавить безусловный detach после каждого false.

Для управления ресурсами используйте finally и явное состояние владения, как в полном файле и lab.php. Не считайте один is_resource достаточным доказательством пригодности уже инвалидированного native-handle. Сначала надо знать, какой вызов им владеет и какая ветка уже его завершила. Независимый SQL-ресурс закрывается отдельно после подтверждения или отката транзакции.

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

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

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

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

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