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

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

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

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

Глава 40 · От первого файла к обмену данными

dBase: создать DBF, понять схему и прочитать таблицу

От двух столбцов и позиционного массива до всех типов DBF, именованного чтения, кодировки и nullable FoxPro.

Зачем нужен DBF и как выглядит его таблица

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

Модель DBF: схема и одинаковые места под строки
  1. Заголовок: SKU C(6), QTY N(4,0)
  2. Запись №1: A-100 + 12
  3. Запись №2: B-200 + 0
  4. 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 состоит из заголовка со схемой и последовательно записанных строк одинаковой длины.

Путь данных в примере
  1. Схема [SKU, C, 6] и [QTY, N, 4, 0] → два столбца
  2. Строка [A-100, 12] → значения в том же порядке
  3. add_record → запись №1
  4. close → файл остаётся на диске
  5. Новое открытие → [SKU => A-100, QTY => 12, deleted => 0]

Что получится после повторного чтения

RecordSKUQTYdeleted
1A-100120
2B-20000
Описание поляПервая строкаЧтение по имени
["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 имеет варианты.

Проверить схему файла до чтения строк

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

Путь данных в примере
  1. schema.dbf → заголовок
  2. get_header_info → шесть описаний
  3. SKU/NAME/QTY/PRICE/ADDED/ACTIVE → сравнение с договорённостью
  4. Разрешённая схема → чтение

Что получится после повторного чтения

IndexNameTypeBytesPrecisionOffsetFormat
0SKUcharacter601%-6s
1NAMEcharacter1207%-12s
2QTYnumber4019%4s
3PRICEnumber8223%8s
4ADDEDdate8031%8s
5ACTIVEboolean1039%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; товар с нулевым остатком не потерялся.

Путь данных в примере
  1. numrecords → 3
  2. for: number=1 → 2 → 3
  3. get_record(number) → [0 => SKU, 1 => QTY, deleted => 0]
  4. Прибавляем QTY → 12 + 0 + 7 = 19

Что получится после повторного чтения

Recordrow[0]: SKUrow[1]: QTY
1A-10012
2B-2000
3C-3007
Основное действие: фрагмент полной программы ниже
$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.

Путь данных в примере
  1. Вход ACTIVE: T или F
  2. get_record_with_names → ACTIVE: bool
  3. ACTIVE и QTY > 0 → available/unavailable
  4. Экранированные значения → result.html

Что получится после повторного чтения

SKUNameQTYPriceAddedActiveStatus
A-100Tea1219.920261026trueavailable
B-200Coffee042.520261025falseunavailable
Основное действие: фрагмент полной программы ниже
$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 как есть, получатель может показать «кракозябры» независимо от кодировки страницы браузера. Сохраним Орех и Чай в согласованной кодировке, прочитаем обратно и покажем названия вместе с байтами.

Путь данных в примере
  1. UTF-8 Орех → iconv → ce f0 e5 f5
  2. 4 байта ≤ C(12) → запись
  3. 12 байт с пробелами → rtrim
  4. Windows-1251 → iconv → UTF-8 Орех

Что получится после повторного чтения

RecordUTF-8 nameBytes without paddingStored hexField bytes
1Орех4cef0e5f512
2Чай3d7e0e912
Основное действие: фрагмент полной программы ниже
$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.

Передать кириллицу в Windows-1251 и проверить байты после чтенияСамостоятельный пример
<?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.

Путь данных в примере
  1. DBASE_TYPE_FOXPRO → nullable-схема
  2. CODE C(6), WHEN T, PRICE N(8,2), ACTIVE L
  3. null → флаг отсутствия
  4. Чтение → PHP null

Что получится после повторного чтения

RecordCodeDateTimePriceActive
1F-120261026123045.12312.5true
2NULLNULLNULLNULL
Основное действие: фрагмент полной программы ниже
$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.

Отличить неизвестное значение NULL от заполненной даты и числаСамостоятельный пример
<?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_VERSION7.1.1Версия расширения, строка
DBASE_RDONLY0open: только чтение
DBASE_RDWR2open: чтение и запись
DBASE_TYPE_DBASE3create: dBASE
DBASE_TYPE_FOXPRO48create: 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. Разные поля имеют разное число аргументов.
typeDBASE_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; каталог и файл должны быть доступны пользователю текущего процесса.
modeDBASE_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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.