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

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

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

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

Глава 93 · После OID, коллекций и транзакций CUBRID

CUBRID SEQUENCE: порядок шагов, замена и удаление по позиции

Чек-лист и маршрут: вставка, замена, удаление одного повтора, атомарный перенос и разница между позициями CUBRID и индексами PHP.

Порядок списка, позиции базы и индексы PHP

Небольшой маршрут доставки или чек-лист заказа — это не просто набор строк. Важны порядок и повторения: «Склад → Клиент → Склад» означает возвращение, а не лишний дубль. Здесь мы вставим проверку, исправим остановку, удалим один заезд и перенесём шаг так, чтобы отмена не потеряла часть списка.

Что получит читатель
  1. Два шага заказа → проверка появляется между ними
  2. Три остановки → изменена только вторая
  3. Два одинаковых склада → удалён выбранный повтор
  4. Перестановка двух команд → commit целого списка или rollback

Эти уроки продолжают подключение и первые SQL-запросы, параметры запроса и выбор одной записи по OID. Коллекция CUBRID хранится внутри одного атрибута строки базы. Полученный PHP-массив — отдельный снимок: изменение $items[0] само по себе ничего не записывает на сервер.

Вызовы проверены в отдельной среде PHP 7.4.33, native-драйвер cubrid 11.2.0.0002, CCI 11.2.0 и сервер CUBRID 11.3.5.1274, UTF-8. Это специальный профиль совместимости расширения, а не подтверждение его сборки для основного PHP 8.5.11. Сам сайт лишь показывает сохранённые результаты. Семь функций появились в PECL CUBRID 8.3.0; различия старого и проверенного поведения указаны отдельно.

В коротких фрагментах $db — открытое соединение, $oid — внутренний идентификатор уже найденной записи, а имя атрибута, например tags, задано приложением. OID получают через SELECT с CUBRID_INCLUDE_OID, позиционирование курсора и cubrid_current_oid; полный исходник повторяет эти шаги. Таблица явно создаётся DONT_REUSE_OID, чтобы поддерживать такой доступ. Внешний пользователь передаёт бизнес-выбор, а приложение проверяет доступ к записи; знание OID разрешением не является.

В SQL объявление tags SET(VARCHAR(40)) означает набор строк длиной до 40 символов. Литерал {'copper','indoor'} задаёт его элементы. В PHP те же прочитанные значения выглядят как ['copper','indoor']; фигурные скобки SQL и квадратные скобки PHP принадлежат разным языкам. Имена таблиц и атрибутов выбираются из доверенной схемы, значения запроса передаются параметрами.

SEQUENCE в этой главе — упорядоченная коллекция внутри столбца steps. Она допускает одинаковые элементы. Это не генератор числовых идентификаторов. Объявление steps SEQUENCE(VARCHAR(80)) означает строковые элементы до 80 символов. Каждый самостоятельный проект создаёт одну таблицу, одну строку и маленький список; исходные данные заполняются через API последовательности.

Что видит человекПозиция CUBRIDИндекс прочитанного PHP-массива
Принять заказ1$steps[0]
Проверить вложение2$steps[1]
Упаковать3$steps[2]

Индекс — номер места сейчас, а не вечный идентификатор шага. Вставка и удаление сдвигают соседей. Массив $steps — снимок чтения, поэтому после изменения базы его перечитывают. В цикле foreach индекс PHP нужно увеличивать на 1 только при переводе в позицию SEQUENCE; дважды прибавлять единицу нельзя.

ПотребностьОперацияРезультат для существующей позиции
Добавить между шагамиcubrid_seq_insertДлина увеличивается, хвост сдвигается вправо
Исправить выбранный шагcubrid_seq_putМеняется значение на месте
Убрать одно вхождениеcubrid_seq_dropДлина уменьшается, хвост сдвигается влево

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

У заказа уже есть два шага. Появилось требование проверить вложение перед упаковкой. Нужна вставка между существующими значениями, а не замена упаковки. Затем добавим отправку в конец и убедимся, что отказ ошибочного индекса не удалил сохранённую работу.

Как меняются конкретные данные
  1. База: 1 = Принять заказ; 2 = Упаковать
  2. Вставить проверку на позицию 2
  3. База: 1 = Принять заказ; 2 = Проверить O'Reilly; 3 = Упаковать
  4. Добавить Отправить на N + 1 = 4 и подтвердить commit

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

$ok = cubrid_seq_insert($db, $oid, 'steps', 2, "Проверить O'Reilly");
if ($ok === false) {
    throw new RuntimeException('Проверка не добавлена');
}
$steps = cubrid_col_get($db, $oid, 'steps');

Число 2 — новая вторая позиция в SEQUENCE. Прежняя вторая запись сдвигается на третью. Возвращается bool, поэтому сам изменённый список читаем отдельным вызовом. Апостроф является частью переданного значения, его не нужно вклеивать в SQL. В полном проекте транзакции завершаются явно, а ошибочный вызов перехватывает c170Capture.

Что получится после запуска

НаблюдениеРезультат
ЗаказЗаказ 17
ДоПринять заказ → Упаковать
После вставки на позицию 2Принять заказ → Проверить O'Reilly → Упаковать
Сохранённый списокПринять заказ → Проверить O'Reilly → Упаковать → Отправить
Недопустимый индекс 0-309
После отказаПринять заказ → Проверить O'Reilly → Упаковать → Отправить
Самостоятельный проект, исходник и архив
Добавить проверку перед упаковкой, не потеряв остальные шагиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c170Config();$db=c170Connect();$runtime=c170Runtime($db);

    function selectedOid170($db,string $table,int $id): string {
        $q=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE id=?',CUBRID_INCLUDE_OID);
        if($q===false){throw new RuntimeException('Не подготовлен выбор записи');}
        try {
            if(!cubrid_bind($q,1,$id,'int')||!cubrid_execute($q)||cubrid_num_rows($q)!==1
                ||!cubrid_move_cursor($q,1,CUBRID_CURSOR_FIRST)){
                throw new RuntimeException('Нужна ровно одна доступная запись');
            }
            $oid=cubrid_current_oid($q);
            if(!is_string($oid)||$oid===''){throw new RuntimeException('Не получен OID');}
            return $oid;
        } finally {c170Release($q);}
    }
    function collection170($db,string $oid,string $attr): array {
        $value=cubrid_col_get($db,$oid,$attr);
        if($value===false){throw new RuntimeException('Не прочитана коллекция');}
        return $value;
    }
    function change170(bool $ok): void {
        if(!$ok){throw new RuntimeException('Изменение коллекции не выполнено');}
    }
    function sorted170(array $items): array {sort($items,SORT_STRING);return $items;}

    // Подготовка одного собственного списка; прикладные изменения идут ниже.
    function seedSequence170($db,array &$owned,string $label,string $title,array $items): array {
        $name=c170OwnName($db,$owned,'TABLE',$label);
        $table=c170Qualified($name);
        c170Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(120),steps SEQUENCE(VARCHAR(80))) DONT_REUSE_OID');
        c170Commit($db);
        c170Command($db,'INSERT INTO '.$table.' (id,title,steps) VALUES (1,?,{})',[$title]);
        $oid=selectedOid170($db,$table,1);
        foreach($items as $index=>$value) {
            change170(cubrid_seq_insert($db,$oid,'steps',$index+1,$value));
        }
        c170Commit($db);
        return [$table,$oid];
    }
    function showSequence170(array $items): string {
        return implode(' → ',array_map(static function($value): string {
            return $value===null?'NULL':$value;
        },$items));
    }

    [$table,$oid]=seedSequence170($db,$owned,'seq_check','Заказ 17',['Принять заказ','Упаковать']);
    $before=collection170($db,$oid,'steps');
    $position=2;
    if($position<1 || $position>count($before)+1) {throw new RuntimeException('Позиция вне списка');}
    change170(cubrid_seq_insert($db,$oid,'steps',$position,"Проверить O'Reilly"));
    $inserted=collection170($db,$oid,'steps');
    change170(cubrid_seq_insert($db,$oid,'steps',count($inserted)+1,'Отправить'));
    c170Commit($db);
    $saved=collection170($db,$oid,'steps');

    // Ноль в API базы не означает первый элемент PHP-массива.
    $bad=c170Capture(static function() use ($db,$oid) {
        return cubrid_seq_insert($db,$oid,'steps',0,'Ошибочный шаг');
    });
    c170Rollback($db);
    $afterFailure=collection170($db,$oid,'steps');
    $title=c170Read($db,'SELECT title FROM '.$table.' WHERE id=?',[1])[0]['title'];
    $rows=[['Заказ',$title],['До',showSequence170($before)],['После вставки на позицию 2',showSequence170($inserted)],
        ['Сохранённый список',showSequence170($saved)],['Недопустимый индекс 0',$bad['code']],
        ['После отказа',showSequence170($afterFailure)]];
    $checks=['before'=> $before===['Принять заказ','Упаковать'],
        'insert_shift'=> $inserted===['Принять заказ',"Проверить O'Reilly",'Упаковать'],
        'append'=> $saved===['Принять заказ',"Проверить O'Reilly",'Упаковать','Отправить'],
        'utf8_apostrophe'=> $saved[1]==="Проверить O'Reilly",
        'zero_refused'=> $bad['value']===false && $bad['code']===-309 && $bad['facility']===1 && $bad['warning'] && $bad['message_present'],
        'saved_survives_failure'=> $afterFailure===$saved,'selected_order'=> $title==='Заказ 17'];
    $observations=['before'=>$before,'inserted'=>$inserted,'saved'=>$saved,'invalid_zero'=>$bad,'after_failure'=>$afterFailure];

} finally {
    if (is_resource($db)) {try {$removed=c170Cleanup($db,$owned);} finally {c170Close($db);}}
}

c170Report('cub170-checklist-insert','Добавить проверку перед упаковкой, не потеряв остальные шаги',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Заказ — Заказ 17
До — Принять заказ → Упаковать
После вставки на позицию 2 — Принять заказ → Проверить O'Reilly → Упаковать
Сохранённый список — Принять заказ → Проверить O'Reilly → Упаковать → Отправить
Недопустимый индекс 0 — -309
После отказа — Принять заказ → Проверить O'Reilly → Упаковать → Отправить

Распакуйте весь ZIP; порядок запуска и создание отдельного стенда описаны в README.txt. example.php содержит операции урока, lab.php — подключение, создание только собственных таблиц и очистку. result.html показывает итог, result.json — исходные наблюдения. В finally удаляется только созданная этим запуском таблица. Это реальный CLI-проект с CUBRID, не PHP-обработчик публичного сайта.

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

Границы решения

У приложения своё правило: вставлять только в диапазон 1…N+1. Проверенный сервер умеет расширять SEQUENCE и при более далёком индексе, заполняя промежуток NULL; для обязательного чек-листа такой результат не подходит. Проверку позиции выполняют по свежему состоянию. Четыре элемента принадлежат одной выбранной строке — это не загрузка всех заказов в PHP.

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

Как вставить «Проверить адрес» первым шагом, сохранив приём заказа?

Разбор

Вызовите cubrid_seq_insert с индексом 1. Он сдвинет прежние элементы вправо. cubrid_seq_put с индексом 1 заменил бы приём заказа; это другое действие.

Исправить одну остановку маршрута и увидеть опасность далёкой позиции

Адрес второй остановки изменился, но маршрут по-прежнему должен состоять из трёх остановок. Замена сохраняет обе поездки на склад и не вставляет четвёртый пункт. Дополнительно воспроизведём ошибку формы с индексом 5 и отменим её: успешный ответ базы ещё не означает правильный маршрут.

Как меняются конкретные данные
  1. Маршрут: Склад → Клиент → Склад
  2. put(2, Пункт O'Reilly): заменить только клиента
  3. Пробная put(5, Ошибочная остановка): позиция 4 становится NULL
  4. rollback пробной правки: снова три корректных остановки

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

$ok = cubrid_seq_put($db, $oid, 'steps', 2, "Пункт O'Reilly");
if ($ok === false) {
    throw new RuntimeException('Остановка не заменена');
}
$route = cubrid_col_get($db, $oid, 'steps');

put записывает новое значение на указанную позицию. Для существующего элемента соседние позиции не сдвигаются. Полный проект сначала проверяет диапазон редактирования; затем отдельным контролируемым вызовом показывает расширение списка и rollback. NULL в четвёртой позиции — значение коллекции, не строка «NULL».

Что получится после запуска

НаблюдениеРезультат
ДоСклад → Клиент → Склад
После замены позиции 2Склад → Пункт O'Reilly → Склад
Проверка API: запись на позицию 5Склад → Пункт O'Reilly → Склад → NULL → Ошибочная остановка
После rollbackСклад → Пункт O'Reilly → Склад
Остановок в принятом маршруте3
Самостоятельный проект, исходник и архив
Исправить одну остановку маршрута и увидеть опасность далёкой позицииСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c170Config();$db=c170Connect();$runtime=c170Runtime($db);

    function selectedOid170($db,string $table,int $id): string {
        $q=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE id=?',CUBRID_INCLUDE_OID);
        if($q===false){throw new RuntimeException('Не подготовлен выбор записи');}
        try {
            if(!cubrid_bind($q,1,$id,'int')||!cubrid_execute($q)||cubrid_num_rows($q)!==1
                ||!cubrid_move_cursor($q,1,CUBRID_CURSOR_FIRST)){
                throw new RuntimeException('Нужна ровно одна доступная запись');
            }
            $oid=cubrid_current_oid($q);
            if(!is_string($oid)||$oid===''){throw new RuntimeException('Не получен OID');}
            return $oid;
        } finally {c170Release($q);}
    }
    function collection170($db,string $oid,string $attr): array {
        $value=cubrid_col_get($db,$oid,$attr);
        if($value===false){throw new RuntimeException('Не прочитана коллекция');}
        return $value;
    }
    function change170(bool $ok): void {
        if(!$ok){throw new RuntimeException('Изменение коллекции не выполнено');}
    }
    function sorted170(array $items): array {sort($items,SORT_STRING);return $items;}

    // Подготовка одного собственного списка; прикладные изменения идут ниже.
    function seedSequence170($db,array &$owned,string $label,string $title,array $items): array {
        $name=c170OwnName($db,$owned,'TABLE',$label);
        $table=c170Qualified($name);
        c170Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(120),steps SEQUENCE(VARCHAR(80))) DONT_REUSE_OID');
        c170Commit($db);
        c170Command($db,'INSERT INTO '.$table.' (id,title,steps) VALUES (1,?,{})',[$title]);
        $oid=selectedOid170($db,$table,1);
        foreach($items as $index=>$value) {
            change170(cubrid_seq_insert($db,$oid,'steps',$index+1,$value));
        }
        c170Commit($db);
        return [$table,$oid];
    }
    function showSequence170(array $items): string {
        return implode(' → ',array_map(static function($value): string {
            return $value===null?'NULL':$value;
        },$items));
    }

    [$table,$oid]=seedSequence170($db,$owned,'seq_route','Маршрут 8',['Склад','Клиент','Склад']);
    $before=collection170($db,$oid,'steps');
    $position=2;
    if($position<1 || $position>count($before)) {throw new RuntimeException('Нет остановки для замены');}
    change170(cubrid_seq_put($db,$oid,'steps',$position,"Пункт O'Reilly"));
    c170Commit($db);
    $saved=collection170($db,$oid,'steps');

    // Диагностика поведения API: индекс 5 при длине 3 создаёт промежуток.
    change170(cubrid_seq_put($db,$oid,'steps',5,'Ошибочная остановка'));
    $gap=collection170($db,$oid,'steps');
    c170Rollback($db);
    $restored=collection170($db,$oid,'steps');
    $rows=[['До',showSequence170($before)],['После замены позиции 2',showSequence170($saved)],
        ['Проверка API: запись на позицию 5',showSequence170($gap)],['После rollback',showSequence170($restored)],
        ['Остановок в принятом маршруте',count($restored)]];
    $checks=['before'=> $before===['Склад','Клиент','Склад'],
        'replace_keeps_size'=> $saved===['Склад',"Пункт O'Reilly",'Склад'] && count($saved)===3,
        'utf8_apostrophe'=> $saved[1]==="Пункт O'Reilly",
        'gap_observed'=> $gap===['Склад',"Пункт O'Reilly",'Склад',null,'Ошибочная остановка'],
        'rollback_restores'=> $restored===$saved];
    $observations=['before'=>$before,'saved'=>$saved,'discarded_gap'=>$gap,'restored'=>$restored];

} finally {
    if (is_resource($db)) {try {$removed=c170Cleanup($db,$owned);} finally {c170Close($db);}}
}

c170Report('cub170-route-replace','Исправить одну остановку маршрута и увидеть опасность далёкой позиции',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До — Склад → Клиент → Склад
После замены позиции 2 — Склад → Пункт O'Reilly → Склад
Проверка API: запись на позицию 5 — Склад → Пункт O'Reilly → Склад → NULL → Ошибочная остановка
После rollback — Склад → Пункт O'Reilly → Склад
Остановок в принятом маршруте — 3

Распакуйте весь ZIP; порядок запуска и создание отдельного стенда описаны в README.txt. example.php содержит операции урока, lab.php — подключение, создание только собственных таблиц и очистку. result.html показывает итог, result.json — исходные наблюдения. В finally удаляется только созданная этим запуском таблица. Это реальный CLI-проект с CUBRID, не PHP-обработчик публичного сайта.

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

Границы решения

В этом приложении замена допускается только для 1…N. Поведение N+1 и N+2 описывает проверенный CUBRID 11.3.5, а не обязательное требование к каждому пользовательскому интерфейсу. Не предлагайте пустую остановку только потому, что сервер разрешил NULL. При одновременном редактировании проверяйте свежую версию списка внутри транзакции.

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

Что выбрать, если диспетчер хочет заехать ещё в один пункт перед возвращением на склад?

Разбор

Это вставка на позицию 3 через cubrid_seq_insert. put(3, новое значение) заменит возвращение на склад и нарушит требование.

Удалить один повторный заезд, сохранив первый

В маршруте два раза встречается «Склад». Отменён только повторный заезд после клиента. Поиск по значению не выражает этот выбор: нужно удалить именно третью позицию и сохранить первый склад. После удаления сервис сдвинется с четвёртой позиции на третью.

Как меняются конкретные данные
  1. До: 1 Склад; 2 Клиент; 3 Склад; 4 Сервис
  2. Выбор диспетчера: удалить позицию 3
  3. После: 1 Склад; 2 Клиент; 3 Сервис
  4. Старой позиции 4 больше нет: отказ, затем rollback

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

$ok = cubrid_seq_drop($db, $oid, 'steps', 3);
if ($ok === false) {
    throw new RuntimeException('Выбранный заезд не удалён');
}
$route = cubrid_col_get($db, $oid, 'steps');

drop принимает номер позиции, а не текст удаляемого значения. У него нет аргумента seq_element. Для сверки выбранного заезда полный проект использует $before[$position - 1]: база нумерует с 1, PHP-массив — с 0. Уменьшение длины и сдвиг подтверждает новое чтение.

Что получится после запуска

НаблюдениеРезультат
ДоСклад → Клиент → Склад → Сервис
Удалённая позиция3
После удаленияСклад → Клиент → Сервис
Новая позиция сервиса3
Удаление несуществующей позиции 4-309
После отказаСклад → Клиент → Сервис
Самостоятельный проект, исходник и архив
Удалить один повторный заезд, сохранив первыйСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c170Config();$db=c170Connect();$runtime=c170Runtime($db);

    function selectedOid170($db,string $table,int $id): string {
        $q=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE id=?',CUBRID_INCLUDE_OID);
        if($q===false){throw new RuntimeException('Не подготовлен выбор записи');}
        try {
            if(!cubrid_bind($q,1,$id,'int')||!cubrid_execute($q)||cubrid_num_rows($q)!==1
                ||!cubrid_move_cursor($q,1,CUBRID_CURSOR_FIRST)){
                throw new RuntimeException('Нужна ровно одна доступная запись');
            }
            $oid=cubrid_current_oid($q);
            if(!is_string($oid)||$oid===''){throw new RuntimeException('Не получен OID');}
            return $oid;
        } finally {c170Release($q);}
    }
    function collection170($db,string $oid,string $attr): array {
        $value=cubrid_col_get($db,$oid,$attr);
        if($value===false){throw new RuntimeException('Не прочитана коллекция');}
        return $value;
    }
    function change170(bool $ok): void {
        if(!$ok){throw new RuntimeException('Изменение коллекции не выполнено');}
    }
    function sorted170(array $items): array {sort($items,SORT_STRING);return $items;}

    // Подготовка одного собственного списка; прикладные изменения идут ниже.
    function seedSequence170($db,array &$owned,string $label,string $title,array $items): array {
        $name=c170OwnName($db,$owned,'TABLE',$label);
        $table=c170Qualified($name);
        c170Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(120),steps SEQUENCE(VARCHAR(80))) DONT_REUSE_OID');
        c170Commit($db);
        c170Command($db,'INSERT INTO '.$table.' (id,title,steps) VALUES (1,?,{})',[$title]);
        $oid=selectedOid170($db,$table,1);
        foreach($items as $index=>$value) {
            change170(cubrid_seq_insert($db,$oid,'steps',$index+1,$value));
        }
        c170Commit($db);
        return [$table,$oid];
    }
    function showSequence170(array $items): string {
        return implode(' → ',array_map(static function($value): string {
            return $value===null?'NULL':$value;
        },$items));
    }

    [$table,$oid]=seedSequence170($db,$owned,'seq_remove','Маршрут 9',['Склад','Клиент','Склад','Сервис']);
    $before=collection170($db,$oid,'steps');
    $position=3;
    if($position<1 || $position>count($before) || $before[$position-1]!=='Склад') {
        throw new RuntimeException('Позиция уже не соответствует выбранному заезду');
    }
    change170(cubrid_seq_drop($db,$oid,'steps',$position));
    c170Commit($db);
    $saved=collection170($db,$oid,'steps');
    $badPosition=count($saved)+1;
    $bad=c170Capture(static function() use ($db,$oid,$badPosition) {
        return cubrid_seq_drop($db,$oid,'steps',$badPosition);
    });
    c170Rollback($db);
    $afterFailure=collection170($db,$oid,'steps');
    $rows=[['До',showSequence170($before)],['Удалённая позиция',$position],['После удаления',showSequence170($saved)],
        ['Новая позиция сервиса',3],['Удаление несуществующей позиции 4',$bad['code']],['После отказа',showSequence170($afterFailure)]];
    $checks=['before'=> $before===['Склад','Клиент','Склад','Сервис'],
        'one_occurrence'=> $saved===['Склад','Клиент','Сервис'],
        'shift_left'=> $saved[2]==='Сервис',
        'missing_refused'=> $bad['value']===false && $bad['code']===-309 && $bad['facility']===1 && $bad['warning'] && $bad['message_present'],
        'failure_keeps_saved'=> $afterFailure===$saved];
    $observations=['before'=>$before,'removed_position'=>$position,'saved'=>$saved,'invalid_position'=>$bad,'after_failure'=>$afterFailure];

} finally {
    if (is_resource($db)) {try {$removed=c170Cleanup($db,$owned);} finally {c170Close($db);}}
}

c170Report('cub170-duplicate-stop-drop','Удалить один повторный заезд, сохранив первый',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До — Склад → Клиент → Склад → Сервис
Удалённая позиция — 3
После удаления — Склад → Клиент → Сервис
Новая позиция сервиса — 3
Удаление несуществующей позиции 4 — -309
После отказа — Склад → Клиент → Сервис

Распакуйте весь ZIP; порядок запуска и создание отдельного стенда описаны в README.txt. example.php содержит операции урока, lab.php — подключение, создание только собственных таблиц и очистку. result.html показывает итог, result.json — исходные наблюдения. В finally удаляется только созданная этим запуском таблица. Это реальный CLI-проект с CUBRID, не PHP-обработчик публичного сайта.

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

Границы решения

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

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

Нужно удалить второй и третий элементы исходного маршрута. Что не так с последовательными drop(2), затем drop(3)?

Разбор

После первого удаления прежний третий элемент уже находится на позиции 2. Вторая команда удалит прежний четвёртый. Для этих исходных позиций выполните drop(3), затем drop(2) в одной транзакции, проверяя каждый ответ.

Перенести маркировку перед проверкой: отменить пробу и сохранить целое изменение

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

Как меняются конкретные данные
  1. Исходно: Сборка → Проверка → Маркировка → Упаковка
  2. Проба drop(3): Маркировка временно отсутствует
  3. rollback: все четыре шага возвращаются
  4. Повторная свежая проверка → drop(3) → insert(2) → commit

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

$step = $steps[2]; // третья позиция базы
if (!cubrid_seq_drop($db, $oid, 'steps', 3)) {
    throw new RuntimeException('Не удалена старая позиция');
}
if (!cubrid_seq_insert($db, $oid, 'steps', 2, $step)) {
    throw new RuntimeException('Не создана новая позиция');
}

Фрагмент — только пара операций внутри уже открытой транзакции. Commit выполняется после обеих успешных команд и проверки списка; при отказе нужен rollback. Полный проект сначала ставит write lock на запись и заново читает список. Блокировка защищает текущую операцию, а сравнение свежего списка с ожидаемым помогает не применить старый выбор к изменившимся данным.

Что получится после запуска

НаблюдениеРезультат
Исходный порядокСборка → Проверка → Маркировка → Упаковка
Промежуточно после drop(3)Сборка → Проверка → Упаковка
Отмена переносаСборка → Проверка → Маркировка → Упаковка
После drop(3) и insert(2)Сборка → Маркировка → Проверка → Упаковка
Число шагов после сохранения4
Самостоятельный проект, исходник и архив
Перенести маркировку перед проверкой: отменить пробу и сохранить целое изменениеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c170Config();$db=c170Connect();$runtime=c170Runtime($db);

    function selectedOid170($db,string $table,int $id): string {
        $q=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE id=?',CUBRID_INCLUDE_OID);
        if($q===false){throw new RuntimeException('Не подготовлен выбор записи');}
        try {
            if(!cubrid_bind($q,1,$id,'int')||!cubrid_execute($q)||cubrid_num_rows($q)!==1
                ||!cubrid_move_cursor($q,1,CUBRID_CURSOR_FIRST)){
                throw new RuntimeException('Нужна ровно одна доступная запись');
            }
            $oid=cubrid_current_oid($q);
            if(!is_string($oid)||$oid===''){throw new RuntimeException('Не получен OID');}
            return $oid;
        } finally {c170Release($q);}
    }
    function collection170($db,string $oid,string $attr): array {
        $value=cubrid_col_get($db,$oid,$attr);
        if($value===false){throw new RuntimeException('Не прочитана коллекция');}
        return $value;
    }
    function change170(bool $ok): void {
        if(!$ok){throw new RuntimeException('Изменение коллекции не выполнено');}
    }
    function sorted170(array $items): array {sort($items,SORT_STRING);return $items;}

    // Подготовка одного собственного списка; прикладные изменения идут ниже.
    function seedSequence170($db,array &$owned,string $label,string $title,array $items): array {
        $name=c170OwnName($db,$owned,'TABLE',$label);
        $table=c170Qualified($name);
        c170Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(120),steps SEQUENCE(VARCHAR(80))) DONT_REUSE_OID');
        c170Commit($db);
        c170Command($db,'INSERT INTO '.$table.' (id,title,steps) VALUES (1,?,{})',[$title]);
        $oid=selectedOid170($db,$table,1);
        foreach($items as $index=>$value) {
            change170(cubrid_seq_insert($db,$oid,'steps',$index+1,$value));
        }
        c170Commit($db);
        return [$table,$oid];
    }
    function showSequence170(array $items): string {
        return implode(' → ',array_map(static function($value): string {
            return $value===null?'NULL':$value;
        },$items));
    }

    [$table,$oid]=seedSequence170($db,$owned,'seq_reorder','Упаковочный лист',['Сборка','Проверка','Маркировка','Упаковка']);
    // Один редактор: блокируем выбранную запись, затем читаем актуальный список.
    change170(cubrid_lock_write($db,$oid));
    $before=collection170($db,$oid,'steps');
    $from=3;$to=2;
    if($from<1 || $from>count($before) || $to<1 || $to>count($before)) {
        throw new RuntimeException('Неверная позиция переноса');
    }
    $value=$before[$from-1];
    change170(cubrid_seq_drop($db,$oid,'steps',$from));
    $intermediate=collection170($db,$oid,'steps');
    // Решение отменено до вставки: половина переноса не должна сохраниться.
    c170Rollback($db);
    $cancelled=collection170($db,$oid,'steps');
    c170Commit($db);

    change170(cubrid_lock_write($db,$oid));
    $fresh=collection170($db,$oid,'steps');
    if($fresh!==$before) {throw new RuntimeException('Список изменился: обновите форму');}
    change170(cubrid_seq_drop($db,$oid,'steps',$from));
    change170(cubrid_seq_insert($db,$oid,'steps',$to,$value));
    $preview=collection170($db,$oid,'steps');
    if($preview!==['Сборка','Маркировка','Проверка','Упаковка']) {
        throw new RuntimeException('Не получен согласованный порядок');
    }
    c170Commit($db);
    $saved=collection170($db,$oid,'steps');
    $rows=[['Исходный порядок',showSequence170($before)],['Промежуточно после drop(3)',showSequence170($intermediate)],
        ['Отмена переноса',showSequence170($cancelled)],['После drop(3) и insert(2)',showSequence170($saved)],
        ['Число шагов после сохранения',count($saved)]];
    $checks=['before'=> $before===['Сборка','Проверка','Маркировка','Упаковка'],
        'intermediate'=> $intermediate===['Сборка','Проверка','Упаковка'],
        'cancel_restores_all'=> $cancelled===$before,
        'fresh_comparison'=> $fresh===$before,
        'atomic_order'=> $saved===['Сборка','Маркировка','Проверка','Упаковка'] && $saved===$preview,
        'same_items'=> count($saved)===count($before) && sorted170($saved)===sorted170($before)];
    $observations=['before'=>$before,'intermediate_not_committed'=>$intermediate,'cancelled'=>$cancelled,'fresh'=>$fresh,'saved'=>$saved];

} finally {
    if (is_resource($db)) {try {$removed=c170Cleanup($db,$owned);} finally {c170Close($db);}}
}

c170Report('cub170-sequence-reorder','Перенести маркировку перед проверкой: отменить пробу и сохранить целое изменение',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Исходный порядок — Сборка → Проверка → Маркировка → Упаковка
Промежуточно после drop(3) — Сборка → Проверка → Упаковка
Отмена переноса — Сборка → Проверка → Маркировка → Упаковка
После drop(3) и insert(2) — Сборка → Маркировка → Проверка → Упаковка
Число шагов после сохранения — 4

Распакуйте весь ZIP; порядок запуска и создание отдельного стенда описаны в README.txt. example.php содержит операции урока, lab.php — подключение, создание только собственных таблиц и очистку. result.html показывает итог, result.json — исходные наблюдения. В finally удаляется только созданная этим запуском таблица. Это реальный CLI-проект с CUBRID, не PHP-обработчик публичного сайта.

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

Границы решения

Это один исполняющий процесс: пример проверяет атомарность переноса, а не моделирует двух одновременно работающих редакторов. Настоящий конфликт блокировок разобран в главе про OID. Для длинной паузы между открытием и отправкой формы добавьте версию записи: удерживать транзакцию всё время чтения формы не следует. sorted170 сравнивает только четыре элемента выбранного списка, а не сортирует выгруженную таблицу базы.

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

Пользователь перетаскивает второй шаг в самый конец. Как определить индекс вставки после удаления?

Разбор

Зафиксируйте соглашение интерфейса: здесь destination — итоговая позиция в готовом списке. При исходных четырёх шагах сохраните значение второго, выполните drop(2), затем insert(4, сохранённое значение). После удаления длина равна 3, поэтому позиция 4 — допустимое добавление N+1.

Границы редактирования: актуальность, доступ и отдельные строки

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

Это самостоятельные вопросы: как хранить редактируемый порядок, почему ожидание блокировки не исправляет устаревшие значения и чем идентификатор отличается от проверки доступа. Они задают реальные требования; правила конкретных CUBRID-функций взяты из документации и проверки драйвера. Порядок SQL-строк без ORDER BY не гарантируется; SEQUENCE хранит свой внутренний порядок независимо от этого.

Коллекция подходит для короткого списка, который принадлежит одной записи. Если остановкам нужны собственные идентификаторы, статусы, права, связи, история или массовый поиск, создайте таблицу остановок с внешним ключом и явным полем порядка. Выбирайте нужный маршрут и сортируйте его в SQL через ORDER BY; не выгружайте все маршруты ради сортировки в PHP.

Три операции над упорядоченной коллекцией

ФункцияКогда использовать
cubrid_seq_insertВставить новый элемент на определённую позицию последовательности.
cubrid_seq_putЗаменить значение на выбранной позиции последовательности.
cubrid_seq_dropУдалить конкретное вхождение по позиции, сохранив одинаковые значения в других местах.

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

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

cubrid_seq_insertВставить новый элемент на определённую позицию последовательности.

PECL CUBRID >= 8.3.0 · cubrid

cubrid_seq_insert(resource $conn_identifier, string $oid, string $attr_name, int $index, string $seq_element): bool

Вставить новый элемент на определённую позицию последовательности.

ПараметрЧто передать
conn_identifierОткрытое native-соединение cubrid.
oidOID выбранной доступной записи referable-таблицы.
attr_nameТочное имя атрибута коллекции; это имя столбца, не индекс PHP-массива.
indexЦелая позиция в SEQUENCE; первая позиция имеет номер 1. Индекс прочитанного PHP-массива начинается с 0.
seq_elementСтрока с новым значением элемента, совместимая с типом домена коллекции. Строковый параметр не означает, что любая строка допустима для SEQUENCE(INT).

Результат: true при успешном изменении, false при отказе. Это не изменённая коллекция и не число элементов; для результата используйте cubrid_col_get.

При false сразу считайте cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не заменил диагностику. В полном проекте c170Capture перехватывает предупреждение и сохраняет эти признаки вместе. Не проверяйте результат только через if (!$value): пустой массив и число 0 могут быть нормальным ответом.

Все параметры обязательны. Функция доступна в PECL CUBRID начиная с 8.3.0. В этой главе проверяется native-модуль 11.2.0.0002 с CCI 11.2.0 и сервером 11.3.5.1274. Она работает с атрибутом SEQUENCE выбранного экземпляра, а не с SQL-генератором CREATE SERIAL и не с PHP-массивом. Для SET и MULTISET используйте операции над наборами.

Используйте текущий OID доступной приложению записи и имя атрибута из известной схемы. Неправильный ресурс, OID, атрибут, тип значения или позиция могут привести к отказу; точный код зависит от причины и версии. При отказе завершите незавершённую бизнес-операцию rollback. При успехе изменение ещё нужно подтвердить commit, если автокоммит выключен. Показанный лабораторный профиль отключает его явно.

Вставка между существующими шагами
  1. До: 1 Приём; 2 Упаковка
  2. insert(..., 2, Проверка)
  3. После: 1 Приём; 2 Проверка; 3 Упаковка

При вставке внутри списка текущий элемент и следующие сдвигаются вправо. Для дописывания используйте N+1, где N — актуальная длина. Прочитанный ранее PHP-массив сам не обновится.

Применение 1: обязательная проверка перед упаковкой

$ok = cubrid_seq_insert($db, $oid, 'steps', 2, 'Проверка');
if ($ok === false) { throw new RuntimeException('Шаг не вставлен'); }
$steps = cubrid_col_get($db, $oid, 'steps');

Для исходных «Приём → Упаковка» будет «Приём → Проверка → Упаковка». Это механизм чек-листа с сохранением и ошибочным индексом.

Применение 2: вернуть перемещаемый шаг в новое место

$moved = $steps[2];
if (!cubrid_seq_drop($db, $oid, 'steps', 3)) { throw new RuntimeException('Удаление не выполнено'); }
if (!cubrid_seq_insert($db, $oid, 'steps', 2, $moved)) { throw new RuntimeException('Вставка не выполнена'); }

Обе команды составляют одну бизнес-операцию: перенос маркировки и отмена промежуточного удаления показывает commit после целого изменения. Условие и позицию проверяют по свежим данным до записи; в интерфейсе с долгой паузой нужна версия списка.

Соседние операции: замена без сдвига существующих элементов и удаление с уменьшением списка. После чтения коллекции рассмотрите NULL, пустую коллекцию и {NULL}.

cubrid_seq_putЗаменить значение на выбранной позиции последовательности.

PECL CUBRID >= 8.3.0 · cubrid

cubrid_seq_put(resource $conn_identifier, string $oid, string $attr_name, int $index, string $seq_element): bool

Заменить значение на выбранной позиции последовательности.

ПараметрЧто передать
conn_identifierОткрытое native-соединение cubrid.
oidOID выбранной доступной записи referable-таблицы.
attr_nameТочное имя атрибута коллекции; это имя столбца, не индекс PHP-массива.
indexЦелая позиция в SEQUENCE; первая позиция имеет номер 1. Индекс прочитанного PHP-массива начинается с 0.
seq_elementСтрока с новым значением элемента, совместимая с типом домена коллекции. Строковый параметр не означает, что любая строка допустима для SEQUENCE(INT).

Результат: true при успешном изменении, false при отказе. Это не изменённая коллекция и не число элементов; для результата используйте cubrid_col_get.

При false сразу считайте cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не заменил диагностику. В полном проекте c170Capture перехватывает предупреждение и сохраняет эти признаки вместе. Не проверяйте результат только через if (!$value): пустой массив и число 0 могут быть нормальным ответом.

Все параметры обязательны. Функция доступна в PECL CUBRID начиная с 8.3.0. В этой главе проверяется native-модуль 11.2.0.0002 с CCI 11.2.0 и сервером 11.3.5.1274. Она работает с атрибутом SEQUENCE выбранного экземпляра, а не с SQL-генератором CREATE SERIAL и не с PHP-массивом. Для SET и MULTISET используйте операции над наборами.

Используйте текущий OID доступной приложению записи и имя атрибута из известной схемы. Неправильный ресурс, OID, атрибут, тип значения или позиция могут привести к отказу; точный код зависит от причины и версии. При отказе завершите незавершённую бизнес-операцию rollback. При успехе изменение ещё нужно подтвердить commit, если автокоммит выключен. Показанный лабораторный профиль отключает его явно.

Замена одной остановки
  1. До: Склад → Клиент → Склад
  2. put(..., 2, Пункт O'Reilly)
  3. После: Склад → Пункт O'Reilly → Склад

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

Применение 1: исправить адрес остановки

$ok = cubrid_seq_put($db, $oid, 'steps', 2, "Пункт O'Reilly");
if ($ok === false) { throw new RuntimeException('Маршрут не исправлен'); }
$route = cubrid_col_get($db, $oid, 'steps');

Конкретный маршрут, апостроф в названии и rollback нежелательного расширения показаны в самостоятельном проекте замены остановки. Значение передаётся API отдельно, а не конкатенируется в SQL.

Применение 2: переименовать обязательный шаг в текущем чек-листе

$steps = cubrid_col_get($db, $oid, 'steps');
if ($steps === false || count($steps) < 2) {
    throw new RuntimeException('Второго шага нет');
}
if (!cubrid_seq_put($db, $oid, 'steps', 2, 'Проверить комплектность')) {
    throw new RuntimeException('Название шага не изменено');
}

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

Для появления нового шага между существующими нужен cubrid_seq_insert, для удаления — cubrid_seq_drop. Контроль целого изменения рассматривается в транзакциях CUBRID.

cubrid_seq_dropУдалить конкретное вхождение по позиции, сохранив одинаковые значения в других местах.

PECL CUBRID >= 8.3.0 · cubrid

cubrid_seq_drop(resource $conn_identifier, string $oid, string $attr_name, int $index): bool

Удалить конкретное вхождение по позиции, сохранив одинаковые значения в других местах.

ПараметрЧто передать
conn_identifierОткрытое native-соединение cubrid.
oidOID выбранной доступной записи referable-таблицы.
attr_nameТочное имя атрибута коллекции; это имя столбца, не индекс PHP-массива.
indexЦелая позиция в SEQUENCE; первая позиция имеет номер 1. Индекс прочитанного PHP-массива начинается с 0.

Результат: true при успешном изменении, false при отказе. Это не изменённая коллекция и не число элементов; для результата используйте cubrid_col_get.

При false сразу считайте cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не заменил диагностику. В полном проекте c170Capture перехватывает предупреждение и сохраняет эти признаки вместе. Не проверяйте результат только через if (!$value): пустой массив и число 0 могут быть нормальным ответом.

Все параметры обязательны. Функция доступна в PECL CUBRID начиная с 8.3.0. В этой главе проверяется native-модуль 11.2.0.0002 с CCI 11.2.0 и сервером 11.3.5.1274. Она работает с атрибутом SEQUENCE выбранного экземпляра, а не с SQL-генератором CREATE SERIAL и не с PHP-массивом. Для SET и MULTISET используйте операции над наборами.

Используйте текущий OID доступной приложению записи и имя атрибута из известной схемы. Неправильный ресурс, OID, атрибут, тип значения или позиция могут привести к отказу; точный код зависит от причины и версии. При отказе завершите незавершённую бизнес-операцию rollback. При успехе изменение ещё нужно подтвердить commit, если автокоммит выключен. Показанный лабораторный профиль отключает его явно.

Удаляется позиция, а не все одинаковые значения
  1. До: 1 Склад; 2 Клиент; 3 Склад; 4 Сервис
  2. drop(..., 3)
  3. После: 1 Склад; 2 Клиент; 3 Сервис

Операция удаляет ровно указанное вхождение. Следующие элементы сдвигаются влево; база не оставляет пустую позицию на месте удалённого элемента. Значение удалённого элемента функция не возвращает — сохраните его до удаления, если оно нужно для перемещения.

Применение 1: отменить повторный заезд

$ok = cubrid_seq_drop($db, $oid, 'steps', 3);
if ($ok === false) { throw new RuntimeException('Заезд не удалён'); }
$remaining = cubrid_col_get($db, $oid, 'steps');

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

Применение 2: первая половина перемещения

$moved = $steps[2];
if (!cubrid_seq_drop($db, $oid, 'steps', 3)) { throw new RuntimeException('Старая позиция осталась'); }
if (!cubrid_seq_insert($db, $oid, 'steps', 2, $moved)) { throw new RuntimeException('Шаг не возвращён'); }

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

Удаление по номеру отличается от удаления значения из SET или MULTISET. Для сохранения элемента, но изменения текста используйте cubrid_seq_put.

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

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

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

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

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