Александр Некрасов · Автономное руководство

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

Разделы этой страницы
  1. Файлы, пути, CSV и потоковая обработка
  2. Первый файл: сохранить памятку и увидеть её снова
  3. Прочитать своё расписание и показать его посетителю
  4. Создать файл, найти его и прочитать записанный текст
  5. Что произойдёт, если каталог недоступен
  6. Дополнить историю заказа, сохранив прежнюю строку
  7. CSV по одной записи: запятая внутри названия
  8. Подготовить список на выдачу и прочитать CSV обратно
  9. Путь — это полномочие доступа
  10. CSV — не просто разделение запятой
  11. Запись должна проверять результат
  12. Конкуренция и переносимость
  13. Функции и методы этой темы
  14. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 24 · С нуля → практика

Файлы, пути, CSV и потоковая обработка

Читаем импорт безопасно, различаем конец файла и ошибку, выбираем полный буфер или поток.

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

После завершения программы переменная исчезает. Чтобы сотрудник позже прочитал «Заказ 104 готов», сохраним эту строку в ready.txt. Это обычный текстовый файл: его можно открыть редактором. Распакуйте пример в отдельную учебную папку; все пути ниже относятся только к ней.

Наш первый файл
  1. $path указывает на ready.txt рядом с example.php
  2. file_put_contents записывает строку заказа
  3. file_get_contents возвращает сохранённую строку
  4. echo показывает «Заказ 104 готов»
ЗаписьЧто она делает
$path = __DIR__ . '/ready.txt';Переменная $path хранит адрес. __DIR__ — папка этого PHP-файла, точка соединяет её с именем.
file_put_contents($path, "Заказ 104 готов\n");Имя функции означает действие. Первый аргумент — куда, второй — что записать. \n внутри двойных кавычек добавляет перевод строки.
echo file_get_contents($path);Сначала читается файл, затем echo выводит результат. Скобки задают аргумент вызова; ; завершает команду.
Три команды — настоящая памяткаСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/ready.txt';
file_put_contents($path, "Заказ 104 готов\n");
echo file_get_contents($path);
Ожидаемый результат
Заказ 104 готов

Успешный первый опыт в доступной учебной папке. Откройте ready.txt: там тот же текст. Замените 104 на 105 и запустите снова — файл заменится. Пока путь и доступ известны; ниже добавим обязательные проверки результата и отказа.

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

Прочитать своё расписание и показать его посетителю

Расписание мастерской хранится в обычном текстовом файле. Нужно показать его на странице, не переписывая строку в PHP при каждом изменении. Читатель получит видимое «Понедельник: 10:00–18:00». Это отличается от переменной: файл остаётся на диске после завершения PHP.

В ZIP лежат example.php и hours.txt. Точное содержимое hours.txt: Понедельник: 10:00–18:00 и перевод строки. __DIR__ — каталог исполняемого файла с этой записью; точка соединяет его с /hours.txt. Так запуск из другого рабочего каталога не меняет источник данных. file_get_contents получает путь и возвращает всё содержимое строкой либо false при отказе.

hours.txt попадает в страницу
  1. Диск: hours.txt, «Понедельник: 10:00–18:00»
  2. file_get_contents → $text
  3. false → сообщение об отказе; строка → HTML-экранирование
  4. echo формирует абзац расписания
Прочитать расписание, затем безопасно вывести HTMLСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/hours.txt';
$text = file_get_contents($path);
if ($text === false) {
    echo '<p>Расписание сейчас недоступно.</p>';
} else {
    echo '<p>', htmlspecialchars(trim($text), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), '</p>';
}
Ожидаемый результат
<p>Понедельник: 10:00–18:00</p>

php example.php показывает точный HTML. php -S 127.0.0.1:8080 и открытие /example.php показывают абзац на странице. trim убирает перевод строки по краям расписания; htmlspecialchars защищает вывод, если редактор позже вставит угловые скобки. Ввод здесь — свой фиксированный файл, не путь из URL.

Пустой существующий файл вернёт пустую строку — это успешное чтение, а не false. При исчезновении файла возможен warning помимо false: проверка результата не отключает диагностику. На HTTP-сервере display_errors выключают, log_errors включают; пользователь увидит нашу фразу, оператор найдёт причину в журнале.

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

Создать файл, найти его и прочитать записанный текст

Теперь программа сама готовит памятку клиенту: «Заказ 104 готов к выдаче». Нужен новый ready.txt рядом с example.php и подтверждение с его содержимым. Данные здесь известны из кода; позже их источником станет проверенная заявка.

file_put_contents($path, $text) создаёт файл или заменяет его содержимое. Возвращается число записанных байтов либо false. Для кириллицы число байтов не равно числу букв, поэтому сравниваем со strlen($text). Запись без FILE_APPEND заменит прежний текст — для первого опыта используйте отдельную учебную папку. Значение " " создаёт перевод строки.

Памятка готовности: настоящий файл и его содержимоеСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/ready.txt';
$text = "Заказ 104 готов к выдаче\n";
$written = file_put_contents($path, $text, LOCK_EX);
if ($written === false || $written !== strlen($text)) {
    exit("Не удалось сохранить памятку полностью.\n");
}
$saved = file_get_contents($path);
if ($saved === false) {
    exit("Памятка записана, но не удалось прочитать её обратно.\n");
}
echo "Создан ready.txt рядом с example.php.\n";
echo "Точное содержимое файла:\n", $saved;
Ожидаемый результат
Создан ready.txt рядом с example.php.
Точное содержимое файла:
Заказ 104 готов к выдаче

Распакуйте в отдельную папку, выполните php example.php, откройте ready.txt обычным редактором. В нём одна строка «Заказ 104 готов к выдаче» и перевод строки. Измените номер 104 на 105 и повторите: старое содержимое заменится. LOCK_EX просит эксклюзивную блокировку на время записи, но чтение другими программами должно тоже соблюдать согласованную схему блокировок.

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

Что произойдёт, если каталог недоступен

Предположим, каталог заявок не создали. Нельзя ответить клиенту «сохранено» только потому, что строка подготовлена. Следующий опыт специально пишет в отсутствующий каталог и переводит warning в контролируемое исключение, чтобы проверка была повторяемой на Windows и Linux.

Новая конструкция set_error_handler временно назначает функцию для предупреждений; throw new RuntimeException прекращает неудачную ветвь и передаёт сообщение в catch. finally выполнится при успехе и при отказе; restore_error_handler вернёт прежнюю обработку предупреждений. Такая узкая обёртка нужна этому опыту, а не для скрытия всех ошибок сайта.

Не обещать сохранение, когда папки нетСамостоятельный пример
<?php
declare(strict_types=1);

set_error_handler(function (int $level, string $message): never {
    throw new RuntimeException('Запись недоступна');
});
try {
    $text = "Заказ 104 готов к выдаче\n";
    $written = file_put_contents(__DIR__ . '/missing-directory/ready.txt', $text);
    if ($written !== strlen($text)) {
        throw new RuntimeException('Неполная запись');
    }
    echo "Памятка сохранена.\n";
} catch (RuntimeException $error) {
    echo "Не удалось сохранить памятку. Сообщите сотруднику номер 104.\n";
} finally {
    restore_error_handler();
}
Ожидаемый результат
Не удалось сохранить памятку. Сообщите сотруднику номер 104.

В свежей папке примера нет missing-directory, поэтому операция отказывает. Код не создаёт папку и не меняет права. Реальная ошибка может быть также связана с учётной записью PHP, диском или запретами сервера. Техническую причину сохраняют в журнале, а человеку дают действие, позволяющее продолжить задачу.

При сбое не делайте chmod 777 общим рецептом. На Windows проверьте ACL, на Unix — владельца и права нужной учётной записи; в обоих случаях убедитесь в реальном абсолютном пути. Ошибка относительного пути и Permission denied обсуждались в этом вопросе Stack Overflow; решение зависит от окружения, а не от замены функции записи.

Дополнить историю заказа, сохранив прежнюю строку

Теперь нужен журнал: сначала «создан», затем «готов». Обычная запись заменит старую строку. Флаг FILE_APPEND меняет именно это действие: новая строка добавляется в конец. Флаг — именованная настройка вызова. Оператор | объединяет совместимые флаги FILE_APPEND и LOCK_EX.

История заказа
  1. history.txt содержит «104: создан»
  2. FILE_APPEND добавляет «104: готов»
  3. LOCK_EX блокирует запись для участников того же протокола
  4. Повторное чтение показывает обе строки
Добавить событие и показать весь журналСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/history.txt';
$event = "104: готов\n";
$written = file_put_contents($path, $event, FILE_APPEND | LOCK_EX);
if ($written !== strlen($event)) {
    throw new RuntimeException('Событие записано не полностью');
}
echo file_get_contents($path);
Ожидаемый результат
104: создан
104: готов

history.txt включён в архив с одной строкой «104: создан». После запуска он содержит две строки. Повторный запуск той же копии добавит событие снова: append сам не устраняет повторы. strlen считает байты; !== сравнивает и значение, и тип, поэтому false также попадает в ошибку.

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

CSV по одной записи: запятая внутри названия

Перед полным обменом списком запишем один товар. CSV — текстовый формат таблицы: запись содержит поля, разделённые запятыми; поле с запятой заключают в кавычки. fputcsv делает это за нас. Массив ниже содержит название и количество; это два поля одной записи.

ЗаписьЧто она делает
fopen($path, 'wb')Открывает поток для записи; w создаёт или очищает файл, b сохраняет байты без текстовых преобразований.
['Дрель, ударная', '2']Массив из двух строк. Запятая внутри кавычек принадлежит названию.
fputcsv($out, $row, ',', '"', '', "\n")Поток, поля, разделитель, ограничитель поля, отключённое особое escape-экранирование, конец записи LF.
fclose($out)Закрывает поток; готовый файл остаётся на диске.
Сохранить одно название с запятойСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/one-product.csv';
$out = fopen($path, 'wb');
if ($out === false) { throw new RuntimeException('Не открыть CSV'); }
fputcsv($out, ['Дрель, ударная', '2'], ',', '"', '', "\n");
fclose($out);
echo file_get_contents($path);
Ожидаемый результат
"Дрель, ударная",2

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

Пустой пятый аргумент явно выбирает удвоение кавычек без дополнительного механизма обратной косой черты. В современных PHP это позволяет согласовать чтение и запись; зависимость от escape по умолчанию устарела с PHP 8.4. Настройки должны совпадать у отправителя и получателя. Полный обмен списком.

Подготовить список на выдачу и прочитать CSV обратно

Сотруднику нужен файл для передачи списка инструментов другой программе. В названии может быть запятая: «Дрель, ударная». Простая склейка через запятую перепутает одно название с двумя колонками. CSV хранит записи с согласованным разделителем и кавычками; fputcsv выполняет экранирование полей, fgetcsv восстанавливает их.

fopen открывает поток: wb создаёт/обнуляет файл для записи, rb открывает существующий для чтения. foreach передаёт по одной записи fputcsv. Здесь разделитель — запятая, enclosure — двойная кавычка, escape — явно пустая строка, конец строки — LF. После fclose можно увидеть готовый файл. Во втором проходе while читает записи до false; заголовок читаем отдельно. Синтаксис циклов подробно объяснён в управлении потоком.

CSV с названием, содержащим запятую: запись и полезный итогСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/pickup.csv';
$rows = [['title', 'quantity'], ['Дрель, ударная', '2'], ['Пила', '1']];
$out = fopen($path, 'wb');
if ($out === false) {
    throw new RuntimeException('Не удалось открыть список для записи');
}
try {
    foreach ($rows as $row) {
        if (fputcsv($out, $row, ',', '"', '', "\n") === false) {
            throw new RuntimeException('Не удалось записать CSV');
        }
    }
} finally {
    fclose($out);
}
$csvText = file_get_contents($path);
if ($csvText === false) {
    throw new RuntimeException('Не удалось прочитать готовый CSV');
}
echo "Точное содержимое pickup.csv:\n", $csvText;
$input = fopen($path, 'rb');
if ($input === false) {
    throw new RuntimeException('Не удалось открыть список для чтения');
}
try {
    fgetcsv($input, 0, ',', '"', '');
    $total = 0;
    echo "Список для сотрудника:\n";
    while (($row = fgetcsv($input, 0, ',', '"', '')) !== false) {
        echo $row[0], ' — ', $row[1], " шт.\n";
        $total += (int) $row[1];
    }
    echo 'Всего выдать: ', $total, " шт.\n";
} finally {
    fclose($input);
}
Ожидаемый результат
Точное содержимое pickup.csv:
title,quantity
"Дрель, ударная",2
Пила,1
Список для сотрудника:
Дрель, ударная — 2 шт.
Пила — 1 шт.
Всего выдать: 3 шт.

После php example.php pickup.csv находится рядом с программой. Запятая внутри названия остаётся в одной колонке, потому что fputcsv добавила CSV-кавычки. Мы читаем собственный только что записанный файл; для чужого CSV надо дополнительно проверить заголовок, число колонок, тип и диапазон количества — такой разбор дан ниже в старшей ступени главы.

Этот файл не содержит непроверенных формул пользователя. При экспорте введённых человеком значений для Excel/Calc дополнительно решают риск формул и договор импорта: CSV-кавычки сами не отключают формульную интерпретацию. Явный escape="" избегает зависимости от устаревшего значения по умолчанию в PHP 8.4 и соответствует обычному удвоению кавычек.

Упражнение: добавьте «Отвёртка» с количеством 4. В CSV появится строка Отвёртка,4, а итог станет «Всего выдать: 7 шт.». Затем добавьте кавычки в название и посмотрите, как fputcsv удвоит их. Следующий шаг — получить проверенную заявку из формы и хранить её отдельно от публичных файлов.

Путь — это полномочие доступа

Наглядная модель

Большой файл можно читать как ленту

Поставщик прислал длинный CSV с остатками. Чтобы не держать весь файл в памяти, обрабатываем по одной записи.

  1. Открыть поток

    Получаем ресурс файла и проверяем, удалось ли его открыть.

  2. Прочитать следующую запись

    fgetcsv разбирает поля с учётом кавычек; проверяем и обрабатываем строку.

  3. Повторять до конца и закрыть

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

Поток экономит память, только если мы не складываем все прочитанные строки в новый огромный массив. CSV с кавычками и переносами нельзя надёжно разобрать простым explode по запятой.

Файл на сервере не является безопасным только потому, что имя пришло из формы. Пользовательский ../../config.php или URL вместо локального пути может изменить цель чтения. Лучше принимать ID разрешенного ресурса и находить путь на сервере. Для разрешенной директории учитывайте нормализацию пути, разделители и символические ссылки; basename не является универсальной защитой. Храните импорт, секреты и рабочие файлы в непубличной директории.

file_get_contents удобно для небольшого конфигурационного файла: оно возвращает всю строку или false. Пустая строка — успешный пустой файл, поэтому нужна строгая проверка. Для гигабайтного отчета такое выделение памяти не подходит. Потоковые fopen/fgets/fgetcsv читают поэтапно, но одна гигантская строка все равно может быть большой: лимиты схемы и размера остаются нужны.

CSV — не просто разделение запятой

Поставщик прислал файл stock.csv с остатками. Нужно получить общий остаток: 2 + 3 = 5. Запятая внутри названия Tea, green относится к названию товара, а не начинает новую колонку. Вот сам входной файл:

sku,title,stock
A,"Tea, green",2
B,Coffee,3
Импорт остатков: показать каждый товар и итогСамостоятельный пример
<?php
declare(strict_types=1);

$input = fopen(__DIR__ . '/stock.csv', 'rb');
if ($input === false) { throw new RuntimeException('Не открыть CSV'); }
$total = 0;
try {
    if (fgetcsv($input, 0, ',', '"', '') !== ['sku', 'title', 'stock']) {
        throw new UnexpectedValueException('Неожиданные колонки');
    }
    while (($row = fgetcsv($input, 0, ',', '"', '')) !== false) {
        if (count($row) !== 3 || preg_match('/\A[0-9]{1,6}\z/', $row[2] ?? '') !== 1) {
            throw new UnexpectedValueException('Некорректная строка остатков');
        }
        $quantity = (int) $row[2];
        echo $row[0], ': ', $row[1], ' — ', $quantity, " шт.\n";
        $total += $quantity;
    }
    if (!feof($input)) { throw new RuntimeException('Ошибка чтения CSV'); }
} finally { fclose($input); }
echo 'Всего на складе: ', $total, " шт.\n";
Ожидаемый результат
A: Tea, green — 2 шт.
B: Coffee — 3 шт.
Всего на складе: 5 шт.

Заголовок задаёт контракт: SKU, название, остаток. count проверяет число полей; шаблон разрешает от одной до шести цифр, затем (int) превращает строку в число. Пустые строки и неправильные записи отвергаются. PHP читает запись CSV, которая может занимать несколько физических строк при переводе строки внутри кавычек.

Запись должна проверять результат

Учебный JSON-файл в рабочем каталогеСамостоятельный пример
<?php
declare(strict_types=1);

$payload = json_encode(['version' => 1, 'items' => ['A', 'B']], JSON_THROW_ON_ERROR);
$written = file_put_contents('catalog-cache.json', $payload, LOCK_EX);
if ($written === false || $written !== strlen($payload)) {
    throw new RuntimeException('Не удалось записать весь кеш');
}
$saved = file_get_contents('catalog-cache.json');
if ($saved === false) {
    throw new RuntimeException('Не удалось прочитать кеш');
}
echo $saved, "\n";
Ожидаемый результат
{"version":1,"items":["A","B"]}

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

Конкуренция и переносимость

Два запроса могут одновременно прочитать счетчик 7 и оба записать 8. Блокировать нужно весь участок «прочитать—изменить—записать», а не только финальную запись. fopen с режимом w обрезает файл до того, как вы успеете взять блокировку. Для локального счетчика берут c+, затем flock, затем чтение и запись с ftruncate; все участники должны соблюдать протокол. Для нескольких серверов и сетевых ФС семантика блокировок требует отдельной проверки.

Схема «записать временный файл рядом→переименовать» часто уменьшает риск частичного видимого результата, но атомарность и замена существующего файла зависят от ОС, ФС и условий. Временный файл создают на той же ФС, обрабатывают ошибку rename, задают права и очищают после неудачи. Проверка is_writable перед записью не отменяет гонку; окончательный результат операции проверяется всегда.

Попробуйте сами. Почему нельзя читать файл как while(!feof($h)){ $line=fgets($h); ... } без проверки $line?

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

EOF становится известен после попытки чтения. Цикл может обработать false как данные. Используйте while(($line=fgets($h))!==false), затем отличите EOF от ошибки и закройте ресурс в finally.

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

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

file_get_contentsПолучить байты небольшого файла одной строкой.

PHP 4.3+; offset<0 с7.1, length null с8.0 · standard

file_get_contents(string $filename, bool $use_include_path = false, ?resource $context = null, int $offset = 0, ?int $length = null): string|false

Получить байты небольшого файла одной строкой.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
filenameСтрока пути; относительный путь разрешается от текущего рабочего каталога (CWD), а не обязательно от папки этого исходника. URL требует подходящей обёртки. Кодируйте компоненты URL, не весь адрес с ://.
use_include_pathbool true ищет include_path; при strict_types не передавайте int FILE_USE_INCLUDE_PATH вместо bool.
contextКонтекст или null; пропустите через null при задании offset.
offsetБайтовое начало; отрицательное — от конца (с7.1). Remote seek не поддерживается надёжно.
lengthnull — до конца; неотрицательный максимум байтов после фильтров потока; 0 даёт пустую строку.
ВозвратСтрока, включая "", "0", или false.
ОтказE_WARNING при I/O/seek/каталоге (с7.4 и на не-Windows). В PHP8 отрицательная length даёт ValueError; старый текст Manual называет warning.
ВерсияPHP 4.3+; offset<0 с7.1, length null с8.0
Первое действие с нашим файлом
  1. stock.txt содержит строку "0"
  2. file_get_contents возвращает строку
  3. === false не срабатывает
  4. Читатель видит «Чай: 0 шт.»
Нулевой остаток — товар закончился, чтение успешноСамостоятельный пример
<?php
declare(strict_types=1);

$stock = file_get_contents(__DIR__ . '/stock.txt');
if ($stock === false) { throw new RuntimeException('Не прочитать остаток'); }
echo 'Чай: ', $stock, " шт.\n";
Ожидаемый результат
Чай: 0 шт.

Строка "0" означает отсутствие товара на складе. if (!$stock) перепутал бы этот результат с ошибкой чтения.

Прочитать SKU и количество из фиксированной записиСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/item.txt';
$sku = file_get_contents($path, false, null, 0, 3);
$quantity = file_get_contents($path, false, null, -2);
echo 'Товар: ', $sku, '; количество: ', $quantity, "\n";
Ожидаемый результат
Товар: TEA; количество: 12

Учебный формат ровно TEA:12: три ASCII-байта SKU, двоеточие, две цифры. offset 0 начинает с первого байта, length 3 берёт SKU, offset -2 берёт последние два байта. Для переменных полей нужен CSV-парсер, а для недоверенной записи — проверка длины и формата.

Применение и границы. Для всей небольшой строки функция обычно удобнее ручного цикла и может использовать оптимизации ОС. Это не разрешение читать файл неизвестного размера целиком. На удалённом источнике нужны timeout/статус/ограничение объёма; SSL-проверку оставляйте включённой. Данные JSON после чтения ещё требуют json_decode и проверки структуры.

Сравните: fread, file, file_put_contents. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

Возвращает строку, включая пустую, либо false с предупреждением при ошибке. Для небольших локальных данных удобно; для больших нужен поток.

Прочитать JSON-кеш доступности мастерскойСамостоятельный пример
<?php
declare(strict_types=1);

$body = file_get_contents(__DIR__ . '/cache.json');
if ($body === false) { throw new RuntimeException('Не прочитать кеш'); }
$data = json_decode($body, true, 16, JSON_THROW_ON_ERROR);
if (!is_array($data) || !isset($data['hours']) || !is_string($data['hours'])) {
    throw new UnexpectedValueException('Нет текстового расписания');
}
echo 'Мастерская: ', $data['hours'], "\n";
Ожидаемый результат
Мастерская: 10:00–18:00

Чтение возвращает JSON-текст. json_decode превращает его в массив; true выбирает массивы, 16 ограничивает вложенность, JSON_THROW_ON_ERROR сообщает о неверном JSON исключением. Проверка hours задаёт отдельный договор данных.

Пустой список заявок: показать рабочее состояниеСамостоятельный пример
<?php
declare(strict_types=1);

$queue = file_get_contents(__DIR__ . '/queue.txt');
if ($queue === false) { throw new RuntimeException('Не прочитать очередь'); }
echo $queue === '' ? "Новых заявок нет.\n" : $queue;
Ожидаемый результат
Новых заявок нет.

Существующий пустой файл — допустимое состояние очереди. Исчезнувший или недоступный файл означал бы отказ и false; это другое решение для сотрудника.

Сравните. fopen/fgets читают поток; HTTP-клиент из раздела интеграций лучше выражает сетевой контракт.

file_put_contentsЗаписать данные одним вызовом; без FILE_APPEND прежнее содержимое заменяется.

PHP 5+ · standard

file_put_contents(string $filename, mixed $data, int $flags = 0, ?resource $context = null): int|false

Записать данные одним вызовом; без FILE_APPEND прежнее содержимое заменяется.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". [...] создаёт массив; $row["size"] читает поле по ключу; [$a, $b] = ... раскладывает список. | объединяет поддерживаемые флаги; это допустимо только для параметра-маски. Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
filenameСтрока пути; относительный путь разрешается от текущего рабочего каталога (CWD), а не обязательно от папки этого исходника. Родительский каталог должен существовать.
dataСтрока; одномерный массив соединяется без разделителя; stream копируется от текущей позиции до конца.
flagsFILE_USE_INCLUDE_PATH, FILE_APPEND, LOCK_EX объединяются через |.
contextКонтекст обёртки или null.
ВозвратЧисло записанных байтов или false; 0 может быть успешной пустой записью.
ОтказПри файловом отказе warning и false; wrapper должен поддерживать запись/блокировку.
ВерсияPHP 5+
Первое действие с нашим файлом
  1. queue.txt содержит заявку 104
  2. Запись пустой строки заменяет содержимое
  3. Возврат 0 — успех
  4. Чтение подтверждает пустую очередь
Очистить учебную очередь после обработкиСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/queue.txt';
$written = file_put_contents($path, '');
if ($written === false) { throw new RuntimeException('Не очистить очередь'); }
echo 'Записано байтов: ', $written, "\n";
echo file_get_contents($path) === '' ? "Очередь пуста.\n" : "Остались заявки.\n";
Ожидаемый результат
Записано байтов: 0
Очередь пуста.

Учебная копия queue.txt содержала заявку 104. Запись пустой строки заменяет содержимое, а файл остаётся. Реальная общая очередь требует согласованной блокировки всей обработки или специализированного хранилища.

Добавить массив фрагментов как одну строку журналаСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/events.log';
$parts = ['104: ', 'готов', "\n"];
$written = file_put_contents($path, $parts, FILE_APPEND | LOCK_EX);
if ($written !== strlen(implode('', $parts))) { throw new RuntimeException('Неполная запись'); }
echo file_get_contents($path);
Ожидаемый результат
104: создан
104: готов

Массив фрагментов соединяется без разделителя. Функция не создаёт JSON или CSV. FILE_APPEND сохраняет строку создания заказа; LOCK_EX относится только к этой записи.

Применение и границы. LOCK_EX удерживается на запись, но не включает ранее выполненное чтение. Для счётчика read→modify→write нужна единая блокировка открытого потока. Читатели тоже должны соблюдать протокол flock; например nginx этого автоматически не делает. LOCK_EX не равен режиму x и не является fsync. Для публикации файла целиком часто нужен staged файл в той же файловой системе и rename с учётом ОС.

Сравните: fwrite, flock, rename, fsync. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

Возвращает число записанных байтов или false. Без FILE_APPEND перезаписывает файл. LOCK_EX запрашивает эксклюзивную блокировку на запись.

Записать состояние выдачи и проверить содержимоеСамостоятельный пример
<?php
declare(strict_types=1);

$path = __DIR__ . '/pickup.txt';
$text = "Заказ 104: можно забирать\n";
if (file_put_contents($path, $text, LOCK_EX) !== strlen($text)) {
    throw new RuntimeException('Неполная запись памятки');
}
echo file_get_contents($path);
Ожидаемый результат
Заказ 104: можно забирать

Число байтов используется для проверки, а человеку показывается сама памятка. На сервере каталог готовят заранее и проверяют результат операции под учётной записью PHP.

Добавить строку в локальный учебный журналСамостоятельный пример
<?php
declare(strict_types=1);

$bytes = file_put_contents('events.log', "created\n", FILE_APPEND | LOCK_EX);
if ($bytes === false) {
    throw new RuntimeException('Не записать событие');
}
echo file_get_contents('events.log');
Ожидаемый результат
created

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

Сравните. Для большого потока используйте fopen/fwrite; для важных конкурентных данных — базу с нужными гарантиями.

fopenОткрыть файл — получить поток с собственной позицией; содержимое ещё не прочитано.

PHP 4+ · standard

fopen(string $filename, string $mode, bool $use_include_path = false, ?resource $context = null): resource|false

Открыть файл — получить поток с собственной позицией; содержимое ещё не прочитано.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. fopen возвращает открытый поток (resource), а не содержимое файла. rb читает байты, w+b создаёт/очищает файл и разрешает чтение с записью. fclose освобождает поток. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". try выполняет попытку, catch обрабатывает указанный отказ, finally освобождает ресурс и не подавляет ошибку. throw new RuntimeException сообщает о сбое. Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
filenameСтрока пути; относительный путь разрешается от текущего рабочего каталога (CWD), а не обязательно от папки этого исходника. Схема scheme:// выбирает обёртку. Для сетевой обёртки нужен allow_url_fopen; local file дополнительно ограничивают права процесса и open_basedir. Неизвестная схема вызывает диагностику и попытку обычного открытия.
modeРежим из таблицы ниже; + добавляет второе направление, b сохраняет байты.
use_include_pathtrue дополнительно ищет в include_path; по умолчанию false.
contextКонтекст настроек конкретной обёртки, null — обычный контекст.
ВозвратПоток resource; false при отказе.
ОтказПри отказе E_WARNING; некорректный аргумент может дать ValueError/TypeError.
ВерсияPHP 4+
РежимСуществующий файлОтсутствующий файл
r / r+С начала; чтение / чтение и записьОтказ
w / w+Обнулить; запись / чтение и записьСоздать
a / a+Запись всегда в конец; + также читаетСоздать
x / x+Отказ без перезаписиСоздать исключительно
c / c+С начала, без обнуленияСоздать

Дополнения: b — байты без перевода строк (режим по умолчанию), t — Windows-перевод LF ↔ CRLF. e ставит close-on-exec на POSIX.1-2008 (с 7.0.16/7.1.2); n ставит non-blocking на соответствующей POSIX-сборке. Эти возможности не переносимы на любой поток. Для a+ seek меняет чтение, но не место записи.

Путь данных в первом опыте
  1. notes.txt содержит TEA
  2. fopen(..., rb) открывает поток
  3. fread берёт 3 байта
  4. fclose закрывает поток
Открыть и прочитать три байтаСамостоятельный пример
<?php
$stream = fopen(__DIR__ . '/notes.txt', 'rb');
if ($stream === false) { throw new RuntimeException('open'); }
echo fread($stream, 3), "
";
fclose($stream);
Ожидаемый результат
TEA

rb читает существующий файл без изменения. Число 3 — байты, не количество букв Unicode.

Создать новый файл выдачи без перезаписи существующегоСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/pickup-104.txt', 'x+b');
if ($stream === false) { throw new RuntimeException('Не создать новый файл'); }
try {
    $text = "104: дрель — 2\n";
    if (fwrite($stream, $text) !== strlen($text)) { throw new RuntimeException('Неполная запись'); }
    rewind($stream);
    echo stream_get_contents($stream);
} finally { fclose($stream); }
Ожидаемый результат
104: дрель — 2

x создаёт только отсутствующий файл; + позволяет затем прочитать его через тот же поток, b задаёт байтовый режим. При повторном запуске в той же папке открытие откажет: это и защищает от случайной перезаписи.

Применение и границы. w обнуляет до последующего flock: для чтения–изменения–записи берите c+, затем блокировку и ftruncate. fopen иногда открывает и каталог; это не подтверждение обычного файла. Доступ определяется пользователем веб-процесса, а не автором PHP-файла. b предпочтителен и для переносимого текста с явно выбранными переносами. Режим некоторых php:// потоков игнорируется. Старое замечание Manual об IIS без SSL close_notify описывает несовместимость сервера: не отключайте TLS-проверку и не скрывайте все предупреждения приложения.

Сравните: fclose, fread, fwrite, flock, is_dir. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

Возвращает ресурс либо false. rb читает, wb создает/обрезает, ab добавляет; c+открывает для чтения и записи без немедленного обрезания.

Прочитать первую строку локального файлаСамостоятельный пример
<?php
declare(strict_types=1);

$handle = fopen('status.txt', 'rb');
if ($handle === false) {
    throw new RuntimeException('Нет доступа');
}
try {
    $line = fgets($handle);
    if ($line === false) {
        throw new UnexpectedValueException('Ожидалась строка');
    }
    echo $line;
} finally {
    fclose($handle);
}
Ожидаемый результат
ready

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

Подготовить текст выдачи во временном потокеСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+b');
if ($stream === false) { throw new RuntimeException('Не создать поток'); }
try {
    $text = "Выдать: чай — 2, кофе — 1\n";
    if (fwrite($stream, $text) !== strlen($text) || !rewind($stream)) {
        throw new RuntimeException('Не подготовить отчёт');
    }
    echo stream_get_contents($stream);
} finally { fclose($stream); }
Ожидаемый результат
Выдать: чай — 2, кофе — 1

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

Сравните. file_get_contents проще для небольшого полного чтения; php://temp дает временный поток без ручного постоянного имени.

fgetsЧитать строку вместе с её переводом строки, либо ограниченную часть строки.

PHP 4+; nullable length PHP 8.0 · standard

fgets(resource $stream, ?int $length = null): string|false

Читать строку вместе с её переводом строки, либо ограниченную часть строки.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. fopen возвращает открытый поток (resource), а не содержимое файла. rb читает байты, w+b создаёт/очищает файл и разрешает чтение с записью. fclose освобождает поток. while (условие) повторяет блок, пока условие истинно; после чтения важно различать конец и ошибку. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". try выполняет попытку, catch обрабатывает указанный отказ, finally освобождает ресурс и не подавляет ошибку. throw new RuntimeException сообщает о сбое. Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
streamОткрытый совместимый поток (resource). Закрытый или чужой ресурс в PHP 8 приводит к TypeError; возможности зависят от обёртки.
lengthnull — до конца строки; заданное число ограничивает чтение length−1 байт. Остановка также на переводе строки или EOF. Для полезного чтения выбирайте >= 2.
ВозвратСтрока, включая найденный LF; false если читать больше нечего или случилась ошибка.
ОтказНевалидная length в PHP 8 может дать ValueError; false не доказывает чистый EOF.
ВерсияPHP 4+; nullable length PHP 8.0
Путь данных в первом опыте
  1. Tea + LF
  2. fgets
  3. Строка с LF
  4. Следующее чтение берёт следующую строку
Перевод строки остаётся в результатеСамостоятельный пример
<?php
$h = fopen(__DIR__ . '/lines.txt', 'rb');
echo json_encode(fgets($h)), "
";
fclose($h);
Ожидаемый результат
"Tea\n"

JSON делает невидимый LF заметным.

Ограничение может разделить одну строкуСамостоятельный пример
<?php
$h = fopen(__DIR__ . '/lines.txt', 'rb');
try {
    while (($part = fgets($h, 4)) !== false) { echo json_encode($part), "
"; }
    if (!feof($h)) { throw new RuntimeException('read'); }
} finally { fclose($h); }
Ожидаемый результат
"ABC"
"DE\n"

4 означает максимум 3 байта. Это уже фрагменты, а не гарантированно целые строки.

Применение и границы. Отказ перед EOF требует обработки. Для огромной строки чтение без лимита расходует память на всю строку. auto_detect_line_endings устарел с 8.1; старые CR-only файлы нормализуйте явно, не полагайтесь на настройку. CSV может содержать перевод строки внутри кавычек: для него fgetcsv.

Сравните: fgetc, fread, fgetcsv, feof. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

Возвращает строку с возможным переводом строки либо false. После остановки различайте EOF и ошибку чтения. Length ограничивает прочитанный фрагмент, не настоящую логическую строку.

Прочитать очередь имён, пропуская пустые строкиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/waiting.txt', 'rb');
if ($stream === false) { throw new RuntimeException('Не открыть список'); }
try {
    while (($line = fgets($stream)) !== false) {
        $name = trim($line);
        if ($name === '') { continue; }
        echo 'Пригласить: ', $name, "\n";
    }
    if (!feof($stream)) { throw new RuntimeException('Ошибка чтения'); }
} finally { fclose($stream); }
Ожидаемый результат
Пригласить: Анна
Пригласить: Борис

while получает по строке; continue пропускает только пустое имя и переходит к следующей строке. В этом формате пробелы вокруг имени незначимы. Для текста, где они важны, используйте rtrim($line, "\r\n"), как в следующем опыте.

Убрать только окончание строкиСамостоятельный пример
<?php
declare(strict_types=1);

$handle = fopen('line.txt', 'rb');
if ($handle === false) { throw new RuntimeException('Не открыть'); }
try {
    $line = fgets($handle);
    if ($line === false) { throw new RuntimeException('Нет строки'); }
    echo '[', rtrim($line, "\r\n"), "]\n";
} finally { fclose($handle); }
Ожидаемый результат
[ value ]

Rtrim удаляет перевод строки, но сохраняет значимые пробелы значения. Обычный trim изменил бы данные.

Сравните. fgetcsv нужен для CSV; stream_get_contents —для оставшейся части потока целиком.

fgetcsvПрочитать одну CSV-запись как список полей; запись может занимать несколько физических строк.

PHP 4+ · standard

fgetcsv(resource $stream, ?int $length = null, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array|false

Прочитать одну CSV-запись как список полей; запись может занимать несколько физических строк.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. fopen возвращает открытый поток (resource), а не содержимое файла. rb читает байты, w+b создаёт/очищает файл и разрешает чтение с записью. fclose освобождает поток. while (условие) повторяет блок, пока условие истинно; после чтения важно различать конец и ошибку. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". try выполняет попытку, catch обрабатывает указанный отказ, finally освобождает ресурс и не подавляет ошибку. throw new RuntimeException сообщает о сбое. [...] создаёт массив; $row["size"] читает поле по ключу; [$a, $b] = ... раскладывает список. Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
streamОткрытый совместимый поток (resource). Закрытый или чужой ресурс в PHP 8 приводит к TypeError; возможности зависят от обёртки.
length0/null/пропуск — без ограничения строки; null с8.0. Заданный предел должен превышать самую длинную строку с концом строки, иначе возможны части, кроме разрыва внутри enclosure. Без лимита немного больше работы парсера.
separatorРазделитель ровно один байт, по умолчанию запятая.
enclosureКавычка поля ровно один байт.
escapeОдин байт или ""; передавайте явно, обычно "".
ВозвратСписок строк полей; пустая строка CSV даёт [null], а не false. false — EOF/отказ.
ОтказValueError при неправильной длине separator/enclosure/escape; некорректный CSV не всегда вызывает исключение: нужно проверить количество/смысл полей.
ВерсияPHP 4+

CSV — таблица, где разделитель отделяет поля, а кавычки позволяют хранить разделитель и перевод строки внутри поля. Удвоенное "" внутри кавычек означает одну кавычку. Это не тот же алгоритм, что explode. Лишний собственный backslash-escape может нарушить совместимость и обратное чтение: задавайте escape: "" явно. Пустой escape поддерживается с7.4; полагаться на default deprecated с8.4, смена default обещана не ранее PHP9.0. Locale LC_CTYPE влияет на чтение однобайтовых кодировок; договоритесь о кодировке файла отдельно.

Путь данных в первом опыте
  1. tea,"black, large"
  2. CSV понимает кавычки
  3. Два поля
  4. Запятая внутри второго сохранена
Запятая внутри названияСамостоятельный пример
<?php
$h = fopen(__DIR__ . '/items.csv', 'rb');
$row = fgetcsv($h, null, ',', '"', '');
echo json_encode($row), "
";
fclose($h);
Ожидаемый результат
["tea","black, large"]

Разделение explode дало бы три части, CSV — два поля.

Комментарий к выдаче занимает две строки CSVСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/pickup.csv', 'rb');
if ($stream === false) { throw new RuntimeException('Не открыть CSV'); }
try {
    while (($row = fgetcsv($stream, null, ',', '"', '')) !== false) {
        if ($row === [null]) { continue; }
        if (count($row) !== 2) { throw new UnexpectedValueException('Нужны номер и комментарий'); }
        echo 'Заказ ', $row[0], ":\n", $row[1], "\n";
    }
    if (!feof($stream)) { throw new RuntimeException('Ошибка чтения'); }
} finally { fclose($stream); }
Ожидаемый результат
Заказ 104:
Забрать после 18:00
Позвонить у входа

Один комментарий содержит LF внутри кавычек. fgetcsv возвращает его одним полем, хотя запись занимает две физические строки. Пустая запись даёт [null]; политика примера её пропускает.

Применение и границы. Числовые поля остаются строками и требуют проверки формата/диапазона. С8.3 последний незакрытый enclosure без содержимого даёт пустую строку вместо одного NUL. Старое CR-only чтение через auto_detect_line_endings deprecated8.1; нормализуйте явно. Большое поле способно занять память даже при потоковом чтении.

Сравните: fputcsv, fgets, file. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

Возвращает массив полей либо false. Separator и enclosure — однобайтовые символы. Передавайте escape явно: пустая строка отключает нестандартное экранирование обратным слешем.

Запятая внутри названия товараСамостоятельный пример
<?php
declare(strict_types=1);

$h = fopen('products.csv', 'rb');
if ($h === false) { throw new RuntimeException('Не открыть'); }
try {
    $row = fgetcsv($h, 0, ',', '"', '');
    if ($row === false || count($row) !== 2) { throw new RuntimeException('Формат'); }
    echo $row[0], ':', $row[1], "\n";
} finally { fclose($h); }
Ожидаемый результат
A:Tea, green

CSV-парсер сохраняет запятую внутри кавычек одним полем. Простое explode дало бы три элемента.

Формат поставщика с точкой с запятойСамостоятельный пример
<?php
declare(strict_types=1);

$h = fopen('supplier.csv', 'rb');
if ($h === false) { throw new RuntimeException('Не открыть'); }
try {
    $row = fgetcsv($h, 0, ';', '"', '');
    if ($row === false) { throw new RuntimeException('Нет записи'); }
    echo implode('|', $row), "\n";
} finally { fclose($h); }
Ожидаемый результат
A|2|149.90

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

Сравните. explode не понимает кавычки. SplFileObject подходит для объектного чтения, но контракт ошибок и схема также нужны.

fputcsvСериализовать список строк в одну CSV-запись и сразу записать её в поток.

PHP5.1+; escape=""7.4, eol8.1, defaultescape deprecated8.4 · standard

fputcsv(resource $stream, array $fields, string $separator = ",", string $enclosure = "\"", string $escape = "\\", string $eol = "\n"): int|false

Сериализовать список строк в одну CSV-запись и сразу записать её в поток.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. fopen возвращает открытый поток (resource), а не содержимое файла. rb читает байты, w+b создаёт/очищает файл и разрешает чтение с записью. fclose освобождает поток. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". [...] создаёт массив; $row["size"] читает поле по ключу; [$a, $b] = ... раскладывает список. Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
streamОткрытый совместимый поток (resource). Закрытый или чужой ресурс в PHP 8 приводит к TypeError; возможности зависят от обёртки. Открыт для записи.
fieldsМассив строк-полей; не строка уже готового CSV.
separatorОдин байт разделителя.
enclosureОдин байт кавычки; внутри поля удваивается, кроме взаимодействия с включённым escape.
escapeОдин байт или пустая строка; рекомендуемый явный "".
eolПоследовательность завершения записи, по умолчанию LF; отдельный параметр с8.1.
ВозвратКоличество записанных байтов с концом строки; false при отказе.
ОтказValueError для недопустимых separator/enclosure/escape; I/O-ошибка может дать false.
ВерсияPHP5.1+; escape=""7.4, eol8.1, defaultescape deprecated8.4

CSV — таблица, где разделитель отделяет поля, а кавычки позволяют хранить разделитель и перевод строки внутри поля. Удвоенное "" внутри кавычек означает одну кавычку. Это не тот же алгоритм, что explode. Лишний собственный backslash-escape может нарушить совместимость и обратное чтение: задавайте escape: "" явно. Пустой escape поддерживается с7.4; полагаться на default deprecated с8.4, смена default обещана не ранее PHP9.0. Locale LC_CTYPE влияет на чтение однобайтовых кодировок; договоритесь о кодировке файла отдельно.

Путь данных в первом опыте
  1. Поля tea и black, large
  2. fputcsv ставит нужные кавычки
  3. Готовая CSV-запись
  4. Запись в файл
Записать поле с запятойСамостоятельный пример
<?php
$h = fopen(__DIR__ . '/items.csv', 'w+b');
fputcsv($h, ['tea', 'black, large'], ',', '"', '', "
");
rewind($h);
echo stream_get_contents($h);
fclose($h);
Ожидаемый результат
tea,"black, large"

Кавычки расставляет сериализатор, не вручную склеенный шаблон.

Кавычки в размере: показать CSV и восстановленный товарСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/sizes.csv', 'w+b');
if ($stream === false) { throw new RuntimeException('Не открыть CSV'); }
try {
    if (fputcsv($stream, ['T-1', 'size "L"'], ';', '"', '', "\r\n") === false) {
        throw new RuntimeException('Не записать CSV');
    }
    rewind($stream);
    echo 'Файл: ', stream_get_contents($stream);
    rewind($stream);
    $row = fgetcsv($stream, null, ';', '"', '');
    if ($row === false || count($row) !== 2) { throw new RuntimeException('Не прочитать товар'); }
    echo 'Товар ', $row[0], ': ', $row[1], "\n";
} finally { fclose($stream); }
Ожидаемый результат
Файл: T-1;"size ""L"""

Товар T-1: size "L"

В файле внутренняя кавычка удвоена, в восстановленном названии снова одна кавычка. Параметры читателя и писателя одинаковы; CRLF задан явно, поток открыт с b. Проверка сравнивает фактические байты после нормализации переводов строк тестовым запуском.

Применение и границы. CSV-экранирование не защищает от формул табличного редактора. Для экспорта внешнего текста в Excel отдельно согласуйте политику полей с началом =,+,-,@ и формат приложения; не меняйте числовые данные случайным префиксом. Для HTTP заголовки/кодировка идут до вывода; не дописывайте отладочный текст в CSV.

Сравните: fgetcsv, fwrite. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

Формирует CSV-строку в поток и возвращает число байтов либо false. Задайте escape=""явно для стандартного удвоения кавычек.

Экспорт названия с запятойСамостоятельный пример
<?php
declare(strict_types=1);

$h = fopen('php://temp', 'w+');
if ($h === false) { throw new RuntimeException('Поток'); }
try {
    if (fputcsv($h, ['A', 'Tea, green'], ',', '"', '') === false) { throw new RuntimeException('Запись'); }
    rewind($h);
    echo stream_get_contents($h);
} finally { fclose($h); }
Ожидаемый результат
A,"Tea, green"

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

Кавычки в названииСамостоятельный пример
<?php
declare(strict_types=1);

$h = fopen('php://temp', 'w+');
if ($h === false) { throw new RuntimeException('Поток'); }
try {
    fputcsv($h, ['Cable "Pro"', 2], ';', '"', '');
    rewind($h);
    echo stream_get_contents($h);
} finally { fclose($h); }
Ожидаемый результат
"Cable ""Pro""";2

Внутренние кавычки удваиваются. Это CSV-синтаксис, а не HTML-сущности; htmlspecialchars здесь исказил бы переносимый текст.

Сравните. implode не экранирует разделители. Для типизированной таблицы с форматами рассмотрите XLSX, а не CSV.

flockСогласовать доступ процессов к целому файлу: читатели вместе либо один писатель.

PHP 4+ · standard

flock(resource $stream, int $operation, int &$would_block = null): bool

Согласовать доступ процессов к целому файлу: читатели вместе либо один писатель.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. fopen возвращает открытый поток (resource), а не содержимое файла. rb читает байты, w+b создаёт/очищает файл и разрешает чтение с записью. fclose освобождает поток. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". try выполняет попытку, catch обрабатывает указанный отказ, finally освобождает ресурс и не подавляет ошибку. throw new RuntimeException сообщает о сбое. | объединяет поддерживаемые флаги; это допустимо только для параметра-маски. Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
streamОткрытый совместимый поток (resource). Закрытый или чужой ресурс в PHP 8 приводит к TypeError; возможности зависят от обёртки. Локальный файл или userspace wrapper со stream_lock().
operationLOCK_SH — совместное чтение; LOCK_EX — исключительная запись; LOCK_UN — отпустить; LOCK_NB добавляется через | для попытки без ожидания.
would_blockНеобязательная переменная по ссылке: 1 если отказ вызван необходимостью ждать (EWOULDBLOCK); не любое false означает занятость.
Возвратtrue при успехе, false при отказе; без LOCK_NB вызов обычно ждёт блокировку.
ОтказНекорректная операция PHP8 даёт ValueError; FS/обёртка может не поддерживать lock. Встроенного общего deadline нет.
ВерсияPHP 4+
Путь данных в первом опыте
  1. counter.txt содержит8
  2. LOCK_SH на открытом потоке
  3. Прочитать8 через тот же handle
  4. LOCK_UN освобождает блокировку
Взять совместную блокировку для чтенияСамостоятельный пример
<?php
$h = fopen(__DIR__ . '/counter.txt', 'rb');
if ($h === false) { throw new RuntimeException('open'); }
try {
    if (!flock($h, LOCK_SH)) { throw new RuntimeException('lock'); }
    echo stream_get_contents($h), "
";
    flock($h, LOCK_UN);
} finally { fclose($h); }
Ожидаемый результат
8

Файл читается через тот же handle. Особенно важно на Windows, где второй handle может не получить доступ.

Защитить весь цикл счётчикаСамостоятельный пример
<?php
$h = fopen(__DIR__ . '/counter.txt', 'c+b');
if ($h === false) { throw new RuntimeException('open'); }
try {
    $wouldBlock = 0;
    if (!flock($h, LOCK_EX | LOCK_NB, $wouldBlock)) {
        throw new RuntimeException($wouldBlock ? 'busy: retry later' : 'lock failed');
    }
    $old = stream_get_contents($h);
    if ($old === false || !ctype_digit($old)) { throw new RuntimeException('bad counter'); }
    $new = (string) ((int) $old + 1);
    rewind($h);
    if (!ftruncate($h, 0) || fwrite($h, $new) !== strlen($new) || !fflush($h)) {
        throw new RuntimeException('write failed');
    }
    flock($h, LOCK_UN);
    echo $new, "
";
} finally { fclose($h); }
Ожидаемый результат
9

ctype_digit разрешает только цифры строки; пример маленького счётчика не решает переполнение int и crash recovery. c+ не обнуляет до lock.

Применение и границы. На POSIX обычные locks кооперативные: все участники должны следовать одному протоколу. Windows использует mandatory locking; повторное открытие заблокированного файла этим же процессом тоже может мешать. Не блокируйте PHP-исходник перед запуском/require дочернего процесса: возможен deadlock. fclose/потеря последней ссылки освобождают lock. Возможности древних FAT, сетевых FS и process-level locks у многопоточного SAPI ограничены. Старое замечание Manual о Linux mandatory/setgid/mand — исторический механизм ОС, а не переносимая настройка современного приложения. LOCK_EX не делает транзакцию с БД и не обеспечивает сохранность после аварии.

Сравните: fopen, ftruncate, fflush, file_put_contents, fsync. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

LOCK_EX — эксклюзивная блокировка, LOCK_SH — совместная для чтения, LOCK_UN — освобождение. Возвращает успех; LOCK_NB просит не ждать занятый файл.

Уменьшить локальный остаток, удерживая блокировкуСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/stock.txt', 'c+b');
if ($stream === false) { throw new RuntimeException('Не открыть остаток'); }
try {
    if (!flock($stream, LOCK_EX)) { throw new RuntimeException('Не получить блокировку'); }
    $raw = stream_get_contents($stream);
    if ($raw === false || preg_match('/\A[0-9]{1,6}\z/', $raw) !== 1) {
        throw new UnexpectedValueException('Некорректный остаток');
    }
    $quantity = (int) $raw;
    if ($quantity < 2) { throw new RuntimeException('Недостаточно товара'); }
    $next = (string) ($quantity - 2);
    if (!rewind($stream) || !ftruncate($stream, 0) || fwrite($stream, $next) !== strlen($next) || !fflush($stream)) {
        throw new RuntimeException('Не сохранить остаток');
    }
    echo 'Выдано: 2. Осталось: ', $next, ".\n";
    flock($stream, LOCK_UN);
} finally { fclose($stream); }
Ожидаемый результат
Выдано: 2. Осталось: 5.

c+b не обрезает файл до блокировки. Чтение, проверка достаточного остатка и запись находятся под одним LOCK_EX. Это учебное согласование процессов, а не устойчивая транзакция магазина: авария между обрезанием и записью может потерять состояние; для реальных остатков обычно нужна БД.

Разрешить только один локальный запуск сборки отчётаСамостоятельный пример
<?php
declare(strict_types=1);

$lock = fopen(__DIR__ . '/report.lock', 'c');
if ($lock === false) { throw new RuntimeException('Не открыть блокировку'); }
try {
    if (!flock($lock, LOCK_EX | LOCK_NB, $blocked)) {
        echo $blocked ? "Отчёт уже собирается.\n" : "Не удалось взять блокировку.\n";
    } else {
        $report = "Сегодня выдать: 3 заказа\n";
        if (file_put_contents(__DIR__ . '/report.txt', $report) !== strlen($report)) {
            throw new RuntimeException('Не сохранить отчёт');
        }
        echo $report;
        flock($lock, LOCK_UN);
    }
} finally { fclose($lock); }
Ожидаемый результат
Сегодня выдать: 3 заказа

Файл report.lock отдельный, а блокировка удерживается до конца записи report.txt. Здесь исполняется одиночный успешный запуск; это не нагрузочное доказательство гонок. Все другие исполнители должны брать тот же lock на той же файловой системе.

Сравните. Для распределенных задач выберите очередь/БД и согласованный протокол блокировок; LOCK_EX у file_put_contents защищает только запись.

realpathПолучить канонический абсолютный путь уже существующего объекта.

PHP 4+ · standard

realpath(string $path): string|false

Получить канонический абсолютный путь уже существующего объекта.

$name — переменная; = сохраняет результат, кавычки задают строку, ; завершает команду. name(...) вызывает функцию с аргументами через запятую. echo выводит; "\n" переносит строку, var_dump показывает также тип. __DIR__ — папка текущего PHP-файла; точка . соединяет строки. function name($x) { ... } объявляет повторяемое действие; return отдаёт результат вызову. if выбирает ветку; === false отличает отказ от 0 байтов и пустой строки, а !== false допускает строку "0". [...] создаёт массив; $row["size"] читает поле по ключу; [$a, $b] = ... раскладывает список. Переменные; Вызов и return; Поток и позиция чтения.

Параметр или результатДоговор
pathСтрока пути; относительный путь разрешается от текущего рабочего каталога (CWD), а не обязательно от папки этого исходника. Пустая строка означает текущий каталог.
ВозвратАбсолютный путь с разрешёнными ./../повторными разделителями и ссылками; false при неудаче.
ОтказНет объекта/прав прохода по каталогам → false. Не предназначен для ещё не созданного файла.
ВерсияPHP 4+
Путь данных в первом опыте
  1. Путь ./stock.txt
  2. Проверка существования и разрешение компонентов
  3. Абсолютный путь; basename даёт stock.txt
Существующий и будущий файлСамостоятельный пример
<?php
$resolved = realpath(__DIR__ . '/./stock.txt');
if ($resolved === false) { throw new RuntimeException('Нет файла'); }
echo basename($resolved), "\n";
var_dump(realpath(__DIR__ . '/future.txt'));
Ожидаемый результат
stock.txt
bool(false)

Абсолютная часть зависит от окружения и поэтому не печатается.

Открыть разрешённый отчёт по публичному ключуСамостоятельный пример
<?php
declare(strict_types=1);

$allowed = ['stock' => __DIR__ . '/stock.txt'];
foreach (['stock', '../secret'] as $requested) {
    $path = isset($allowed[$requested]) ? realpath($allowed[$requested]) : false;
    if ($path === false) {
        echo 'Отчёт ', $requested, ": недоступен\n";
        continue;
    }
    $text = file_get_contents($path);
    if ($text === false) { throw new RuntimeException('Ошибка чтения отчёта'); }
    echo 'Отчёт ', $requested, ': ', $text;
}
Ожидаемый результат
Отчёт stock: TEA: 8 шт.
Отчёт ../secret: недоступен

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

Применение и границы. Не является песочницей: ../ разрешается в настоящий родитель, а не удаляется как опасный текст. Проверка префикса должна учитывать границу каталога, регистр файловой системы и подмену между проверкой/открытием. На Windows возможна ненормализованная капитализация, ссылки на каталоги/junction разворачиваются только на один уровень. Виртуальный путь внутри Phar не поддерживается.

Сравните: basename, dirname, clearstatcache, realpath_cache_get. Полный выбор файловых операций.

Продолжение: сохранённая прежняя практика.

Разрешает компоненты пути и символические ссылки, возвращая абсолютный путь или false. Полезно для сравнения с разрешенной корневой директорией.

Убедиться, что файл существуетСамостоятельный пример
<?php
declare(strict_types=1);

$resolved = realpath('sample.txt');
echo $resolved === false ? "Нет файла\n" : basename($resolved) . "\n";
Ожидаемый результат
sample.txt

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

Не принимать неизвестное ресурсное имяСамостоятельный пример
<?php
declare(strict_types=1);

$allowed = ['guide' => 'guide.txt'];
$requested = 'guide';
$path = isset($allowed[$requested]) ? realpath($allowed[$requested]) : false;
echo $path === false ? "Не найдено\n" : basename($path) . "\n";
Ожидаемый результат
guide.txt

Разрешенная карта ID исключает произвольный путь в параметре. Для динамического хранилища аналогом будет проверенная запись метаданных и право доступа.

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

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

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

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

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

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