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

Дерево руководства
Глава 24 · С нуля → практика
Файлы, пути, CSV и потоковая обработка
Читаем импорт безопасно, различаем конец файла и ошибку, выбираем полный буфер или поток.
Первый файл: сохранить памятку и увидеть её снова
После завершения программы переменная исчезает. Чтобы сотрудник позже прочитал «Заказ 104 готов», сохраним эту строку в ready.txt. Это обычный текстовый файл: его можно открыть редактором. Распакуйте пример в отдельную учебную папку; все пути ниже относятся только к ней.
- $path указывает на ready.txt рядом с example.php
- file_put_contents записывает строку заказа
- file_get_contents возвращает сохранённую строку
- 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, «Понедельник: 10:00–18:00»
- file_get_contents → $text
- false → сообщение об отказе; строка → HTML-экранирование
- echo формирует абзац расписания
<?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.
- history.txt содержит «104: создан»
- FILE_APPEND добавляет «104: готов»
- LOCK_EX блокирует запись для участников того же протокола
- Повторное чтение показывает обе строки
<?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; заголовок читаем отдельно. Синтаксис циклов подробно объяснён в управлении потоком.
<?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, которая может занимать несколько физических строк при переводе строки внутри кавычек.
Запись должна проверять результат
<?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_path | bool true ищет include_path; при strict_types не передавайте int FILE_USE_INCLUDE_PATH вместо bool. |
| context | Контекст или null; пропустите через null при задании offset. |
| offset | Байтовое начало; отрицательное — от конца (с7.1). Remote seek не поддерживается надёжно. |
| length | null — до конца; неотрицательный максимум байтов после фильтров потока; 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 |
- stock.txt содержит строку "0"
- file_get_contents возвращает строку
- === false не срабатывает
- Читатель видит «Чай: 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) перепутал бы этот результат с ошибкой чтения.
<?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 с предупреждением при ошибке. Для небольших локальных данных удобно; для больших нужен поток.
<?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 копируется от текущей позиции до конца. |
| flags | FILE_USE_INCLUDE_PATH, FILE_APPEND, LOCK_EX объединяются через |. |
| context | Контекст обёртки или null. |
| Возврат | Число записанных байтов или false; 0 может быть успешной пустой записью. |
| Отказ | При файловом отказе warning и false; wrapper должен поддерживать запись/блокировку. |
| Версия | PHP 5+ |
- queue.txt содержит заявку 104
- Запись пустой строки заменяет содержимое
- Возврат 0 — успех
- Чтение подтверждает пустую очередь
<?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_path | true дополнительно ищет в 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 меняет чтение, но не место записи.
- notes.txt содержит TEA
- fopen(..., rb) открывает поток
- fread берёт 3 байта
- 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; возможности зависят от обёртки. |
| length | null — до конца строки; заданное число ограничивает чтение length−1 байт. Остановка также на переводе строки или EOF. Для полезного чтения выбирайте >= 2. |
| Возврат | Строка, включая найденный LF; false если читать больше нечего или случилась ошибка. |
| Отказ | Невалидная length в PHP 8 может дать ValueError; false не доказывает чистый EOF. |
| Версия | PHP 4+; nullable length PHP 8.0 |
- Tea + LF
- fgets
- Строка с LF
- Следующее чтение берёт следующую строку
<?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; возможности зависят от обёртки. |
| length | 0/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 влияет на чтение однобайтовых кодировок; договоритесь о кодировке файла отдельно.
- tea,"black, large"
- CSV понимает кавычки
- Два поля
- Запятая внутри второго сохранена
<?php
$h = fopen(__DIR__ . '/items.csv', 'rb');
$row = fgetcsv($h, null, ',', '"', '');
echo json_encode($row), "
";
fclose($h);
Ожидаемый результат
["tea","black, large"]
Разделение explode дало бы три части, 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 влияет на чтение однобайтовых кодировок; договоритесь о кодировке файла отдельно.
- Поля tea и black, large
- fputcsv ставит нужные кавычки
- Готовая CSV-запись
- Запись в файл
<?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"
Кавычки расставляет сериализатор, не вручную склеенный шаблон.
<?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(). |
| operation | LOCK_SH — совместное чтение; LOCK_EX — исключительная запись; LOCK_UN — отпустить; LOCK_NB добавляется через | для попытки без ожидания. |
| would_block | Необязательная переменная по ссылке: 1 если отказ вызван необходимостью ждать (EWOULDBLOCK); не любое false означает занятость. |
| Возврат | true при успехе, false при отказе; без LOCK_NB вызов обычно ждёт блокировку. |
| Отказ | Некорректная операция PHP8 даёт ValueError; FS/обёртка может не поддерживать lock. Встроенного общего deadline нет. |
| Версия | PHP 4+ |
- counter.txt содержит8
- LOCK_SH на открытом потоке
- Прочитать8 через тот же handle
- 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+ |
- Путь ./stock.txt
- Проверка существования и разрешение компонентов
- Абсолютный путь; 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 сам по себе не проверяет реальную цель символической ссылки.
Источники и обсуждения
- file_get_contents() — официальный справочник
- fgetcsv() — официальный справочник
- flock() — официальный справочник
- PHP Manual: security.filesystem
- fopen() — официальный справочник
- PHP: function.file-get-contents
- PHP: function.file-put-contents
- PHP: function.fputcsv
- PHP: function.fgetcsv
- PHP: security.filesystem
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


