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

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

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

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

Глава 79 · После SQL-запросов, транзакций и Service Manager Firebird

Firebird fbird_*: восстановить заказы, подготовить стенд и убрать временную копию

Связанные заказы после восстановления, пустой стенд с ключами, проверяемая настройка страниц и временная аналитическая копия.

От задания службы к восстановленным заказам

Резервная копия полезна тогда, когда из неё удаётся получить нужные приложению данные. Здесь читатель восстановит два заказа с покупателями и позициями, создаст отдельный пустой стенд, проверит настройку кэша и завершит эксперимент с ценой удалением только временной копии. В отчётах видны сами заказы, суммы, структура и результат решения приложения.

Почему здесь три разных файла
  1. Исходная FDB: покупатели → заказы → позиции
  2. fbird_backup через Services API → логический архив FBK
  3. fbird_restore CREATE → новая FDB, исходник не заменяется
  4. Новое SQL-подключение → названия, количества, цены и итог
  5. Отчёт остаётся отдельно; одноразовая FDB удаляется по проверке владельца

Покупатель связан с заказами через ID; позиции содержат ORDER_ID и внешний ключ. Внешний ключ проверяет допустимость ссылки, JOIN получает связанные поля, GROUP BY собирает позиции одного заказа для SUM. Число копеек хранится целым, чтобы не вводить дробную двоичную арифметику. Если это первый материал о базах, начните с таблицы и SELECT, параметризованной записи и подтверждения транзакции.

Для сервисной команды нужен ресурс Service Manager; для SELECT и drop_db — соединение с конкретной базой. Пути backup/restore принадлежат серверу Firebird, даже когда PHP находится на другом компьютере. В наших проектах всё выполняется локально в отдельной лаборатории; служебный guard отклоняет чужие имена. Изменения подтверждаются до запуска backup, поскольку его служба читает базу своим подключением.

Имена fbird_* — aliases того же расширения interbase. Профиль этой главы: PHP 8.2.35 NTS x64, interbase 3.0.1, Firebird 5.0.4 embedded SuperClassic. Порядок строковых аргументов restore в этой версии расходится с PHP Manual; ниже это разобрано явно. Архивы самостоятельных программ содержат example.php, lab.php, README и синтетический seed. Временные собственные FDB/FBK после наблюдений удаляются, поэтому результатом лаборатории служит отчёт, а не долгосрочная копия рабочего сайта.

Репетиция восстановления двух заказов со связями и ценами

Перед изменением приложения нужно убедиться, что резервная копия возвращает рабочий экран заказов. У заказа есть покупатель и несколько позиций; открыть один файл или посчитать таблицы недостаточно. Создадим полный backup, восстановим его под новым именем и получим заказ 101 на 41500 копеек и заказ 102 на 12500 копеек с исходными товарами.

Путь данных и результатов
  1. Покупатель 1 «Мастерская» → заказ 101 → Кабель 3 × 12500; Клемма 2 × 2000
  2. Покупатель 2 «Сервис» → заказ 102 → Кабель 1 × 12500
  3. COMMIT → три таблицы доступны отдельному сервисному подключению
  4. fbird_backup → FBK; fbird_restore CREATE → новая FDB
  5. JOIN возвращает названия и позиции; SQL SUM даёт 41500 и 12500
  6. Сравниваем строки, количества и два внешних ключа

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

$backupResult = fbird_backup($service, $sourceDatabase, $newBackupFile, 0, true);
if ($backupResult === false) {
    throw new RuntimeException('Резервная копия не получена');
}
// Следующий шаг: восстановить в НОВУЮ базу и прочитать заказы.

Сначала передаётся ресурс Service Manager, затем имя исходной базы и нового FBK на сервере. Число 0 выбирает обычную полную копию. Последний true в драйвере 3.0.1 включает сбор сервисного вывода. Переменная backupResult содержит результат операции, а не массив покупателей. Полная программа отдельно подтверждает запись, завершает сервисные задания и подключается к новой базе.

Что будет видно после запуска

НаблюдениеРезультат
Заказ 101Мастерская: Кабель 3 × 12500 + Клемма 2 × 2000 = 41500 коп.
Заказ 102Сервис: Кабель 1 × 12500 = 12500 коп.
Состав копии2 покупателя, 2 заказа, 3 позиции
Внешние ключиFK_ITEM_ORDER; FK_ORDER_CUSTOMER
Сравнение с исходникомда
Полная самостоятельная программа и подготовка
Репетиция восстановления двух заказов со связями и ценамиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

function orderSchema163($db): void {
    $statements=[
        'CREATE TABLE LAB_CUSTOMERS(ID INTEGER NOT NULL PRIMARY KEY, CUSTOMER_NAME VARCHAR(40) NOT NULL)',
        'CREATE TABLE LAB_ORDERS(ID INTEGER NOT NULL PRIMARY KEY, CUSTOMER_ID INTEGER NOT NULL, CONSTRAINT FK_ORDER_CUSTOMER FOREIGN KEY(CUSTOMER_ID) REFERENCES LAB_CUSTOMERS(ID))',
        'CREATE TABLE LAB_ORDER_ITEMS(ORDER_ID INTEGER NOT NULL, LINE_NO INTEGER NOT NULL, ITEM_TITLE VARCHAR(40) NOT NULL, QTY INTEGER NOT NULL CHECK(QTY > 0), UNIT_CENTS INTEGER NOT NULL CHECK(UNIT_CENTS >= 0), CONSTRAINT PK_ORDER_ITEMS PRIMARY KEY(ORDER_ID, LINE_NO), CONSTRAINT FK_ITEM_ORDER FOREIGN KEY(ORDER_ID) REFERENCES LAB_ORDERS(ID))',
        'CREATE INDEX IX_ITEM_TITLE ON LAB_ORDER_ITEMS(ITEM_TITLE)',
    ];
    foreach($statements as $sql){f163Command($db,$sql);f163Commit($db);}
}
function orderSeed163($db): void {
    foreach([[1,'Мастерская'],[2,'Сервис']] as $row){
        f163Command($db,'INSERT INTO LAB_CUSTOMERS(ID,CUSTOMER_NAME) VALUES (?,?)',$row);
    }
    foreach([[101,1],[102,2]] as $row){
        f163Command($db,'INSERT INTO LAB_ORDERS(ID,CUSTOMER_ID) VALUES (?,?)',$row);
    }
    foreach([[101,1,'Кабель',3,12500],[101,2,'Клемма',2,2000],[102,1,'Кабель',1,12500]] as $row){
        f163Command($db,'INSERT INTO LAB_ORDER_ITEMS(ORDER_ID,LINE_NO,ITEM_TITLE,QTY,UNIT_CENTS) VALUES (?,?,?,?,?)',$row);
    }
    f163Commit($db);
}
function orderTotals163($db): array {
    return f163Read($db,'SELECT O.ID AS ORDER_ID, C.CUSTOMER_NAME, CAST(SUM(I.QTY * I.UNIT_CENTS) AS BIGINT) AS TOTAL_CENTS FROM LAB_ORDERS O JOIN LAB_CUSTOMERS C ON C.ID=O.CUSTOMER_ID JOIN LAB_ORDER_ITEMS I ON I.ORDER_ID=O.ID GROUP BY O.ID,C.CUSTOMER_NAME ORDER BY O.ID');
}
function orderCounts163($db): array {
    $counts=[];
    foreach(['LAB_CUSTOMERS','LAB_ORDERS','LAB_ORDER_ITEMS'] as $table){
        // Table names come only from this fixed application list, never from input.
        $counts[$table]=(int)f163Read($db,'SELECT COUNT(*) AS N FROM '.$table)[0]['N'];
    }
    return $counts;
}
function orderKeys163($db): array {
    return f163Read($db,"SELECT TRIM(RDB\$CONSTRAINT_NAME) AS KEY_NAME FROM RDB\$RELATION_CONSTRAINTS WHERE RDB\$CONSTRAINT_TYPE='FOREIGN KEY' AND RDB\$RELATION_NAME IN ('LAB_ORDERS','LAB_ORDER_ITEMS') ORDER BY RDB\$CONSTRAINT_NAME");
}

    $source=f163CreateDatabase($service,$owned,'orders');
    $db=f163Connect($source);orderSchema163($db);orderSeed163($db);
    $before=orderTotals163($db);f163Close($db);
    $backup=f163NewBackup('orders');
    f163Backup($service,$source,$backup);
    $copy=f163RestoreDatabase($service,$owned,'restored',$backup);
    $db=f163Connect($copy);
    $totals=orderTotals163($db);$counts=orderCounts163($db);$keys=orderKeys163($db);
    $lines=f163Read($db,'SELECT O.ID AS ORDER_ID,C.CUSTOMER_NAME,I.LINE_NO,I.ITEM_TITLE,I.QTY,I.UNIT_CENTS FROM LAB_ORDERS O JOIN LAB_CUSTOMERS C ON C.ID=O.CUSTOMER_ID JOIN LAB_ORDER_ITEMS I ON I.ORDER_ID=O.ID ORDER BY O.ID,I.LINE_NO');
    $expected=[['ORDER_ID'=>101,'CUSTOMER_NAME'=>'Мастерская','TOTAL_CENTS'=>41500],['ORDER_ID'=>102,'CUSTOMER_NAME'=>'Сервис','TOTAL_CENTS'=>12500]];
    $expectedLines=[
        ['ORDER_ID'=>101,'CUSTOMER_NAME'=>'Мастерская','LINE_NO'=>1,'ITEM_TITLE'=>'Кабель','QTY'=>3,'UNIT_CENTS'=>12500],
        ['ORDER_ID'=>101,'CUSTOMER_NAME'=>'Мастерская','LINE_NO'=>2,'ITEM_TITLE'=>'Клемма','QTY'=>2,'UNIT_CENTS'=>2000],
        ['ORDER_ID'=>102,'CUSTOMER_NAME'=>'Сервис','LINE_NO'=>1,'ITEM_TITLE'=>'Кабель','QTY'=>1,'UNIT_CENTS'=>12500],
    ];
    f163Close($db);
    $rows=[['Заказ 101','Мастерская: Кабель 3 × 12500 + Клемма 2 × 2000 = 41500 коп.'],['Заказ 102','Сервис: Кабель 1 × 12500 = 12500 коп.'],['Состав копии','2 покупателя, 2 заказа, 3 позиции'],['Внешние ключи','FK_ITEM_ORDER; FK_ORDER_CUSTOMER'],['Сравнение с исходником',$totals===$before]];
    $checks=['source_totals'=>$before===$expected,'restored_totals'=>$totals===$expected,'restored_lines'=>$lines===$expectedLines,'counts_match'=>$counts===['LAB_CUSTOMERS'=>2,'LAB_ORDERS'=>2,'LAB_ORDER_ITEMS'=>3],'foreign_keys_present'=>$keys===[['KEY_NAME'=>'FK_ITEM_ORDER'],['KEY_NAME'=>'FK_ORDER_CUSTOMER']]];
    $observations=['source_totals'=>$before,'restored_totals'=>$totals,'lines'=>$lines,'counts'=>$counts,'foreign_keys'=>$keys];

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

f163Report('fb163-restore-drill','Репетиция восстановления двух заказов со связями и ценами',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Заказ 101 — Мастерская: Кабель 3 × 12500 + Клемма 2 × 2000 = 41500 коп.
Заказ 102 — Сервис: Кабель 1 × 12500 = 12500 коп.
Состав копии — 2 покупателя, 2 заказа, 3 позиции
Внешние ключи — FK_ITEM_ORDER; FK_ORDER_CUSTOMER
Сравнение с исходником — да

Архив содержит полную подготовку своих синтетических данных и README с отдельным окружением. Выполните example.php: получится result.html, result.json и metrics.json. Создаются 2 собственные базы. lab.php проверяет реестр, пути и маркер владельца перед удалением; только собственные FDB и промежуточные FBK убираются после наблюдений. Сырые сервисные журналы с путями в публичный результат не печатаются.

Открыть результат программы · Открыть данные результата JSON.

Границы и ошибки

Внешний ключ хранит правило «позиция относится к существующему заказу», а JOIN соединяет нужные данные для экрана. Проверяем оба свойства: одна удачная выборка не доказывает наличие ограничения. SUM выполняется в SQL с группировкой по заказу: мы не загружаем всю рабочую базу для подсчёта в PHP. CAST(... AS BIGINT) ограничивает тип результата для interbase 3.0.1: Firebird 5 может расширять сумму целых до INT128, которого этот драйвер не понимает. Цены и итог данного набора заведомо помещаются в BIGINT; для произвольных больших сумм это ограничение надо обосновать отдельно. Это репетиция восстановления своих семи строк, а не проверка всех правил произвольного приложения.

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

Попробуйте изменить условие

Достаточно ли после восстановления получить общий итог 54000 копеек, если принадлежность заказа покупателю могла измениться?

Разбор решения

Нет. Одна сумма допускает разные строки и связи. Здесь отдельно сверяются покупатель, номер заказа, товар, количество, цена, два итога и определения внешних ключей. Для своего приложения составьте такой же список значимых данных до проверки копии.

Создать пустой стенд с прежними ключами и заполнить тестовым заказом

Для нового теста нужна знакомая структура без старых покупателей и заказов. Создадим metadata-only FBK, восстановим пустые таблицы, проверим оба внешних ключа и активный индекс по названию позиции. Затем добавим собственный заказ 901: два тестовых комплекта по 1800 копеек. Это демонстрирует готовую к записи схему, а не просто пустой экран.

Путь данных и результатов
  1. Исходник: 2 покупателя, 2 заказа, 3 позиции
  2. METADATA_ONLY → определения таблиц, ключей и индексов без прикладных строк
  3. Новая база → COUNT даёт 0 / 0 / 0, ключи и IX_ITEM_TITLE сохранены
  4. INSERT покупателя 9 → заказа 901 → одной позиции → COMMIT
  5. JOIN + SUM → Учебный стенд, заказ 901, 3600 копеек
  6. В исходнике по-прежнему заказы 101 и 102

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

$schemaResult = fbird_backup($service, $sourceDatabase, $newSchemaBackup,
    IBASE_BKP_METADATA_ONLY, true);
if ($schemaResult === false) {
    throw new RuntimeException('Шаблон схемы не получен');
}

Метаданные описывают устройство базы: имена столбцов, типы, ключи, индексы и другие объекты. IBASE_BKP_METADATA_ONLY исключает строки из копии, но не удаляет исходные данные. При заполнении восстановленной схемы порядок важен: сначала родительский покупатель, затем заказ с его ID и только потом позиция с ID заказа.

Что будет видно после запуска

НаблюдениеРезультат
Перед наполнениемПокупатели: 0; заказы: 0; позиции: 0
Схема связейFK_ITEM_ORDER; FK_ORDER_CUSTOMER
ИндексIX_ITEM_TITLE → ITEM_TITLE; активен
Новый тестовый заказ901: Учебный стенд — 2 × 1800 = 3600 коп.
Исходные заказы101: 41500 коп.; 102: 12500 коп.
Полная самостоятельная программа и подготовка
Создать пустой стенд с прежними ключами и заполнить тестовым заказомСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

function orderSchema163($db): void {
    $statements=[
        'CREATE TABLE LAB_CUSTOMERS(ID INTEGER NOT NULL PRIMARY KEY, CUSTOMER_NAME VARCHAR(40) NOT NULL)',
        'CREATE TABLE LAB_ORDERS(ID INTEGER NOT NULL PRIMARY KEY, CUSTOMER_ID INTEGER NOT NULL, CONSTRAINT FK_ORDER_CUSTOMER FOREIGN KEY(CUSTOMER_ID) REFERENCES LAB_CUSTOMERS(ID))',
        'CREATE TABLE LAB_ORDER_ITEMS(ORDER_ID INTEGER NOT NULL, LINE_NO INTEGER NOT NULL, ITEM_TITLE VARCHAR(40) NOT NULL, QTY INTEGER NOT NULL CHECK(QTY > 0), UNIT_CENTS INTEGER NOT NULL CHECK(UNIT_CENTS >= 0), CONSTRAINT PK_ORDER_ITEMS PRIMARY KEY(ORDER_ID, LINE_NO), CONSTRAINT FK_ITEM_ORDER FOREIGN KEY(ORDER_ID) REFERENCES LAB_ORDERS(ID))',
        'CREATE INDEX IX_ITEM_TITLE ON LAB_ORDER_ITEMS(ITEM_TITLE)',
    ];
    foreach($statements as $sql){f163Command($db,$sql);f163Commit($db);}
}
function orderSeed163($db): void {
    foreach([[1,'Мастерская'],[2,'Сервис']] as $row){
        f163Command($db,'INSERT INTO LAB_CUSTOMERS(ID,CUSTOMER_NAME) VALUES (?,?)',$row);
    }
    foreach([[101,1],[102,2]] as $row){
        f163Command($db,'INSERT INTO LAB_ORDERS(ID,CUSTOMER_ID) VALUES (?,?)',$row);
    }
    foreach([[101,1,'Кабель',3,12500],[101,2,'Клемма',2,2000],[102,1,'Кабель',1,12500]] as $row){
        f163Command($db,'INSERT INTO LAB_ORDER_ITEMS(ORDER_ID,LINE_NO,ITEM_TITLE,QTY,UNIT_CENTS) VALUES (?,?,?,?,?)',$row);
    }
    f163Commit($db);
}
function orderTotals163($db): array {
    return f163Read($db,'SELECT O.ID AS ORDER_ID, C.CUSTOMER_NAME, CAST(SUM(I.QTY * I.UNIT_CENTS) AS BIGINT) AS TOTAL_CENTS FROM LAB_ORDERS O JOIN LAB_CUSTOMERS C ON C.ID=O.CUSTOMER_ID JOIN LAB_ORDER_ITEMS I ON I.ORDER_ID=O.ID GROUP BY O.ID,C.CUSTOMER_NAME ORDER BY O.ID');
}
function orderCounts163($db): array {
    $counts=[];
    foreach(['LAB_CUSTOMERS','LAB_ORDERS','LAB_ORDER_ITEMS'] as $table){
        // Table names come only from this fixed application list, never from input.
        $counts[$table]=(int)f163Read($db,'SELECT COUNT(*) AS N FROM '.$table)[0]['N'];
    }
    return $counts;
}
function orderKeys163($db): array {
    return f163Read($db,"SELECT TRIM(RDB\$CONSTRAINT_NAME) AS KEY_NAME FROM RDB\$RELATION_CONSTRAINTS WHERE RDB\$CONSTRAINT_TYPE='FOREIGN KEY' AND RDB\$RELATION_NAME IN ('LAB_ORDERS','LAB_ORDER_ITEMS') ORDER BY RDB\$CONSTRAINT_NAME");
}

    $source=f163CreateDatabase($service,$owned,'schema');
    $db=f163Connect($source);orderSchema163($db);orderSeed163($db);f163Close($db);
    $backup=f163NewBackup('schema');
    f163Backup($service,$source,$backup,IBASE_BKP_METADATA_ONLY);
    $sandbox=f163RestoreDatabase($service,$owned,'sandbox',$backup);
    $db=f163Connect($sandbox);
    $empty=orderCounts163($db);$keys=orderKeys163($db);
    $index=f163Read($db,"SELECT TRIM(I.RDB\$INDEX_NAME) AS INDEX_NAME,TRIM(S.RDB\$FIELD_NAME) AS FIELD_NAME,COALESCE(I.RDB\$INDEX_INACTIVE,0) AS INACTIVE FROM RDB\$INDICES I JOIN RDB\$INDEX_SEGMENTS S ON S.RDB\$INDEX_NAME=I.RDB\$INDEX_NAME WHERE I.RDB\$INDEX_NAME='IX_ITEM_TITLE' ORDER BY S.RDB\$FIELD_POSITION");
    f163Command($db,'INSERT INTO LAB_CUSTOMERS(ID,CUSTOMER_NAME) VALUES (?,?)',[9,'Учебный стенд']);
    f163Command($db,'INSERT INTO LAB_ORDERS(ID,CUSTOMER_ID) VALUES (?,?)',[901,9]);
    f163Command($db,'INSERT INTO LAB_ORDER_ITEMS(ORDER_ID,LINE_NO,ITEM_TITLE,QTY,UNIT_CENTS) VALUES (?,?,?,?,?)',[901,1,'Тестовый комплект',2,1800]);
    f163Commit($db);
    $newOrder=orderTotals163($db);$after=orderCounts163($db);f163Close($db);
    $db=f163Connect($source);$sourceOrders=orderTotals163($db);f163Close($db);
    $rows=[['Перед наполнением','Покупатели: 0; заказы: 0; позиции: 0'],['Схема связей','FK_ITEM_ORDER; FK_ORDER_CUSTOMER'],['Индекс','IX_ITEM_TITLE → ITEM_TITLE; активен'],['Новый тестовый заказ','901: Учебный стенд — 2 × 1800 = 3600 коп.'],['Исходные заказы','101: 41500 коп.; 102: 12500 коп.']];
    $checks=['empty_application_tables'=>$empty===['LAB_CUSTOMERS'=>0,'LAB_ORDERS'=>0,'LAB_ORDER_ITEMS'=>0],'keys_preserved'=>$keys===[['KEY_NAME'=>'FK_ITEM_ORDER'],['KEY_NAME'=>'FK_ORDER_CUSTOMER']],'index_preserved'=>$index===[['INDEX_NAME'=>'IX_ITEM_TITLE','FIELD_NAME'=>'ITEM_TITLE','INACTIVE'=>0]],'fixture_created'=>$newOrder===[['ORDER_ID'=>901,'CUSTOMER_NAME'=>'Учебный стенд','TOTAL_CENTS'=>3600]],'only_new_rows'=>$after===['LAB_CUSTOMERS'=>1,'LAB_ORDERS'=>1,'LAB_ORDER_ITEMS'=>1],'source_unchanged'=>$sourceOrders===[['ORDER_ID'=>101,'CUSTOMER_NAME'=>'Мастерская','TOTAL_CENTS'=>41500],['ORDER_ID'=>102,'CUSTOMER_NAME'=>'Сервис','TOTAL_CENTS'=>12500]]];
    $observations=['empty_counts'=>$empty,'foreign_keys'=>$keys,'index'=>$index,'new_order'=>$newOrder,'new_counts'=>$after,'source_orders'=>$sourceOrders];

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

f163Report('fb163-metadata-sandbox','Создать пустой стенд с прежними ключами и заполнить тестовым заказом',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Перед наполнением — Покупатели: 0; заказы: 0; позиции: 0
Схема связей — FK_ITEM_ORDER; FK_ORDER_CUSTOMER
Индекс — IX_ITEM_TITLE → ITEM_TITLE; активен
Новый тестовый заказ — 901: Учебный стенд — 2 × 1800 = 3600 коп.
Исходные заказы — 101: 41500 коп.; 102: 12500 коп.

Архив содержит полную подготовку своих синтетических данных и README с отдельным окружением. Выполните example.php: получится result.html, result.json и metrics.json. Создаются 2 собственные базы. lab.php проверяет реестр, пути и маркер владельца перед удалением; только собственные FDB и промежуточные FBK убираются после наблюдений. Сырые сервисные журналы с путями в публичный результат не печатаются.

Открыть результат программы · Открыть данные результата JSON.

Границы и ошибки

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

Попробуйте изменить условие

Почему в новой базе покупатель создаётся раньше заказа, хотя обе записи будут подтверждены одним commit?

Разбор решения

INSERT заказа проверяет внешний ключ при выполнении команды. Ссылка должна указывать на уже существующую строку, в том числе на строку своей текущей транзакции. Обещание добавить покупателя позднее не заменяет эту строку. Вставьте родителя, затем зависимую запись и подтвердите всю выбранную работу.

Проверить и вернуть настройку кэша страниц собственной базы

В скрипте обслуживания встретился fbird_maintain_db с числом буферов. Нужно понять единицы, убедиться, что команда применилась именно к выбранной базе, и не оставить экспериментальную настройку. Пример читает исходный заголовок, задаёт 256 страниц, читает новое значение и возвращает прежнее в finally. После изменения остаются заметка о проверке и тот же каталог.

Путь данных и результатов
  1. Своя база → заголовок Page buffers = 0: используется значение по умолчанию
  2. PAGE_BUFFERS + 256 → принято сервисное задание
  3. Прочитать окончание задания → прочитать заголовок: 256
  4. Новое SQL-подключение → заметка и товары существуют
  5. finally → вернуть исходное значение 0
  6. Ещё раз прочитать заголовок: 0; убрать только свои временные файлы

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

$started = fbird_maintain_db($service, $database, IBASE_PRP_PAGE_BUFFERS, 256);
if ($started !== true) {
    throw new RuntimeException('Настройка кэша не принята');
}
// Это только запуск. Далее читают ответ службы и заголовок базы.

Третий аргумент выбирает свойство, четвёртый содержит его значение. PAGE_BUFFERS — число страниц кэша, а не размер страницы или мегабайты. Нельзя объединить несколько имён свойств через | и ожидать их одновременной настройки. Помощник f163Maintain дополнительно проверяет владение своей базой и дочитывает ответ службы; f163Header запрашивает заголовок, из которого извлекается только нужное число.

Что будет видно после запуска

НаблюдениеРезультат
Заголовок до изменения0
Запрошено страниц256
Прочитано после изменения256
Восстановлено исходное значение0
Служебная заметкаПроверка политики кэша
Данные каталогаCable: 3; Clamp: 0
Полная самостоятельная программа и подготовка
Проверить и вернуть настройку кэша страниц собственной базыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

function pageBuffers163(&$service,string $name): int {
    $header=f163Header($service,$name);
    if(!preg_match('/Page buffers\s+(\d+)/i',$header['text'],$match)){
        throw new RuntimeException('Число буферов не найдено в заголовке');
    }
    return (int)$match[1];
}
    $name=f163CreateDatabase($service,$owned,'cache');
    $db=f163Connect($name);
    f163Command($db,'CREATE TABLE LAB_MAINTENANCE_NOTES(ID INTEGER NOT NULL PRIMARY KEY, PURPOSE VARCHAR(60) NOT NULL)');f163Commit($db);
    f163Command($db,'INSERT INTO LAB_MAINTENANCE_NOTES(ID,PURPOSE) VALUES (?,?)',[1,'Проверка политики кэша']);f163Commit($db);f163Close($db);
    $before=pageBuffers163($service,$name);$restoreNeeded=false;
    try{
        $restoreNeeded=true;
        $applied=f163Maintain($service,$name,IBASE_PRP_PAGE_BUFFERS,256);
        $during=pageBuffers163($service,$name);
        $db=f163Connect($name);
        $notes=f163Read($db,'SELECT ID,PURPOSE FROM LAB_MAINTENANCE_NOTES ORDER BY ID');
        $catalog=f163Read($db,'SELECT ID,TITLE,QTY FROM LAB_ITEMS ORDER BY ID');f163Close($db);
    }finally{
        if(is_resource($db)){f163Close($db);}
        if($restoreNeeded){
            if(!is_resource($service)){$service=f163Service();}
            $restored=f163Maintain($service,$name,IBASE_PRP_PAGE_BUFFERS,$before);
        }
    }
    $after=pageBuffers163($service,$name);
    $rows=[['Заголовок до изменения',$before],['Запрошено страниц',256],['Прочитано после изменения',$during],['Восстановлено исходное значение',$after],['Служебная заметка',$notes[0]['PURPOSE']],['Данные каталога','Cable: 3; Clamp: 0']];
    $checks=['initial_default'=>$before===0,'request_completed'=>$applied['started']===true,'requested_read_back'=>$during===256,'restored_default'=>$restored['started']===true&&$after===$before,'note_preserved'=>$notes===[['ID'=>1,'PURPOSE'=>'Проверка политики кэша']],'catalog_preserved'=>$catalog===[['ID'=>1,'TITLE'=>'Cable','QTY'=>3],['ID'=>2,'TITLE'=>'Clamp','QTY'=>0]]];
    $observations=['page_buffers'=>['before'=>$before,'requested'=>256,'during'=>$during,'after'=>$after],'notes'=>$notes,'catalog'=>$catalog,'unit'=>'pages','performance_measured'=>false];

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

f163Report('fb163-maintenance-policy','Проверить и вернуть настройку кэша страниц собственной базы',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Заголовок до изменения — 0
Запрошено страниц — 256
Прочитано после изменения — 256
Восстановлено исходное значение — 0
Служебная заметка — Проверка политики кэша
Данные каталога — Cable: 3; Clamp: 0

Архив содержит полную подготовку своих синтетических данных и README с отдельным окружением. Выполните example.php: получится result.html, result.json и metrics.json. Создаются 1 собственные базы. lab.php проверяет реестр, пути и маркер владельца перед удалением; только собственные FDB и промежуточные FBK убираются после наблюдений. Сырые сервисные журналы с путями в публичный результат не печатаются.

Открыть результат программы · Открыть данные результата JSON.

Границы и ошибки

Ноль в заголовке означает использование значения по умолчанию, а не отсутствие кэша. Возврат к 0 здесь проверяется через native Services API закреплённого профиля; это не обещание, что любая версия команды gfix принимает 0. Расход памяти зависит от размера страницы, архитектуры сервера и числа подключений. Изменение заголовка не доказывает ускорение и не означает немедленную замену кэша всех ранее открытых соединений. Здесь создаются новые подключения после изменения и не измеряется производительность. READONLY и аварийное исправление повреждений в этой программе не выполняются.

Новое подключение не смогло выделить память под кэш страниц. Число соединений и размер каждого кэша оказались существеннее повторения проверочного SELECT. Обсуждение задачи на Stack Overflow. Сохраняем исходную настройку, различаем страницы и байты, проверяем заголовок и возвращаем прежнее значение. Пример не объявляет 256 оптимальным числом и не обещает ускорения: выбор зависит от режима сервера, размера страницы и нагрузки.

Попробуйте изменить условие

Если размер страницы 8192 байта, какой объём страниц соответствует 256 буферам и доказывает ли это общий расход процесса?

Разбор решения

256 × 8192 = 2097152 байта, то есть 2 МиБ полезного объёма страниц. У процесса есть другие структуры памяти; в Classic/SuperClassic отдельные подключения могут иметь свои кэши. Это расчёт составляющей, а не измерение всего процесса и не рекомендация увеличить значение.

Отменить ценовой эксперимент и удалить только аналитическую копию

Нужно пересчитать заказ с новой ценой, не изменяя рабочий источник. Получим отдельную базу через backup/restore, сохраним в ней цену Кабеля 10000 вместо 12500 и увидим новый итог заказа 101: 34000 копеек. Затем проверка приложения отклонит несогласованную цену. finally удалит только временную базу, а повторное чтение источника покажет прежние 41500 и 12500.

Путь данных и результатов
  1. Источник: заказ 101 = 41500; заказ 102 = 12500 копеек
  2. Полный FBK → новая аналитическая база
  3. UPDATE только в копии → COMMIT → заказ 101 = 34000
  4. Цена не согласована → DomainException перехвачено приложением
  5. finally → закрыть SQL-соединение → guarded fbird_drop_db копии
  6. Источник снова открыт → 41500 и 12500; файл копии отсутствует

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

// После закрытия результатов, завершения транзакций и проверки владения:
if (fbird_drop_db($verifiedOwnCopyConnection) !== true) {
    throw new RuntimeException('Временная копия не удалена');
}
$verifiedOwnCopyConnection = null;

drop_db получает ресурс соединения с удаляемой базой, а не ресурс Service Manager. В отличие от close, удаление уничтожает саму базу. В полном проекте f163DropDatabase сначала сверяет реестр своего запуска, фактический путь базы и случайный маркер владельца. try описывает эксперимент, catch обрабатывает ожидаемый отказ бизнес-правила, finally завершает жизнь копии независимо от этого отказа.

Что будет видно после запуска

НаблюдениеРезультат
Расчёт в копииЗаказ 101: 34000 коп.; заказ 102: 12500 коп.
Решение приложенияНовая цена не согласована
Временная копия удаленада
Источник после удаленияЗаказ 101: 41500 коп.; заказ 102: 12500 коп.
Исходная база ещё доступнада
Полная самостоятельная программа и подготовка
Отменить ценовой эксперимент и удалить только аналитическую копиюСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f163Config();$service=f163Service();$runtime=f163Runtime($service);

function orderSchema163($db): void {
    $statements=[
        'CREATE TABLE LAB_CUSTOMERS(ID INTEGER NOT NULL PRIMARY KEY, CUSTOMER_NAME VARCHAR(40) NOT NULL)',
        'CREATE TABLE LAB_ORDERS(ID INTEGER NOT NULL PRIMARY KEY, CUSTOMER_ID INTEGER NOT NULL, CONSTRAINT FK_ORDER_CUSTOMER FOREIGN KEY(CUSTOMER_ID) REFERENCES LAB_CUSTOMERS(ID))',
        'CREATE TABLE LAB_ORDER_ITEMS(ORDER_ID INTEGER NOT NULL, LINE_NO INTEGER NOT NULL, ITEM_TITLE VARCHAR(40) NOT NULL, QTY INTEGER NOT NULL CHECK(QTY > 0), UNIT_CENTS INTEGER NOT NULL CHECK(UNIT_CENTS >= 0), CONSTRAINT PK_ORDER_ITEMS PRIMARY KEY(ORDER_ID, LINE_NO), CONSTRAINT FK_ITEM_ORDER FOREIGN KEY(ORDER_ID) REFERENCES LAB_ORDERS(ID))',
        'CREATE INDEX IX_ITEM_TITLE ON LAB_ORDER_ITEMS(ITEM_TITLE)',
    ];
    foreach($statements as $sql){f163Command($db,$sql);f163Commit($db);}
}
function orderSeed163($db): void {
    foreach([[1,'Мастерская'],[2,'Сервис']] as $row){
        f163Command($db,'INSERT INTO LAB_CUSTOMERS(ID,CUSTOMER_NAME) VALUES (?,?)',$row);
    }
    foreach([[101,1],[102,2]] as $row){
        f163Command($db,'INSERT INTO LAB_ORDERS(ID,CUSTOMER_ID) VALUES (?,?)',$row);
    }
    foreach([[101,1,'Кабель',3,12500],[101,2,'Клемма',2,2000],[102,1,'Кабель',1,12500]] as $row){
        f163Command($db,'INSERT INTO LAB_ORDER_ITEMS(ORDER_ID,LINE_NO,ITEM_TITLE,QTY,UNIT_CENTS) VALUES (?,?,?,?,?)',$row);
    }
    f163Commit($db);
}
function orderTotals163($db): array {
    return f163Read($db,'SELECT O.ID AS ORDER_ID, C.CUSTOMER_NAME, CAST(SUM(I.QTY * I.UNIT_CENTS) AS BIGINT) AS TOTAL_CENTS FROM LAB_ORDERS O JOIN LAB_CUSTOMERS C ON C.ID=O.CUSTOMER_ID JOIN LAB_ORDER_ITEMS I ON I.ORDER_ID=O.ID GROUP BY O.ID,C.CUSTOMER_NAME ORDER BY O.ID');
}
function orderCounts163($db): array {
    $counts=[];
    foreach(['LAB_CUSTOMERS','LAB_ORDERS','LAB_ORDER_ITEMS'] as $table){
        // Table names come only from this fixed application list, never from input.
        $counts[$table]=(int)f163Read($db,'SELECT COUNT(*) AS N FROM '.$table)[0]['N'];
    }
    return $counts;
}
function orderKeys163($db): array {
    return f163Read($db,"SELECT TRIM(RDB\$CONSTRAINT_NAME) AS KEY_NAME FROM RDB\$RELATION_CONSTRAINTS WHERE RDB\$CONSTRAINT_TYPE='FOREIGN KEY' AND RDB\$RELATION_NAME IN ('LAB_ORDERS','LAB_ORDER_ITEMS') ORDER BY RDB\$CONSTRAINT_NAME");
}

    $source=f163CreateDatabase($service,$owned,'source');
    $db=f163Connect($source);orderSchema163($db);orderSeed163($db);f163Close($db);
    $backup=f163NewBackup('analysis');f163Backup($service,$source,$backup);
    $copy=f163RestoreDatabase($service,$owned,'analysis',$backup);
    $copyDeleted=false;$businessRefused=false;
    try{
        $db=f163Connect($copy);
        f163Command($db,'UPDATE LAB_ORDER_ITEMS SET UNIT_CENTS=? WHERE ORDER_ID=? AND LINE_NO=?',[10000,101,1]);
        f163Commit($db); // The experiment is deliberately saved in the COPY only.
        $forecast=orderTotals163($db);f163Close($db);
        $priceApproved=false;
        if(!$priceApproved){throw new DomainException('Новая цена не согласована');}
    }catch(DomainException $error){
        $businessRefused=true;$reason=$error->getMessage();
    }finally{
        if(is_resource($db)){f163Close($db);}
        $copyDeleted=f163DropDatabase($owned,$copy);
    }
    $db=f163Connect($source);$original=orderTotals163($db);f163Close($db);
    clearstatcache(true,f163Path($copy));
    $rows=[['Расчёт в копии','Заказ 101: 34000 коп.; заказ 102: 12500 коп.'],['Решение приложения',$reason],['Временная копия удалена',$copyDeleted&&!file_exists(f163Path($copy))],['Источник после удаления','Заказ 101: 41500 коп.; заказ 102: 12500 коп.'],['Исходная база ещё доступна',is_file(f163Path($source))]];
    $checks=['copy_price_changed'=>$forecast===[['ORDER_ID'=>101,'CUSTOMER_NAME'=>'Мастерская','TOTAL_CENTS'=>34000],['ORDER_ID'=>102,'CUSTOMER_NAME'=>'Сервис','TOTAL_CENTS'=>12500]],'business_refusal_caught'=>$businessRefused&&$reason==='Новая цена не согласована','copy_deleted_in_finally'=>$copyDeleted&&!file_exists(f163Path($copy)),'source_still_present'=>is_file(f163Path($source)),'source_prices_unchanged'=>$original===[['ORDER_ID'=>101,'CUSTOMER_NAME'=>'Мастерская','TOTAL_CENTS'=>41500],['ORDER_ID'=>102,'CUSTOMER_NAME'=>'Сервис','TOTAL_CENTS'=>12500]]];
    $observations=['forecast_in_copy'=>$forecast,'business_refusal'=>$reason,'copy_removed_before_source_read'=>$copyDeleted,'source_orders'=>$original,'saved_copy_change_was_not_rolled_back'=>true];

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

f163Report('fb163-copy-lifecycle','Отменить ценовой эксперимент и удалить только аналитическую копию',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Расчёт в копии — Заказ 101: 34000 коп.; заказ 102: 12500 коп.
Решение приложения — Новая цена не согласована
Временная копия удалена — да
Источник после удаления — Заказ 101: 41500 коп.; заказ 102: 12500 коп.
Исходная база ещё доступна — да

Архив содержит полную подготовку своих синтетических данных и README с отдельным окружением. Выполните example.php: получится result.html, result.json и metrics.json. Создаются 2 собственные базы. lab.php проверяет реестр, пути и маркер владельца перед удалением; только собственные FDB и промежуточные FBK убираются после наблюдений. Сырые сервисные журналы с путями в публичный результат не печатаются.

Открыть результат программы · Открыть данные результата JSON.

Границы и ошибки

Изменение цены намеренно подтверждено в копии: это не пример rollback одной транзакции. Источник защищён разделением файлов и соединений. Непредвиденную ошибку SQL нельзя маскировать под «цена не согласована»: здесь catch ограничен DomainException, остальные ошибки продолжают выполнение cleanup и передаются вызывающему коду. Удаление базы не отменяется rollback и может отказать из-за прав или активных подключений. На время анализа источник остаётся доступен; после сохранения наблюдений общий учебный cleanup удаляет и собственный синтетический источник. Готовые HTML/JSON-отчёты остаются.

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

Попробуйте изменить условие

Если расчёт успешно выполнен, можно ли заменить удаление копии простым fbird_close?

Разбор решения

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

Справочник четырёх административных aliases

ФункцияЗадача
fbird_backupПолучить логическую полную копию либо копию схемы через Service Manager.
fbird_restoreСоздать восстановленную базу с явной проверкой версии и порядка путей.
fbird_drop_dbУдалить базу указанного SQL-соединения после проверки её владельца.
fbird_maintain_dbОтправить административное действие и отдельно подтвердить его результат.

Зафиксировать критерии пригодности копии

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

Продолжить можно через диагностику и статистику службы, подтверждение и откат транзакций и права пользователей на данные. Alias не меняет модель безопасности и не заменяет план хранения резервных копий. Рабочий backup требует расписания, контроля доступа, хранения поколений и периодической репетиции восстановления в отдельное место.

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

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

fbird_backupПолучить логическую полную копию либо копию схемы через Service Manager.

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

fbird_backup(resource $service_handle, string $source_db, string $dest_file, int $options = 0, bool $verbose = false): mixed

Перед изменением схемы полезно получить архив, из которого можно вернуть заказы. fbird_backup запускает логическое копирование через Service Manager. Файл FBK хранит материал для восстановления; обычным SQL-подключением его не открывают. Полный и metadata-only архивы служат разным задачам.

fbird_backup — другое имя ibase_backup, зарегистрированное на ту же C-реализацию и аргументы. Оно не даёт отдельного формата, ускорения или миграции на PDO. Историческая отметка PHP Manual «PHP 5, PHP 7 < 7.4» относится к встроенному расширению; в этой главе используется внешнее interbase 3.0.1 с PHP 8.2.35 NTS x64 и Firebird 5.0.4. Наличие функции и совместимость конкретной сборки проверяются отдельно.

Аргумент / результатКонтракт
service_handleОбязательный действующий ресурс от fbird_service_attach с полномочиями на резервное копирование. Обычное SQL-соединение сюда не подходит.
source_dbИмя/alias базы или её путь на сервере Firebird; сохранённые изменения должны быть видимы сервисному заданию.
dest_fileПуть нового FBK на сервере. Это не путь в браузере и не автоматически файл на компьютере PHP-клиента. Проверьте назначение и право службы создавать файл.
options = 0Целая битовая маска разрешённых параметров backup. 0 — обычное копирование; несколько совместимых флагов объединяются через |.
verbose = falseПо умолчанию после запуска возвращается true. В исходнике 3.0.1 значение true дополнительно читает строки сервисного вывода до завершения.
ВозвратПри verbose=false: true означает, что задание принято; при verbose=true в проверенной реализации возвращается текст вывода либо true, если текст отсутствует. false означает отказ старта/чтения. Это mixed, а не массив строк базы.
ОшибкиСохраните код, сообщение и предупреждение сразу, до следующего вызова драйвера. Неправильные типы PHP могут вызвать TypeError. Ошибка в конце задания важнее раннего факта создания файла.

Все исторические опции backup

ФлагНазначение и граница
IBASE_BKP_METADATA_ONLYКопировать структуру без строк; подходит для отдельного пустого стенда, не для сохранения заказов.
IBASE_BKP_NO_GARBAGE_COLLECTНе выполнять сборку мусора как часть чтения для backup; это не исключение текущих строк.
IBASE_BKP_IGNORE_CHECKSUMSИгнорировать ошибки контрольных сумм при аварийном извлечении; не доказывает исправность архива.
IBASE_BKP_IGNORE_LIMBOПропускать limbo-транзакции в соответствующей аварийной процедуре; не универсальная обработка ошибок.
IBASE_BKP_OLD_DESCRIPTIONSСтарое представление описаний метаданных для исторической совместимости.
IBASE_BKP_NON_TRANSPORTABLEНе использовать переносимое представление; такой архив ограничен совместимыми платформами.
IBASE_BKP_CONVERTПреобразовать внешние таблицы в обычные внутренние при создании копии; это не преобразование UTF-8.

Применение 1: полная репетиция восстановления заказов

$backupResult = fbird_backup($service, $sourceDatabase, $newBackupFile, 0, true);
if ($backupResult === false) {
    throw new RuntimeException('Резервная копия не получена');
}
// Следующий шаг: восстановить в НОВУЮ базу и прочитать заказы.

Путь от полного FBK к заказам 101 и 102 заканчивается чтением новой базы. Даже если вызов вернул true, файл может оказаться непригодным или задание завершиться поздней ошибкой. Программа читает сервисный итог, открывает копию и сверяет выбранный предметный контракт.

Применение 2: схема для теста без старых покупателей

$schemaResult = fbird_backup($service, $sourceDatabase, $newSchemaBackup,
    IBASE_BKP_METADATA_ONLY, true);
if ($schemaResult === false) {
    throw new RuntimeException('Шаблон схемы не получен');
}

В песочнице для нового заказа 901 нулевые количества строк — ожидаемый результат, а не потеря данных. Один и тот же backup API создаёт два разных вида артефактов, поэтому назначение надо сохранять вместе с архивом.

PHP Manual исторически называет verbose бесполезным; это расходится с веткой !verbose / _php_ibase_service_query в закреплённом исходнике 3.0.1. Здесь описание соответствует этому исходнику. Это также означает, что true может сделать вызов блокирующим на время задания и собирать журнал в памяти. Для большой рабочей базы нужна отдельная задача с расписанием и контролем времени. Локальный file_exists подтверждает серверный файл только в нашей лаборатории на одном компьютере. Для удалённого сервера проверять нужно на его стороне. Связанные шаги: fbird_restore и fbird_server_info.

fbird_restoreСоздать восстановленную базу с явной проверкой версии и порядка путей.

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

fbird_restore(resource $service_handle, string $source_file, string $dest_db, int $options = 0, bool $verbose = false): mixed

Восстановление превращает FBK в новую базу, которую уже можно открыть и читать. Для репетиции выбирают свободное имя, чтобы не уничтожить источник. В данной сборке есть существенное расхождение порядка аргументов с документацией; замена имени ibase_restore на fbird_restore его не устраняет.

fbird_restore — другое имя ibase_restore, зарегистрированное на ту же C-реализацию и аргументы. Оно не даёт отдельного формата, ускорения или миграции на PDO. Историческая отметка PHP Manual «PHP 5, PHP 7 < 7.4» относится к встроенному расширению; в этой главе используется внешнее interbase 3.0.1 с PHP 8.2.35 NTS x64 и Firebird 5.0.4. Наличие функции и совместимость конкретной сборки проверяются отдельно.

Аргумент / результатДокументированный контракт
service_handleРесурс Service Manager с нужными полномочиями.
source_fileДокументированно — абсолютный серверный путь к существующему FBK.
dest_dbДокументированно — серверный путь/alias новой базы. По этому назначению не должно быть рабочей базы, которую требуется сохранить.
options = 0Битовая маска restore. Наши программы явно разрешают только IBASE_RES_CREATE и новое свободное имя.
verbose = falsefalse подтверждает старт; true в реализации 3.0.1 читает сервисный вывод. Старое утверждение страницы о бесполезности verbose не соответствует этой реализации.
Возвратtrue при принятом старте без verbose; при verbose=true текст/true по наличию вывода; false при отказе. Файл может появиться до окончательной ошибки.
ДиагностикаНемедленно фиксируются код/сообщение/предупреждение; затем разбирается вся последовательность сервисных сообщений, начиная с первой причины. Нельзя считать частичное восстановление рабочей копией.

Порядок путей: документация и закреплённая версия 3.0.1

Одно назначение, два явно различённых интерфейса
  1. Документированный интерфейс: service, source.fbk, new.fdb
  2. f163Restore принимает source.fbk → new.fdb и проверяет точную версию
  3. В interbase 3.0.1 фактический вызов: service, new.fdb, source.fbk
  4. Восстановление только CREATE; при отказе автоматического обмена путей нет
// Только для подтверждённого interbase 3.0.1, после проверки путей:
if (phpversion('interbase') !== '3.0.1') {
    throw new RuntimeException('Нужен проверенный адаптер установленной версии');
}
$restored = fbird_restore($service, $newDatabase, $existingBackup, IBASE_RES_CREATE, true);
if ($restored === false) { throw new RuntimeException('Восстановление отказало'); }

Этот порядок соответствует тому, как 3.0.1 заполняет dbname и backup_file в Service API, и ранее подтверждён отдельным наблюдением среды. Это не исправление документации «по догадке»: адаптер рассчитан на одну версию. Нельзя сначала пробовать один порядок, а после любой ошибки автоматически менять пути местами. При другой сборке сначала выясните её контракт на изолированном примере. Параметры и имена файлов приложения сохраняйте семантически как источник и назначение.

Все исторические опции restore

ФлагНазначение
IBASE_RES_CREATEСоздать новую базу. Учебный guard дополнительно отклоняет существующее назначение.
IBASE_RES_REPLACEРазрешить замену существующей базы; разрушительная операция, не используется в примерах.
IBASE_RES_DEACTIVATE_IDXОставить индексы неактивными; это влияет на ограничения и запросы, а не только на скорость восстановления.
IBASE_RES_NO_SHADOWНе восстанавливать shadow-файлы; обычный архив FBK от этого не превращается в shadow.
IBASE_RES_NO_VALIDITYНе восстанавливать предусмотренные проверки допустимости; особая процедура, не доказательство исправности данных.
IBASE_RES_ONE_AT_A_TIMEПодтверждать восстановление по таблицам; отказ может оставить ранее подтверждённые части.
IBASE_RES_USE_ALL_SPACEИспользовать место страниц без обычного резерва для будущих версий строк.

В списке старой страницы также встречаются IBASE_PRP_PAGE_BUFFERS и IBASE_PRP_SWEEP_INTERVAL и повтор CREATE. PRP-константы обозначают отдельные команды свойств с собственным числовым argument, а не дополнительные биты restore. Не переносите их в options; используйте fbird_maintain_db. Возможности современного gbak шире этой старой сигнатуры: например, параметр CLI не становится доступным простым подбором числовой константы.

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

$copy = f163RestoreDatabase($service, $owned, 'restored', $backup);
$db = f163Connect($copy);
$orders = orderTotals163($db);
f163Close($db);

Это контекстный фрагмент из полной репетиции: все помощники и исходные данные находятся в архиве. За успешным restore следует JOIN и расчёт конкретных заказов, а не только echo true.

Применение 2: восстановить определения и заполнить свой стенд

$sandbox = f163RestoreDatabase($service, $owned, 'sandbox', $schemaBackup);
$db = f163Connect($sandbox);
$counts = orderCounts163($db); // Три существующие прикладные таблицы: 0 / 0 / 0.
f163Close($db);

Проект песочницы затем записывает заказ 901 в сохранённую схему. Результат зависит от содержимого FBK: metadata-only не вернёт старые строки. Архив и установленный сервер должны быть совместимы; наличие расширения PHP само по себе не означает совместимости формата backup/версий базы. Для удаления только проверочной копии существует fbird_drop_db.

fbird_drop_dbУдалить базу указанного SQL-соединения после проверки её владельца.

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

fbird_drop_db([resource $connection]): bool

Одноразовая база после эксперимента занимает место и содержит данные. fbird_drop_db закрывает и удаляет целую базу переданного SQL-соединения. Здесь эта возможность применяется к зарегистрированной копии после ценового расчёта; исходник остаётся доступен для проверки.

fbird_drop_db — другое имя ibase_drop_db, зарегистрированное на ту же C-реализацию и аргументы. Оно не даёт отдельного формата, ускорения или миграции на PDO. Историческая отметка PHP Manual «PHP 5, PHP 7 < 7.4» относится к встроенному расширению; в этой главе используется внешнее interbase 3.0.1 с PHP 8.2.35 NTS x64 и Firebird 5.0.4. Наличие функции и совместимость конкретной сборки проверяются отдельно.

Аргумент / результатЧто учитывать
connectionНеобязательный ресурс обычного соединения от fbird_connect/fbird_pconnect, не Service Manager и не имя файла. Без аргумента исторически выбирается последнее соединение. Для удаления всегда передавайте явное проверенное соединение.
null в документацииДокументированная запись показывает значение по умолчанию null, но parser 3.0.1 принимает необязательный resource (|r). Пропуск аргумента не следует заменять явной передачей null.
trueСервер удалил базу; handle больше не используется. Переменную сразу присваивают null. В этом драйвере внешний вид PHP resource после вызова не гарантирует пригодность соединения.
falseУдаление не выполнено; сразу фиксируются предупреждение и код/сообщение драйвера. Причинами могут быть права и другие активные подключения. Неверный тип аргумента может вызвать исключение PHP.

SQL DELETE удаляет выбранные строки, close освобождает соединение, а drop_db уничтожает базу целиком. Это разные действия. Перед drop освобождают результаты/подготовленные запросы и явно завершают свои транзакции. После успешного удаления не вызывают close, commit или повторный drop для того же handle. Откат транзакции не восстанавливает удалённую базу.

Применение 1: убрать копию даже после отказа расчёта

try {
    // Работа только со своей аналитической копией; результат/ошибку сохраняем отдельно.
    runCopyAnalysis($copyName);
} finally {
    f163DropDatabase($owned, $copyName);
}

runCopyAnalysis обозначает действие приложения, а не встроенную функцию. В полной программе это UPDATE цены, расчёт 34000 копеек и явный DomainException. Guard внутри f163DropDatabase проверяет собственный реестр, путь фактически подключённой базы и её маркер до настоящего fbird_drop_db.

Применение 2: закончить успешную репетицию восстановления

// После сравнения восстановленных заказов и закрытия SQL-подключения:
$removed = f163DropDatabase($owned, $restoredCopy);
if ($removed !== true) { throw new RuntimeException('Проверочная копия осталась'); }
// Отчёт с прочитанными заказами хранится отдельно от удалённой базы.

В репетиции восстановления общая процедура завершения выполняет это действие для своих временных баз и оставляет result.html/result.json. В рабочей политике хранения успешно восстановленная копия иногда должна остаться: срок жизни определяется задачей, а не обязательным вызовом drop после каждого restore. Не принимайте произвольный путь из HTTP-параметра и не считайте совпадение префикса имени доказательством владения. Связаны fbird_restore и fbird_close.

fbird_maintain_dbОтправить административное действие и отдельно подтвердить его результат.

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

fbird_maintain_db(resource $service_handle, string $db, int $action, int $argument = 0): bool

В обслуживаемом приложении иногда нужно поменять свойство базы или выполнить административную операцию. fbird_maintain_db выбирает действие по константе и передаёт его серверу через Service Manager. Это не запрос к таблице и не функция автоматического исправления любой ошибки.

fbird_maintain_db — другое имя ibase_maintain_db, зарегистрированное на ту же C-реализацию и аргументы. Оно не даёт отдельного формата, ускорения или миграции на PDO. Историческая отметка PHP Manual «PHP 5, PHP 7 < 7.4» относится к встроенному расширению; в этой главе используется внешнее interbase 3.0.1 с PHP 8.2.35 NTS x64 и Firebird 5.0.4. Наличие функции и совместимость конкретной сборки проверяются отдельно.

Аргумент / результатКонтракт
service_handleОбязательный ресурс fbird_service_attach с полномочиями на обслуживание. Ресурс обычного SQL-соединения не подходит.
dbПуть/имя базы со стороны сервера. Правило относится к выбранной базе, не ко всем базам сервера.
actionОдно распознаваемое основное действие IBASE_PRP_* или IBASE_RPR_*. Его выбор определяет смысл четвёртого аргумента.
argument = 0Числовое значение, именованный режим или разрешённые дополнительные флаги — в зависимости от action. Ноль не универсальное «ничего не менять».
trueisc_service_start принял задание. Итог ещё надо получить через ту же службу и проверить по заголовку/данным.
falseЛокально нераспознанная команда или серверный отказ. Диагностика сохраняется немедленно; неверные типы могут вызвать исключение PHP. Поздний отказ возможен при чтении ответа.

На основной странице PHP Manual пока есть только сигнатура. Каталог ниже составлен по точному switch Service API interbase 3.0.1 и документации Firebird. Для свойства передают отдельное значение, а не OR нескольких имён свойств. Пример PAGE_BUFFERS | SWEEP_INTERVAL ошибочен даже тогда, когда получившееся число случайно совпало с другой допустимой командой.

Свойства с четырёхбайтовым числовым argument

actionСмысл argument
IBASE_PRP_PAGE_BUFFERSЧисло страниц кэша; не размер страницы и не мегабайты. В программе 0 означает сохранённый default, 256 — проверяемая настройка, а не универсальная рекомендация.
IBASE_PRP_SWEEP_INTERVALПорог автоматического sweep по состоянию транзакций; 0 отключает автоматический запуск. Это не секунды и не расписание cron.
IBASE_PRP_SHUTDOWN_DBОжидание перед принудительным отключением выбранной базы. Может затронуть активные подключения/транзакции.
IBASE_PRP_DENY_NEW_TRANSACTIONSСрок ожидания существующих транзакций при запрете новых в процедуре отключения.
IBASE_PRP_DENY_NEW_ATTACHMENTSСрок ожидания существующих подключений при запрете новых в процедуре отключения.
IBASE_PRP_SET_SQL_DIALECTДиалект 1 или 3; это не автоматическая миграция данных и выражений.

Свойства с однобайтовым режимом

actionИменованные значения
IBASE_PRP_RESERVE_SPACEIBASE_PRP_RES сохраняет резерв в страницах для новых версий строк; IBASE_PRP_RES_USE_FULL разрешает более полное заполнение. Не свободное место всего диска.
IBASE_PRP_WRITE_MODEIBASE_PRP_WM_SYNC либо IBASE_PRP_WM_ASYNC. Выбор затрагивает сохранность данных при сбое, а не только производительность.
IBASE_PRP_ACCESS_MODEIBASE_PRP_AM_READONLY либо IBASE_PRP_AM_READWRITE для целой базы. Это не права конкретного пользователя.

Проверка, уборка версий и специальные действия

КонстантаПрименение и ограничение
IBASE_RPR_SWEEP_DBРучной sweep: обслуживание версий строк и транзакционного состояния; не удаляет актуальные заказы и не исправляет все повреждения.
IBASE_RPR_VALIDATE_DBПроверка структуры; без ограничивающего режима возможны изменения служебных структур.
IBASE_RPR_CHECK_DBДополнительный режим проверки без обновления, обычно argument к VALIDATE_DB. Также распознаётся основным switch, но смысл сочетания задаёт native API.
IBASE_RPR_FULLДополнительная проверка фрагментов записей в argument; не самостоятельный action этого switch.
IBASE_RPR_IGNORE_CHECKSUMИгнорирование контрольных сумм в специальной аварийной процедуре, не доказательство исправности.
IBASE_RPR_MEND_DBПодготовка повреждённой базы к извлечению доступных данных; возможна потеря записей. Не routine-операция для живой базы.
IBASE_RPR_KILL_SHADOWSУдалить ссылки на недоступные shadow-копии; не удаление FBK.
IBASE_PRP_ACTIVATEАктивация shadow в специальной процедуре восстановления.
IBASE_PRP_DB_ONLINEВернуть ранее отключённую базу к работе. Современные режимы gfix шире этого старого адаптера.

Для repair драйвер формирует options как action | argument. Дополнительные разрешённые флаги помещаются в четвёртый параметр, а основное действие оставляют отдельным. Не каждая комбинация перечисленных констант допустима. У числовых свойств проверяют диапазон до упаковки в 32 бита; у режимов — конкретное именованное значение.

Применение 1: ограниченное изменение числа страниц

$started = fbird_maintain_db($service, $database, IBASE_PRP_PAGE_BUFFERS, 256);
if ($started !== true) {
    throw new RuntimeException('Настройка кэша не принята');
}
// Это только запуск. Далее читают ответ службы и заголовок базы.

Полный цикл настройки и возврата дочитывает сервисный результат, сохраняет исходное число, читает изменённое и восстанавливает его в finally. По заголовку и заметке видно, к какой учебной задаче относится изменение. Число 256 не выдаётся за найденный оптимум.

Применение 2: ручное обслуживание старых версий строк

$started = fbird_maintain_db($service, $ownDatabase, IBASE_RPR_SWEEP_DB, 0);
if ($started !== true) { throw new RuntimeException('Sweep не принят'); }
$output = fbird_server_info($service, 62); // Native isc_info_svc_line, не константа PHP.
if ($output === false) { throw new RuntimeException('Итог sweep не прочитан'); }
// Затем отдельным SQL-подключением проверяют нужные данные.

Это контекстный вариант, а не ещё один исполненный проект данной главы. Чтение item 62 поддержано switch драйвера и собирает строки до завершающего пустого блока; true возможно при отсутствии текста. Для понимания sweep есть самостоятельный пример и границы обслуживания. Не выполняйте следующую сервисную операцию, пока не разобран результат предыдущей.

Особенность 3.0.1: на некоторых native-ошибках сервисный ресурс освобождается внутри C-реализации. Помощник отличает локально неизвестный action (наблюдённый код -999) от вызванного native-действия, зануляет использованный handle при native-отказе и не пытается повторно detach его. Ненулевой код сам по себе не доказывает, что задание дошло до сервера. В embedded-профиле ранее наблюдался отказ validation -924 для secondary attachment, а полный цикл READONLY→READWRITE не стал надёжным самостоятельным примером. Эти административные возможности перечислены для полноты контракта, но здесь не выдаются за успешно выполненные сценарии. Для реальной повреждённой базы нужна отдельная процедура на копии с поддерживаемым первичным подключением и анализом первичной причины. Связаны fbird_db_info, fbird_server_info и ibase_maintain_db.

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

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

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

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

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