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

Дерево руководства
Глава 40 · От первого файла к обмену данными
dBase: создать DBF, понять схему и прочитать таблицу
От двух столбцов и позиционного массива до всех типов DBF, именованного чтения, кодировки и nullable FoxPro.
Зачем нужен DBF и как выглядит его таблица
Выгрузка из старой учётной системы может приходить таблицей .dbf. Здесь учимся читать её структуру и строки, создавать согласованный файл для обмена, сохранять нулевые остатки и отличать отсутствующее значение. Начинаем с двух колонок, затем добавляем типы, кодировку и проверку результата. Это расширение PHP для работы с форматом обмена, а не SQL-сервер.
- Заголовок: SKU C(6), QTY N(4,0)
- Запись №1: A-100 + 12
- Запись №2: B-200 + 0
- PHP: массив значений одной записи
Аналогия — журнал с заранее размеченными графами. В отличие от листа Excel, каждая строка здесь имеет фиксированную ширину, а формат и байты колонок нужно согласовать. PHP-массив служит представлением прочитанной строки; он не является самим DBF-файлом. Понятия массива, пути к файлу и цикла используются с коротким объяснением по мере появления.
PHP manual рекомендует SQLite либо SQL-сервер для основной базы сайта; dBase полезен прежде всего для импорта/экспорта. Поддерживаются уровни DBF 3–5 и перечисленные возможности FoxPro. Индексы и memo не поддерживаются. Расширение вынесено в PECL с PHP 5.3.0; наличие PHP само по себе не означает наличие функций dbase.
Подготовить PHP и понять ресурс dbase
Проверьте расширение именно в том PHP, которым запускается example.php. Пакет этой главы выполнен в изолированном PHP 8.5.11 NTS x64 с официальным dbase 7.1.1; iconv нужен сценарию кодировки. На сервере PHP CLI и FPM/Apache могут читать разные php.ini. Установка расширения не требует создавать SQL-сервер.
Установка и первая проверка
Источник — PECL dbase и инструкция установки PECL. В Linux расширение собирают для установленного PHP: нужны его совместимые phpize/php-config и средства сборки; после установки включают extension=dbase. На Windows выбирайте официальную DLL под точную ветку PHP, x64/x86, TS/NTS и компилятор, затем подключайте её в ini или ключом -d extension=полный-путь. DLL для другой версии PHP нельзя исправить простым переименованием.
php --ini
php --ri dbaseЭто команды терминала, не PHP-код. --ini показывает загруженную конфигурацию, --ri dbase — сведения о расширении. Если расширения нет, сначала устраните эту причину. Проверочный локальный запуск использует -n и явную DLL, поэтому не зависит от набора других расширений пользователя. Скачанные исходники, URL и контрольные суммы окружения сохранены в доказательствах пакета.
Ресурс и время его жизни
С dbase 7.0.0 create/open возвращают resource типа dbase. Это дескриптор открытого файла, не массив строк и не имя файла. После проверки !== false передавайте его в операции, затем закрывайте. Закрытый handle больше недействителен. finally закрывает файл даже при ошибке обработки; само исключение не скрывается.
Создать два столбца, добавить товары и прочитать сохранённый файл
Старая складская программа принимает остатки в DBF: у артикула A-100 есть 12 единиц, у B-200 — ноль. Создадим настоящий файл с двумя столбцами, закроем его и прочитаем заново. Получится переносимая таблица catalog.dbf. DBF состоит из заголовка со схемой и последовательно записанных строк одинаковой длины.
- Схема [SKU, C, 6] и [QTY, N, 4, 0] → два столбца
- Строка [A-100, 12] → значения в том же порядке
- add_record → запись №1
- close → файл остаётся на диске
- Новое открытие → [SKU => A-100, QTY => 12, deleted => 0]
Что получится после повторного чтения
| Record | SKU | QTY | deleted |
|---|---|---|---|
| 1 | A-100 | 12 | 0 |
| 2 | B-200 | 0 | 0 |
| Описание поля | Первая строка | Чтение по имени |
|---|---|---|
["SKU", "C", 6] | [0] = "A-100" | $row["SKU"] |
["QTY", "N", 4, 0] | [1] = 12 | $row["QTY"] |
$schema = [['SKU', 'C', 6], ['QTY', 'N', 4, 0]];
$db = dbase_create(dbfPath('catalog.dbf'), $schema);
dbase_add_record($db, ['A-100', 12]);
dbase_close($db);$schema — PHP-массив описаний столбцов. В первом описании SKU — имя, C — строка, 6 — её ширина в байтах; во втором N — число, 4 — общая ширина, 0 — число дробных знаков. Квадратные скобки создают массив; запятые разделяют элементы. dbase_create получает путь и схему, возвращает ресурс открытого файла либо false. dbase_add_record получает этот ресурс и позиционный массив: сначала SKU, затем QTY. В полной программе проверяются оба отказа. dbase_close закрывает ресурс; данные остаются. dbfPath из helper не разрешает перезаписать существующий учебный файл.
Открыть таблицу реального запуска · Скачать данные результата. HTML генерирует сама PHP-программа из прочитанных значений; для просмотра этой таблицы сервер не нужен.
Учебные DBF: catalog.dbf.
<?php
declare(strict_types=1);
require __DIR__ . '/dbf105-helper.php';
$schema = [['SKU', 'C', 6], ['QTY', 'N', 4, 0]];
$db = dbase_create(dbfPath('catalog.dbf'), $schema);
if ($db === false) { throw new RuntimeException('Cannot create catalog'); }
try {
foreach ([['A-100', 12], ['B-200', 0]] as $values) {
if (!dbase_add_record($db, $values)) { throw new RuntimeException('Cannot add product'); }
}
} finally { dbase_close($db); }
$reader = dbfOpen('catalog.dbf');
try {
$count = dbase_numrecords($reader); $rows = [];
for ($number = 1; $number <= $count; $number++) {
$row = dbase_get_record_with_names($reader, $number);
if ($row === false) { throw new RuntimeException('Cannot read product'); }
$rows[] = [$number, rtrim($row['SKU'], ' '), $row['QTY'], $row['deleted']];
}
} finally { dbase_close($reader); }
dbfReport(['Record', 'SKU', 'QTY', 'deleted'], $rows, ['reopened' => true, 'records' => $count]);
echo "catalog.dbf: 2 records; A-100=12, B-200=0\n";
Ожидаемый результат
catalog.dbf: 2 records; A-100=12, B-200=0
Скачайте комплект, распакуйте в пустой каталог и выполните php example.php с dbase. Helper и README включены; input.csv приложен к импорту. Helper содержит общие операции создания/чтения и отчёт; его исходник доступен в комплекте. Все DBF принадлежат этому примеру. Если файл уже существует, создание останавливается: для повтора возьмите другую пустую папку. Изменения проверяются новым открытием; таблица сохраняется в result.html и result.json.
Схема не содержит товаров: она задаёт места под их значения. Столбец SKU шириной 6 хранит A-100 и пробел заполнения. При чтении мы убираем только хвостовые пробелы через rtrim($value, " "). Ноль сохраняется числом 0. Колонку deleted мы не создавали: это служебный признак API чтения. Файл с расширением .dbf не обязательно совместим с любым читателем: семейство DBF имеет варианты.
Проверить схему файла до чтения строк
Перед импортом нужно убедиться, что колонки файла означают ожидаемые данные. Нельзя читать «третий элемент как количество», пока не проверена схема. Пример сам создаёт согласованный файл, затем получает описания его колонок тем же способом, которым проверяется полученная выгрузка.
- schema.dbf → заголовок
- get_header_info → шесть описаний
- SKU/NAME/QTY/PRICE/ADDED/ACTIVE → сравнение с договорённостью
- Разрешённая схема → чтение
Что получится после повторного чтения
| Index | Name | Type | Bytes | Precision | Offset | Format |
|---|---|---|---|---|---|---|
| 0 | SKU | character | 6 | 0 | 1 | %-6s |
| 1 | NAME | character | 12 | 0 | 7 | %-12s |
| 2 | QTY | number | 4 | 0 | 19 | %4s |
| 3 | PRICE | number | 8 | 2 | 23 | %8s |
| 4 | ADDED | date | 8 | 0 | 31 | %8s |
| 5 | ACTIVE | boolean | 1 | 0 | 39 | %1s |
$header = dbase_get_header_info($db);
$columns = dbase_numfields($db);
$actualNames = array_column($header, 'name');dbase_get_header_info возвращает список полей. name — имя, type — понятное название типа, length — байты, precision — дробные знаки, offset — смещение от начала записи, format — предложенный формат printf. Первая позиция списка имеет индекс 0. numfields возвращает количество колонок, numrecords — количество записей. array_column выбирает имена для простого сравнения. В рабочем импорте сравнивайте также типы, ширину и точность: одни имена не доказывают совместимость чисел.
Открыть таблицу реального запуска · Скачать данные результата. HTML генерирует сама PHP-программа из прочитанных значений; для просмотра этой таблицы сервер не нужен.
Учебные DBF: schema.dbf.
<?php
declare(strict_types=1);
require __DIR__ . '/dbf105-helper.php';
dbfSeed('schema.dbf', [['SKU', 'C', 6], ['NAME', 'C', 12], ['QTY', 'N', 4, 0], ['PRICE', 'N', 8, 2], ['ADDED', 'D'], ['ACTIVE', 'L']], [['A-100', 'Tea', 12, 19.90, '20261026', 'T'], ['B-200', 'Coffee', 0, 42.50, '20261025', 'F']]);
$db = dbfOpen('schema.dbf');
try {
$header = dbase_get_header_info($db);
if ($header === false) { throw new RuntimeException('Cannot read header'); }
$columns = dbase_numfields($db); $count = dbase_numrecords($db);
$actualNames = array_column($header, 'name');
$expectedNames = ['SKU', 'NAME', 'QTY', 'PRICE', 'ADDED', 'ACTIVE'];
if ($actualNames !== $expectedNames) { throw new RuntimeException('Unexpected columns'); }
$rows = [];
foreach ($header as $index => $field) {
$rows[] = [$index, $field['name'], $field['type'], $field['length'], $field['precision'], $field['offset'], $field['format']];
}
} finally { dbase_close($db); }
dbfReport(['Index', 'Name', 'Type', 'Bytes', 'Precision', 'Offset', 'Format'], $rows, ['fields' => $columns, 'records' => $count, 'schema_matches' => true]);
echo "schema.dbf: 6 fields, 2 records; schema accepted\n";
Ожидаемый результат
schema.dbf: 6 fields, 2 records; schema accepted
Скачайте комплект, распакуйте в пустой каталог и выполните php example.php с dbase. Helper и README включены; input.csv приложен к импорту. Helper содержит общие операции создания/чтения и отчёт; его исходник доступен в комплекте. Все DBF принадлежат этому примеру. Если файл уже существует, создание останавливается: для повтора возьмите другую пустую папку. Изменения проверяются новым открытием; таблица сохраняется в result.html и result.json.
Offset первой колонки равен 1: перед ней расположен байт пометки удаления. PRICE: N, ширина 8, precision 2 оставляет место и для точки, и для знака. D возвращается строкой YYYYMMDD, не DateTime. Format форматирует, но не проверяет достоверность входа. Массив заголовка нельзя напрямую передать в create: структура определений другая. Полный перечень типов и ограничения приведены далее.
Пройти записи с единицы и вычислить общий остаток
Посчитаем общий остаток трёх товаров обычным for. Таблица показывает номер прочитанной записи и два индекса полученного PHP-массива. Результат — 19; товар с нулевым остатком не потерялся.
- numrecords → 3
- for: number=1 → 2 → 3
- get_record(number) → [0 => SKU, 1 => QTY, deleted => 0]
- Прибавляем QTY → 12 + 0 + 7 = 19
Что получится после повторного чтения
| Record | row[0]: SKU | row[1]: QTY |
|---|---|---|
| 1 | A-100 | 12 |
| 2 | B-200 | 0 |
| 3 | C-300 | 7 |
$row = dbase_get_record($db, 1);
$sku = rtrim($row[0], ' ');
$quantity = $row[1];В DBF запись считается с 1, колонки PHP-массива — с 0. Поэтому get_record($db, 1) читает первый товар, а $row[0] содержит его SKU. Цикл начинает $number с 1, работает пока $number <= $count и увеличивает номер после прохода. $total += $row[1] прибавляет количество к сумме. numfields здесь равен 2, но count($row) был бы 3 из-за дополнительного deleted.
Открыть таблицу реального запуска · Скачать данные результата. HTML генерирует сама PHP-программа из прочитанных значений; для просмотра этой таблицы сервер не нужен.
Учебные DBF: stock.dbf.
<?php
declare(strict_types=1);
require __DIR__ . '/dbf105-helper.php';
dbfSeed('stock.dbf', [['SKU', 'C', 6], ['QTY', 'N', 4, 0]], [['A-100', 12], ['B-200', 0], ['C-300', 7]]);
$db = dbfOpen('stock.dbf');
try {
$count = dbase_numrecords($db); $fieldCount = dbase_numfields($db);
$rows = []; $total = 0;
for ($number = 1; $number <= $count; $number++) {
$row = dbase_get_record($db, $number);
if ($row === false) { throw new RuntimeException('Cannot read stock'); }
$rows[] = [$number, rtrim($row[0], ' '), $row[1]];
$total += $row[1];
}
} finally { dbase_close($db); }
dbfReport(['Record', 'row[0]: SKU', 'row[1]: QTY'], $rows, ['fields' => $fieldCount, 'total_qty' => $total]);
echo "stock.dbf: 3 records; total_qty=19\n";
Ожидаемый результат
stock.dbf: 3 records; total_qty=19
Скачайте комплект, распакуйте в пустой каталог и выполните php example.php с dbase. Helper и README включены; input.csv приложен к импорту. Helper содержит общие операции создания/чтения и отчёт; его исходник доступен в комплекте. Все DBF принадлежат этому примеру. Если файл уже существует, создание останавливается: для повтора возьмите другую пустую папку. Изменения проверяются новым открытием; таблица сохраняется в result.html и result.json.
В этом файле пока нет помеченных удалёнными строк. При импорте существующего DBF проверяйте deleted перед суммированием, как показано в следующей главе. Номер записи — текущая позиция, не постоянный идентификатор. Полный проход читает n строк; это не SQL-агрегация и не индексный поиск. Большой файл лучше считать потоком без накопления всех строк в памяти и HTML.
Построить таблицу доступных товаров по именам столбцов
Менеджеру нужна таблица каталога: название, остаток, цена, дата и доступность заказа. Наше правило доступности: товар активен и количество больше нуля. Читаем поля по именам и формируем HTML из значений закрытого и повторно открытого products.dbf.
- Вход ACTIVE: T или F
- get_record_with_names → ACTIVE: bool
- ACTIVE и QTY > 0 → available/unavailable
- Экранированные значения → result.html
Что получится после повторного чтения
| SKU | Name | QTY | Price | Added | Active | Status |
|---|---|---|---|---|---|---|
| A-100 | Tea | 12 | 19.9 | 20261026 | true | available |
| B-200 | Coffee | 0 | 42.5 | 20261025 | false | unavailable |
$item = dbase_get_record_with_names($db, 1);
$name = rtrim($item['NAME'], ' ');
$status = $item['ACTIVE'] && $item['QTY'] > 0 ? 'available' : 'unavailable';get_record_with_names возвращает ассоциативный массив: $item["NAME"] понятнее $item[1]. && означает «оба условия истинны». N без дроби возвращается целым, PRICE — float, ADDED — строкой. Вход L: T/Y для истины, F/N для лжи, ? для неопределённого. В нашем реальном runtime PHP true при записи дал false после чтения; поэтому исходные значения намеренно "T" и "F". После чтения уже получаем bool для условий.
Открыть таблицу реального запуска · Скачать данные результата. HTML генерирует сама PHP-программа из прочитанных значений; для просмотра этой таблицы сервер не нужен.
Учебные DBF: products.dbf.
<?php
declare(strict_types=1);
require __DIR__ . '/dbf105-helper.php';
dbfSeed('products.dbf', [['SKU', 'C', 6], ['NAME', 'C', 12], ['QTY', 'N', 4, 0], ['PRICE', 'N', 8, 2], ['ADDED', 'D'], ['ACTIVE', 'L']], [['A-100', 'Tea', 12, 19.90, '20261026', 'T'], ['B-200', 'Coffee', 0, 42.50, '20261025', 'F']]);
$db = dbfOpen('products.dbf');
try {
$rows = []; $available = 0; $count = dbase_numrecords($db);
for ($number = 1; $number <= $count; $number++) {
$item = dbase_get_record_with_names($db, $number);
if ($item === false) { throw new RuntimeException('Cannot read item'); }
$status = $item['ACTIVE'] && $item['QTY'] > 0 ? 'available' : 'unavailable';
if ($status === 'available') { $available++; }
$rows[] = [rtrim($item['SKU'], ' '), rtrim($item['NAME'], ' '), $item['QTY'], $item['PRICE'], $item['ADDED'], $item['ACTIVE'], $status];
}
} finally { dbase_close($db); }
dbfReport(['SKU', 'Name', 'QTY', 'Price', 'Added', 'Active', 'Status'], $rows, ['available_products' => $available, 'logical_input' => 'T/F']);
echo "products.dbf: available=1; result.html contains 2 products\n";
Ожидаемый результат
products.dbf: available=1; result.html contains 2 products
Скачайте комплект, распакуйте в пустой каталог и выполните php example.php с dbase. Helper и README включены; input.csv приложен к импорту. Helper содержит общие операции создания/чтения и отчёт; его исходник доступен в комплекте. Все DBF принадлежат этому примеру. Если файл уже существует, создание останавливается: для повтора возьмите другую пустую папку. Изменения проверяются новым открытием; таблица сохраняется в result.html и result.json.
Для HTML нужен htmlspecialchars; helper применяет его к каждому значению. Экранирование HTML не меняет кодировку DBF. deleted не равен нашему ACTIVE: выключенный товар и помеченная запись различаются. Не называйте колонку deleted для чтения по именам: ключ занят меткой. PRICE демонстрирует N с двумя знаками; float не обеспечивает точную десятичную арифметику. Денежные расчёты обычно ведут отдельно в целых минимальных единицах.
Передать кириллицу в Windows-1251 и проверить байты после чтения
Получатель ждёт Windows-1251, сайт работает в UTF-8. Если записать UTF-8 как есть, получатель может показать «кракозябры» независимо от кодировки страницы браузера. Сохраним Орех и Чай в согласованной кодировке, прочитаем обратно и покажем названия вместе с байтами.
Что получится после повторного чтения
| Record | UTF-8 name | Bytes without padding | Stored hex | Field bytes |
|---|---|---|---|---|
| 1 | Орех | 4 | cef0e5f5 | 12 |
| 2 | Чай | 3 | d7e0e9 | 12 |
$bytes = iconv('UTF-8', 'Windows-1251', $text);
if ($bytes === false || strlen($bytes) > 12) { throw new InvalidArgumentException('Text does not fit NAME'); }
$encoded[] = [$bytes];Windows-1251 — договорённость с получателем, а не угадывание. iconv преобразует байты; strlen после преобразования измеряет размер для C-поля. Длинный текст отвергаем до записи: расширение может тихо обрезать его. bin2hex показывает байты без вывода неправильного UTF-8. Пример читает две записи и отдельно отказывает 13-байтному названию при лимите 12.
Открыть таблицу реального запуска · Скачать данные результата. HTML генерирует сама PHP-программа из прочитанных значений; для просмотра этой таблицы сервер не нужен.
Учебные DBF: names.dbf.
<?php
declare(strict_types=1);
require __DIR__ . '/dbf105-helper.php';
$input = ['Орех', 'Чай']; $encoded = [];
foreach ($input as $text) {
$bytes = iconv('UTF-8', 'Windows-1251', $text);
if ($bytes === false || strlen($bytes) > 12) { throw new InvalidArgumentException('Text does not fit NAME'); }
$encoded[] = [$bytes];
}
dbfSeed('names.dbf', [['NAME', 'C', 12]], $encoded);
$db = dbfOpen('names.dbf');
try {
$rows = [];
for ($number = 1; $number <= dbase_numrecords($db); $number++) {
$row = dbase_get_record($db, $number);
if ($row === false) { throw new RuntimeException('Cannot read name'); }
$stored = rtrim($row[0], ' ');
$utf8 = iconv('Windows-1251', 'UTF-8', $stored);
if ($utf8 === false) { throw new RuntimeException('Invalid agreed encoding'); }
$rows[] = [$number, $utf8, strlen($stored), bin2hex($stored), strlen($row[0])];
}
} finally { dbase_close($db); }
$tooLong = str_repeat('Я', 13);
$rejected = strlen(iconv('UTF-8', 'Windows-1251', $tooLong)) > 12;
dbfReport(['Record', 'UTF-8 name', 'Bytes without padding', 'Stored hex', 'Field bytes'], $rows, ['encoding' => 'Windows-1251', 'overlong_rejected_before_write' => $rejected]);
echo "names.dbf: Cyrillic round trip OK; 13-byte name rejected before write\n";
Ожидаемый результат
names.dbf: Cyrillic round trip OK; 13-byte name rejected before write
Скачайте комплект, распакуйте в пустой каталог и выполните php example.php с dbase. Helper и README включены; input.csv приложен к импорту. Helper содержит общие операции создания/чтения и отчёт; его исходник доступен в комплекте. Все DBF принадлежат этому примеру. Если файл уже существует, создание останавливается: для повтора возьмите другую пустую папку. Изменения проверяются новым открытием; таблица сохраняется в result.html и result.json.
Кодировка не задаётся HTTP-заголовком или create. В API нет переключателя автоматического перекодирования. Языковой идентификатор в заголовке DBF может быть неполным или неверным; пример проверяет фактические байты, не обещает настройку всех внешних программ. Непредставимые emoji нельзя молча удалять через //IGNORE: нужен другой договор или отказ. Проверяйте false от iconv и сохраняйте предупреждение. Значимые хвостовые пробелы неотличимы от заполнения C-поля; rtrim здесь — осознанное правило названий.
Реальная задача из обсуждения Issue with Special Characters (ñ) in DBF File Generated by PHP: Поле DBF с буквой ñ показывается неверно, хотя HTML-ответ объявлен UTF-8. Здесь написано собственное решение для обозначенного учебного договора; вопрос служит источником ситуации, а правила API сверены с PHP/PECL.
Отличить неизвестное значение NULL от заполненной даты и числа
Для FoxPro неизвестная цена может отличаться от нулевой. Покажем nullable-поля и время: первая строка заполнена, во второй действительно NULL. Не подменяем неизвестность пустотой или числом 0.
- DBASE_TYPE_FOXPRO → nullable-схема
- CODE C(6), WHEN T, PRICE N(8,2), ACTIVE L
- null → флаг отсутствия
- Чтение → PHP null
Что получится после повторного чтения
| Record | Code | DateTime | Price | Active |
|---|---|---|---|---|
| 1 | F-1 | 20261026123045.123 | 12.5 | true |
| 2 | NULL | NULL | NULL | NULL |
$schema = [['CODE', 'C', 6, true], ['WHEN', 'T', true], ['PRICE', 'N', 8, 2, true], ['ACTIVE', 'L', true]];
dbfSeed('nullable.dbf', $schema, [['F-1', '20261026123045.123', 12.50, 'T'], [null, null, null, null]], DBASE_TYPE_FOXPRO);Nullable поддерживается с dbase 7.0.0 для DBASE_TYPE_FOXPRO. true ставится после обычных аргументов типа: у C после длины, у N/F после длины и точности, у D/T/L сразу после буквы. Поэтому ["PRICE", "N", 8, 2, true] и ["ACTIVE", "L", true] имеют разную длину. T принимает YYYYMMDDhhmmss.uuu: 20261026123045.123 означает 26 октября 2026, 12:30:45 и 123 мс. В файле это специальное восьмибайтное представление, которое PHP возвращает строкой.
Открыть таблицу реального запуска · Скачать данные результата. HTML генерирует сама PHP-программа из прочитанных значений; для просмотра этой таблицы сервер не нужен.
Учебные DBF: nullable.dbf.
<?php
declare(strict_types=1);
require __DIR__ . '/dbf105-helper.php';
$schema = [['CODE', 'C', 6, true], ['WHEN', 'T', true], ['PRICE', 'N', 8, 2, true], ['ACTIVE', 'L', true]];
dbfSeed('nullable.dbf', $schema, [['F-1', '20261026123045.123', 12.50, 'T'], [null, null, null, null]], DBASE_TYPE_FOXPRO);
$db = dbfOpen('nullable.dbf');
try {
$fieldCount = dbase_numfields($db); $rows = [];
for ($number = 1; $number <= dbase_numrecords($db); $number++) {
$row = dbase_get_record_with_names($db, $number);
if ($row === false) { throw new RuntimeException('Cannot read nullable row'); }
$rows[] = [$number, $row['CODE'] === null ? null : rtrim($row['CODE'], ' '), $row['WHEN'], $row['PRICE'], $row['ACTIVE']];
}
} finally { dbase_close($db); }
dbfReport(['Record', 'Code', 'DateTime', 'Price', 'Active'], $rows, ['type' => DBASE_TYPE_FOXPRO, 'user_fields' => $fieldCount, 'null_is_not_zero' => $rows[1][3] === null]);
echo "nullable.dbf: 4 user fields; second record has 4 NULL values\n";
Ожидаемый результат
nullable.dbf: 4 user fields; second record has 4 NULL values
Скачайте комплект, распакуйте в пустой каталог и выполните php example.php с dbase. Helper и README включены; input.csv приложен к импорту. Helper содержит общие операции создания/чтения и отчёт; его исходник доступен в комплекте. Все DBF принадлежат этому примеру. Если файл уже существует, создание останавливается: для повтора возьмите другую пустую папку. Изменения проверяются новым открытием; таблица сохраняется в result.html и result.json.
T не проверяет календарную корректность и не задаёт часовой пояс: это обязанности приложения. numfields показывает 4 пользовательских поля; физический FoxPro-заголовок содержит дополнительное _NullFlags, которое не надо добавлять к данным. Читатель обычного dBASE III может не поддержать вариант. PHP manual заявляет уровни DBF 3–5 и отдельно эти возможности FoxPro; это не обещание всех диалектов Visual FoxPro. Для старого DBF без NULL согласуйте отдельный признак отсутствия.
Пять констант расширения
| Константа | Значение в проверенной версии | Когда нужна |
|---|---|---|
| DBASE_VERSION | 7.1.1 | Версия расширения, строка |
| DBASE_RDONLY | 0 | open: только чтение |
| DBASE_RDWR | 2 | open: чтение и запись |
| DBASE_TYPE_DBASE | 3 | create: dBASE |
| DBASE_TYPE_FOXPRO | 48 | create: FoxPro, в том числе nullable |
Все пять констант доступны только при загруженном расширении, введены в dbase 7.0.0. В программе используйте имена, а не числа 0/2/3/48: они показывают назначение аргумента. Режим 1 не поддерживает нужное чтение заголовка. Версия PHP и DBASE_VERSION — разные версии.
Все типы полей и ограничения формата
| Тип и определение | Запись / чтение | Граница |
|---|---|---|
| C: ["NAME", "C", 12] | Строковые байты / строка с пробелами справа | Длину задают в байтах. Переполнение тихо обрезается; проверяйте до записи. |
| N: ["QTY", "N", 4, 0] | Число фиксированной ширины | Длина и precision обязательны; точка и знак входят в ширину. Большое целое вне PHP-int возвращается строкой. |
| F: ["RATE", "F", 8, 2] | Число, правила аналогичны N | Не гарантирует точные финансовые вычисления. |
| D: ["ADDED", "D"] | YYYYMMDD / строка | Фиксированные 8 байт; собственная проверка календарной даты. |
| T: ["WHEN", "T"] | YYYYMMDDhhmmss.uuu / строка | FoxPro, с 7.0.0; внутри 8 байт. Корректность даты/времени не проверяется. |
| L: ["ACTIVE", "L"] | T/Y → true, F/N → false, ? → null | С 7.0.0 чтение bool/null, раньше int0/1. Обратная запись требует букв, не bool. |
| M: Memo | Не поддерживается, поле игнорируется PHP | Не обещайте перенос длинных заметок или внешних memo-файлов этим API. |
Имена полей ограничены десятью символами в manual; реализация проверяет байтовую длину, поэтому для совместимого договора используйте ASCII до десяти байт. Nullable поддерживается у FoxPro с 7.0.0: флаг true следует после параметров конкретного типа. Null — отдельное состояние, не пустота и не ноль. Для D/T заранее проверьте дату, для C — кодировку и длину, для N/F — диапазон и формат; DBF не проверяет правила вашего бизнеса.
Блокировки, совместимость и границы применения
С dbase 7.0.0 расширение автоматически использует flock. Старое утверждение «блокировок нет» относится к более ранним версиям. Обычное чтение и запись получают соответствующий lock на время открытого ресурса; это может ожидать освобождения, поэтому не держите writer во время долгих сетевых запросов. На системах с блокировками уровня процесса многопоточный SAPI сохраняет особый риск, указанный PHP manual. Другой инструмент должен соблюдать совместимый протокол блокировок; несколько операций не становятся SQL-транзакцией.
Учебные DBF лежат в отдельной папке; реальный вход импортёра копируйте в контролируемый путь и ограничивайте размер. Файл не должен быть одновременно переписываем поставщиком. Проверяйте схему, кодировку и значения перед записью. Для сайта с конкурентными обновлениями выбирайте SQLite или SQL через PDO. Для маленького файлового словаря «ключ → строка» посмотрите DBA и его конкретные handlers.
Все функции dBase: перейти к контракту
Ниже — все 12 функций расширения, сгруппированные по жизненному циклу: создание/открытие/закрытие, схема и чтение, добавление/замена/удаление/уплотнение. У каждой карточки есть параметры, результат, отказы, ограничения и две ссылки на самостоятельные программы. Все программы создают собственные входные DBF: чужой /tmp/test.dbf заранее не требуется.
| Функция | Действие |
|---|---|
dbase_create | Создать таблицу по явной схеме |
dbase_open | Открыть существующий DBF с нужным доступом |
dbase_close | Освободить открытый ресурс после работы |
dbase_add_record | Добавить одну строку в конец таблицы |
dbase_get_header_info | Получить структуру колонок и проверить входной контракт |
dbase_get_record | Прочитать одну запись по позициям столбцов |
dbase_get_record_with_names | Прочитать одну запись по именам колонок |
dbase_numfields | Узнать количество полей схемы |
dbase_numrecords | Получить границы обхода записей |
dbase_replace_record | Заменить содержимое существующей записи |
dbase_delete_record | Пометить строку для удаления |
dbase_pack | Физически удалить помеченные строки и уплотнить файл |
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
dbase_createСоздать таблицу по явной схеме
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_create(string $path, array $fields, int $type = DBASE_TYPE_DBASE): resource|falseСоздать таблицу по явной схеме.
| Параметр | Что передать |
|---|---|
| path | Относительный или абсолютный путь к файлу. Ограничивается open_basedir; существующий файл не обнуляется автоматически. |
| fields | Список определений в порядке колонок: имя до 10 байт, тип и его длина/точность/nullable. Имена в учебной схеме ASCII. Разные поля имеют разное число аргументов. |
| type | DBASE_TYPE_DBASE или DBASE_TYPE_FOXPRO. Третий аргумент и nullable FoxPro доступны с 7.0.0. |
Результат и отказ. Успех — открытый ресурс типа dbase, отказ — false. С 7.0.0 возвращается resource вместо старого целочисленного идентификатора. Не используйте ресурс после close. Ошибочные аргументы современной сборки также могут вызвать исключение: проверка false относится к возврату операции, не к каждому возможному виду сбоя.
Границы применения. Не применяйте create как безопасное «пересоздать с нуля»: файл не усечён. Хотя pack может принудительно усечь DBF, в наших сценариях новый путь проверяется до create. Присвоение схемы существующим неизвестным данным не является миграцией. Memo и индексы этим API не создаются.
Два применения с выполненными программами
Создать два столбца, добавить товары и прочитать сохранённый файл: catalog.dbf: 2 records; A-100=12, B-200=0
Отличить неизвестное значение NULL от заполненной даты и числа: nullable.dbf: 4 user fields; second record has 4 NULL values
Связанные операции: dbase_open, dbase_add_record.
dbase_openОткрыть существующий DBF с нужным доступом
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_open(string $path, int $mode): resource|falseОткрыть существующий DBF с нужным доступом.
| Параметр | Что передать |
|---|---|
| path | Путь к DBF; может быть абсолютным или относительным. Учитывается open_basedir; каталог и файл должны быть доступны пользователю текущего процесса. |
| mode | DBASE_RDONLY (0) — чтение, DBASE_RDWR (2) — чтение и запись. 1 (только запись) непригоден: функция обязана прочитать заголовок. |
Результат и отказ. Ресурс типа dbase при успехе, false при отказе. Имена режимов и ресурсный результат введены в 7.0.0, раньше использовались числа и integer handle. Режим 0 не создаёт отсутствующий файл; write mode 2 тоже не является create.
Границы применения. CLI и PHP веб-сервера могут иметь разные ini и права. Не повторяйте открытие с новым writer, пока текущий держит lock того же файла. С dbase 7.0.0 действует автоматический flock, а не отсутствие блокировок из старых описаний. Это не SQL-транзакция и не общий протокол для любых чужих программ.
Два применения с выполненными программами
Создать два столбца, добавить товары и прочитать сохранённый файл: catalog.dbf: 2 records; A-100=12, B-200=0
Различить ошибку доступа, неверный номер и закрытый ресурс: safe.dbf: 6 failure cases handled; original record unchanged
Связанные операции: dbase_close, dbase_create.
dbase_closeОсвободить открытый ресурс после работы
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_close(resource $database): boolОсвободить открытый ресурс после работы.
| Параметр | Что передать |
|---|---|
| database | Ресурс, возвращённый успешным create/open. С 7.0.0 это resource, не integer. |
Результат и отказ. true при успешном закрытии, false при отказе. Файл остаётся на диске. Операция завершает использование handle; закрытый ресурс нельзя передавать в чтение или запись. В проверенной PHP8.5-сборке последующий numrecords на закрытом handle вызывает TypeError.
Границы применения. Закрывайте handle в finally, если работа может выбросить исключение. Before copy/pack последовательность существенна: сначала закрытый согласованный исходник, затем копия, затем новый writer копии. Не объявляйте close доказательством того, что бизнес-данные верны: после записи прочитайте их заново.
Два применения с выполненными программами
Создать два столбца, добавить товары и прочитать сохранённый файл: catalog.dbf: 2 records; A-100=12, B-200=0
Уплотнить копию DBF и увидеть новые номера записей: packed.dbf: 3 -> 2 records; C-300 record 3 -> 2; backup unchanged
Связанные операции: dbase_open, dbase_numrecords.
dbase_add_recordДобавить одну строку в конец таблицы
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_add_record(resource $database, array $data): boolДобавить одну строку в конец таблицы.
| Параметр | Что передать |
|---|---|
| database | Открытый для записи ресурс, полученный create или open с DBASE_RDWR. Ресурс вместо старого integer — с 7.0.0. |
| data | Позиционный массив ровно из числа полей таблицы, в их порядке. Не включает служебный deleted. Строка C передаётся в согласованной кодировке; для L используйте T/F/?. |
Результат и отказ. true, если запись добавлена; false при отказе. Она дописывается в конец, не заменяет похожий SKU. В нашей сборке число элементов сверх схемы и попытка записи через read-only handle дают warning+false.
Границы применения. Количество элементов проверяется, но это не полная валидация: длинный C тихо обрезается, формат даты сам не гарантирует её календарную корректность. Поле N имеет ограничение ширины. Уникальность артикула, верхнюю границу числа и отсутствие потерь кодировки проверяет приложение до вызова. Из прочитанной записи удалите deleted; L преобразуйте обратно в формат записи.
Два применения с выполненными программами
Создать два столбца, добавить товары и прочитать сохранённый файл: catalog.dbf: 2 records; A-100=12, B-200=0
Экспортировать только живые строки без изменения исходного файла: live.dbf: 2 records; source unchanged; same rows as packed copy
Связанные операции: dbase_replace_record, dbase_numfields.
dbase_get_header_infoПолучить структуру колонок и проверить входной контракт
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_get_header_info(resource $database): array|falseПолучить структуру колонок и проверить входной контракт.
| Параметр | Что передать |
|---|---|
| database | Открытый ресурс dbase; с 7.0.0 ресурс заменил прежний integer. |
Результат и отказ. Индексированный список с нуля; каждый элемент содержит name, type, length, precision, format и offset. type — читаемое имя типа, length — байты, precision — дробные знаки, format — подсказка printf, offset — положение поля в записи. Если заголовок не прочитан, false.
Границы применения. Описание не является строкой данных и не совпадает по форме с fields для create. Порядок колонок из него полезен для проекции при replace. Совпадение имён недостаточно: сверяйте типы, размеры и точность. Memo API не обслуживает; скрытые FoxPro-поля не следует вручную дописывать к данным.
Два применения с выполненными программами
Проверить схему файла до чтения строк: schema.dbf: 6 fields, 2 records; schema accepted
Изменить остаток по артикулу и сохранить логическое поле: updated.dbf: A-100 12 -> 15; ACTIVE remains true
Связанные операции: dbase_numfields, dbase_get_record_with_names.
dbase_get_recordПрочитать одну запись по позициям столбцов
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_get_record(resource $database, int $number): array|falseПрочитать одну запись по позициям столбцов.
| Параметр | Что передать |
|---|---|
| database | Открытый ресурс dbase, начиная с 7.0.0 вместо integer. |
| number | Номер записи от 1 до numrecords. Это не индекс PHP-массива и не неизменный ID. |
Результат и отказ. Массив полей с индексами 0..numfields-1 плюс ключ deleted (1 у помеченной записи, 0 у остальных); при ошибке чтения false. Типы преобразуются: C с заполнением пробелами, D и T — строки, N/F — числа; целые за пределами PHP_INT_MIN..PHP_INT_MAX остаются строками. L с 7.0.0 — bool/null, раньше 0/1. В текущей сборке номер 0 выбрасывает ValueError.
Границы применения. count результата больше числа обычных полей из-за deleted. Перед add/replace удалите метку и соблюдите формат L. Пометка не мешает чтению: фильтрация удалённых строк — ваше решение. Для известной схемы индексы кратки, для изменяемой предпочтительнее имена с предварительной проверкой заголовка.
Два применения с выполненными программами
Пройти записи с единицы и вычислить общий остаток: stock.dbf: 3 records; total_qty=19
Экспортировать только живые строки без изменения исходного файла: live.dbf: 2 records; source unchanged; same rows as packed copy
Связанные операции: dbase_get_record_with_names, dbase_numrecords.
dbase_get_record_with_namesПрочитать одну запись по именам колонок
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_get_record_with_names(resource $database, int $number): array|falseПрочитать одну запись по именам колонок.
| Параметр | Что передать |
|---|---|
| database | Ресурс успешного create/open; с 7.0.0 вместо integer. |
| number | Номер записи 1..numrecords. После pack номера могут измениться. |
Результат и отказ. Ассоциативный массив с полями и дополнительным deleted=0/1, либо false при ошибке чтения. Собственное поле deleted через этот метод получить нельзя: ключ занят. C возвращается с хвостовым заполнением, D/T строками, большие целые вне PHP_INT_MIN..PHP_INT_MAX — строками, L с 7.0.0 bool/null вместо прежних 0/1.
Границы применения. Этот массив нельзя бездумно передать на запись: там лишняя метка, имена вместо позиций и bool вместо букв L. Проецируйте по заголовку. Null FoxPro отличайте от пустой строки и 0 строгим сравнением. При HTML-выводе экранируйте значения отдельно от декодирования байтов DBF.
Два применения с выполненными программами
Построить таблицу доступных товаров по именам столбцов: products.dbf: available=1; result.html contains 2 products
Изменить остаток по артикулу и сохранить логическое поле: updated.dbf: A-100 12 -> 15; ACTIVE remains true
Связанные операции: dbase_get_header_info, dbase_get_record.
dbase_numfieldsУзнать количество полей схемы
PECL dbase7.0+; examples PHP8.2+ · dbase
dbase_numfields(resource $database): int|falseУзнать количество полей схемы.
| Параметр | Что передать |
|---|---|
| database | Открытый ресурс dbase; до 7.0.0 использовался integer handle. |
Результат и отказ. Количество полей или false при отказе. Их индексы идут 0..numfields-1. Это не количество записей: номера записей 1..numrecords. Дополнительный ключ deleted в результате чтения не является полем схемы.
Границы применения. Число колонок помогает проверить размер массива перед добавлением, но не проверяет смысл, тип, порядок или точность. Для этого нужен get_header_info. В nullable-примере FoxPro API показывает четыре пользовательских поля, а физический файл содержит ещё внутреннее поле флагов.
Два применения с выполненными программами
Проверить схему файла до чтения строк: schema.dbf: 6 fields, 2 records; schema accepted
Пройти записи с единицы и вычислить общий остаток: stock.dbf: 3 records; total_qty=19
Связанные операции: dbase_get_header_info, dbase_numrecords.
Источники и обсуждения
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


