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

Дерево руководства
Глава 78 · После SQL и базовых служебных функций Firebird
Firebird fbird_*: отчёт для поддержки, ёмкость таблицы и правильный маршрут базы
Заявка поддержки с реальными товарами, физическая статистика рядом с ограниченным SELECT, изоляция EAST/WEST и чтение после исправления маршрута.
От сообщения пользователя к конкретному выводу
Старый PHP-код вызывает fbird_*, а поддержке нужно понять, где именно проблема: не отвечает экземпляр, выбран чужой файл или у товара просто нет остатка. В этой главе получатся четыре законченных результата: заявка с предметными данными, экран из трёх SKU рядом со статистикой, правильная выдача из EAST и восстановленное чтение после ошибки маршрута.
- Выбрать понятный маршрут приложения: main-catalog или EAST
- service_attach → экземпляр; server_info → его версия
- db_info с отдельным путём → свойства выбранного файла
- Обычный SQL по этому маршруту → нужные товары
- Сохранить различимые результаты и завершить принадлежащие ресурсы
| Область | Что проверяет | Чего не доказывает |
|---|---|---|
| Экземпляр 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 единиц. Нулевой остаток становится понятным предметным результатом, а не ошибкой связи.
- SUPPORT-163 → разрешённый маршрут main-catalog
- service_attach → server_info: версия и платформа экземпляра
- db_info(HDR_PAGES) → ODS 13.1 и диалект 3
- SELECT двух ID → Cable USB/12 и Fuse 10A/0
- Отдельные выводы о сервисе, файле и данных → закрыть ресурсы
Сначала один вызов
$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 |
| Формат файла ODS | 13.1 |
| SQL-диалект | 3 |
| Проверено товарных строк | 2 |
| Остаток Cable USB | 12 |
| Остаток Fuse 10A | 0 |
| Вывод для поддержки | Сервис отвечает; 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. В раскрываемом отчёте отдельно появятся фактические страницы данных и структура индекса.
- 120 SKU; qty=id%5 → остатки 0,1,2,3,4
- WHERE qty BETWEEN 1 AND 2 → 48 подходящих строк в базе
- ORDER BY qty,id ROWS 3 → 3 строки для экрана
- db_info(DATA_PAGES, IDX_PAGES) → физические показатели
- Сохранить обе группы отдельно; не объявлять статистику планом запроса
Сначала один вызов
$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: их не подставляют заранее.
Видимый результат
| Наблюдение | Результат |
|---|---|
| Всего синтетических SKU | 120 |
| Остаток от 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.
- EAST → собственный файл A; WEST → собственный файл B
- db_info обоих файлов → одинаковые ODS 13.1 и диалект 3
- Запрос EAST → разрешённая карта routes → BRANCH_INFO=EAST
- UPDATE qty=qty-2 WHERE id=1 AND qty>=2 → commit
- Свежие 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 |
| Выдано из EAST | 2 |
| 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. Перезапуск сервера и создание пустого файла для этого не нужны.
- Заранее отсутствующее имя → db_info отказывает
- Сохранить false, код, warning и наличие сообщения сразу
- Нативная ветка уже инвалидировала service → убрать ссылку
- Новый service_attach → версия отвечает
- Разрешённый 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. |
| bool | PHP 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-ресурс закрывается отдельно после подтверждения или отката транзакции.
Источники и обсуждения
- PHP Manual — fbird_service_attach
- PHP Manual — ibase_service_attach
- PHP Manual — fbird_server_info
- PHP Manual — ibase_server_info
- PHP Manual — fbird_db_info
- PHP Manual — ibase_db_info
- PHP Manual — fbird_service_detach
- PHP Manual — ibase_service_detach
- FirebirdSQL 3.0.1 — Services API
- FirebirdSQL 3.0.1 — aliases
- Firebird — gstat и смысл статистики
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


