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

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

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

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

Глава 72 · После подключения, запросов и транзакций Firebird

Пользователи Firebird: создание, смена пароля и проверяемое удаление доступа

Шесть самостоятельных сценариев на отдельном TCP-сервере: новая учётная запись, профиль, смена секрета, отсутствие и пустота полей, удаление и замена оператора.

Логин на сервере, профиль и право читать каталог

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

Что меняется и что проверяем
  1. Администратор → выбранная security database → создать или изменить учётную запись
  2. Новое TCP-подключение → проверка логина и пароля
  3. CURRENT_USER → кто именно вошёл
  4. Отдельный GRANT в прикладной базе → какие данные разрешены
  5. Удаление учётной записи → новый вход отклонён; другие собственные записи сохранены

Полезная аналогия — пропуск в здание и ключи от помещений. Пропуск подтверждает личность, ключи определяют доступ к комнатам. Учётная запись Firebird отвечает за вход; SQL-права определяют доступ к объектам конкретной базы. Аналогия не описывает криптографию или сетевой протокол и не означает, что один вход автоматически выдаёт все права.

СущностьГде используетсяЧто она не заменяет
Пользователь FirebirdВ security database выбранного экземпляра/конфигурации; сервер аутентифицирует подключение.Не строка users вашей CRM и не PHP-сессия посетителя.
SQL-права и ролиВ прикладной базе: кому разрешён SELECT, UPDATE и другие действия.Не создают пароль и не гарантируют успешный вход.
First/middle/last nameДополнительные сведения профиля серверной учётной записи.Не разрешения и не доказательство личности посетителя сайта.
Пользователь веб-приложенияВ вашей модели пользователей, сессий и бизнес-прав.Не обязан иметь персональную учётную запись Firebird.

Обычно PHP-приложение подключается к базе под ограниченной служебной учётной записью, а посетителей проверяет само. Не вызывайте add_user из публичной регистрации каждого покупателя. Для импорта, панели администратора или обслуживания отдельной интеграции серверная учётная запись бывает уместна. Выбор набора SQL-прав подробно разобран в группировке прав и ролей.

Страницы PHP Manual исторически помечены PHP 5 / PHP 7 до 7.4. В примерах используется отдельное расширение FirebirdSQL interbase 3.0.1 в PHP 8.2.35 и Firebird 5.0.4. Кроме того, именно управление пользователями через Services API считается устаревшим в Firebird начиная с версии 3. Для новой системы предпочтительны SQL-команды CREATE USER, ALTER USER и DROP USER. Мы сохраняем полный контракт трёх функций официального PHP-справочника, показываем их реальные границы и указываем современный путь, а не выдаём исторический API за универсальный выбор.

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

Примеры работают через TCP на loopback отдельного учебного экземпляра Firebird с собственной security database. Это существенно: embedded-подключение из прошлых глав само по себе не доказывает проверку пароля сервером. Рабочие экземпляры, их пользователи и конфигурация не меняются. Имена учебных баз и пользователей генерируются заново, регистрируются до операции и удаляются в finally только после проверки принадлежности запуску.

В каждом ZIP находятся полный example.php, lab.php, README и синтетические данные каталога. f160* — открытые обёртки лаборатории, а не функции PHP: они проверяют выбранный экземпляр, создают собственные объекты, сохраняют диагностику сразу, дожидаются сервисного результата и освобождают ресурсы. Все реальные ibase_* вызовы доступны в lab.php. Секреты генерируются при запуске либо читаются из частного окружения; публичный JSON не содержит их значений.

Каждая проверка пароля использует новое подключение после закрытия прежнего ресурса. Если оставить старое соединение и выполнить на нём ещё один SELECT, это не будет проверкой нового пароля. Одноимённые PHP-переменные тоже не гарантируют независимость подключения: обычный connect может переиспользовать совпадающее соединение в текущем выполнении. Порядок завершения является частью примера.

Создать отдельную учётную запись и действительно войти

Импорту нужна собственная учётная запись, чтобы не использовать административный пароль. Сначала докажем только её существование и подлинный вход: сервер проверит секрет по TCP, а SELECT вернёт личность вошедшего пользователя.

Как меняется доступ
  1. f160Password() → отдельный секрет только в памяти
  2. ibase_add_user(service, новый логин, секрет) → завершённое сервисное действие
  3. Закрытая прежняя сессия → новое TCP-подключение с этим секретом
  4. SELECT TRIM(CURRENT_USER) → сравнение с ожидаемым логином; результат true

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

$created = ibase_add_user($service, $login, $password);
// Затем новое подключение с $login/$password и SELECT CURRENT_USER.

$service обозначает ресурс менеджера выбранного сервера. $login и $password — передаваемые значения, а $created получает результат вызова. CURRENT_USER — SQL-значение личности текущего соединения. RDB$DATABASE содержит одну служебную строку и позволяет проверить соединение без чтения каталога. TRIM убирает пробелы фиксированной строки. Полная программа завершает сервисную задачу и проверяет фактический вход.

Что увидим после выполнения

НаблюдениеРезультат
Сервисное создание завершенода
Новый TCP-вход выполненда
CURRENT_USER совпадает с новым логиномда
Учётная запись найдена в security databaseда
Создать отдельную учётную запись и действительно войтиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f160Config();$service=f160Service();$runtime=f160Runtime($service);

    $loginAs = static function(string $database,string $user,string $password): array {
        $connection=f160ConnectUser($database,$user,$password);
        try {
            $rows=f160Read($connection,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE');
            return ['connected'=>true,'identity_matches'=>($rows[0]['login']??null)===$user];
        } finally { f160Close($connection); }
    };
    $tryLogin = static function(string $database,string $user,string $password): array {
        $call=f160TryConnectUser($database,$user,$password);
        $connection=$call['value'];$call['value']=null;
        $connected=is_resource($connection);
        if($connected)f160Close($connection);
        return ['connected'=>$connected,'warning'=>$call['warning'],'code'=>$call['code'],
            'message_present'=>$call['message_present']];
    };

    $database=f160CreateDatabase($service,$owned,'user_create');
    $user=f160NewUser($owned,'import');
    $password=f160Password();
    $action=f160AddUser($service,$user,$password);
    $identity=$loginAs($database,$user,$password);
    $db=f160Connect($database);
    $profile=f160UserProfile($db,$user);f160Commit($db);
    $rows=[['Сервисное создание завершено',$action['completed']],
        ['Новый TCP-вход выполнен',$identity['connected']],
        ['CURRENT_USER совпадает с новым логином',$identity['identity_matches']],
        ['Учётная запись найдена в security database',$profile!==null]];
    $checks=['create_completed'=>$action['completed'],'fresh_login'=>$identity['connected'],
        'identity_matches'=>$identity['identity_matches'],'profile_exists'=>$profile!==null];
    $observations=['transport'=>'tcp_loopback_authenticated','account'=>$identity,
        'profile_found'=>$profile!==null,'service_action'=>$action];

} finally {
    if(is_resource($db)){f160Close($db);}
    if(is_resource($service)){f160Detach($service);}
    $removed=f160Cleanup($owned);
}

f160Report('fb160-user-create','Создать отдельную учётную запись и действительно войти',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Сервисное создание завершено — да
Новый TCP-вход выполнен — да
CURRENT_USER совпадает с новым логином — да
Учётная запись найдена в security database — да

Самостоятельный CLI-пример работает с одной собственной базой и случайными учебными пользователями отдельного TCP-сервера. Полный код, lab.php и README приложены. После завершения откройте result.html; result.json содержит те же результаты и наблюдения без паролей.

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

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

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

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

Замените проверку CURRENT_USER чтением имени своей базы через RDB$GET_CONTEXT. Какие два разных факта будут подтверждаться?

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

CURRENT_USER подтверждает личность соединения; сведения о базе — выбранное назначение. Оба наблюдения нужны при нескольких экземплярах, но ни одно само по себе не доказывает право читать таблицу заказов.

Создать оператора с именем и отличить профиль от прав

Администратору нужно понимать, кому предназначен технический логин. При создании передадим имя Anna, среднее имя M и фамилию Reader, затем прочитаем профиль ровно этой учётной записи. Одновременно подтвердим, что профиль не помешал новому входу.

Как меняется доступ
  1. Три обязательных значения + Anna/M/Reader → ibase_add_user
  2. Сервис сохраняет запись выбранного user manager
  3. SEC$USERS WHERE SEC$USER_NAME = ? → только профиль созданного оператора
  4. Отдельный вход → CURRENT_USER соответствует логину; фамилия не выдаёт права

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

$created = ibase_add_user($service, $login, $password, "Anna", "M", "Reader");

Аргументы идут по порядку: сервис, логин, пароль, first_name, middle_name, last_name. Кавычки обозначают строки, запятые разделяют аргументы. Необязательное поле означает, что его можно не передавать в конце списка; позицию фамилии нельзя заменить именем. f160UserProfile выполняет параметризованный SELECT из SEC$USERS с фильтром по одному собственному логину.

Что увидим после выполнения

НаблюдениеРезультат
ИмяAnna
Среднее имяM
ФамилияReader
Новый вход под этой учётной записьюда
Создать оператора с именем и отличить профиль от правСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f160Config();$service=f160Service();$runtime=f160Runtime($service);

    $loginAs = static function(string $database,string $user,string $password): array {
        $connection=f160ConnectUser($database,$user,$password);
        try {
            $rows=f160Read($connection,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE');
            return ['connected'=>true,'identity_matches'=>($rows[0]['login']??null)===$user];
        } finally { f160Close($connection); }
    };
    $tryLogin = static function(string $database,string $user,string $password): array {
        $call=f160TryConnectUser($database,$user,$password);
        $connection=$call['value'];$call['value']=null;
        $connected=is_resource($connection);
        if($connected)f160Close($connection);
        return ['connected'=>$connected,'warning'=>$call['warning'],'code'=>$call['code'],
            'message_present'=>$call['message_present']];
    };

    $database=f160CreateDatabase($service,$owned,'user_profile');
    $user=f160NewUser($owned,'operator');$password=f160Password();
    $action=f160AddUser($service,$user,$password,['Anna','M','Reader']);
    $identity=$loginAs($database,$user,$password);
    $db=f160Connect($database);
    $profile=f160UserProfile($db,$user);f160Commit($db);
    $rows=[['Имя',$profile['first']??null],['Среднее имя',$profile['middle']??null],
        ['Фамилия',$profile['last']??null],['Новый вход под этой учётной записью',$identity['identity_matches']]];
    $checks=['profile_name'=>($profile['first']??null)==='Anna',
        'profile_middle'=>($profile['middle']??null)==='M',
        'profile_last'=>($profile['last']??null)==='Reader','profile_login'=>$identity['identity_matches']];
    $observations=['profile'=>$profile,'account'=>$identity,'service_action'=>$action];

} finally {
    if(is_resource($db)){f160Close($db);}
    if(is_resource($service)){f160Detach($service);}
    $removed=f160Cleanup($owned);
}

f160Report('fb160-user-profile','Создать оператора с именем и отличить профиль от прав',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Имя — Anna
Среднее имя — M
Фамилия — Reader
Новый вход под этой учётной записью — да

Самостоятельный CLI-пример работает с одной собственной базой и случайными учебными пользователями отдельного TCP-сервера. Полный код, lab.php и README приложены. После завершения откройте result.html; result.json содержит те же результаты и наблюдения без паролей.

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

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

SEC$USERS относится к управлению пользователями, а RDB$USER_PRIVILEGES — к SQL-правам; список GRANT не является каталогом всех учётных записей. Пример намеренно использует короткие ASCII-поля: у сервисной функции нет отдельного аргумента charset, и настройки UTF8 SQL-соединения не являются обещанием кодировки Service API.

Список получателей GRANT не показывает всех серверных пользователей, поэтому административный инструмент получает неполный перечень. Реальный вопрос на Stack Overflow. Объясняем разные источники данных: security database и SEC$USERS для профиля, RDB$USER_PRIVILEGES для прав конкретной базы. В примере запрашивается ровно одна собственная запись, а не весь список учётных записей сервера.

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

Создайте пользователя с одним дополнительным полем first_name. Как увидеть, что остальные поля действительно не были заданы?

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

Передайте ровно четыре аргумента и прочитайте собственный профиль. Сравнивайте null и пустую строку строго; не объединяйте их через empty(). Для отображения можно явно написать «не задано», сохранив исходный тип в JSON.

Сменить пароль и проверить оба варианта новым входом

Интеграции меняют пароль после планового обновления секрета. Булево true от modify_user недостаточно: нужно доказать, что старый пароль перестал открывать новые подключения, новый работает, а профиль оператора сохранился.

Как меняется доступ
  1. Процесс before: исходный секрет → TCP-вход → личность и attachment → процесс завершён
  2. ibase_modify_user(service, логин, новый секрет) → имя не передаём
  3. Отдельный процесс old: старый секрет → отказ; код сохранён немедленно
  4. Отдельный процесс new: новый секрет → та же личность, другой attachment; профиль сохранён

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

$changed = ibase_modify_user($service, $login, $newPassword);

Здесь третий аргумент становится новым паролем. First/middle/last отсутствуют в вызове: драйвер не посылает их как пустые строки. $authenticateFresh запускает auth-child.php отдельным процессом, передаёт секрет через STDIN и дожидается полного завершения. Child один раз вызывает ibase_connect и возвращает только безопасные признаки входа. CURRENT_CONNECTION различает серверные подключения, NETWORK_PROTOCOL подтверждает TCPv4. Исходный и новый успешные входы имеют разные номера; старый секрет после изменения не получает подключения.

Что увидим после выполнения

НаблюдениеРезультат
До изменения старый пароль работалда
После изменения старый пароль отклонёнда
Новый пароль принятда
Не переданные поля профиля сохраненыда
Сменить пароль и проверить оба варианта новым входомСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f160Config();$service=f160Service();$runtime=f160Runtime($service);

    $loginAs = static function(string $database,string $user,string $password): array {
        $connection=f160ConnectUser($database,$user,$password);
        try {
            $rows=f160Read($connection,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE');
            return ['connected'=>true,'identity_matches'=>($rows[0]['login']??null)===$user];
        } finally { f160Close($connection); }
    };
    $tryLogin = static function(string $database,string $user,string $password): array {
        $call=f160TryConnectUser($database,$user,$password);
        $connection=$call['value'];$call['value']=null;
        $connected=is_resource($connection);
        if($connected)f160Close($connection);
        return ['connected'=>$connected,'warning'=>$call['warning'],'code'=>$call['code'],
            'message_present'=>$call['message_present']];
    };

    $authChildrenStopped=true;
    $authenticateFresh=static function(string $phase,string $database,string $user,string $password) use (&$authChildrenStopped): array {
        if(!in_array($phase,['before','old','new'],true))throw new InvalidArgumentException('Неизвестный этап проверки.');
        $state=&f160State();f160UserIdentifier($user);
        if(!isset($state['databases'][$database])||isset($state['removed'][$database])||!is_file(f160Path($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-firebird160-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;
            $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;
                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=f160CreateDatabase($service,$owned,'password');
    $user=f160NewUser($owned,'integration');
    $oldPassword=f160Password();$newPassword=f160Password();
    f160AddUser($service,$user,$oldPassword,['Anna','M','Reader']);
    $before=$authenticateFresh('before',$database,$user,$oldPassword);
    $action=f160ModifyUser($service,$user,$newPassword);
    $oldAttempt=$authenticateFresh('old',$database,$user,$oldPassword);
    $after=$authenticateFresh('new',$database,$user,$newPassword);
    $db=f160Connect($database);$profile=f160UserProfile($db,$user);f160Commit($db);
    $preserved=($profile['first']??null)==='Anna'&&($profile['middle']??null)==='M'&&($profile['last']??null)==='Reader';
    $rows=[['До изменения старый пароль работал',$before['identity_matches']],
        ['После изменения старый пароль отклонён',!$oldAttempt['connected']],
        ['Новый пароль принят',$after['identity_matches']],['Не переданные поля профиля сохранены',$preserved]];
    $checks=['before_login'=>$before['identity_matches'],'old_password_rejected'=>!$oldAttempt['connected'],
        'rejection_diagnosed'=>$oldAttempt['code']===-902&&$oldAttempt['warning']&&$oldAttempt['message_present'],
        'new_password_accepted'=>$after['identity_matches'],'omitted_profile_preserved'=>$preserved,
        'separate_tcp_attachments'=>$before['attachment_id']!==$after['attachment_id']
            &&$before['network_protocol']==='TCPv4'&&$after['network_protocol']==='TCPv4'];
    $observations=['before'=>$before,'old_password_attempt'=>$oldAttempt,'after'=>$after,
        'profile'=>$profile,'service_action'=>$action,'authentication_checks'=>['before'=>$before,'old'=>$oldAttempt,'new'=>$after]];

} finally {
    if(is_resource($db)){f160Close($db);}
    if(is_resource($service)){f160Detach($service);}
    if(isset($authChildrenStopped)&&!$authChildrenStopped){throw new RuntimeException("Проверка входа ещё работает; удаление базы запрещено.");}
    $removed=f160Cleanup($owned);
}

f160Report('fb160-user-password','Сменить пароль и проверить оба варианта новым входом',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До изменения старый пароль работал — да
После изменения старый пароль отклонён — да
Новый пароль принят — да
Не переданные поля профиля сохранены — да

Самостоятельный CLI-пример работает с одной собственной базой и случайными учебными пользователями отдельного TCP-сервера. Полный код, lab.php и README приложены. После завершения откройте result.html; result.json содержит те же результаты и наблюдения без паролей.

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

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

Действующее старое соединение может продолжать работу; смена пароля не равна принудительному завершению всех сеансов. В interbase 3.0.1 повторный connect с теми же аргументами внутри процесса может переиспользовать кешированный ресурс, а explicit close зависит от счётчика ссылок/default_link. Поэтому формального вызова close недостаточно для доказательства повторной аутентификации; здесь выбрана явная граница отдельных процессов. Это не повторные попытки и не ожидание случайной задержки. Исходник закреплённого драйвера: подключение и закрытие. Для приложения нужно согласованно обновить хранилище секрета и подключения. Хеш или зашифрованное поле чужой программы не заменяет исходный пароль.

У старой программы сохранилось преобразованное представление пароля, но подключение из PHP с этим значением не работает. Реальный вопрос на Stack Overflow. Для нового соединения нужен настоящий секрет поддерживаемого механизма аутентификации. Наш сценарий использует отдельную учётную запись и разрешённую смену пароля, затем проверяет оба новых входа; попытки восстановить чужой секрет из старого представления в руководство не переносятся.

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

Добавьте независимую проверку профиля до изменения и сравните только first/middle/last после смены пароля.

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

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

Пропустить поле или очистить его: два разных изменения

В панели администратора исправили имя оператора, а потом отдельно очистили среднее имя. Если превращать все отсутствующие поля формы в пустые строки, можно незаметно стереть остальной профиль. Покажем оба намерения явно: изменить Anna на Ann, сохранив M/Reader, а затем передать пустой middle и оставить Reader.

Как меняется доступ
  1. Исходный профиль Anna / M / Reader
  2. modify_user(service, логин, тот же пароль, Ann) → Ann / M / Reader
  3. modify_user(service, логин, тот же пароль, Ann, пустая строка) → Ann / строка длины 0 / Reader
  4. Свежий вход с тем же паролем → true; JSON сохраняет отличие строки от null

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

$ok = ibase_modify_user($service, $login, $password, "Ann");
// Чтобы явно очистить middle_name, передаём пятый аргумент:
$ok = ibase_modify_user($service, $login, $password, "Ann", "");

Позиционные аргументы различают «не передано» и «передана строка длины ноль». В первом вызове middle/last отсутствуют; во втором middle_name передан как "", а last_name отсутствует. Пустая строка является значением, null означает отсутствие значения — это разные PHP-типы. Полный код сравнивает === и сохраняет все три снимка, завершая читающую транзакцию перед следующим наблюдением.

Что увидим после выполнения

НаблюдениеРезультат
Исходное имяAnna
Новое имяAnn
Пропущенные middle/last сохраненыда
После явного пустого middle"" (пустая строка)
Фамилия после обоих измененийReader
Вход с тем же паролем работаетда
Пропустить поле или очистить его: два разных измененияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f160Config();$service=f160Service();$runtime=f160Runtime($service);

    $loginAs = static function(string $database,string $user,string $password): array {
        $connection=f160ConnectUser($database,$user,$password);
        try {
            $rows=f160Read($connection,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE');
            return ['connected'=>true,'identity_matches'=>($rows[0]['login']??null)===$user];
        } finally { f160Close($connection); }
    };
    $tryLogin = static function(string $database,string $user,string $password): array {
        $call=f160TryConnectUser($database,$user,$password);
        $connection=$call['value'];$call['value']=null;
        $connected=is_resource($connection);
        if($connected)f160Close($connection);
        return ['connected'=>$connected,'warning'=>$call['warning'],'code'=>$call['code'],
            'message_present'=>$call['message_present']];
    };

    $database=f160CreateDatabase($service,$owned,'profile_edit');
    $user=f160NewUser($owned,'profile_edit');$password=f160Password();
    f160AddUser($service,$user,$password,['Anna','M','Reader']);
    $db=f160Connect($database);
    $before=f160UserProfile($db,$user);f160Commit($db);
    $firstAction=f160ModifyUser($service,$user,$password,['Ann']);
    $afterFirst=f160UserProfile($db,$user);f160Commit($db);
    $emptyAction=f160ModifyUser($service,$user,$password,['Ann','']);
    $afterEmpty=f160UserProfile($db,$user);f160Commit($db);
    $identity=$loginAs($database,$user,$password);
    $omittedPreserved=($afterFirst['middle']??null)==='M'&&($afterFirst['last']??null)==='Reader';
    $emptyString=array_key_exists('middle',$afterEmpty)&&$afterEmpty['middle']==='';
    $middleLabel=$emptyString?'"" (пустая строка)':'другое значение';
    $rows=[['Исходное имя',$before['first']??null],['Новое имя',$afterFirst['first']??null],
        ['Пропущенные middle/last сохранены',$omittedPreserved],
        ['После явного пустого middle',$middleLabel],['Фамилия после обоих изменений',$afterEmpty['last']??null],
        ['Вход с тем же паролем работает',$identity['identity_matches']]];
    $checks=['initial_profile'=>($before['first']??null)==='Anna'&&($before['middle']??null)==='M',
        'first_changed'=>($afterFirst['first']??null)==='Ann','omitted_fields_preserved'=>$omittedPreserved,
        'empty_is_string'=>$emptyString,'last_preserved'=>($afterEmpty['last']??null)==='Reader',
        'password_still_valid'=>$identity['identity_matches']];
    $observations=['before'=>$before,'after_first_only'=>$afterFirst,'after_explicit_empty'=>$afterEmpty,
        'account'=>$identity,'service_actions'=>[$firstAction,$emptyAction]];

} finally {
    if(is_resource($db)){f160Close($db);}
    if(is_resource($service)){f160Detach($service);}
    $removed=f160Cleanup($owned);
}

f160Report('fb160-user-profile-edit','Пропустить поле или очистить его: два разных изменения',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Исходное имя — Anna
Новое имя — Ann
Пропущенные middle/last сохранены — да
После явного пустого middle — "" (пустая строка)
Фамилия после обоих изменений — Reader
Вход с тем же паролем работает — да

Самостоятельный CLI-пример работает с одной собственной базой и случайными учебными пользователями отдельного TCP-сервера. Полный код, lab.php и README приложены. После завершения откройте result.html; result.json содержит те же результаты и наблюдения без паролей.

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

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

Пароль в этом PHP API обязателен даже при правке профиля. Если его не следует знать администратору формы, используйте современный SQL ALTER USER с нужными свойствами и без PASSWORD. Не применяйте empty() для доказательства различия "" и null: она сочтёт оба значения пустыми. Поведение другого user manager нужно проверять отдельно.

Профиль с не-ASCII буквой не создаётся старым интерфейсом управления пользователями, хотя ASCII-профиль работает. Реальный вопрос на Stack Overflow. Ситуация исходного вопроса относится к старому C API; она не доказывает поведение любого PHP-драйвера. Наш разбор отделяет SQL charset от сервисного буфера, проверяет короткие ASCII-поля в закреплённой версии и указывает SQL ALTER USER как современный интерфейс, где кодировка соединения задана явно.

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

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

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

Храните намерение отдельно от строки. При «не изменять» не посылайте изменение, при «очистить» явно задайте пустое значение, при «задать» передайте проверенную строку. Позиционное старое API затрудняет частичную правку дальних полей, поэтому для новой панели уместнее SQL ALTER USER с отдельными именованными свойствами.

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

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

Как меняется доступ
  1. Собственная временная запись → успешный вход → закрытие соединения
  2. ibase_delete_user(service, точный логин) → сервисное завершение
  3. SEC$USERS WHERE имя = ? → ни одной строки
  4. Новое TCP-подключение со старыми реквизитами → отказ, без публикации секрета

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

$deleted = ibase_delete_user($service, $temporaryLogin);

У delete_user два аргумента: сервис и логин. Пароль самого удаляемого пользователя не нужен; административные полномочия уже принадлежат сервисному соединению. Это не запрос DELETE FROM users вашего сайта. В полной программе отдельная запись владения защищает от удаления произвольного существующего логина.

Что увидим после выполнения

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

    $loginAs = static function(string $database,string $user,string $password): array {
        $connection=f160ConnectUser($database,$user,$password);
        try {
            $rows=f160Read($connection,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE');
            return ['connected'=>true,'identity_matches'=>($rows[0]['login']??null)===$user];
        } finally { f160Close($connection); }
    };
    $tryLogin = static function(string $database,string $user,string $password): array {
        $call=f160TryConnectUser($database,$user,$password);
        $connection=$call['value'];$call['value']=null;
        $connected=is_resource($connection);
        if($connected)f160Close($connection);
        return ['connected'=>$connected,'warning'=>$call['warning'],'code'=>$call['code'],
            'message_present'=>$call['message_present']];
    };

    $database=f160CreateDatabase($service,$owned,'user_delete');
    $user=f160NewUser($owned,'temporary');$password=f160Password();
    f160AddUser($service,$user,$password,['Anna']);
    $before=$loginAs($database,$user,$password);
    // Внешние SQL-подключения закрыты; helper может проверить SEC$USERS своим соединением.
    $action=f160DeleteUser($service,$user);
    $attempt=$tryLogin($database,$user,$password);
    $db=f160Connect($database);$profile=f160UserProfile($db,$user);f160Commit($db);
    $rows=[['До удаления вход работал',$before['identity_matches']],
        ['Удаление завершено',$action['completed']],['Профиль отсутствует',$profile===null],
        ['Новый вход отклонён',!$attempt['connected']]];
    $checks=['before_delete_login'=>$before['identity_matches'],'delete_completed'=>$action['completed'],
        'profile_absent'=>$profile===null,'deleted_login_rejected'=>!$attempt['connected'],
        'delete_rejection_diagnosed'=>$attempt['code']!==0&&$attempt['message_present']];
    $observations=['before'=>$before,'profile_after'=>$profile,'login_after'=>$attempt,'service_action'=>$action];

} finally {
    if(is_resource($db)){f160Close($db);}
    if(is_resource($service)){f160Detach($service);}
    $removed=f160Cleanup($owned);
}

f160Report('fb160-user-delete','Удалить временный доступ и подтвердить отказ нового входа',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До удаления вход работал — да
Удаление завершено — да
Профиль отсутствует — да
Новый вход отклонён — да

Самостоятельный CLI-пример работает с одной собственной базой и случайными учебными пользователями отдельного TCP-сервера. Полный код, lab.php и README приложены. После завершения откройте result.html; result.json содержит те же результаты и наблюдения без паролей.

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

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

Операция проверяет запрет новых входов и не обещает отключить уже работающие сеансы. Если нужен временный запрет с сохранением профиля, рассмотрите SQL ALTER USER ... INACTIVE и возможности выбранного user manager. Проверку отсутствия нельзя выполнять по случайному совпадению отображаемого имени.

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

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

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

Успех требует завершённого действия, отсутствия профиля и ожидаемого отказа нового входа. Ошибка чтения SEC$USERS не равна пустому результату: сначала обработайте отказ запроса, затем рассматривайте null как отсутствие записи.

Передать чтение каталога другому оператору

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

Как меняется доступ
  1. Два разных логина → отдельные пароли и профили Anna/Boris
  2. GRANT SELECT в собственной базе + COMMIT → чтение разрешено обоим
  3. Прежний оператор читает Cable/3 → соединение закрыто → его запись удалена
  4. Свежий прежний вход отклонён; свежий вход замены возвращает Cable/3

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

$deleted = ibase_delete_user($service, $previousLogin);
// Каталог дальше читается свежим подключением другого пользователя.

В примере добавлен GRANT SELECT — SQL-разрешение чтения таблицы для конкретного пользователя. Оно применяется в прикладной базе, отдельно от создания серверного логина, и фиксируется COMMIT. Идентификатор нельзя подставлять из произвольного запроса посетителя; здесь он получен из закрытого реестра собственных учебных имён. WHERE, ORDER BY и ROWS 1 сразу выбирают один нужный товар на сервере.

Что увидим после выполнения

НаблюдениеРезультат
Прежний оператор читал каталогда
После удаления прежний вход отклонёнда
Новый оператор читает товарCable
Доступное количество3
Сохранён независимый профиль заменыда
Передать чтение каталога другому операторуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$owned=[];$removed=[];$db=$service=null;
try {
    $config=f160Config();$service=f160Service();$runtime=f160Runtime($service);

    $loginAs = static function(string $database,string $user,string $password): array {
        $connection=f160ConnectUser($database,$user,$password);
        try {
            $rows=f160Read($connection,'SELECT TRIM(CURRENT_USER) AS "login" FROM RDB$DATABASE');
            return ['connected'=>true,'identity_matches'=>($rows[0]['login']??null)===$user];
        } finally { f160Close($connection); }
    };
    $tryLogin = static function(string $database,string $user,string $password): array {
        $call=f160TryConnectUser($database,$user,$password);
        $connection=$call['value'];$call['value']=null;
        $connected=is_resource($connection);
        if($connected)f160Close($connection);
        return ['connected'=>$connected,'warning'=>$call['warning'],'code'=>$call['code'],
            'message_present'=>$call['message_present']];
    };

    $database=f160CreateDatabase($service,$owned,'user_replace');
    $previous=f160NewUser($owned,'previous');$replacement=f160NewUser($owned,'replacement');
    $previousPassword=f160Password();$replacementPassword=f160Password();
    f160AddUser($service,$previous,$previousPassword,['Anna']);
    f160AddUser($service,$replacement,$replacementPassword,['Boris']);
    $db=f160Connect($database);
    foreach([$previous,$replacement] as $user){
        // Identifier разрешён только для зарегистрированного случайного учебного имени.
        $identifier=f160UserIdentifier($user);
        f160Command($db,'GRANT SELECT ON LAB_ITEMS TO USER "'.$identifier.'"');
    }
    f160Commit($db);f160Close($db);
    $readCatalog=static function(string $database,string $user,string $password): array {
        $connection=f160ConnectUser($database,$user,$password);
        try{return f160Read($connection,'SELECT TITLE AS "title", QTY AS "qty" FROM LAB_ITEMS WHERE QTY>0 ORDER BY ID ROWS 1');}
        finally{f160Close($connection);}
    };
    $before=$readCatalog($database,$previous,$previousPassword);
    f160DeleteUser($service,$previous);
    $oldAttempt=$tryLogin($database,$previous,$previousPassword);
    $after=$readCatalog($database,$replacement,$replacementPassword);
    $db=f160Connect($database);$oldProfile=f160UserProfile($db,$previous);f160Commit($db);
    $newProfile=f160UserProfile($db,$replacement);f160Commit($db);
    $expected=[['title'=>'Cable','qty'=>3]];
    $rows=[['Прежний оператор читал каталог',$before===$expected],
        ['После удаления прежний вход отклонён',!$oldAttempt['connected']],
        ['Новый оператор читает товар',$after[0]['title']??null],['Доступное количество',$after[0]['qty']??null],
        ['Сохранён независимый профиль замены',($newProfile['first']??null)==='Boris']];
    $checks=['previous_catalog_read'=>$before===$expected,'previous_login_closed'=>!$oldAttempt['connected'],
        'previous_profile_removed'=>$oldProfile===null,'replacement_catalog_read'=>$after===$expected,
        'replacement_profile_preserved'=>($newProfile['first']??null)==='Boris'];
    $observations=['before_catalog'=>$before,'previous_login_after'=>$oldAttempt,
        'replacement_catalog'=>$after,'previous_profile_after'=>$oldProfile,'replacement_profile'=>$newProfile];

} finally {
    if(is_resource($db)){f160Close($db);}
    if(is_resource($service)){f160Detach($service);}
    $removed=f160Cleanup($owned);
}

f160Report('fb160-user-replacement','Передать чтение каталога другому оператору',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Прежний оператор читал каталог — да
После удаления прежний вход отклонён — да
Новый оператор читает товар — Cable
Доступное количество — 3
Сохранён независимый профиль замены — да

Самостоятельный CLI-пример работает с одной собственной базой и случайными учебными пользователями отдельного TCP-сервера. Полный код, lab.php и README приложены. После завершения откройте result.html; result.json содержит те же результаты и наблюдения без паролей.

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

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

Удаление имени не превращается в безопасное переименование сотрудника и не переносит права автоматически. Мы заранее выдали независимый минимальный доступ двум конкретным записям. Если старое имя планируется использовать повторно, отдельно исследуйте оставшиеся GRANT и владение объектами: создание нового человека под тем же логином может вернуть исторические права.

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

Уберите GRANT для замены. В каком месте должен измениться результат и почему смена пароля этого не исправит?

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

Новый логин всё ещё может пройти аутентификацию, но SELECT LAB_ITEMS получит отказ доступа. Пароль доказывает личность, а право чтения нужно выдать отдельно. Подробный контролируемый отказ и варианты GRANT показаны в следующей главе о SQL-правах.

Три операции жизненного цикла пользователя

ПотребностьПолный контракт
Создать серверную учётную запись и необязательные поля профиля.ibase_add_user
Изменить пароль и переданные поля существующего пользователя.ibase_modify_user
Удалить конкретную серверную учётную запись.ibase_delete_user

В сигнатурах = ? обозначает необязательный аргумент документации, а не литерал, который надо копировать в PHP. Неиспользуемые поля опускают с конца списка. Совместимые имена расширения — fbird_add_user, fbird_modify_user, fbird_delete_user; это aliases, а не отдельные способы проверки паролей.

Дальше: какие действия разрешены вошедшему пользователю

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

Для прикладных данных продолжают действовать параметризованные запросы и явное завершение транзакций. Для настройки выбранного экземпляра есть окружение и конфигурация Firebird. Эта глава раскрывает три официальные функции и их реальные ограничения; она не заменяет полный курс администрирования и не объявляет все конфигурации аутентификации проверенными.

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

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

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

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

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

Когда появилась новая интеграция, ей выделяют отдельную учётную запись с собственным паролем. add_user создаёт эту запись в системе безопасности выбранного сервиса; предметные таблицы и PHP-массивы пользователей функция не меняет.

АргументНазначение
$service_handleРесурс сервисного подключения к выбранному экземпляру Firebird.
$user_nameЛогин создаваемого серверного пользователя. Не изменяемое отображаемое имя приложения.
$passwordПароль новой учётной записи. В примере это переменная с созданным при запуске секретом, а не опубликованный литерал.
$first_nameНеобязательное имя в профиле.
$middle_nameНеобязательное среднее имя/отчество; интерпретацию выбирает приложение.
$last_nameНеобязательная фамилия в профиле.
$ok = ibase_add_user($service, $newLogin, $newPassword);
if ($ok !== true) {
    $error = ibase_errcode(); // Сохранить немедленно.
    throw new RuntimeException("Учётная запись не создана");
}

Это минимальный вызов с уже подготовленным $service. Скобки передают значения аргументов; результат помещается в $ok. Создание с тем же логином не является безопасным обновлением существующего пользователя. Пример новой учётной записи подтверждает вход отдельным TCP-подключением и сопоставляет CURRENT_USER с зарегистрированным именем.

$ok = ibase_add_user($service, $newLogin, $newPassword, "Anna", "M", "Reader");

Во втором варианте добавляются три поля профиля. Полная программа создания оператора читает только его сведения и проверяет новый вход. Фамилия Reader сама по себе не означает право чтения. Отсутствующий необязательный аргумент не передаётся сервису; пустая строка является явно переданным значением. Их фактическое сохранение показано в разделе изменения профиля.

service_handle должен быть действующим ресурсом ibase_service_attach к правильному экземпляру, а не обычным ресурсом ibase_connect. Для управления чужими серверными пользователями нужны административные полномочия в соответствующей security database. Успешный SQL GRANT в прикладной базе не заменяет эти полномочия.

Во всех трёх функциях Manual описывает true при успехе и false при ошибке. Исходник 3.0.1 запускает сервисное действие через isc_service_start: для прикладного отчёта мы дополнительно дожидаемся сервисного ответа и проверяем профиль или новый вход. Не пишите «учётная запись готова» только по одному булеву результату. Код ошибки и наличие сообщения сохраняйте немедленно, до очистки и нового вызова.

Параметры передаются в сервисный буфер, а не вставляются в SQL-строку. Тем не менее в закреплённом драйвере этот буфер имеет всего 128 байт: ограничения современного SQL-синтаксиса нельзя считать гарантированной вместимостью старого PHP API. Учебные имена и пароль намеренно короткие, ASCII; строки с нулевым байтом не подходят. Политика длины/чувствительности пароля зависит от user manager. Не обещайте, что произвольный длинный Unicode-профиль обязательно пройдёт этой функцией.

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

Для нового проекта SQL CREATE USER позволяет явно выбрать user manager через USING PLUGIN, задать активность и другие поддерживаемые сервером свойства; у этой PHP-функции соответствующих аргументов нет. Имя пользователя в Firebird 5 SQL следует правилам идентификаторов, но старый сервисный API и его буфер вводят собственные границы. Не переносите SQL-лимиты напрямую в форму поверх add_user.

ibase_modify_userИзменить пароль и переданные поля существующего пользователя.

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

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

У интеграции меняют секрет либо исправляют профиль оператора. modify_user выбирает существующую учётную запись по логину и меняет переданные сведения; это не переименование логина и не выдача SQL-прав.

АргументНазначение и отличие от создания
$service_handleДействующий ресурс менеджера выбранного сервиса.
$user_nameЛогин существующего пользователя, которого надо изменить.
$passwordОбязательный новый пароль. Для изменения только профиля в учебном API повторно передаётся известный текущий пароль; отсутствие аргумента не поддерживается.
$first_nameНеобязательное новое значение имени; при отсутствии аргумента соответствующее поле не посылается.
$middle_nameНеобязательное новое значение среднего имени. Пустая строка посылается как отдельное значение, а не означает «пропустить».
$last_nameНеобязательное новое значение фамилии; отсутствие аргумента отличается от явной пустой строки.
$ok = ibase_modify_user($service, $login, $newPassword);
if ($ok !== true) { throw new RuntimeException("Изменение не выполнено"); }
// Далее завершить старое подключение и проверить новый вход.

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

$ok = ibase_modify_user($service, $login, $currentPassword, "Ann");
// middle_name и last_name в этом вызове отсутствуют.

Второй вариант меняет только переданное имя профиля, повторяя известный пароль. Полная последовательность изменения профиля сопоставляет отсутствие необязательных аргументов с явно переданной пустой строкой, сохраняет остальные сведения и подтверждает вход. Не подставляйте null вместо неизвестного пароля и не восстанавливайте секрет из хеша: функция принимает пароль, а не password_hash вашего веб-приложения.

service_handle должен быть действующим ресурсом ibase_service_attach к правильному экземпляру, а не обычным ресурсом ibase_connect. Для управления чужими серверными пользователями нужны административные полномочия в соответствующей security database. Успешный SQL GRANT в прикладной базе не заменяет эти полномочия.

Во всех трёх функциях Manual описывает true при успехе и false при ошибке. Исходник 3.0.1 запускает сервисное действие через isc_service_start: для прикладного отчёта мы дополнительно дожидаемся сервисного ответа и проверяем профиль или новый вход. Не пишите «учётная запись готова» только по одному булеву результату. Код ошибки и наличие сообщения сохраняйте немедленно, до очистки и нового вызова.

Параметры передаются в сервисный буфер, а не вставляются в SQL-строку. Тем не менее в закреплённом драйвере этот буфер имеет всего 128 байт: ограничения современного SQL-синтаксиса нельзя считать гарантированной вместимостью старого PHP API. Учебные имена и пароль намеренно короткие, ASCII; строки с нулевым байтом не подходят. Политика длины/чувствительности пароля зависит от user manager. Не обещайте, что произвольный длинный Unicode-профиль обязательно пройдёт этой функцией.

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

В закреплённой лаборатории отсутствие полей при создании даёт null; отсутствие middle/last при изменении сохраняет прежние M/Reader. Явная пустая строка для middle_name сохраняется как строка длины 0, а не как null. Это наблюдение Firebird 5.0.4 с user manager Srp и interbase 3.0.1; оно не заменяет проверку другого менеджера пользователей.

Если администратор не должен знать текущий пароль и нужно изменить только профиль, современный SQL ALTER USER позволяет задать FIRSTNAME без PASSWORD. Для временного отключения SQL INACTIVE имеет иной смысл, чем удаление учётной записи. Три рассматриваемые PHP-функции не предоставляют аргументов ACTIVE, INACTIVE, USING PLUGIN или TAGS; не имитируйте их случайной сменой имени или пустым паролем.

ibase_delete_userУдалить конкретную серверную учётную запись.

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

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

Старая интеграция или временный оператор больше не должны открывать новые соединения. delete_user удаляет конкретную учётную запись из системы безопасности выбранного сервиса. Это не удаление прикладной базы, таблиц или строки покупателя на сайте.

Аргумент / результатКонтракт
$service_handleРесурс менеджера сервиса, в котором находится удаляемая учётная запись.
$user_nameТочное имя пользователя. Пароль удаляемого пользователя по официальной сигнатуре не передаётся.
Возвратtrue при успехе или false при отказе; отдельно проверяется отсутствие записи и новый вход.
$ok = ibase_delete_user($service, $retiredLogin);
if ($ok !== true) { throw new RuntimeException("Учётная запись не удалена"); }

В первом применении завершается доступ временного оператора: после удаления новый вход с прежними реквизитами должен получить отказ. Проверка выполняется новым соединением; она не делает утверждений о принудительном завершении всех ранее открытых сеансов.

$ok = ibase_delete_user($service, $previousOperator);
// Следующий вход выполняется под отдельным $replacementOperator.

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

service_handle должен быть действующим ресурсом ibase_service_attach к правильному экземпляру, а не обычным ресурсом ibase_connect. Для управления чужими серверными пользователями нужны административные полномочия в соответствующей security database. Успешный SQL GRANT в прикладной базе не заменяет эти полномочия.

Во всех трёх функциях Manual описывает true при успехе и false при ошибке. Исходник 3.0.1 запускает сервисное действие через isc_service_start: для прикладного отчёта мы дополнительно дожидаемся сервисного ответа и проверяем профиль или новый вход. Не пишите «учётная запись готова» только по одному булеву результату. Код ошибки и наличие сообщения сохраняйте немедленно, до очистки и нового вызова.

Параметры передаются в сервисный буфер, а не вставляются в SQL-строку. Тем не менее в закреплённом драйвере этот буфер имеет всего 128 байт: ограничения современного SQL-синтаксиса нельзя считать гарантированной вместимостью старого PHP API. Учебные имена и пароль намеренно короткие, ASCII; строки с нулевым байтом не подходят. Политика длины/чувствительности пароля зависит от user manager. Не обещайте, что произвольный длинный Unicode-профиль обязательно пройдёт этой функцией.

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

Два аргумента delete_user подтверждены реальным вызовом в interbase 3.0.1. При этом декларация arginfo в исходнике этой версии содержит устаревшее минимальное число 3, противоречащее её же обработчику rs и официальной странице. Не добавляйте выдуманный пароль ради этой метаинформации; различайте сигнатуру фактического вызова и неточные сведения анализатора.

В конфигурациях с несколькими user manager одинаковый текст логина может обозначать разные записи. Этот PHP API не даёт параметра выбора плагина; в SQL DROP USER можно явно указать USING PLUGIN. Не рассчитывайте на удаление всех одноимённых записей сразу. Отдельно проверяют права и владение объектами в прикладных базах: удаление пользователя не следует считать универсальной командой очистки схемы или отзывом всех исторических GRANT.

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

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

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

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

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