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

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

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

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

Глава 80 · После SQL-подключения и прав Firebird

Firebird fbird_*: отдельный читатель, профиль, смена пароля и закрытие доступа

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

Что получает интеграция и что меняет администратор

В старой PHP-интеграции встречаются fbird_add_user, fbird_modify_user и fbird_delete_user. Нужно подключить новый экран остатков, исправить профиль оператора, обновить секрет или закончить временную выгрузку. Здесь эти имена приводят к наблюдаемым результатам: читатель видит два товара, попытка изменения запрещена, новый пароль работает, а после удаления логина документ остаётся в базе.

Четыре разных решения о доступе
  1. Создать серверную запись → логин и секрет для нового подключения
  2. Назначить SQL-права отдельно → что разрешено этому подключению в нужной базе
  3. Исправить профиль или заменить секрет → новая проверка соответствующего результата
  4. Завершить доступ → удалить собственную запись → отказ нового входа; прикладные документы сохранены

Учётная запись Firebird похожа на пропуск, а GRANT — на разрешение открыть определённую комнату. Пропуск без разрешения ещё не даёт доступа ко всем помещениям. Это аналогия двух задач, а не описание шифрования или файловой структуры. Поля имени в профиле — надпись на пропуске: фамилия Reader сама по себе не выдаёт SELECT.

Что меняемГде это находитсяЧто остаётся отдельной задачей
Серверный логин и парольВ системе безопасности выбранного экземпляра/конфигурации Firebird.Пользователь сайта в таблице users, PHP-сессия и права посетителя.
FIRST/MIDDLE/LAST NAMEДополнительные сведения о серверной записи.Логин, права на таблицы, подтверждённая личность человека.
GRANT SELECT или UPDATEПрава на таблицы и представления конкретной прикладной базы.Создание серверной учётной записи и проверка секрета.
Заказ ORD-164Строка прикладной LAB_ORDERS.Существование или удаление серверного логина читателя.

Обычному посетителю сайта обычно не создают персональный серверный логин: PHP работает под ограниченной служебной записью, а пользователей приложения различает собственная модель. Здесь рассматривается обслуживание интеграций и оператора базы. Если вы ещё не работали с SQL-подключением, начните с первого запроса Firebird и разницы входа, GRANT и роли.

Alias — другое имя того же обработчика. В закреплённом interbase 3.0.1 fbird_add_user вызывает реализацию ibase_add_user; остальные две пары устроены так же. Переименование не добавляет проверку пароля, скорость, изоляцию или SQL-права. Полные канонические объяснения доступны в полный разбор ibase_add_user, полный разбор ibase_modify_user и полный разбор ibase_delete_user. Ниже сохранён самостоятельный контракт каждого fbird-имени и добавлены отдельные рабочие ситуации.

PHP Manual помечает семейство как PHP 5 / PHP 7 до 7.4.0. Нынешний учебный профиль — отдельное расширение interbase 3.0.1, PHP 8.2.35 и Firebird 5.0.4, а не заявление о последних доступных версиях. Управление пользователями через старый Services API устарело начиная с Firebird 3: для нового административного интерфейса рассматривайте SQL CREATE USER, ALTER USER и DROP USER. Разбор исторических функций нужен, чтобы понимать существующий код и переносить его осмысленно.

Запуск, изоляция и доказательство нового входа

Каждый архив создаёт одну собственную базу и одного случайного учебного пользователя в отдельном TCP-экземпляре Firebird. В README приведены настройка, запуск и ожидаемые файлы. Учётные записи рабочего сервера не используются; secret-значения генерируются при запуске и не входят в HTML/JSON. После работы удаляются только зарегистрированные база и учётная запись данного запуска.

Функции f164* в example.php — приложенные открытые обёртки lab.php, не встроенные функции PHP. Внутри f164AddUser, f164ModifyUser и f164DeleteUser выполняются настоящие fbird_* вызовы. Обёртка проверяет назначение и собственное имя, сохраняет ошибку немедленно, дочитывает ответ сервисной задачи и только затем даёт сценарию проверить профиль, права или новый вход. Самостоятельный код содержит эти действия полностью.

В примерах ротации и удаления также есть auth-child.php. Три или два коротких дочерних процесса проверяют новые TCP-входы независимо от кеша ресурсов родительского PHP. Каждый делает одну попытку; исходный секрет получает только через STDIN, а в ответе оставляет безопасные признаки входа, CURRENT_CONNECTION, TCPv4 и код отказа. Порядок: дождаться завершения child, затем продолжить сценарий. Работающий child блокирует удаление базы.

ФайлЧто с ним делать
example.phpЗапустить один раз по команде README в собственной учебной среде.
lab.php и seed.*Оставить рядом с программой: они создают изолированную базу и обеспечивают воспроизводимость.
auth-child.php, если приложенНе передавать пароль в командной строке; его вызывает основной пример.
result.htmlОткрыть после запуска: видимые значения и раскрываемые наблюдения.
result.jsonТе же результаты структурно, без паролей и закрытых путей.

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

Дать экрану остатков отдельного читателя

Экран остатков должен показывать товары, но не менять склад. Создадим отдельный серверный логин для этого экрана, дадим только чтение LAB_ITEMS и действительно выполним SELECT от его имени. На экране будут «Кабель USB — 6» и «Предохранитель 10A — 0». Попытку присвоить остатку 999 сервер отклонит; администратор затем подтвердит прежние значения.

От действия к проверяемому результату
  1. fbird_add_user(service, отдельный логин, секрет) → запись для входа
  2. GRANT SELECT ON LAB_ITEMS → commit → разрешено только чтение этой таблицы
  3. TCP-вход читателя → CURRENT_USER → SELECT двух строк
  4. UPDATE до 999 → отказ -551 → повторное чтение администратором: 6 и 0

Минимальный вызов

$started = fbird_add_user($service, $login, $password);
if ($started !== true) { throw new RuntimeException("Создание не запущено"); }

Первый аргумент — сервисное соединение администратора, второй — имя новой серверной учётной записи, третий — её секрет. $started получает результат вызова; это не строка товара. Полная программа дочитывает сервисный результат и отдельно выдаёт GRANT: add_user сам не разрешает SELECT. Идентификатор в GRANT берётся из собственного реестра имён, а значения в SELECT/UPDATE передаются параметрами. WHERE, ORDER BY и ROWS оставляют выбор нужных данных серверу.

Результат самостоятельной программы

НаблюдениеРезультат
Создана отдельная учётная записьда
Вошёл именно читательда
Строк на экране2
Кабель USB, шт.6
Предохранитель 10A, шт.0
Изменение остатка запрещенода
Остатки не изменилисьда
Необязательное имя не заданода
Дать экрану остатков отдельного читателяСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);

    $database=f164CreateDatabase($service,$owned,'reader');
    $user=f164NewUser($owned,'catalog_reader');$password=f164Password();
    $created=f164AddUser($service,$user,$password);
    $db=f164Connect($database);
    f164Command($db,'UPDATE LAB_ITEMS SET TITLE=?,QTY=? WHERE ID=?',['Кабель USB',6,1]);
    f164Command($db,'UPDATE LAB_ITEMS SET TITLE=?,QTY=? WHERE ID=?',['Предохранитель 10A',0,2]);
    f164Command($db,'GRANT SELECT ON LAB_ITEMS TO USER '.f164UserIdentifier($user));
    f164Commit($db);f164Close($db);
    $reader=f164ConnectUser($database,$user,$password);
    try {
        $identity=f164Read($reader,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE')[0];
        $items=f164Read($reader,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE ID IN (?,?) ORDER BY ID ROWS 2',[1,2]);
        $denied=f164Capture(static fn()=>fbird_query($reader,'UPDATE LAB_ITEMS SET QTY=? WHERE ID=?',999,1));
        $refusal=['allowed'=>$denied['value']!==false,'code'=>$denied['code'],'warning'=>$denied['warning'],'message_present'=>$denied['message_present']];
        if(is_resource($denied['value'])){fbird_free_result($denied['value']);}$denied['value']=null;unset($denied);
    } finally {f164Close($reader);}
    $db=f164Connect($database);
    $actual=f164Read($db,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE ID IN (?,?) ORDER BY ID ROWS 2',[1,2]);
    $profile=f164UserProfile($db,$user);f164Commit($db);f164Close($db);
    $expected=[['id'=>1,'title'=>'Кабель USB','qty'=>6],['id'=>2,'title'=>'Предохранитель 10A','qty'=>0]];
    $rows=[['Создана отдельная учётная запись',$created['completed']],['Вошёл именно читатель',$identity['login']===$user],
        ['Строк на экране',count($items)],['Кабель USB, шт.',$items[0]['qty']],['Предохранитель 10A, шт.',$items[1]['qty']],
        ['Изменение остатка запрещено',!$refusal['allowed']],['Остатки не изменились',$actual===$expected],
        ['Необязательное имя не задано',$profile['first']===null]];
    $checks=['account_created'=>$created['completed'],'reader_identity'=>$identity['login']===$user,
        'bounded_catalog'=>$items===$expected,'zero_stock_preserved'=>$items[1]['qty']===0,
        'update_refused'=>!$refusal['allowed']&&$refusal['code']===-551&&$refusal['warning']&&$refusal['message_present'],
        'database_unchanged'=>$actual===$expected,'optional_profile_absent'=>$profile['first']===null&&$profile['middle']===null&&$profile['last']===null,
        'connections_closed'=>$reader===null&&$db===null];
    $observations=['selected_items'=>$items,'after_denied_update'=>$actual,'update_refusal'=>$refusal,
        'profile'=>['first'=>$profile['first'],'middle'=>$profile['middle'],'last'=>$profile['last'],'plugin'=>trim($profile['plugin'])],
        'service_action'=>$created,'authentication_identity_matches'=>$identity['login']===$user,
        'granted_privilege'=>'SELECT ON LAB_ITEMS','password_published'=>false];

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

f164Report('fb164-reader-account','Дать экрану остатков отдельного читателя',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Создана отдельная учётная запись — да
Вошёл именно читатель — да
Строк на экране — 2
Кабель USB, шт. — 6
Предохранитель 10A, шт. — 0
Изменение остатка запрещено — да
Остатки не изменились — да
Необязательное имя не задано — да

Полный самостоятельный CLI-пример: одна собственная база, один учебный пользователь, отдельный TCP-сервер. Все исходники и README находятся в архиве. result.html показывает данные и наблюдения, result.json сохраняет их без секретов.

HTML с видимым результатом · JSON с наблюдениями.

Ошибки и границы

«Разрешено чтение» означает конкретный GRANT в этой учебной базе, а не глобальную гарантию для всех таблиц и процедур сервера. Мы сознательно не подключаем экран как SYSDBA. Ноль в QTY — корректное значение товара; он не заменяется сообщением об ошибке. Администрирование учётных записей не должно находиться в публичной регистрации посетителей сайта. После ожидаемого отказа код и признак сообщения сохраняются до следующего запроса; исходный текст может содержать закрытые сведения и в HTML не выводится.

В реальном вопросе пользователь подключился с RDB$ADMIN к прикладной базе, но создание серверной учётной записи закончилось отказом. Обсуждение на Stack Overflow. Разделяем полномочия в прикладной базе и системе безопасности. Наш администратор создаёт запись только в отдельном стенде; экран получает лишь SELECT. Версионные правила проверены по документации Firebird, а не перенесены из ответа наугад.

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

Экрану нужен только товар ID=1 с положительным остатком. Где лучше отфильтровать вторую строку?

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

Измените SQL на WHERE ID=? AND QTY>0 с параметром 1, оставьте только нужные столбцы и предел ROWS 1. Не запрашивайте весь каталог ради array_filter. Разрешения учётной записи при этом остаются теми же; условие выборки и GRANT решают разные задачи.

Исправить имя оператора, не стерев остальные сведения

В служебной карточке оператора обнаружили опечатку: Mila должна быть Mia. Сначала исправим только имя, сохранив K и Novak. Затем отдельно очистим среднее имя. Такой интерфейс должен различать «не изменять поле» и «сохранить пустую строку»; иначе редактирование одного поля может стереть остальные.

От действия к проверяемому результату
  1. Создание профиля → Mila / K / Novak
  2. modify_user с единственным optional first_name=Mia → Mia / K / Novak
  3. modify_user с first_name=Mia, middle_name="" → Mia / пустая строка / Novak
  4. Каждое чтение SEC$USERS завершает транзакцию → новый снимок после изменения

Минимальный вызов

$started = fbird_modify_user($service, $login, $currentPassword, "Mia");
// middle_name и last_name отсутствуют: это не пустые строки.

У этой старой PHP-функции пароль обязателен даже при правке имени. В примере известный текущий секрет передаётся снова. Необязательные параметры идут по порядку: first, middle, last. Чтобы очистить middle, нужно передать first и затем ""; пропустить middle и позиционно передать только last нельзя. Профиль читается по одному собственному логину, а commit после чтения завершает снимок перед следующей сервисной операцией.

Результат самостоятельной программы

НаблюдениеРезультат
Имя до исправленияMila
Имя после исправленияMia
Пропущенное среднее имя сохраненода
Пропущенная фамилия сохраненада
После явной очистки длина middle_name0
Пустая строка отличается от nullда
Фамилия после очисткиNovak
Тот же логин по-прежнему входитда
Исправить имя оператора, не стерев остальные сведенияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);

    $database=f164CreateDatabase($service,$owned,'profile');
    $user=f164NewUser($owned,'operator');$password=f164Password();
    f164AddUser($service,$user,$password,['Mila','K','Novak']);
    $db=f164Connect($database);$before=f164UserProfile($db,$user);f164Commit($db);
    $firstAction=f164ModifyUser($service,$user,$password,['Mia']);
    $partial=f164UserProfile($db,$user);f164Commit($db);
    $emptyAction=f164ModifyUser($service,$user,$password,['Mia','']);
    $cleared=f164UserProfile($db,$user);f164Commit($db);f164Close($db);
    $connection=f164ConnectUser($database,$user,$password);
    try {$identity=f164Read($connection,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE')[0];}
    finally {f164Close($connection);}
    $rows=[['Имя до исправления',$before['first']],['Имя после исправления',$partial['first']],
        ['Пропущенное среднее имя сохранено',$partial['middle']==='K'],['Пропущенная фамилия сохранена',$partial['last']==='Novak'],
        ['После явной очистки длина middle_name',strlen($cleared['middle'])],['Пустая строка отличается от null',$cleared['middle']===''&&$cleared['middle']!==null],
        ['Фамилия после очистки',$cleared['last']],['Тот же логин по-прежнему входит',$identity['login']===$user]];
    $checks=['initial_profile'=>$before['first']==='Mila'&&$before['middle']==='K'&&$before['last']==='Novak',
        'partial_update'=>$partial['first']==='Mia'&&$partial['middle']==='K'&&$partial['last']==='Novak',
        'explicit_empty'=>$cleared['middle']===''&&$cleared['last']==='Novak'&&$cleared['first']==='Mia',
        'actions_completed'=>$firstAction['completed']&&$emptyAction['completed'],
        'identity_unchanged'=>$identity['login']===$user,'connections_closed'=>$db===null&&$connection===null];
    $profileFields=static fn(array $value):array=>['first'=>$value['first'],'middle'=>$value['middle'],'last'=>$value['last'],'plugin'=>trim($value['plugin'])];
    $observations=['before'=>$profileFields($before),'after_first_only'=>$profileFields($partial),'after_explicit_empty'=>$profileFields($cleared),
        'optional_argument_counts'=>[3,1,2],'service_actions'=>[$firstAction,$emptyAction],
        'same_password_supplied_without_publishing_it'=>true,'login_name_changed'=>false];

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

f164Report('fb164-profile-update','Исправить имя оператора, не стерев остальные сведения',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Имя до исправления — Mila
Имя после исправления — Mia
Пропущенное среднее имя сохранено — да
Пропущенная фамилия сохранена — да
После явной очистки длина middle_name — 0
Пустая строка отличается от null — да
Фамилия после очистки — Novak
Тот же логин по-прежнему входит — да

Полный самостоятельный CLI-пример: одна собственная база, один учебный пользователь, отдельный TCP-сервер. Все исходники и README находятся в архиве. result.html показывает данные и наблюдения, result.json сохраняет их без секретов.

HTML с видимым результатом · JSON с наблюдениями.

Ошибки и границы

В лаборатории использованы короткие ASCII-значения профиля. UTF8 на SQL-соединении не является настройкой кодировки Services API. Не превращайте отсутствующий ключ HTML-формы в "" автоматически. Для современного интерфейса SQL ALTER USER позволяет изменить FIRSTNAME без повторной передачи PASSWORD; это предпочтительнее, если приложение не должно знать текущий секрет. Профиль — описание серверной записи, а не имя авторизованного посетителя сайта и не его SQL-права.

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

Требуется изменить только фамилию, не зная текущего пароля. Нужно ли передавать null третьим аргументом?

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

Нет: официальный контракт modify_user требует строковый пароль. Для нового административного интерфейса выберите разрешённый SQL ALTER USER ... LASTNAME и явный commit, не меняя PASSWORD. Если поддерживаете этот старый API, получите корректно управляемый секрет и передайте прежние first/middle вместе с новой фамилией; не выдумывайте пропуск через null.

Обновить секрет интеграции и проверить новый вход

При плановой ротации секрета интеграция должна снова подключиться и продолжить читать нужный товар. Недостаточно увидеть true у modify_user: проверим, что новый вход со старым секретом запрещён, с новым разрешён, а товар «Сканер штрихкода — 4» доступен и поля профиля не потеряны.

От действия к проверяемому результату
  1. Отдельный PHP before → исходный секрет → TCPv4 и номер attachment → процесс завершён
  2. fbird_modify_user(service, логин, новый секрет) → сервисное завершение
  3. Отдельный PHP old → прежний секрет → отказ -902; отдельный PHP new → новый секрет → другой attachment
  4. SELECT с новым секретом → Сканер штрихкода / 4; SEC$USERS → Mila / K / Novak

Минимальный вызов

$started = fbird_modify_user($service, $login, $newPassword);
// Далее проверить отдельные новые входы со старым и новым секретами.

Три обязательных аргумента меняют секрет указанного логина. Поля first/middle/last не передаются и должны сохраниться. Полная программа запускает три коротких процесса через proc_open: каждый делает одну попытку входа, получает личность/attachment/протокол и завершается. Секрет передаётся только через STDIN, административный пароль не наследуется. После проверки новый секрет также используется для конкретного SELECT, а не только для открытия соединения.

Результат самостоятельной программы

НаблюдениеРезультат
Исходный пароль работалда
После смены старый пароль отклонёнда
Новый пароль принятда
Успешные входы имеют разные attachmentда
Протокол нового входаTCPv4
Товар прочитан с новым секретомСканер штрихкода
Остаток товара после смены4
Поля профиля сохраненыда
Обновить секрет интеграции и проверить новый входСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);

    $authChildrenStopped=true;
    $authenticateFresh=static function(string $phase,string $database,string $user,string $password) use (&$authChildrenStopped): array {
        if(!in_array($phase,['before','old','new','after'],true))throw new InvalidArgumentException('Неизвестный этап проверки.');
        $state=&f164State();f164UserIdentifier($user);
        if(!isset($state['databases'][$database])||isset($state['removed'][$database])||!is_file(f164Path($database)))
            throw new RuntimeException('Нет собственной базы для проверки входа.');
        $configured=getenv('PHPMAN_INTERBASE_EXTENSION');
        $extension=is_string($configured)&&$configured!==''?$configured:dirname(PHP_BINARY).'/ext/php_interbase.dll';
        $extension=realpath($extension);
        if($extension===false||!is_file($extension))throw new RuntimeException('Укажите DLL interbase 3.0.1 через PHPMAN_INTERBASE_EXTENSION; см. README.');
        $command=[PHP_BINARY,'-n','-d','extension='.$extension,__DIR__.'/auth-child.php'];
        $environment=getenv();unset($environment['PHPMAN_FIREBIRD_PASSWORD']);
        $log='auth-'.$phase;$stdoutFile=$log.'.stdout.log';$stderrFile=$log.'.stderr.log';
        $record=['schema'=>'aen-firebird164-auth-process-v1','phase'=>$phase,'started'=>false,'stopped'=>false,
            'final_running'=>false,'timed_out'=>false,'pid'=>null,'exit_code'=>null,'proc_close_return'=>null,
            'automatic_retries'=>0,'status'=>'failed','response_sha256'=>null,'stdout_file'=>$stdoutFile,'stderr_file'=>$stderrFile];
        $process=null;$pipes=[];$exit=null;$stopped=false;static $unreaped=[];
        try{
            $process=proc_open($command,[0=>['pipe','r'],1=>['file',__DIR__.'/'.$stdoutFile,'w'],
                2=>['file',__DIR__.'/'.$stderrFile,'w']],$pipes,__DIR__,$environment,
                ['bypass_shell'=>true,'create_no_window'=>true]);
            if(!is_resource($process))throw new RuntimeException('Проверка нового входа не запущена.');
            $authChildrenStopped=false;$state['auth_children_running']=true;
            $status=proc_get_status($process);$record['started']=true;$record['pid']=(int)$status['pid'];
            $request=json_encode(['database'=>$database,'user'=>$user,'password'=>$password],JSON_THROW_ON_ERROR);
            $written=fwrite($pipes[0],$request);fclose($pipes[0]);$pipes=[];
            if($written!==strlen($request))throw new RuntimeException('Параметры нового входа не переданы полностью.');
            unset($request,$password);
            $deadline=microtime(true)+8;
            do{
                if(!$status['running']){$exit=(int)$status['exitcode'];$stopped=true;break;}
                usleep(50000);$status=proc_get_status($process);
            }while(microtime(true)<$deadline);
            if(!$stopped){$record['timed_out']=true;throw new RuntimeException('Проверка нового входа превысила восемь секунд.');}
            $body=file_get_contents(__DIR__.'/'.$stdoutFile);
            $reply=$body===false?null:json_decode($body,true,512,JSON_THROW_ON_ERROR);
            if($exit!==0||!is_array($reply)||!is_bool($reply['connected']??null)||!is_bool($reply['identity_matches']??null)
                ||!is_int($reply['code']??null)||!is_bool($reply['warning']??null)||!is_bool($reply['message_present']??null))
                throw new RuntimeException('Нет корректного результата отдельного входа.');
            if($reply['connected']&&(!is_int($reply['attachment_id']??null)||($reply['network_protocol']??null)!=='TCPv4'))
                throw new RuntimeException('Нет TCP attachment нового входа.');
            $record['response_sha256']=hash('sha256',(string)$body);$record['status']='passed';
            return $reply;
        }finally{
            foreach($pipes as $pipe)if(is_resource($pipe))fclose($pipe);
            if(is_resource($process)){
                $status=proc_get_status($process);
                if($status['running']){
                    proc_terminate($process);$deadline=microtime(true)+2;
                    do{$status=proc_get_status($process);if(!$status['running'])break;usleep(50000);}while(microtime(true)<$deadline);
                }
                $stopped=!$status['running'];$record['stopped']=$stopped;$record['final_running']=$status['running'];
                $authChildrenStopped=$stopped;$state['auth_children_running']=!$stopped;
                if($exit===null&&!$status['running'])$exit=(int)$status['exitcode'];$record['exit_code']=$exit;
                if($stopped)$record['proc_close_return']=proc_close($process);
                else{$record['proc_close_return']=null;$unreaped[]=$process;$record['status']='failed';}
                $process=null;
            }
            file_put_contents(__DIR__.'/'.$log.'-process.json',json_encode($record,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR)."\n");
        }
    };

    $database=f164CreateDatabase($service,$owned,'rotation');
    $user=f164NewUser($owned,'integration');$oldPassword=f164Password();$newPassword=f164Password();
    f164AddUser($service,$user,$oldPassword,['Mila','K','Novak']);
    $db=f164Connect($database);
    f164Command($db,'UPDATE LAB_ITEMS SET TITLE=?,QTY=? WHERE ID=?',['Сканер штрихкода',4,1]);
    f164Command($db,'GRANT SELECT ON LAB_ITEMS TO USER '.f164UserIdentifier($user));
    f164Commit($db);f164Close($db);
    $before=$authenticateFresh('before',$database,$user,$oldPassword);
    $action=f164ModifyUser($service,$user,$newPassword);
    $oldAttempt=$authenticateFresh('old',$database,$user,$oldPassword);
    $after=$authenticateFresh('new',$database,$user,$newPassword);
    $reader=f164ConnectUser($database,$user,$newPassword);
    try {$items=f164Read($reader,'SELECT ID AS "id",TITLE AS "title",QTY AS "qty" FROM LAB_ITEMS WHERE ID=? ROWS 1',[1]);}
    finally {f164Close($reader);}
    $db=f164Connect($database);$profile=f164UserProfile($db,$user);f164Commit($db);f164Close($db);
    $profilePreserved=$profile['first']==='Mila'&&$profile['middle']==='K'&&$profile['last']==='Novak';
    $rows=[['Исходный пароль работал',$before['identity_matches']],['После смены старый пароль отклонён',!$oldAttempt['connected']],
        ['Новый пароль принят',$after['identity_matches']],['Успешные входы имеют разные attachment',$before['attachment_id']!==$after['attachment_id']],
        ['Протокол нового входа',$after['network_protocol']],['Товар прочитан с новым секретом',$items[0]['title']],
        ['Остаток товара после смены',$items[0]['qty']],['Поля профиля сохранены',$profilePreserved]];
    $checks=['initial_login'=>$before['identity_matches'],'rotation_completed'=>$action['completed'],
        'old_secret_refused'=>!$oldAttempt['connected']&&$oldAttempt['code']===-902&&$oldAttempt['warning']&&$oldAttempt['message_present'],
        'new_secret_accepted'=>$after['identity_matches'],
        'separate_tcp_attachments'=>$before['attachment_id']!==$after['attachment_id']&&$before['network_protocol']==='TCPv4'&&$after['network_protocol']==='TCPv4',
        'catalog_still_readable'=>$items===[['id'=>1,'title'=>'Сканер штрихкода','qty'=>4]],
        'omitted_profile_preserved'=>$profilePreserved,'children_and_connections_closed'=>$authChildrenStopped&&$reader===null&&$db===null];
    $observations=['authentication_checks'=>['before'=>$before,'old'=>$oldAttempt,'new'=>$after],
        'service_action'=>$action,'catalog_read_using_new_secret'=>$items,
        'profile'=>['first'=>$profile['first'],'middle'=>$profile['middle'],'last'=>$profile['last'],'plugin'=>trim($profile['plugin'])],
        'passwords_printed'=>false,'automatic_authentication_retries'=>0];

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

f164Report('fb164-password-rotation','Обновить секрет интеграции и проверить новый вход',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Исходный пароль работал — да
После смены старый пароль отклонён — да
Новый пароль принят — да
Успешные входы имеют разные attachment — да
Протокол нового входа — TCPv4
Товар прочитан с новым секретом — Сканер штрихкода
Остаток товара после смены — 4
Поля профиля сохранены — да

Полный самостоятельный CLI-пример: одна собственная база, один учебный пользователь, отдельный TCP-сервер. Все исходники и README находятся в архиве. result.html показывает данные и наблюдения, result.json сохраняет их без секретов.

HTML с видимым результатом · JSON с наблюдениями.

Ошибки и границы

Это не отключение всех старых сессий. В interbase 3.0.1 повторный connect с теми же аргументами в одном процессе способен вернуть кешированный ресурс; имя переменной и вызов close сами по себе не доказывают повторную аутентификацию. Поэтому доказательство строится на границе процессов и разных CURRENT_CONNECTION. Мы не делаем искусственных пауз и повторов до случайного успеха. Несколько user manager могут хранить разные записи с одним логином: проверяйте SEC$PLUGIN и выбранный экземпляр, а не меняйте все пароли подряд. Для реальной интеграции отдельно обновляют менеджер секретов и её жизненный цикл подключений.

После смены пароля в описанной ситуации принимались оба секрета: одинаковый логин существовал у разных authentication plugins. Обсуждение на Stack Overflow. Указываем границу выбранного Srp-стенда, проверяем plugin и реальные новые TCP-входы. Это отдельная причина от кеша PHP-соединения. Не включаем legacy-auth и не редактируем security-таблицы вручную ради результата теста.

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

Старая открытая интеграция успешно выполняет SELECT после ротации. Доказывает ли это, что старый пароль всё ещё принимается?

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

Нет. SELECT использует уже существующее подключение. Проверьте ровно новый вход к тому же серверу и user manager в отдельном процессе. Сохраните безопасный отказ старого секрета и успешный результат нового; не публикуйте пароль или полный текст нативной ошибки.

Закрыть временный логин и сохранить документы

Временная выгрузка заказов закончилась. Её логин больше не нужен, но документ ORD-164 с количеством 4 должен остаться в прикладной базе. Удалим только собственную серверную учётную запись, подтвердим отказ нового входа и перечитаем тот же документ администратором.

От действия к проверяемому результату
  1. Учётная запись читателя → новый TCP-вход → документ ORD-164 / 4
  2. Закрыть читателя → fbird_delete_user(service, точный логин) → сервисное завершение
  3. Отдельный PHP after → прежние реквизиты → отказ нового входа
  4. SEC$USERS → записи нет; LAB_ORDERS → прежний ORD-164 / 4

Минимальный вызов

$started = fbird_delete_user($service, $temporaryLogin);
if ($started !== true) { throw new RuntimeException("Удаление не запущено"); }

У удаления два аргумента: сервис администратора и логин. Пароль удаляемого пользователя не передаётся. Это не DELETE FROM LAB_ORDERS и не удаление файла базы. В полной программе будущий логин заранее занесён в реестр собственных учебных записей; проверка принадлежности не позволяет превратить опечатку в удаление чужой записи. Child after не переиспользует соединение before: каждый процесс полностью завершён.

Результат самостоятельной программы

НаблюдениеРезультат
До удаления логин работалда
Документ читателяORD-164
Удаление учётной записи завершенода
Новый вход с прежними реквизитами запрещёнда
Запись профиля отсутствуетда
Документ после удаления логинаORD-164
Количество в документе4
Документ полностью сохранёнда
Закрыть временный логин и сохранить документыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f164Config();$service=f164Service();$runtime=f164Runtime($service);

    $authChildrenStopped=true;
    $authenticateFresh=static function(string $phase,string $database,string $user,string $password) use (&$authChildrenStopped): array {
        if(!in_array($phase,['before','old','new','after'],true))throw new InvalidArgumentException('Неизвестный этап проверки.');
        $state=&f164State();f164UserIdentifier($user);
        if(!isset($state['databases'][$database])||isset($state['removed'][$database])||!is_file(f164Path($database)))
            throw new RuntimeException('Нет собственной базы для проверки входа.');
        $configured=getenv('PHPMAN_INTERBASE_EXTENSION');
        $extension=is_string($configured)&&$configured!==''?$configured:dirname(PHP_BINARY).'/ext/php_interbase.dll';
        $extension=realpath($extension);
        if($extension===false||!is_file($extension))throw new RuntimeException('Укажите DLL interbase 3.0.1 через PHPMAN_INTERBASE_EXTENSION; см. README.');
        $command=[PHP_BINARY,'-n','-d','extension='.$extension,__DIR__.'/auth-child.php'];
        $environment=getenv();unset($environment['PHPMAN_FIREBIRD_PASSWORD']);
        $log='auth-'.$phase;$stdoutFile=$log.'.stdout.log';$stderrFile=$log.'.stderr.log';
        $record=['schema'=>'aen-firebird164-auth-process-v1','phase'=>$phase,'started'=>false,'stopped'=>false,
            'final_running'=>false,'timed_out'=>false,'pid'=>null,'exit_code'=>null,'proc_close_return'=>null,
            'automatic_retries'=>0,'status'=>'failed','response_sha256'=>null,'stdout_file'=>$stdoutFile,'stderr_file'=>$stderrFile];
        $process=null;$pipes=[];$exit=null;$stopped=false;static $unreaped=[];
        try{
            $process=proc_open($command,[0=>['pipe','r'],1=>['file',__DIR__.'/'.$stdoutFile,'w'],
                2=>['file',__DIR__.'/'.$stderrFile,'w']],$pipes,__DIR__,$environment,
                ['bypass_shell'=>true,'create_no_window'=>true]);
            if(!is_resource($process))throw new RuntimeException('Проверка нового входа не запущена.');
            $authChildrenStopped=false;$state['auth_children_running']=true;
            $status=proc_get_status($process);$record['started']=true;$record['pid']=(int)$status['pid'];
            $request=json_encode(['database'=>$database,'user'=>$user,'password'=>$password],JSON_THROW_ON_ERROR);
            $written=fwrite($pipes[0],$request);fclose($pipes[0]);$pipes=[];
            if($written!==strlen($request))throw new RuntimeException('Параметры нового входа не переданы полностью.');
            unset($request,$password);
            $deadline=microtime(true)+8;
            do{
                if(!$status['running']){$exit=(int)$status['exitcode'];$stopped=true;break;}
                usleep(50000);$status=proc_get_status($process);
            }while(microtime(true)<$deadline);
            if(!$stopped){$record['timed_out']=true;throw new RuntimeException('Проверка нового входа превысила восемь секунд.');}
            $body=file_get_contents(__DIR__.'/'.$stdoutFile);
            $reply=$body===false?null:json_decode($body,true,512,JSON_THROW_ON_ERROR);
            if($exit!==0||!is_array($reply)||!is_bool($reply['connected']??null)||!is_bool($reply['identity_matches']??null)
                ||!is_int($reply['code']??null)||!is_bool($reply['warning']??null)||!is_bool($reply['message_present']??null))
                throw new RuntimeException('Нет корректного результата отдельного входа.');
            if($reply['connected']&&(!is_int($reply['attachment_id']??null)||($reply['network_protocol']??null)!=='TCPv4'))
                throw new RuntimeException('Нет TCP attachment нового входа.');
            $record['response_sha256']=hash('sha256',(string)$body);$record['status']='passed';
            return $reply;
        }finally{
            foreach($pipes as $pipe)if(is_resource($pipe))fclose($pipe);
            if(is_resource($process)){
                $status=proc_get_status($process);
                if($status['running']){
                    proc_terminate($process);$deadline=microtime(true)+2;
                    do{$status=proc_get_status($process);if(!$status['running'])break;usleep(50000);}while(microtime(true)<$deadline);
                }
                $stopped=!$status['running'];$record['stopped']=$stopped;$record['final_running']=$status['running'];
                $authChildrenStopped=$stopped;$state['auth_children_running']=!$stopped;
                if($exit===null&&!$status['running'])$exit=(int)$status['exitcode'];$record['exit_code']=$exit;
                if($stopped)$record['proc_close_return']=proc_close($process);
                else{$record['proc_close_return']=null;$unreaped[]=$process;$record['status']='failed';}
                $process=null;
            }
            file_put_contents(__DIR__.'/'.$log.'-process.json',json_encode($record,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR)."\n");
        }
    };

    $database=f164CreateDatabase($service,$owned,'retirement');
    $user=f164NewUser($owned,'temporary');$password=f164Password();
    f164AddUser($service,$user,$password,['Mila']);
    $db=f164Connect($database);
    f164Command($db,'CREATE TABLE LAB_ORDERS(ID INTEGER NOT NULL PRIMARY KEY,ORDER_NO VARCHAR(20) NOT NULL,QTY INTEGER NOT NULL)');f164Commit($db);
    f164Command($db,'INSERT INTO LAB_ORDERS(ID,ORDER_NO,QTY) VALUES (?,?,?)',[1,'ORD-164',4]);
    f164Command($db,'GRANT SELECT ON LAB_ORDERS TO USER '.f164UserIdentifier($user));f164Commit($db);f164Close($db);
    $before=$authenticateFresh('before',$database,$user,$password);
    $reader=f164ConnectUser($database,$user,$password);
    try {$documentBefore=f164Read($reader,'SELECT ORDER_NO AS "number",QTY AS "qty" FROM LAB_ORDERS WHERE ID=? ROWS 1',[1])[0];}
    finally {f164Close($reader);}
    // Внешние SQL-ресурсы закрыты: операция удаления сама проверит SEC$USERS.
    $action=f164DeleteUser($service,$user);
    $after=$authenticateFresh('after',$database,$user,$password);
    $db=f164Connect($database);$profileAfter=f164UserProfile($db,$user);
    $documentAfter=f164Read($db,'SELECT ORDER_NO AS "number",QTY AS "qty" FROM LAB_ORDERS WHERE ID=? ROWS 1',[1])[0];
    f164Commit($db);f164Close($db);
    $rows=[['До удаления логин работал',$before['identity_matches']],['Документ читателя',$documentBefore['number']],
        ['Удаление учётной записи завершено',$action['completed']],['Новый вход с прежними реквизитами запрещён',!$after['connected']],
        ['Запись профиля отсутствует',$profileAfter===null],['Документ после удаления логина',$documentAfter['number']],
        ['Количество в документе',$documentAfter['qty']],['Документ полностью сохранён',$documentAfter===$documentBefore]];
    $checks=['initial_login'=>$before['identity_matches']&&$before['network_protocol']==='TCPv4',
        'initial_document'=>$documentBefore===['number'=>'ORD-164','qty'=>4],'delete_completed'=>$action['completed'],
        'new_login_refused'=>!$after['connected']&&$after['code']===-902&&$after['warning']&&$after['message_present'],
        'profile_absent'=>$profileAfter===null,'document_preserved'=>$documentAfter===$documentBefore,
        'children_and_connections_closed'=>$authChildrenStopped&&$reader===null&&$db===null];
    $observations=['authentication_checks'=>['before'=>$before,'after'=>$after],
        'document_before'=>$documentBefore,'document_after'=>$documentAfter,'profile_after'=>$profileAfter,
        'service_action'=>$action,'application_data_deleted'=>false,'existing_session_termination_claimed'=>false,
        'privilege_catalog_cleanup_claimed'=>false];

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

f164Report('fb164-account-retirement','Закрыть временный логин и сохранить документы',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До удаления логин работал — да
Документ читателя — ORD-164
Удаление учётной записи завершено — да
Новый вход с прежними реквизитами запрещён — да
Запись профиля отсутствует — да
Документ после удаления логина — ORD-164
Количество в документе — 4
Документ полностью сохранён — да

Полный самостоятельный CLI-пример: одна собственная база, один учебный пользователь, отдельный TCP-сервер. Все исходники и README находятся в архиве. result.html показывает данные и наблюдения, result.json сохраняет их без секретов.

HTML с видимым результатом · JSON с наблюдениями.

Ошибки и границы

Удаление не следует считать отключением каждого уже открытого сеанса, удалением всех прежних SQL GRANT или передачей владения таблицами или процедурами. В примере таблицу создал администратор; мы не удаляем пользователя-владельца схемы и не заявляем универсальное поведение всех видов ресурсов базы. Для временной приостановки современный SQL ALTER USER ... INACTIVE может быть уместнее удаления. Повторное создание прежнего имени требует отдельной ревизии прав: один и тот же текст логина связан с привилегиями прикладной базы.

Автор вопроса пытался управлять паролем embedded-базы и ожидал, что это изменит проверку при локальном открытии файла. Обсуждение на Stack Overflow. Для проверки запрета новых входов используем отдельный сервер и TCP с аутентификацией. Успешное embedded-подключение не выдаём за доказательство правильного или удалённого пароля. Документы перечитываются отдельно от проверки личности.

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

После удаления нужно удалить и документы оператора. Должно ли это происходить скрыто внутри delete_user?

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

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

Три совместимых имени и их реальные задачи

ЗадачаКарточка
Создать отдельную серверную учётную запись и необязательный профиль.fbird_add_user
Сменить пароль или переданные поля существующей учётной записи.fbird_modify_user
Удалить конкретную запись системы безопасности и проверить новый вход.fbird_delete_user

Короткий фрагмент предполагает уже подготовленный $service; полный ZIP содержит всю подготовку, обработку результата и очистку. Историческое имя и каноническое имя ведут к одному механизму, но не освобождают от проверки нужного результата.

Дальше: срок жизни подключений и реакция на изменения

Теперь учётная запись имеет определённую задачу и проверяемый жизненный цикл. Следующий шаг — понять, сколько живёт её подключение и когда приложение должно перечитать изменившиеся данные. Это разобрано в постоянных подключениях и событиях Firebird. Границы SQL-прав можно расширить через роли, отзыв SELECT и разрешённый UPDATE отдельных столбцов.

Если нужна новая административная панель, начните с современного SQL user management и отдельной модели полномочий. Эта глава охватывает три официальных alias-функции в указанном окружении; она не утверждает, что проверены все user manager, версии сервера или сценарии восстановления доступа.

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

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

fbird_add_userСоздать отдельную серверную учётную запись и необязательный профиль.

PHP 5 / PHP 7 < 7.4; отдельное interbase 3.0.1 · interbase

fbird_add_user(resource $service_handle, string $user_name, string $password, string $first_name = ?, string $middle_name = ?, string $last_name = ?): bool

Новой интеграции нужен собственный вход вместо использования административного SYSDBA. fbird_add_user создаёт запись для этого входа и при необходимости заполняет три поля имени. SQL-права на каталог выдаются отдельно. Это совместимое имя ibase_add_user, а не функция регистрации покупателей сайта.

ПараметрСмысл и вход
service_handle: resourceДействующее соединение с менеджером сервисов выбранного экземпляра; административные реквизиты использованы при его открытии.
user_name: stringНовое серверное имя входа. Уже существующая запись не считается запросом на безопасное обновление.
password: stringПароль новой записи, не хеш веб-пароля и не имя переменной строкой. В примерах значение создаётся при запуске.
first_name: string, необязательноИмя профиля; отсутствие отличается от явно переданного пустого значения.
middle_name: string, необязательноСреднее имя/отчество или иная выбранная подпись соответствующего поля.
last_name: string, необязательноФамилия профиля; не назначение роли или права чтения.

1. Учётная запись только для чтения экрана

$started = fbird_add_user($service, $login, $secret);
if ($started !== true) {
    $code = fbird_errcode();
    throw new RuntimeException("Создание не запущено");
}
// Дождаться завершения; отдельно выдать SELECT и проверить вход.

Три значения в скобках передаются функции по порядку. Переменная $started хранит результат, а точка с запятой завершает инструкцию. В полном экране остатков запись создана без дополнительных полей: профиль содержит null, читатель видит 6 и 0, UPDATE получает отказ. Никакого GRANT функция самостоятельно не делает.

2. Учётная запись с читаемым профилем оператора

$started = fbird_add_user($service, $login, $secret, "Mila", "K", "Novak");
// После сервисного завершения прочитать SEC$USERS для этого логина.

Здесь переданы все три поля. История исправления профиля начинает с Mila/K/Novak и показывает, как затем изменить только нужную часть. Одинаковая фамилия у двух записей не делает их одним логином; профиль не заменяет проверку CURRENT_USER. Для отсутствующих полей не подставляйте автоматически пустые строки.

Общая часть контрактаПрактическое значение
Возврат boolОфициально true при успехе, false при отказе. В исходнике 3.0.1 true следует за isc_service_start: код дочитывает сервисное завершение и проверяет нужное состояние, не объявляя одним флагом, что пользователь уже вошёл.
ДиагностикаСохраните fbird_errcode и fbird_errmsg сразу после отказа. Следующая операция способна изменить диагностическое состояние. Неверные PHP-типы/ресурсы также могут дать TypeError; это не строка false, которую надо сравнивать с паролем.
Ресурс сервисаТребуется service_handle выбранного экземпляра, а не SQL-соединение $db. Полномочия администратора прикладной базы не автоматически равны управлению системой безопасности.
Необязательные аргументы= ? в документации означает возможность не передавать аргумент с конца списка. Это не литерал PHP. Пропущенный аргумент не отправляется; явная пустая строка отправляется как значение.
Версииfbird_* и ibase_* разделяют обработчик interbase 3.0.1; метка исторической встроенной поставки не доказывает доступность расширения в любом PHP 8.

У закреплённого драйвера для пользовательской сервисной команды внутренний SPB-буфер равен 128 байтам. Это общий буфер, а не разрешение на 128 символов в каждом поле. В лаборатории используются короткие ASCII-логины и профиль, случайный 24-символьный секрет; обёртка проверяет суммарный размер. Не переносите максимумы SQL-идентификаторов в эту старую сигнатуру и не включайте в значения нулевые байты. SQL charset не является параметром кодировки Services API.

Нативный отказ некоторых сервисных веток 3.0.1 инвалидирует handle. Приложенная обёртка сохраняет причину и убирает ссылку, чтобы finally не освобождал тот же ресурс повторно. В новой попытке нужен новый service; автоматически повторять административную команду без проверки результата нельзя. Разбор этого поведения есть в сервисной диагностике и восстановлении.

Старый API использует настроенный user manager и не принимает USING PLUGIN, ACTIVE/INACTIVE, TAGS, роль или путь к security database. Одно имя под разными plugins может обозначать разные записи. Для современных вариантов явного выбора используйте SQL-управление пользователями Firebird. При переходе на SQL отдельно учитывается commit пользовательского DDL; это не объяснение сервисного вызова через чужую SQL-транзакцию.

Следующий шаг после создания — ограничить операции через GRANT и роли. Для завершения временной интеграции есть fbird_delete_user; поведение канонического имени раскрывает полный разбор ibase_add_user.

fbird_modify_userСменить пароль или переданные поля существующей учётной записи.

PHP 5 / PHP 7 < 7.4; отдельное interbase 3.0.1 · interbase

fbird_modify_user(resource $service_handle, string $user_name, string $password, string $first_name = ?, string $middle_name = ?, string $last_name = ?): bool

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

ПараметрСмысл и отличие от создания
service_handle: resourceДействующий ресурс менеджера выбранного экземпляра.
user_name: stringИмя уже существующей записи, которую требуется изменить. Это выбор цели, не новое имя.
password: stringОбязательный пароль, который записывается этой операцией. При исправлении только профиля старый API всё равно требует известный пароль; null не означает «оставить как есть».
first_name: string, необязательноНовое имя, если аргумент передан. При отсутствии поле не включается в сервисную команду.
middle_name: string, необязательноНовое среднее имя; "" означает передать пустую строку, а не пропустить параметр.
last_name: string, необязательноНовая фамилия. Чтобы передать её позиционно, передают и предшествующие поля.

1. Сменить секрет, сохранив профиль

$started = fbird_modify_user($service, $login, $newSecret);
if ($started !== true) { throw new RuntimeException("Изменение не запущено"); }
// После завершения — разные новые входы со старым и новым секретами.

Полей имени здесь нет. Рабочая ротация сохраняет Mila/K/Novak, подтверждает отказ старого секрета и чтение товара под новым. Старый активный attachment не является повторной проверкой пароля. Нужна граница нового входа, а в закреплённом PHP-драйвере — ещё и защита от переиспользования кешированного ресурса.

2. Исправить имя, затем отдельно очистить среднее имя

$started = fbird_modify_user($service, $login, $knownSecret, "Mia");
// После завершения и чтения: Mia / K / Novak.
$started = fbird_modify_user($service, $login, $knownSecret, "Mia", "");
// После завершения и чтения: Mia / пустая строка / Novak.

Это два последовательных изменения, между которыми полный пример профиля проверяет состояние. Нельзя подменять отсутствие полей формы пустотой. В выбранном Firebird 5.0.4/Srp пустой middle сохраняется как "", а пропущенный last остаётся Novak; другое окружение требует своей проверки. Для изменения профиля без знания секрета разумнее современный SQL ALTER USER без PASSWORD.

Общая часть контрактаПрактическое значение
Возврат boolОфициально true при успехе, false при отказе. В исходнике 3.0.1 true следует за isc_service_start: код дочитывает сервисное завершение и проверяет нужное состояние, не объявляя одним флагом, что пользователь уже вошёл.
ДиагностикаСохраните fbird_errcode и fbird_errmsg сразу после отказа. Следующая операция способна изменить диагностическое состояние. Неверные PHP-типы/ресурсы также могут дать TypeError; это не строка false, которую надо сравнивать с паролем.
Ресурс сервисаТребуется service_handle выбранного экземпляра, а не SQL-соединение $db. Полномочия администратора прикладной базы не автоматически равны управлению системой безопасности.
Необязательные аргументы= ? в документации означает возможность не передавать аргумент с конца списка. Это не литерал PHP. Пропущенный аргумент не отправляется; явная пустая строка отправляется как значение.
Версииfbird_* и ibase_* разделяют обработчик interbase 3.0.1; метка исторической встроенной поставки не доказывает доступность расширения в любом PHP 8.

У закреплённого драйвера для пользовательской сервисной команды внутренний SPB-буфер равен 128 байтам. Это общий буфер, а не разрешение на 128 символов в каждом поле. В лаборатории используются короткие ASCII-логины и профиль, случайный 24-символьный секрет; обёртка проверяет суммарный размер. Не переносите максимумы SQL-идентификаторов в эту старую сигнатуру и не включайте в значения нулевые байты. SQL charset не является параметром кодировки Services API.

Нативный отказ некоторых сервисных веток 3.0.1 инвалидирует handle. Приложенная обёртка сохраняет причину и убирает ссылку, чтобы finally не освобождал тот же ресурс повторно. В новой попытке нужен новый service; автоматически повторять административную команду без проверки результата нельзя. Разбор этого поведения есть в сервисной диагностике и восстановлении.

Старый API использует настроенный user manager и не принимает USING PLUGIN, ACTIVE/INACTIVE, TAGS, роль или путь к security database. Одно имя под разными plugins может обозначать разные записи. Для современных вариантов явного выбора используйте SQL-управление пользователями Firebird. При переходе на SQL отдельно учитывается commit пользовательского DDL; это не объяснение сервисного вызова через чужую SQL-транзакцию.

Самостоятельного аргумента «новый логин» здесь нет. Активность записи, plugin и административные роли тоже не передаются этой функцией. Канонический разбор: полный разбор ibase_modify_user. Полное прекращение новых входов разобрано в закрытии временной записи.

fbird_delete_userУдалить конкретную запись системы безопасности и проверить новый вход.

PHP 5 / PHP 7 < 7.4; отдельное interbase 3.0.1 · interbase

fbird_delete_user(resource $service_handle, string $user_name): bool

Временная выгрузка закончилась: надо закрыть её будущие подключения и сохранить документы. fbird_delete_user удаляет выбранную серверную запись. Он не удаляет строки бизнес-таблиц и не подменяет отзыв отдельных прав. Это alias ibase_delete_user.

Параметр / результатКонтракт
service_handle: resourceРесурс менеджера, уже открытый с необходимыми полномочиями.
user_name: stringТочное имя удаляемой записи. Пароль этой записи не передаётся.
boolОфициально true при успехе, false при отказе. Проверка после завершения должна подтвердить отсутствие нужной записи и отказ нового входа.

1. Завершить доступ временной интеграции

$started = fbird_delete_user($service, $temporaryLogin);
if ($started !== true) { throw new RuntimeException("Удаление не запущено"); }
// Затем: запись отсутствует, новый вход отклонён, документ перечитан.

В полном закрытии доступа до удаления виден ORD-164/4. После операции новый отдельный PHP-процесс получает отказ входа, но администратор читает тот же документ. Это две независимые проверяемые части требования. Удаление бизнес-данных потребовало бы собственного SQL и отдельного решения.

2. Убрать одноразового читателя после получения отчёта

try {
    // Создать собственный временный логин и получить ограниченный отчёт.
} finally {
    // Закрыть его SQL-ресурсы и проверить принадлежность имени этому запуску.
    $started = fbird_delete_user($service, $ownedReportLogin);
    // Проверить сервисное завершение и отсутствие записи.
}

В демонстрации ограниченного читателя HTML остаётся доступным после удаления собственного тестового логина: отчёт больше не должен держать бессрочный доступ к серверу. Приложенный lab.php реализует проверку владения и результата очистки. Это содержимое finally, а не слепой вызов delete_user для любого имени из HTTP-запроса; сбой очистки нельзя скрывать флагом «успешно».

Общая часть контрактаПрактическое значение
Возврат boolОфициально true при успехе, false при отказе. В исходнике 3.0.1 true следует за isc_service_start: код дочитывает сервисное завершение и проверяет нужное состояние, не объявляя одним флагом, что пользователь уже вошёл.
ДиагностикаСохраните fbird_errcode и fbird_errmsg сразу после отказа. Следующая операция способна изменить диагностическое состояние. Неверные PHP-типы/ресурсы также могут дать TypeError; это не строка false, которую надо сравнивать с паролем.
Ресурс сервисаТребуется service_handle выбранного экземпляра, а не SQL-соединение $db. Полномочия администратора прикладной базы не автоматически равны управлению системой безопасности.
Необязательные аргументы= ? в документации означает возможность не передавать аргумент с конца списка. Это не литерал PHP. Пропущенный аргумент не отправляется; явная пустая строка отправляется как значение.
Версииfbird_* и ibase_* разделяют обработчик interbase 3.0.1; метка исторической встроенной поставки не доказывает доступность расширения в любом PHP 8.

У закреплённого драйвера для пользовательской сервисной команды внутренний SPB-буфер равен 128 байтам. Это общий буфер, а не разрешение на 128 символов в каждом поле. В лаборатории используются короткие ASCII-логины и профиль, случайный 24-символьный секрет; обёртка проверяет суммарный размер. Не переносите максимумы SQL-идентификаторов в эту старую сигнатуру и не включайте в значения нулевые байты. SQL charset не является параметром кодировки Services API.

Нативный отказ некоторых сервисных веток 3.0.1 инвалидирует handle. Приложенная обёртка сохраняет причину и убирает ссылку, чтобы finally не освобождал тот же ресурс повторно. В новой попытке нужен новый service; автоматически повторять административную команду без проверки результата нельзя. Разбор этого поведения есть в сервисной диагностике и восстановлении.

Старый API использует настроенный user manager и не принимает USING PLUGIN, ACTIVE/INACTIVE, TAGS, роль или путь к security database. Одно имя под разными plugins может обозначать разные записи. Для современных вариантов явного выбора используйте SQL-управление пользователями Firebird. При переходе на SQL отдельно учитывается commit пользовательского DDL; это не объяснение сервисного вызова через чужую SQL-транзакцию.

У официальной сигнатуры и фактического rs-парсера interbase 3.0.1 два аргумента. В arginfo этой версии осталось ошибочное минимальное число 3; не придумывайте третий пароль на основании метаданных анализатора. Рабочие вызовы используют два аргумента. Публикуемый результат отдельно фиксирует завершение и состояние, а не полагается на эту метаинформацию.

Удаление не обещает остановить все существующие сеансы. Оно также не является универсальной очисткой прежних прав и владения схемой. При нескольких user manager одноимённые записи различаются по plugin; данный PHP API не позволяет выбрать его аргументом. Для управляемой временной блокировки современный SQL INACTIVE и для явного удаления USING PLUGIN решают иные, более точные задачи. Канонический разбор: полный разбор ibase_delete_user.

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

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

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

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

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