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

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

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

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

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

CUBRID: признаки товара, комплекты и состояния коллекций SET/MULTISET

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

Набор признаков и повторяющиеся детали — разные задачи

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

Коллекция CUBRID — значение одного столбца, внутри которого находится несколько элементов. Представьте коробку с карточками значений: SET оставляет только разные карточки, MULTISET сохраняет все экземпляры, SEQUENCE дополнительно закрепляет порядок. Коробка относится к одной записи базы. Эта модель не даёт элементам самостоятельных прав, цен или внешних ключей и не заменяет проектирование связей.

МодельПример значенияСмысл результатаКогда подходит
SET{'copper','indoor','copper'}Два различных признакаМаленький набор независимых признаков товара
MULTISET{'washer','washer','bolt'}Три детали, два названияКороткий состав, где повтор обозначает экземпляр
SEQUENCE{'Склад','Цех','Склад'}Три остановки в данном порядкеМаршруты и последовательности действий

Сначала один столбец и одно чтение

// SQL-форма объявления столбца внутри CREATE TABLE:
// tags SET(VARCHAR(40))
// SQL-значение: {'copper','indoor'}

// $db уже подключён; $oid выбранной записи получен способом ниже.
$tags = cubrid_col_get($db, $oid, 'tags');
if ($tags === false) { throw new RuntimeException('Признаки не прочитаны'); }
// Получен массив ['copper','indoor'] (порядок SET не обещан).
echo count($tags); // 2, если атрибут содержит эти два признака.

VARCHAR(40) задаёт домен строковых элементов, SET — правило уникальности. В SQL фигурные скобки задают значение коллекции; в PHP квадратные скобки обозначают другую структуру — массив в памяти программы. Оператор = сохраняет возвращённый массив в переменной, а === false отличает отказ от корректного пустого массива. count здесь считает уже прочитанную копию для объяснения; когда нужен только размер, используйте cubrid_col_size. Основа синтаксиса разобрана в массивах и вызовах функций.

В коротких фрагментах $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 принадлежат разным языкам. Имена таблиц и атрибутов выбираются из доверенной схемы, значения запроса передаются параметрами.

Эти уроки продолжают подключение и первые 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; различия старого и проверенного поведения указаны отдельно.

От конкретной записи к видимому результату
  1. Выбрать доступный Кабель: SELECT id,title WHERE id=? → его OID
  2. Прочитать tags или передать одно значение set_add/set_drop
  3. Проверить состояние приложения → при успехе commit, при отказе rollback
  4. Перечитать сохранённую коллекцию → показать метки или количество деталей

Коллекции допускают NULL как значение всего атрибута и как отдельный элемент; это разные случаи. SET и MULTISET не гарантируют порядок, SEQUENCE сохраняет его. В CUBRID 11.3 коллекционные типы не поддерживают COLLATE; BLOB/CLOB не используются как их элементы. В этой главе каждый домен однородный VARCHAR(40), а в приложении дополнительно проверяется словарь и отсутствие неполных меток. Не следует предполагать, что произвольный PHP-массив или объект можно передать вместо одного строкового аргумента native API.

Практическая задачаЧто будет видно
Признаки товараМедь не дублируется, старая метка снята, кириллица и апостроф сохранены
Состав комплектаОдна шайба снята из трёх; итог — две детали одного вида
Состояние формыNULL, {} и {NULL} дают разные подсказки, ошибки не скрываются
Сохранение нескольких измененийПротиворечивый предпросмотр отменён; корректная замена сохранена

Признаки кабеля: добавить метку без дубля и убрать старую

В карточке кабеля надо оставить медную жилу (copper), добавить гибкость (flexible) и снять устаревшую пометку для помещения (indoor). Повторный щелчок «медь» не должен создавать второй одинаковый признак. SET подходит для небольшого набора уникальных признаков одной записи. После commit программа снова читает данные, а Хомут остаётся со своим признаком steel.

Как меняются конкретные данные
  1. Кабель: {copper, indoor}; Хомут: {steel}
  2. Кабель: добавить flexible; повторить copper → {copper, indoor, flexible}
  3. Кабель: убрать indoor; добавить Серия O'Reilly → commit
  4. Повторное чтение: copper, flexible, Серия O'Reilly; Хомут: steel

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

$tags = cubrid_col_get($db, $oid, 'tags');
if ($tags === false) { throw new RuntimeException('Метки не прочитаны'); }
// До изменения: ['copper', 'indoor']; порядок SET не гарантируется.
if (!cubrid_set_add($db, $oid, 'tags', 'flexible')) {
    throw new RuntimeException('Метка не добавлена');
}
if (!cubrid_commit($db)) { throw new RuntimeException('Сохранение не подтверждено'); }

$tags получает обычный числовой PHP-массив. Второй вызов изменяет атрибут tags записи с данным OID; строка flexible — отдельный аргумент, её не склеивают с SQL. commit подтверждает изменение в ручном режиме транзакций. В полной программе перед этим создаются две строки и выбирается OID именно кабеля. Для вывода небольшой копии SET используется sort: это оформление трёх меток, а не сортировка выгруженного каталога.

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

НаблюдениеРезультат
ТоварКабель
До измененияcopper, indoor
После сохраненияcopper, flexible, Серия O'Reilly
Количество признаков3
Повторное добавление copper успешнода
Кириллица и апостроф сохраненыда
Хомут не изменёнда
Изменение массива PHP не записалосьда
Самостоятельный проект, исходник и архив
Признаки кабеля: добавить метку без дубля и убрать старуюСамостоятельный пример
<?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;}

    $name=c170OwnName($db,$owned,'TABLE','product_tags');$t=c170Qualified($name);
    c170Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),tags SET(VARCHAR(40))) DONT_REUSE_OID');
    c170Command($db,"INSERT INTO $t (id,title,tags) VALUES (?, ?, {'copper','indoor'})",[10,'Кабель']);
    c170Command($db,"INSERT INTO $t (id,title,tags) VALUES (?, ?, {'steel'})",[20,'Хомут']);
    c170Commit($db);
    $oid=selectedOid170($db,$t,10);
    $before=sorted170(collection170($db,$oid,'tags'));
    change170(cubrid_set_add($db,$oid,'tags','flexible'));
    $duplicate=cubrid_set_add($db,$oid,'tags','copper');change170($duplicate);
    $afterDuplicate=sorted170(collection170($db,$oid,'tags'));
    change170(cubrid_set_drop($db,$oid,'tags','indoor'));
    $series="Серия O'Reilly";
    change170(cubrid_set_add($db,$oid,'tags',$series));
    c170Commit($db);
    $saved=sorted170(collection170($db,selectedOid170($db,$t,10),'tags'));
    $size=cubrid_col_size($db,$oid,'tags');
    if($size===false){throw new RuntimeException('Размер набора не прочитан');}
    $neighbor=collection170($db,selectedOid170($db,$t,20),'tags');
    // PHP-массив — копия. Эта строка не вызывает ни одного изменения базы.
    $local=$saved;$local[]='local-only';
    $unchanged=sorted170(collection170($db,$oid,'tags'));
    c170Commit($db);
    $rows=[['Товар','Кабель'],['До изменения',implode(', ',$before)],
        ['После сохранения',implode(', ',$saved)],['Количество признаков',$size],
        ['Повторное добавление copper успешно',$duplicate],['Кириллица и апостроф сохранены',in_array($series,$saved,true)],
        ['Хомут не изменён',$neighbor===['steel']],['Изменение массива PHP не записалось',$unchanged===$saved]];
    $checks=['initial_members'=>$before===['copper','indoor'],'duplicate_success'=>$duplicate===true,
        'duplicate_not_multiplied'=>$afterDuplicate===['copper','flexible','indoor'],
        'saved_members'=>$saved===['copper','flexible',"Серия O'Reilly"],'saved_size'=>$size===3,
        'utf8_quote_roundtrip'=>in_array($series,$saved,true),'neighbor_unchanged'=>$neighbor===['steel'],
        'local_snapshot_not_write'=>$unchanged===$saved&&count($local)===4];
    $observations=['before'=>$before,'after_duplicate'=>$afterDuplicate,'saved'=>$saved,
        'size'=>$size,'neighbor'=>$neighbor,'php_copy'=>$local,'reread'=>$unchanged,
        'series_bytes'=>strlen($series),'series_sha256'=>hash('sha256',$series),
        'display_order'=>'SORT_STRING on a small copy; SET storage has no promised order'];

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

c170Report('cub170-product-tags','Признаки кабеля: добавить метку без дубля и убрать старую',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Товар — Кабель
До изменения — copper, indoor
После сохранения — copper, flexible, Серия O'Reilly
Количество признаков — 3
Повторное добавление copper успешно — да
Кириллица и апостроф сохранены — да
Хомут не изменён — да
Изменение массива PHP не записалось — да

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

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

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

true от set_add означает успешную операцию, а не обязательный рост размера. Коллекции SET и MULTISET не обещают порядка; сортируется только маленькая копия для экрана. Названия характеристик с отдельной ценой, переводами, правами или массовым поиском удобнее хранить в таблицах со связями. Апостроф передаётся значением функции, поэтому вручную удваивать его здесь не нужно. HTML результата экранирует текст, но это отдельная задача от передачи значения в базу.

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

Добавьте flexible ещё раз и объясните, изменятся ли размер и список. Затем добавьте к PHP-массиву строку local-only без вызова CUBRID.

Разбор

Повтор flexible завершится успешно, но SET сохранит одно вхождение. Массив PHP изменится только в памяти процесса. Для изменения записи нужны отдельный вызов записи и подтверждение транзакции.

Комплект крепежа: считать все детали и вынуть только одну шайбу

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

Как меняются конкретные данные
  1. Исходно: bolt + washer + washer = 3 детали
  2. Добавить washer → bolt + washer + washer + washer = 4
  3. Вынуть одну washer → bolt + washer + washer = 3
  4. Убрать bolt, подтвердить → washer + washer = 2 детали одного вида

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

// parts — MULTISET(VARCHAR(40)), исходно {'washer','washer','bolt'}.
if (!cubrid_set_drop($db, $oid, 'parts', 'washer')) {
    throw new RuntimeException('Деталь не удалена');
}
$parts = cubrid_col_get($db, $oid, 'parts');
if ($parts === false) { throw new RuntimeException('Комплект не прочитан'); }
// Одна washer и один bolt: удалено одно вхождение, не все washer.

set_drop выбирает элемент по значению. В проверенном MULTISET один успешный вызов удаляет одно совпадающее вхождение. col_size считает все оставшиеся элементы, включая повторения. array_count_values в полном проекте только оформляет маленький комплект после чтения; это не команда изменения базы и не количество всех строк таблицы.

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

НаблюдениеРезультат
До комплектацииbolt, washer, washer
После добавления шайбыbolt, washer, washer, washer
Деталей после добавления4
После извлечения одной шайбыbolt, washer, washer
Сохранённый комплектwasher, washer
Осталось шайб2
Всего деталей2
Удаление отсутствующей nut успешнода
Самостоятельный проект, исходник и архив
Комплект крепежа: считать все детали и вынуть только одну шайбуСамостоятельный пример
<?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;}

    $name=c170OwnName($db,$owned,'TABLE','kit_parts');$t=c170Qualified($name);
    c170Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),parts MULTISET(VARCHAR(40))) DONT_REUSE_OID');
    c170Command($db,"INSERT INTO $t (id,title,parts) VALUES (?, ?, {'washer','washer','bolt'})",[10,'Монтажный комплект']);
    c170Commit($db);$oid=selectedOid170($db,$t,10);
    $initial=sorted170(collection170($db,$oid,'parts'));
    change170(cubrid_set_add($db,$oid,'parts','washer'));
    $packed=sorted170(collection170($db,$oid,'parts'));$packedSize=cubrid_col_size($db,$oid,'parts');
    change170(cubrid_set_drop($db,$oid,'parts','washer'));
    $afterOne=sorted170(collection170($db,$oid,'parts'));
    change170(cubrid_set_drop($db,$oid,'parts','bolt'));
    $missing=cubrid_set_drop($db,$oid,'parts','nut');change170($missing);
    c170Commit($db);
    $saved=sorted170(collection170($db,selectedOid170($db,$t,10),'parts'));
    $savedSize=cubrid_col_size($db,$oid,'parts');
    if($packedSize===false||$savedSize===false){throw new RuntimeException('Размер комплекта не прочитан');}
    $counts=array_count_values($saved);c170Commit($db);
    $rows=[['До комплектации',implode(', ',$initial)],['После добавления шайбы',implode(', ',$packed)],
        ['Деталей после добавления',$packedSize],['После извлечения одной шайбы',implode(', ',$afterOne)],
        ['Сохранённый комплект',implode(', ',$saved)],['Осталось шайб',$counts['washer']??0],
        ['Всего деталей',$savedSize],['Удаление отсутствующей nut успешно',$missing]];
    $checks=['initial_duplicates'=>$initial===['bolt','washer','washer'],
        'multiset_add_keeps_repeat'=>$packed===['bolt','washer','washer','washer'],'four_packed'=>$packedSize===4,
        'one_occurrence_removed'=>$afterOne===['bolt','washer','washer'],'bolt_removed'=>$saved===['washer','washer'],
        'two_actual_items'=>$savedSize===2,'one_kind_two_instances'=>count($counts)===1&&($counts['washer']??0)===2,
        'missing_drop_success'=>$missing===true];
    $observations=['initial'=>$initial,'packed'=>$packed,'after_one_drop'=>$afterOne,'saved'=>$saved,
        'saved_size'=>$savedSize,'counts_by_value'=>$counts,'missing_drop'=>$missing,
        'business_meaning'=>'Each repeated washer represents one physical piece; no guaranteed storage order'];

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

c170Report('cub170-kit-multiset','Комплект крепежа: считать все детали и вынуть только одну шайбу',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До комплектации — bolt, washer, washer
После добавления шайбы — bolt, washer, washer, washer
Деталей после добавления — 4
После извлечения одной шайбы — bolt, washer, washer
Сохранённый комплект — washer, washer
Осталось шайб — 2
Всего деталей — 2
Удаление отсутствующей nut успешно — да

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

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

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

true при удалении nut, которой нет, не означает, что что-то исчезло: проверенный драйвер считает такой вызов успешным. Если важен факт снятия позиции, сравните нужное число вхождений до и после в одной корректно защищённой транзакции. Повторный запрос на удаление шайбы уже может удалить вторую: такой бизнес-запрос нельзя бесконтрольно повторять после сетевой неопределённости. Для состава с ценами, партиями и большим количеством деталей нужна отдельная таблица строк комплекта с количеством.

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

Как получить три шайбы и ни одного болта, если исходный комплект тот же? Почему нельзя заменить MULTISET на SET?

Разбор

Добавьте washer один раз, уберите bolt и подтвердите транзакцию. SET оставит единственное значение washer, поэтому по нему нельзя восстановить число физических деталей.

Форма заполнения: не перепутать неизвестный набор, пустой набор и NULL-элемент

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

Как меняются конкретные данные
  1. id=10: tags=NULL → IS NULL=1; col_get=[]; col_size=−1
  2. id=20: tags={} → IS NULL=0; col_get=[]; col_size=0
  3. id=30: tags={NULL} → IS NULL=0; col_get=[null]; col_size=1
  4. missing170 или plain VARCHAR → false + сохранённая диагностика, не «пустой набор»

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

$size = cubrid_col_size($db, $oid, 'tags');
if ($size === false) {
    throw new RuntimeException('Не удалось прочитать размер');
}
if ($size === -1) { echo 'Набор ещё не заполнен'; }
elseif ($size === 0) { echo 'Явно сохранён пустой набор'; }
else { echo 'Элементов: ', $size; }
// -1 для SQL NULL наблюдается в закреплённой версии; это не false.

=== сравнивает и значение, и тип: целое 0, целое −1 и логическое false имеют разный смысл. get возвращает PHP-массив, но для SQL NULL и {} в этом профиле получается одинаковый []. Поэтому полный пример дополнительно читает tags IS NULL в SQL и размер. {NULL} содержит один элемент null: этот элемент не является отсутствием самой коллекции.

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

НаблюдениеРезультат
SQL NULLне заполнено; размер -1
Пустая коллекция {}подтверждено: меток нет; размер 0
Коллекция {NULL}требуется заполнить один элемент; размер 1
Массивы первых двух одинаковыда
Неизвестный атрибут: код-202
Обычная строка вместо коллекции: код-10020
Самостоятельный проект, исходник и архив
Форма заполнения: не перепутать неизвестный набор, пустой набор и NULL-элементСамостоятельный пример
<?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;}

    $name=c170OwnName($db,$owned,'TABLE','state');$t=c170Qualified($name);
    c170Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),tags SET(VARCHAR(40)),plain VARCHAR(40)) DONT_REUSE_OID');
    c170Command($db,"INSERT INTO $t (id,title,tags,plain) VALUES (?, ?, NULL, ?)",[10,'Не заполнено','обычный текст']);
    c170Command($db,"INSERT INTO $t (id,title,tags,plain) VALUES (?, ?, {}, ?)",[20,'Подтверждено: меток нет','обычный текст']);
    c170Command($db,"INSERT INTO $t (id,title,tags,plain) VALUES (?, ?, {NULL}, ?)",[30,'Нужна правка элемента','обычный текст']);
    c170Commit($db);$states=[];
    $selected=c170Read($db,"SELECT id,title,CASE WHEN tags IS NULL THEN 1 ELSE 0 END AS collection_is_null FROM $t WHERE id IN (10,20,30) ORDER BY id LIMIT 3");
    foreach($selected as $record){
        $oid=selectedOid170($db,$t,(int)$record['id']);
        $get=c170Capture(static function()use($db,$oid){return cubrid_col_get($db,$oid,'tags');});
        $size=c170Capture(static function()use($db,$oid){return cubrid_col_size($db,$oid,'tags');});
        if($get['value']===false||$get['warning']||$size['value']===false||$size['warning']){c170Failure('Состояние не прочитано');}
        $status=(int)$record['collection_is_null']===1?'не заполнено':($size['value']===0?'подтверждено: меток нет':'требуется заполнить один элемент');
        $states[]=['id'=>(int)$record['id'],'sql_null'=>(int)$record['collection_is_null']===1,
            'items'=>$get['value'],'size'=>$size['value'],'status'=>$status];
    }
    $oid=selectedOid170($db,$t,10);
    $wrong=c170Capture(static function()use($db,$oid){return cubrid_col_get($db,$oid,'missing170');});
    $scalar=c170Capture(static function()use($db,$oid){return cubrid_col_size($db,$oid,'plain');});
    // These controlled refusals are diagnostics, not empty collections.
    c170Rollback($db);
    $rows=[['SQL NULL',$states[0]['status'].'; размер '.$states[0]['size']],
        ['Пустая коллекция {}',$states[1]['status'].'; размер '.$states[1]['size']],
        ['Коллекция {NULL}',$states[2]['status'].'; размер '.$states[2]['size']],
        ['Массивы первых двух одинаковы',$states[0]['items']===$states[1]['items']],
        ['Неизвестный атрибут: код',$wrong['code']],['Обычная строка вместо коллекции: код',$scalar['code']]];
    $checks=['three_bounded_records'=>count($states)===3,'null_is_unknown'=>$states[0]['sql_null']&&$states[0]['items']===[]&&$states[0]['size']===-1,
        'empty_is_known'=>!$states[1]['sql_null']&&$states[1]['items']===[]&&$states[1]['size']===0,
        'one_null_is_one'=>!$states[2]['sql_null']&&$states[2]['items']===[null]&&$states[2]['size']===1,
        'get_alone_loses_distinction'=>$states[0]['items']===$states[1]['items'],
        'unknown_attribute_refused'=>$wrong['value']===false&&$wrong['code']===-202&&$wrong['facility']===1&&$wrong['warning']&&$wrong['message_present'],
        'scalar_refused'=>$scalar['value']===false&&$scalar['code']===-10020&&$scalar['facility']===2&&$scalar['warning']&&$scalar['message_present']];
    $observations=['states'=>$states,'wrong_attribute'=>$wrong,'not_collection'=>$scalar,
        'nullable_form_policy'=>'SQL NULL unknown; empty collection explicitly no tags; NULL member needs correction'];

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

c170Report('cub170-collection-state','Форма заполнения: не перепутать неизвестный набор, пустой набор и NULL-элемент',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
SQL NULL — не заполнено; размер -1
Пустая коллекция {} — подтверждено: меток нет; размер 0
Коллекция {NULL} — требуется заполнить один элемент; размер 1
Массивы первых двух одинаковы — да
Неизвестный атрибут: код — -202
Обычная строка вместо коллекции: код — -10020

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

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

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

Значение −1 для NULL подтверждено CCI и проверенной сборкой; не путайте его со старым возвратом −1 при ошибке col_size до версии 8.3.1. В новых версиях ошибка — false. Конкретные коды отказа привязаны к стенду, а приложение должно прежде всего проверять false и сохранять текущую диагностику. Наличие NULL-элемента не даёт универсального правила формы: здесь это наша явная политика, запрещающая неопределённые метки.

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

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

Разбор

Не заменяйте NULL на {} автоматически и не вызывайте empty($items) как единственный классификатор. Храните отдельный SQL NULL и показывайте его состояние через IS NULL; пустой известный набор оставляйте {}.

Изменение способов выдачи: отменить противоречивый набор и сохранить корректный

Магазин меняет выдачу шкафа: вместо самовывоза остаётся только доставка. Две метки pickup и delivery-only одновременно противоречат правилу магазина. Сначала покажем, что успешный set_add ещё не делает набор допустимым: предпросмотр отменяется. Затем удалим старый признак и добавим новый в одной транзакции, подтвердим и перечитаем. Полка сохранит самовывоз.

Как меняются конкретные данные
  1. Шкаф: {pickup}; Полка: {pickup}
  2. Предпросмотр шкафа {pickup, delivery-only} → конфликт → rollback → {pickup}
  3. Новый выбор: удалить pickup + добавить delivery-only → commit
  4. Перечитать: Шкаф {delivery-only}; Полка {pickup}

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

// $db работает с cubrid_set_autocommit($db, false).
try {
    if (!cubrid_set_drop($db, $oid, 'tags', 'pickup') ||
        !cubrid_set_add($db, $oid, 'tags', 'delivery-only')) {
        throw new RuntimeException('Способ выдачи не изменён');
    }
    if (!cubrid_commit($db)) { throw new RuntimeException('Изменение не подтверждено'); }
} catch (Throwable $failure) {
    cubrid_rollback($db);
    throw $failure;
}

try объединяет работу, catch обрабатывает отказ и отменяет незавершённые изменения; разбор этой конструкции связан с главой транзакций. В простом фрагменте показана граница двух операций, а полный проект сначала проверяет допустимое значение и затем итоговый набор. Временный предпросмотр выполняется в рамках короткой транзакции внутри одного запуска, а не во время ожидания действий человека в браузере.

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

НаблюдениеРезультат
Исходный способ выдачиpickup
Предпросмотрdelivery-only, pickup
Конфликт обнаруженда
После отменыpickup
Сохранённый способ для шкафаdelivery-only
Способ для полкиpickup
Самостоятельный проект, исходник и архив
Изменение способов выдачи: отменить противоречивый набор и сохранить корректныйСамостоятельный пример
<?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;}

    $name=c170OwnName($db,$owned,'TABLE','membership');$t=c170Qualified($name);
    c170Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),tags SET(VARCHAR(40))) DONT_REUSE_OID');
    c170Command($db,"INSERT INTO $t (id,title,tags) VALUES (?, ?, {'pickup'})",[10,'Шкаф']);
    c170Command($db,"INSERT INTO $t (id,title,tags) VALUES (?, ?, {'pickup'})",[20,'Полка']);
    c170Commit($db);
    $oid=selectedOid170($db,$t,10);$original=collection170($db,$oid,'tags');
    change170(cubrid_set_add($db,$oid,'tags','delivery-only'));
    $preview=sorted170(collection170($db,$oid,'tags'));
    $conflict=in_array('pickup',$preview,true)&&in_array('delivery-only',$preview,true);
    if(!$conflict){throw new RuntimeException('Ожидался конфликт правил выдачи');}
    c170Rollback($db);
    $afterCancel=collection170($db,selectedOid170($db,$t,10),'tags');
    // A new allowed choice is validated before the two writes.
    $requested='delivery-only';$allowed=['pickup','delivery-only'];
    if(!in_array($requested,$allowed,true)){throw new InvalidArgumentException('Неизвестный способ выдачи');}
    $oid=selectedOid170($db,$t,10);
    try{
        change170(cubrid_set_drop($db,$oid,'tags','pickup'));
        change170(cubrid_set_add($db,$oid,'tags',$requested));
        $candidate=collection170($db,$oid,'tags');
        if($candidate!==['delivery-only']){throw new RuntimeException('Неверный итоговый набор');}
        c170Commit($db);
    }catch(Throwable $failure){c170Rollback($db);throw $failure;}
    $saved=collection170($db,selectedOid170($db,$t,10),'tags');
    $neighbor=collection170($db,selectedOid170($db,$t,20),'tags');c170Commit($db);
    $rows=[['Исходный способ выдачи',implode(', ',$original)],['Предпросмотр',implode(', ',$preview)],
        ['Конфликт обнаружен',$conflict],['После отмены',implode(', ',$afterCancel)],
        ['Сохранённый способ для шкафа',implode(', ',$saved)],['Способ для полки',implode(', ',$neighbor)]];
    $checks=['original_pickup'=>$original===['pickup'],'preview_conflict'=>$preview===['delivery-only','pickup']&&$conflict,
        'rollback_restored'=>$afterCancel===$original,'requested_allowed'=>in_array($requested,$allowed,true),
        'saved_new_policy'=>$saved===['delivery-only'],'no_removed_pickup'=>!in_array('pickup',$saved,true),
        'neighbor_unchanged'=>$neighbor===['pickup']];
    $observations=['original'=>$original,'preview'=>$preview,'after_rollback'=>$afterCancel,
        'saved'=>$saved,'neighbor'=>$neighbor,'allowed_values'=>$allowed,
        'policy'=>'pickup and delivery-only are mutually exclusive for this item'];

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

c170Report('cub170-membership-save','Изменение способов выдачи: отменить противоречивый набор и сохранить корректный',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Исходный способ выдачи — pickup
Предпросмотр — delivery-only, pickup
Конфликт обнаружен — да
После отмены — pickup
Сохранённый способ для шкафа — delivery-only
Способ для полки — pickup

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

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

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

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

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

Что произойдёт, если после удаления pickup второй вызов вернёт false? Почему нельзя делать commit сразу после удаления?

Разбор

В ручном режиме catch откатит первое изменение. Ранний commit сохранил бы промежуточное состояние без способа выдачи, и последующий rollback его уже не отменил бы.

Когда вместо коллекции нужна таблица со связями

Вопрос о пользовательских метках и отдельной таблице полезен именно выбором модели: строка с запятыми, коллекция и таблица связей дают разные возможности. Здесь применяется типизированная коллекция CUBRID, а не разбор строки explode. Для меток с собственными названиями, переводами и поиском по многим товарам рассмотрите таблицы tags и product_tags с ключами; схема и составные ключи показывают основы такого решения.

Обсуждения заранее заданных меток и слабых сравнений NULL подсказывают две отдельные обязанности приложения: проверять допустимые значения и различать отсутствие данных. Синтаксис MySQL SET и его битовые представления сюда не переносятся: SQL-коллекция CUBRID имеет собственный контракт. Решения этой главы сверены с PHP Manual, CCI и закреплённым native-драйвером.

Четыре функции по задаче

ФункцияЗачем нужна
cubrid_col_getПрочитать элементы одного атрибута коллекции в числовой PHP-массив.
cubrid_col_sizeУзнать количество элементов, включая повторения и NULL-элементы, не передавая их все в PHP.
cubrid_set_addДобавить одно значение в SET/MULTISET; для SEQUENCE проверенный вызов добавляет его в конец.
cubrid_set_dropУдалить одно совпадающее значение из SET или одно вхождение из MULTISET.

Следующий шаг: порядок элементов

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

Если важны одновременные изменения, вернитесь к доступу по OID и блокировкам и транзакциям. Для обычного каталога строк продолжайте выбирать нужные поля, WHERE, ORDER BY и LIMIT в SQL: работа с коллекцией одной записи не является поводом загружать всю таблицу в PHP.

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

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

cubrid_col_getПрочитать элементы одного атрибута коллекции в числовой PHP-массив.

PECL CUBRID >= 8.3.0; у col_size возврат ошибки изменён в 8.3.1. · cubrid

cubrid_col_get(resource $conn_identifier, string $oid, string $attr_name): array|false

Экран карточки должен показать признаки выбранного товара. Функция читает содержимое одного атрибута SET, MULTISET или SEQUENCE по OID, а не все строки таблицы.

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

Результат: Числовой массив с ключами 0, 1, …; false при отказе. В проверенном native-драйвере элементы читаются через CCI как строки, SQL NULL-элементы становятся PHP null. Результат уже является массивом, не resource для fetch/close_request.

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

Пустая коллекция даёт []. Существенная граница профиля 11.2.0.0002/CCI 11.2.0: SQL NULL всего атрибута также даёт [], а {NULL} — [null]. Не восстанавливайте состояние формы по одному empty($items). Размер различает −1, 0 и 1, а SQL tags IS NULL явно проверяет отсутствие значения атрибута. Подробности и отказы собраны в трёх состояниях поля.

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

$items = cubrid_col_get($db, $oid, 'tags');
if ($items === false) { throw new RuntimeException('Признаки недоступны'); }
sort($items, SORT_STRING); // Только небольшой набор для стабильного вывода.
echo htmlspecialchars(implode(', ', $items), ENT_QUOTES, 'UTF-8');

Перед этим приложение знает, что tags — заполненный SET строк без NULL. В карточке кабеля после сохранения видны copper, flexible и Серия O'Reilly. sort не задаёт порядок хранения SET и не записывает его обратно. Для NULL-элемента нужна отдельная подпись, а не неявное превращение в пустую строку.

Применение 2: посчитать повторяющиеся детали комплекта

$parts = cubrid_col_get($db, $oid, 'parts');
if ($parts === false) { throw new RuntimeException('Состав не прочитан'); }
// Здесь схема и проверка приложения допускают только непустые строки.
$byPart = array_count_values($parts);
echo $byPart['washer'] ?? 0; // 2 у сохранённого комплекта из урока.

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

SET/MULTISET не обещают порядок, а SEQUENCE сохраняет позиции: её массив нельзя сортировать для последующей позиционной записи. Чтение делает снимок, не живую связь с базой. После изменения массива данные сервера остаются прежними. После добавления или удаления перечитайте атрибут в нужной границе транзакции. Внутренний CCI-request функция освобождает сама.

Ошибка имени атрибута, неколлекционный тип или недоступный OID дают отказ; не представляйте его пользователю пустым списком. Проверенный неизвестный атрибут дал −202/facility 1, VARCHAR вместо коллекции — −10020/facility 2. Это наблюдения конкретного профиля, а не полный перечень возможных кодов. Вызов выполняется на существующем соединении; он не открывает базу и не проверяет бизнес-разрешение пользователя.

cubrid_col_sizeУзнать количество элементов, включая повторения и NULL-элементы, не передавая их все в PHP.

PECL CUBRID >= 8.3.0; у col_size возврат ошибки изменён в 8.3.1. · cubrid

cubrid_col_size(resource $conn_identifier, string $oid, string $attr_name): int|false

Надпись «в комплекте 4 детали» требует общего числа элементов, а не полного списка. Функция получает размер одного атрибута коллекции; это не количество строк результата SELECT и не число уникальных названий.

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

Результат: Целое число элементов SET/MULTISET/SEQUENCE; false при ошибке начиная с PECL CUBRID 8.3.1. До 8.3.1 документация указывает −1 как возврат ошибки. В проверенном новом профиле размер −1 без ошибки означает SQL NULL коллекции по контракту CCI.

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

Сохранено в атрибутеcol_getcol_sizeЧто сказать человеку
NULL[]−1Набор ещё не задан
{}[]0Явно известно, что элементов нет
{NULL}[null]1Есть один неизвестный элемент
MULTISET {'washer','washer'}['washer','washer']2Две детали одного вида

Применение 1: количество деталей, а не видов

$count = cubrid_col_size($db, $oid, 'parts');
if ($count === false) { throw new RuntimeException('Количество не получено'); }
if ($count === -1) { echo 'Состав не задан'; }
else { echo 'Деталей: ', $count; }

В комплекте после добавления третьей шайбы число равно 4, после финального сохранения — 2. Если нужен только счётчик, col_get и подсчёт большого PHP-массива не требуются. Число видов — другой вопрос; оно не равно размеру MULTISET.

Применение 2: не обнулить незаполненное поле формы

$count = cubrid_col_size($db, $oid, 'tags');
if ($count === false) { throw new RuntimeException('Не удалось загрузить поле'); }
$caption = $count === -1 ? 'Не заполнено'
    : ($count === 0 ? 'Метки не требуются' : 'Выбрано элементов: '.$count);

Самостоятельная программа сверяет это с SQL IS NULL и показывает, почему if (!$count) смешивает корректный ноль с false. Не перепутайте старый ошибочный −1 и нынешний NULL-размер: версия драйвера, тип возвращённого значения и немедленная диагностика имеют значение.

Здесь элементы измеряются штуками, не символами и не байтами текста. Размер до изменения и после него может совпасть, например, при повторном добавлении SET-метки. При параллельных изменениях раздельные вызовы get/size не являются автоматически общим снимком: выбирайте подходящую изоляцию и ограничение ожидания. Для числа выбранных строк предназначен cubrid_num_rows, а не этот API.

cubrid_set_addДобавить одно значение в SET/MULTISET; для SEQUENCE проверенный вызов добавляет его в конец.

PECL CUBRID >= 8.3.0; у col_size возврат ошибки изменён в 8.3.1. · cubrid

cubrid_set_add(resource $conn_identifier, string $oid, string $attr_name, string $set_element): bool

Добавить один признак товару или одну физическую деталь комплекту можно без пересборки всей коллекции. Функция принимает значение отдельным аргументом и изменяет атрибут выбранной записи.

ПараметрЧто передать
conn_identifierОткрытое native-соединение cubrid.
oidOID выбранной доступной записи referable-таблицы.
attr_nameТочное имя атрибута коллекции; это имя столбца, не индекс PHP-массива.
set_elementОдин добавляемый элемент, переданный строкой. Тип элемента должен подходить объявленному домену коллекции; в strict_types профиле не подменяйте строку массивом или целым числом.

Результат: true при успешной операции; false при ошибке. true не гарантирует, что размер увеличился: SET не хранит второй такой же элемент. В MULTISET повтор является отдельным элементом.

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

PHP Manual относит вызов к SET, MULTISET и SEQUENCE. Для SEQUENCE подготовка подтвердила добавление в конец; когда важна конкретная позиция, используйте cubrid_seq_insert. Для SET порядок добавления не является порядком будущего вывода.

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

$ok = cubrid_set_add($db, $oid, 'tags', 'copper');
if ($ok !== true) { throw new RuntimeException('Признак не установлен'); }
// Повтор того же вызова в SET не создаёт второй copper.
if (!cubrid_commit($db)) { throw new RuntimeException('Не подтверждено'); }

Кабель с признаками показывает повторный вызов и реальное чтение без дубля. Имя tags задано схемой приложения, а не произвольным полем запроса. Для контролируемых признаков проверьте допустимый словарь значений: коллекция строк сама по себе не запрещает опечатку coppre.

Применение 2: добавить ещё одну одинаковую деталь

$ok = cubrid_set_add($db, $oid, 'parts', 'washer');
if ($ok !== true) { throw new RuntimeException('Шайба не добавлена'); }
$count = cubrid_col_size($db, $oid, 'parts');
if ($count === false) { throw new RuntimeException('Количество недоступно'); }
// Две washer + bolt становятся тремя washer + bolt: размер 4.

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

API передаёт значение через CCI, а не склеивает SQL: строка с апострофом передаётся как есть. Экранирование HTML выполняется отдельно при выводе. Политика приложения может запретить технически успешный набор: пример способов выдачи отменяет конфликт и подтверждает только допустимое состояние. Выключенный autocommit требует явного commit, а при отказе всей операции — rollback. Функция не заменяет проверку прав, блокировку либо контроль версии редактируемой строки.

cubrid_set_dropУдалить одно совпадающее значение из SET или одно вхождение из MULTISET.

PECL CUBRID >= 8.3.0; у col_size возврат ошибки изменён в 8.3.1. · cubrid

cubrid_set_drop(resource $conn_identifier, string $oid, string $attr_name, string $set_element): bool

Снять устаревший признак или вынуть одну одинаковую деталь — это удаление элемента по значению, а не удаление строки товара. Документированные коллекции этой функции: SET и MULTISET.

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

Результат: true при успешной операции; false при ошибке. В проверенном MULTISET удаляется одно совпадающее вхождение. Если значения не было, проверенный вызов тоже возвращает true, сохраняя коллекцию без изменений.

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

Применение 1: снять старую метку товара

if (!cubrid_set_drop($db, $oid, 'tags', 'indoor')) {
    throw new RuntimeException('Метка не снята');
}
if (!cubrid_commit($db)) { throw new RuntimeException('Сохранение не подтверждено'); }
$tags = cubrid_col_get($db, $oid, 'tags');
if ($tags === false) { throw new RuntimeException('Результат не прочитан'); }

В наборе признаков кабеля indoor исчезает, остальные метки сохраняются. Функция не удаляет сам столбец, строку или всю таблицу. Если интерфейсу нужен факт «метка присутствовала и была снята», true без проверки состояния не отвечает на этот вопрос.

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

// parts содержит три washer и один bolt.
if (!cubrid_set_drop($db, $oid, 'parts', 'washer')) {
    throw new RuntimeException('Деталь не снята');
}
// После успешного чтения останутся две washer и один bolt.
// Ещё один такой вызов снимет ещё одну washer.

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

Для SEQUENCE значение может встречаться в нескольких позициях с разным смыслом; используйте позиционное удаление cubrid_seq_drop. Если удаление — часть замены одного правила другим, объедините шаги, как в сохранении способа выдачи. NULL атрибута, отсутствующее значение, неправильный атрибут и false от функции — разные состояния; для классификации смотрите состояния коллекции и сохраняйте диагностику до следующего CUBRID-вызова.

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

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

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

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

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