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

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

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

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

Глава 84 · Практика

Каталоги: полный справочник Directory и функций

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

Из папки выдачи — к списку документов

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

Имя файла и его содержимое — два разных шага

Сервису нужен список полученных документов. Сначала он читает каталог, затем открывает нужный документ.

  1. Папка catalog

    На диске лежат a.txt и b.txt; внутри каждого есть данные.

  2. scandir('catalog')

    Возвращает массив имён, а не содержимое документов.

  3. Имя a.txt

    Соединяем основание catalog и имя: catalog/a.txt.

  4. Чтение документа

    file_get_contents получает содержимое выбранного файла отдельным действием.

Для большого каталога можно читать имена по одному через opendir/readdir. Имена и пути не заменяют проверку доступа к документу.

У сотрудника есть папка documents с памятками заказов 104 и 105. Нужно показать, какие документы готовы. Каталог хранит имена файлов и подкаталогов; scandir читает эти имена, но пока не читает сами памятки. Сначала получим список, затем откроем каждый подходящий документ и обойдём архив.

Папка превращается в список
  1. documents/104.txt: «Выдать две дрели»
  2. documents/105.txt: «Выдать одну пилу»
  3. scandir получает имена рядом с . и ..
  4. PHP выводит два имени документов
ЗаписьЧто она делает
scandir(__DIR__ . '/documents')__DIR__ задаёт папку исходника; точка соединяет путь. Функция возвращает массив имён либо false.
$name === '.' || $name === '..'Точное сравнение отбрасывает служебные имена текущей и родительской папки. || означает «или».
foreach ($names as $name)Берёт имена из массива по одному; continue переходит к следующему имени.
Показать два готовых документаСамостоятельный пример
<?php
declare(strict_types=1);

$names = scandir(__DIR__ . '/documents');
if ($names === false) { throw new RuntimeException('Не прочитать папку'); }
foreach ($names as $name) {
    if ($name === '.' || $name === '..') { continue; }
    echo 'Документ: ', $name, "\n";
}
Ожидаемый результат
Документ: 104.txt
Документ: 105.txt

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

Для маленького списка удобно scandir. Для обработки по одному имени — opendir, readdir и closedir. Объект Directory объединяет тот же доступ в свойства и методы. Массивы; Циклы; Пути и чтение.

Все константы и переносимость путей

Константа — готовое именованное значение. DIRECTORY_SEPARATOR относится к разделителю внутри пути, PATH_SEPARATOR — к разделителю списка нескольких путей. Это разные задачи; менять одним другое нельзя.

Путь и список путей
  1. Каталог + разделитель + имя → один путь
  2. DIRECTORY_SEPARATOR выбирает системный разделитель
  3. Путь1 + PATH_SEPARATOR + путь2 → список путей
  4. SCANDIR_SORT_* управляет порядком имён, а не путями
КонстантаСмысл
DIRECTORY_SEPARATORСистемный разделитель компонентов пути: обратная косая черта в Windows, / в Unix. PHP-файловые вызовы Windows обычно принимают также /.
PATH_SEPARATOR; в Windows и : в остальных системах; например для include_path. Не является URL-разделителем.
SCANDIR_SORT_ASCENDINGscandir сортирует имена по возрастанию; стандартный режим.
SCANDIR_SORT_DESCENDINGСортировка имён по убыванию.
SCANDIR_SORT_NONEБез сортировки: порядок файловой системы.
Собрать путь из двух известных частейСамостоятельный пример
<?php
declare(strict_types=1);

$path = 'catalog' . DIRECTORY_SEPARATOR . 'a.txt';
echo file_get_contents($path);
var_dump(strlen(PATH_SEPARATOR) === 1);
Ожидаемый результат
A
bool(true)

Точка в PHP склеивает строки. Построение пути само по себе не проверяет, что пользовательский ввод не вышел из разрешённой папки; здесь обе части заданы автором.

Список файлов на сайте: безопасность и стоимость

Каталог не является таблицей доверенных пользователей. Имя файла может содержать пробел, кавычку, HTML-символы, быть "0" или начинаться с точки. Не выводите имя без HTML-экранирования и не превращайте произвольный GET-параметр в путь сервера. Для скачивания удобно выдавать публичный идентификатор документа и по нему находить разрешённый путь после проверки доступа.

Документ посетителя
  1. Пользователь выбрал ID документа
  2. Сервис проверил владельца/разрешение
  3. Нашёл свой фиксированный путь
  4. Открыл файл; HTML-подпись экранируется отдельно
Безопасно подготовить видимую подпись имениСамостоятельный пример
<?php
declare(strict_types=1);

$name = 'report & draft.txt';
echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), "\n";
Ожидаемый результат
report &amp; draft.txt

HTML-экранирование защищает текстовую подпись в HTML. Оно не кодирует URL, не проверяет владельца и не делает путь безопасным для открытия.

Отбрасывайте . и .. точным сравнением. Проверка первого символа на точку имеет другой смысл — скрыть все Unix-style dotfiles; Windows использует также атрибут скрытости. is_file/is_dir требует путь с основанием каталога; проверка только имени ошибочно смотрит в cwd. Проверка пути и последующее открытие могут быть разделены гонкой изменений; в недоверенной общей папке нужны ограничения записи и подходящая модель доступа.

scandir выделяет массив всех имён и по умолчанию сортирует его. opendir/readdir с обработкой по одному имени ограничивает накопление списка, но сортировка всего набора снова требует памяти. Рекурсивный обход дополнительно требует решения о символических ссылках, глубине и циклах; рекурсия объясняет остановку обхода. DirectoryIterator и RecursiveDirectoryIterator из SPL предоставляют другой интерфейс; они не делают произвольную рекурсию автоматически безопасной. Для высокой нагрузки измеряйте свой каталог: тип файловой системы, число файлов и сетевой доступ меняют стоимость.

Упражнение: в catalog лежат файлы 0, a.txt, b.txt и каталог archive. Получите количество только обычных файлов верхнего уровня, не загружая все имена в массив. Ожидаем 3: archive/old.txt не относится к верхнему уровню.

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

$handle = opendir('catalog');
if ($handle === false) { throw new RuntimeException('Cannot open catalog'); }
$files = 0;
try {
    while (($name = readdir($handle)) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        if (is_file('catalog/' . $name)) { ++$files; }
    }
} finally { closedir($handle); }
echo $files, "\n";
Ожидаемый результат
3

Различие с простым подсчётом записей — is_file на пути с основанием catalog. В большом каталоге в памяти остаются счётчик и текущее имя. Если задача меняется на рекурсивный обход, заранее решите правила перехода по ссылкам и максимальную глубину.

Готовая опись: пройти дерево документов с ограничением глубины

Теперь документы лежат и в archive. Получим опись всех обычных файлов внутри своей папки: относительный путь и первую строку памятки. Функция вызывает сама себя для подкаталога — это рекурсия. Параметр $depth считает вложенность; предел 3 прекращает неожиданно глубокий обход. Символические ссылки пропускаются до проверки каталога, чтобы не уйти к внешней цели или по циклу.

Спуск и возврат по нашим папкам
  1. listDocuments(documents, "", 0)
  2. 104.txt → вывести памятку
  3. archive → listDocuments(documents, "archive/", 1)
  4. 103.txt → вывести архивную строку
  5. Возврат на уровень 0, затем завершение
Опись готовых и архивных памятокСамостоятельный пример
<?php
declare(strict_types=1);

function listDocuments(string $root, string $relative = '', int $depth = 0): void
{
    if ($depth > 3) { throw new RuntimeException('Слишком глубокое дерево'); }
    $names = scandir($root . '/' . $relative);
    if ($names === false) { throw new RuntimeException('Не прочитать каталог'); }
    foreach ($names as $name) {
        if ($name === '.' || $name === '..') { continue; }
        $local = $relative . $name;
        $path = $root . '/' . $local;
        if (is_link($path)) { continue; }
        if (is_dir($path)) {
            listDocuments($root, $local . '/', $depth + 1);
            continue;
        }
        if (!is_file($path)) { continue; }
        $stream = fopen($path, 'rb');
        if ($stream === false) { throw new RuntimeException('Не открыть документ'); }
        try {
            $preview = fgets($stream, 256);
            if ($preview === false) {
                if (!feof($stream)) { throw new RuntimeException('Ошибка чтения документа'); }
                $preview = '[пустой документ]';
            }
            echo $local, ': ', rtrim($preview, "\r\n"), "\n";
        } finally { fclose($stream); }
    }
}
listDocuments(__DIR__ . '/documents');
Ожидаемый результат
104.txt: Выдать две дрели
105.txt: Выдать одну пилу
archive/103.txt: Заказ закрыт

scandir даёт сортировку в каждом каталоге. $relative хранит путь только внутри выбранного корня; новый вызов получает глубину +1, а после возврата старый продолжает свой foreach. Первая строка ограничена 255 байтами: это короткий предварительный просмотр, не полный текст. В недоверенном изменяемом дереве проверки могут устареть, поэтому процессу нужны ограниченные права и отдельная модель доступа.

Для огромного дерева добавляют общий предел числа файлов, времени и суммарного объёма; либо используют итераторы SPL. Здесь выполнен небольшой локальный обход заранее известной папки. Попробуйте добавить archive/102.txt и сравните опись; затем установите предел глубины 0 и получите контролируемый отказ на archive. Порядок рекурсивных вызовов и возвратов.

Что ломалось в реальных задачах и как это воспроизвести

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

ПроблемаПричина и современное решение
Почему обход останавливается на файле 0Имя0 допустимо, но ложно при нестрогой проверке. Сравниваем результат readdir строго с false. Договор PHP.
Почему is_dir проверяет не тот каталогreaddir возвращает имя; проверку делают по исходной папке плюс это имя. Относительный путь может быть корректным, если известна его точка отсчёта. Договор PHP.
Когда нужен readdir, а когда scandirВыбор между поочерёдным получением имён и готовым массивом со своим порядком. Вопрос о скорости не даёт универсального коэффициента ускорения. Договор PHP.
Почему tempnam создал файл в другой папкеПри недоступном каталоге возможен системный fallback. Пример проверяет фактический каталог, а публикация отчёта требует совместимого хранилища. Договор PHP.

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

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

scandirПолучить массив имён файлов и подкаталогов одного каталога.

PHP 5+ · standard

scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING, ?resource $context = null): array|false

Получить массив имён файлов и подкаталогов одного каталога.

Часть вызоваДоговор
directoryПуть к каталогу. Возвращаются имена внутри него, без префикса пути; вложенные каталоги автоматически не обходятся.
sorting_orderSCANDIR_SORT_ASCENDING по умолчанию: алфавитный порядок. DESCENDING — обратный, NONE — без сортировки. Числовые суффиксы имён не превращаются в естественную сортировку.
contextНеобязательный контекст потока, null разрешён с PHP 8.0. URL работает только у обёртки, которая поддерживает операции каталога; HTTP-страница не становится каталогом.
Возвратarray имён, включая . и .. у обычной файловой системы, либо false. Недоступный/неверный путь вызывает E_WARNING.
ЦенаЧитает все имена в память, по умолчанию ещё сортирует. Для очень большого каталога opendir/readdir позволяет обрабатывать по одному имени.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Каталог превращается в список
  1. catalog содержит a.txt и b.txt
  2. scandir("catalog") читает имена
  3. Получаем массив [".", "..", "a.txt", "b.txt"]
  4. Отбрасываем только служебные . и ..

Каталог — контейнер имён в файловой системе. Массив — уже данные внутри PHP. Точка означает текущий каталог, две точки — родительский; это не пустые файлы. Сначала простой вывод двух известных имён:

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

$names = scandir('catalog');
if ($names === false) { throw new RuntimeException('Cannot read catalog'); }
echo implode(', ', $names), "\n";
Ожидаемый результат
., .., a.txt, b.txt

implode соединяет элементы массива строкой-разделителем. Если каталог изменится, его список тоже изменится; здесь используются собственные неизменяемые учебные файлы.

Оставить обычные файлы, сохранив имя «0»Самостоятельный пример
<?php
declare(strict_types=1);

$names = scandir('catalog');
if ($names === false) { throw new RuntimeException('Cannot read catalog'); }
$files = [];
foreach ($names as $name) {
    if ($name === '.' || $name === '..') { continue; }
    if (is_file('catalog/' . $name)) { $files[] = $name; }
}
echo implode(', ', $files), "\n";
Ожидаемый результат
0, a.txt, b.txt

is_file получает полный путь относительно текущего каталога, а не голое имя из списка. archive пропущен как каталог; имя 0 остаётся файлом. Это отбор для отчёта, не гарантия безопасности будущего открытия при одновременном изменении файлов.

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

$ascending = scandir('catalog', SCANDIR_SORT_ASCENDING);
$descending = scandir('catalog', SCANDIR_SORT_DESCENDING);
if ($ascending === false || $descending === false) { throw new RuntimeException('Cannot read catalog'); }
echo implode(', ', $ascending), "\n", implode(', ', $descending), "\n";
Ожидаемый результат
., .., a.txt, b.txt
b.txt, a.txt, .., .

SCANDIR_SORT_NONE экономит сортировку, но не обещает конкретный порядок. Если затем всё равно сортировать полный массив, выигрыша в памяти не будет.

Рядом: opendir, readdir, dir, is_file, sort. Как читать каталог.

opendirОткрыть каталог и получить ресурс для последовательного чтения.

PHP 4+ · standard

opendir(string $directory, ?resource $context = null): resource|false

Открыть каталог и получить ресурс для последовательного чтения.

Часть вызоваДоговор
directoryПуть к существующему доступному каталогу. Пустой/неверный путь, права и ошибки файловой системы мешают открытию.
contextКонтекст потока или null; nullable с 8.0. Возможности зависят от обёртки.
ВозвратРесурс каталога, не массив имён. При неуспехе false и E_WARNING. Сначала проверяют результат, затем вызывают readdir.
РесурсХранит позицию чтения. Передавайте конкретный handle дальше и закрывайте его в finally.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Путь конкретного документа
  1. Открыть documents
  2. Получать имена до строгого false
  3. Закрыть перечисление и отсортировать имена
  4. Показать содержимое каждого обычного файла
Открыть папку и прочитать готовые памяткиСамостоятельный пример
<?php
declare(strict_types=1);

$handle = opendir(__DIR__ . '/documents');
if ($handle === false) { throw new RuntimeException('Не открыть документы'); }
$names = [];
try {
    while (($name = readdir($handle)) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        if (is_file(__DIR__ . '/documents/' . $name)) { $names[] = $name; }
    }
} finally { closedir($handle); }
sort($names, SORT_STRING);
foreach ($names as $name) {
    echo $name, ': ', file_get_contents(__DIR__ . '/documents/' . $name);
}
Ожидаемый результат
0: Заказ без присвоенного номера
104.txt: Выдать две дрели
105.txt: Выдать одну пилу

opendir возвращает доступ к каталогу. readdir последовательно даёт имена; is_file получает путь с основанием documents. Для воспроизводимого вывода имена сортируются после чтения; ниже показана обработка без накопления списка.

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

$folder = __DIR__ . '/documents';
$handle = opendir($folder);
if ($handle === false) { throw new RuntimeException('Не открыть документы'); }
$bytes = 0;
try {
    while (($name = readdir($handle)) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        $path = $folder . '/' . $name;
        if (!is_file($path)) { continue; }
        $size = filesize($path);
        if ($size === false) { throw new RuntimeException('Не получить размер'); }
        $bytes += $size;
    }
} finally { closedir($handle); }
echo 'Документы текущей выдачи: ', $bytes, " байт\n";
Ожидаемый результат
Документы текущей выдачи: 22 байт

Это прикладной размер подборки: только обычные файлы первого уровня, без archive/103.txt. В памяти остаётся текущая запись и сумма; имена не накапливаются. Размеры относятся к моментам отдельных проверок, не к атомарному снимку.

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

$warning = false;
set_error_handler(static function (int $level) use (&$warning): bool {
    if ($level !== E_WARNING) { return false; }
    $warning = true;
    return true;
});
try { $handle = opendir(__DIR__ . '/missing-documents'); }
finally { restore_error_handler(); }
if ($handle === false) { echo "Выдача недоступна: папка документов не подготовлена.\n"; }
if ($warning) { echo "PHP также сообщил файловое предупреждение.\n"; }
Ожидаемый результат
Выдача недоступна: папка документов не подготовлена.
PHP также сообщил файловое предупреждение.

Отсутствующая папка даёт два сигнала: false и E_WARNING. В опыте handler регистрирует только warning, затем восстанавливается. В приложении техническую причину направляют в журнал, а сотруднику дают действие; не скрывают все предупреждения глобально.

Рядом: readdir, closedir, rewinddir, scandir. Как читать каталог.

readdirПрочитать имя следующей записи или получить false при окончании/неуспехе.

PHP 4+ · standard

readdir(?resource $dir_handle = null): string|false

Прочитать имя следующей записи или получить false при окончании/неуспехе.

Часть вызоваДоговор
dir_handleРесурс opendir. Исторический null выбирает последний открытый handle; с 8.0 nullable, с 8.5 null и пропуск deprecated. Передавайте ресурс явно.
ВозвратИмя string без пути либо false. Строка "0" — корректное имя, но в нестрогой логической проверке ложна.
ПорядокПорядок файловой системы, не обещанная сортировка. Список не является атомарным снимком изменяемого каталога.
ГлубинаОдин уровень; . и .. надо пропускать явно. Для is_file/is_dir соединяют путь каталога и имя.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Одно чтение двигает позицию
  1. Позиция у следующей записи
  2. read возвращает строку, например "0"
  3. Позиция перемещается дальше
  4. Только значение false завершает перебор
Собрать имена с правильной проверкой концаСамостоятельный пример
<?php
declare(strict_types=1);

$handle = opendir('catalog');
if ($handle === false) { throw new RuntimeException('Cannot open catalog'); }
$names = [];
try {
    while (($name = readdir($handle)) !== false) {
        if ($name !== '.' && $name !== '..') { $names[] = $name; }
    }
} finally { closedir($handle); }
sort($names, SORT_STRING);
echo implode(', ', $names), "\n";
Ожидаемый результат
0, a.txt, archive, b.txt

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

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

$handle = opendir(__DIR__ . '/documents');
if ($handle === false) { throw new RuntimeException('Не открыть документы'); }
$names = [];
try {
    while (($name = readdir($handle)) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        if (is_file(__DIR__ . '/documents/' . $name)) { $names[] = $name; }
    }
} finally { closedir($handle); }
sort($names, SORT_STRING);
foreach ($names as $name) {
    echo $name, ': ', file_get_contents(__DIR__ . '/documents/' . $name);
}
Ожидаемый результат
0: Заказ без присвоенного номера
104.txt: Выдать две дрели
105.txt: Выдать одну пилу

В папке действительно есть файл 0 с содержимым. Строгое !== false позволяет обработать его; нестрогое while ($name = ...) остановилось бы на этом имени. Сортировка здесь служит только стабильному показу.

Рядом: opendir, dir, scandir, is_dir. Как читать каталог.

Directory::readПрочитать имя следующей записи или получить false при окончании/неуспехе.

PHP 4+ · standard

public function read(): string|false

Прочитать имя следующей записи или получить false при окончании/неуспехе.

Часть вызоваДоговор
ПараметрыНет с PHP 8.0; раньше метод допускал аргумент handle. Используется handle объекта.
ВозвратИмя string без пути либо false. Строка "0" — корректное имя, но в нестрогой логической проверке ложна.
ПорядокПорядок файловой системы, не обещанная сортировка. Список не является атомарным снимком изменяемого каталога.
ГлубинаОдин уровень; . и .. надо пропускать явно. Для is_file/is_dir соединяют путь каталога и имя.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". dir(...) создаёт объект; ->read() вызывает его метод, ->path читает свойство. Сам Directory через new здесь не создают. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Одно чтение двигает позицию
  1. Позиция у следующей записи
  2. read возвращает строку, например "0"
  3. Позиция перемещается дальше
  4. Только значение false завершает перебор
Собрать имена с правильной проверкой концаСамостоятельный пример
<?php
declare(strict_types=1);

$directory = dir('catalog');
if ($directory === false) { throw new RuntimeException('Cannot open catalog'); }
$names = [];
try {
    while (($name = $directory->read()) !== false) {
        if ($name !== '.' && $name !== '..') { $names[] = $name; }
    }
} finally { $directory->close(); }
sort($names, SORT_STRING);
echo implode(', ', $names), "\n";
Ожидаемый результат
0, a.txt, archive, b.txt

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

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

$directory = dir(__DIR__ . '/documents');
if ($directory === false) { throw new RuntimeException('Не открыть документы'); }
$names = [];
try {
    while (($name = $directory->read()) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        if (is_file(__DIR__ . '/documents/' . $name)) { $names[] = $name; }
    }
} finally { $directory->close(); }
sort($names, SORT_STRING);
foreach ($names as $name) {
    echo $name, ': ', file_get_contents(__DIR__ . '/documents/' . $name);
}
Ожидаемый результат
0: Заказ без присвоенного номера
104.txt: Выдать две дрели
105.txt: Выдать одну пилу

В папке действительно есть файл 0 с содержимым. Строгое !== false позволяет обработать его; нестрогое while ($name = ...) остановилось бы на этом имени. Сортировка здесь служит только стабильному показу.

Рядом: opendir, dir, scandir, is_dir. Как читать каталог.

closedirЗакрыть открытый ресурс каталога, когда чтение закончено.

PHP 4+ · standard

closedir(?resource $dir_handle = null): void

Закрыть открытый ресурс каталога, когда чтение закончено.

Часть вызоваДоговор
dir_handleРесурс opendir. null/пропуск исторически выбирает последний открытый ресурс, deprecated с PHP 8.5; nullable с 8.0.
Возвратvoid: полезного результата нет; присвоенное значение наблюдается как null.
После закрытияДескриптор уже нельзя читать или закрывать повторно. Переменная может оставаться, но ресурс больше не действителен.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Путь конкретного документа
  1. Открыт documents
  2. Имена104.txt и105.txt получены
  3. Закрыт ресурс перечисления
  4. Сам документ104.txt остаётся доступным
Завершить чтение списка, сохранив сами документыСамостоятельный пример
<?php
declare(strict_types=1);

$handle = opendir(__DIR__ . '/documents');
if ($handle === false) { throw new RuntimeException('Не открыть документы'); }
$names = [];
try {
    while (($name = readdir($handle)) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        $names[] = $name;
    }
} finally { closedir($handle); }
sort($names, SORT_STRING);
echo 'Прочитанные имена: ', implode(', ', $names), "\n";
echo 'Документ после закрытия каталога: ', file_get_contents(__DIR__ . '/documents/104.txt');
Ожидаемый результат
Прочитанные имена: 104.txt, 105.txt
Документ после закрытия каталога: Выдать две дрели

Закрывается ресурс перечисления каталога; файлы сохраняются. У close нет полезного возвращаемого значения — void. Для нового перечисления открывают новый ресурс, не повторно используют закрытый.

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

$handle = opendir(__DIR__ . '/documents');
if ($handle === false) { throw new RuntimeException('Не открыть документы'); }
try {
    try {
        while (($name = readdir($handle)) !== false) {
            if ($name === '.' || $name === '..') { continue; }
            $text = file_get_contents(__DIR__ . '/documents/' . $name);
            json_decode($text, true, 8, JSON_THROW_ON_ERROR);
        }
    } finally { closedir($handle); }
} catch (JsonException $error) { echo "Импорт остановлен: документ содержит неверный JSON.\n"; }
echo is_resource($handle) ? "Каталог ещё открыт.\n" : "Каталог закрыт после отказа.\n";
Ожидаемый результат
Импорт остановлен: документ содержит неверный JSON.
Каталог закрыт после отказа.

В файле 104.json отсутствует значение после двоеточия. finally закрывает каталог при реальном отказе парсера; внешний catch объясняет исправление. После закрытия ресурс не читают и не закрывают второй раз.

Рядом: opendir, dir, readdir. Как читать каталог.

Directory::closeЗакрыть открытый ресурс каталога, когда чтение закончено.

PHP 4+ · standard

public function close(): void

Закрыть открытый ресурс каталога, когда чтение закончено.

Часть вызоваДоговор
ПараметрыНет с PHP 8.0; раньше допускался handle. Закрывается handle объекта.
Возвратvoid: полезного результата нет; присвоенное значение наблюдается как null.
После закрытияДескриптор уже нельзя читать или закрывать повторно. Переменная может оставаться, но ресурс больше не действителен.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". dir(...) создаёт объект; ->read() вызывает его метод, ->path читает свойство. Сам Directory через new здесь не создают. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Путь конкретного документа
  1. Открыт documents
  2. Имена104.txt и105.txt получены
  3. Закрыт ресурс перечисления
  4. Сам документ104.txt остаётся доступным
Завершить чтение списка, сохранив сами документыСамостоятельный пример
<?php
declare(strict_types=1);

$directory = dir(__DIR__ . '/documents');
if ($directory === false) { throw new RuntimeException('Не открыть документы'); }
$names = [];
try {
    while (($name = $directory->read()) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        $names[] = $name;
    }
} finally { $directory->close(); }
sort($names, SORT_STRING);
echo 'Прочитанные имена: ', implode(', ', $names), "\n";
echo 'Документ после закрытия каталога: ', file_get_contents(__DIR__ . '/documents/104.txt');
Ожидаемый результат
Прочитанные имена: 104.txt, 105.txt
Документ после закрытия каталога: Выдать две дрели

Закрывается ресурс перечисления каталога; файлы сохраняются. У close нет полезного возвращаемого значения — void. Для нового перечисления открывают новый ресурс, не повторно используют закрытый.

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

$directory = dir(__DIR__ . '/documents');
if ($directory === false) { throw new RuntimeException('Не открыть документы'); }
try {
    try {
        while (($name = $directory->read()) !== false) {
            if ($name === '.' || $name === '..') { continue; }
            $text = file_get_contents(__DIR__ . '/documents/' . $name);
            json_decode($text, true, 8, JSON_THROW_ON_ERROR);
        }
    } finally { $directory->close(); }
} catch (JsonException $error) { echo "Импорт остановлен: документ содержит неверный JSON.\n"; }
echo is_resource($directory->handle) ? "Каталог ещё открыт.\n" : "Каталог закрыт после отказа.\n";
Ожидаемый результат
Импорт остановлен: документ содержит неверный JSON.
Каталог закрыт после отказа.

В файле 104.json отсутствует значение после двоеточия. finally закрывает каталог при реальном отказе парсера; внешний catch объясняет исправление. После закрытия ресурс не читают и не закрывают второй раз.

Рядом: opendir, dir, readdir. Как читать каталог.

rewinddirВернуть позицию открытого каталога к началу для повторного прохода.

PHP 4+ · standard

rewinddir(?resource $dir_handle = null): void

Вернуть позицию открытого каталога к началу для повторного прохода.

Часть вызоваДоговор
dir_handleРесурс opendir; nullable с 8.0. null/пропуск выбора последнего handle deprecated с PHP 8.5.
Возвратvoid; метод не возвращает первое имя. Чтобы получить его, отдельно вызывают read/readdir.
ОграничениеНе является откатом изменений файловой системы или стабильным снимком. При изменении каталога повторный проход может дать другой набор/порядок.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Повторное чтение
  1. Первый проход дошёл до false
  2. rewind переносит курсор к началу
  3. Следующий read снова читает первое имя
  4. Изменения файлов на диске при этом не отменяются
Повторно перечислить документы после возврата к началуСамостоятельный пример
<?php
declare(strict_types=1);

$handle = opendir(__DIR__ . '/documents');
if ($handle === false) { throw new RuntimeException('Не открыть документы'); }
try {
    foreach (['Предпросмотр', 'Подборка'] as $label) {
        $names = [];
        while (($name = readdir($handle)) !== false) {
            if ($name !== '.' && $name !== '..') { $names[] = $name; }
        }
        sort($names, SORT_STRING);
        echo $label, ': ', implode(', ', $names), "\n";
        rewinddir($handle);
    }
} finally { closedir($handle); }
Ожидаемый результат
Предпросмотр: 104.txt, 105.txt
Подборка: 104.txt, 105.txt

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

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

$handle = opendir(__DIR__ . '/documents');
if ($handle === false) { throw new RuntimeException('Не открыть документы'); }
try {
    $names = [];
    while (($name = readdir($handle)) !== false) {
        if ($name !== '.' && $name !== '..') { $names[] = $name; }
    }
    sort($names, SORT_STRING);
    echo 'Предпросмотр: ', implode(', ', $names), "\n";
    rewinddir($handle);
    $documents = [];
    while (($name = readdir($handle)) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        $text = file_get_contents(__DIR__ . '/documents/' . $name);
        if ($text === false) { throw new RuntimeException('Документ недоступен'); }
        $documents[$name] = $text;
    }
    ksort($documents, SORT_STRING);
    foreach ($documents as $name => $text) { echo $name, ': ', $text; }
} finally { closedir($handle); }
Ожидаемый результат
Предпросмотр: 104.txt, 105.txt
104.txt: Выдать две дрели
105.txt: Выдать одну пилу

Первый проход получает только имена, второй заново перечисляет каталог и читает содержимое. Без перемотки второй цикл уже находится в конце. Каталог может измениться; это не snapshot. Когда свежий обход не требуется, проще сохранить и использовать первый список.

Рядом: opendir, readdir, dir, scandir. Как читать каталог.

Directory::rewindВернуть позицию открытого каталога к началу для повторного прохода.

PHP 4+ · standard

public function rewind(): void

Вернуть позицию открытого каталога к началу для повторного прохода.

Часть вызоваДоговор
ПараметрыНет с PHP 8.0; прежде допускался handle. Используется ресурс объекта.
Возвратvoid; метод не возвращает первое имя. Чтобы получить его, отдельно вызывают read/readdir.
ОграничениеНе является откатом изменений файловой системы или стабильным снимком. При изменении каталога повторный проход может дать другой набор/порядок.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". dir(...) создаёт объект; ->read() вызывает его метод, ->path читает свойство. Сам Directory через new здесь не создают. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Повторное чтение
  1. Первый проход дошёл до false
  2. rewind переносит курсор к началу
  3. Следующий read снова читает первое имя
  4. Изменения файлов на диске при этом не отменяются
Повторно перечислить документы после возврата к началуСамостоятельный пример
<?php
declare(strict_types=1);

$directory = dir(__DIR__ . '/documents');
if ($directory === false) { throw new RuntimeException('Не открыть документы'); }
try {
    foreach (['Предпросмотр', 'Подборка'] as $label) {
        $names = [];
        while (($name = $directory->read()) !== false) {
            if ($name !== '.' && $name !== '..') { $names[] = $name; }
        }
        sort($names, SORT_STRING);
        echo $label, ': ', implode(', ', $names), "\n";
        $directory->rewind();
    }
} finally { $directory->close(); }
Ожидаемый результат
Предпросмотр: 104.txt, 105.txt
Подборка: 104.txt, 105.txt

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

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

$directory = dir(__DIR__ . '/documents');
if ($directory === false) { throw new RuntimeException('Не открыть документы'); }
try {
    $names = [];
    while (($name = $directory->read()) !== false) {
        if ($name !== '.' && $name !== '..') { $names[] = $name; }
    }
    sort($names, SORT_STRING);
    echo 'Предпросмотр: ', implode(', ', $names), "\n";
    $directory->rewind();
    $documents = [];
    while (($name = $directory->read()) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        $text = file_get_contents(__DIR__ . '/documents/' . $name);
        if ($text === false) { throw new RuntimeException('Документ недоступен'); }
        $documents[$name] = $text;
    }
    ksort($documents, SORT_STRING);
    foreach ($documents as $name => $text) { echo $name, ': ', $text; }
} finally { $directory->close(); }
Ожидаемый результат
Предпросмотр: 104.txt, 105.txt
104.txt: Выдать две дрели
105.txt: Выдать одну пилу

Первый проход получает только имена, второй заново перечисляет каталог и читает содержимое. Без перемотки второй цикл уже находится в конце. Каталог может измениться; это не snapshot. Когда свежий обход не требуется, проще сохранить и использовать первый список.

Рядом: opendir, readdir, dir, scandir. Как читать каталог.

dirОткрыть каталог через объект Directory с методами read, rewind и close.

PHP 4+ · standard

dir(string $directory, ?resource $context = null): Directory|false

Открыть каталог через объект Directory с методами read, rewind и close.

Часть вызоваДоговор
directoryПуть к каталогу; у объекта path сохраняется открытый путь.
contextКонтекст потока либо null, nullable с PHP 8.0; возможности зависят от обёртки.
ВозвратDirectory при успехе, false при ошибке; неудачное открытие может выдать E_WARNING.
МодельОбёртка над ресурсом каталога. read возвращает по одному имени; порядок системный. Создавайте через dir(), не new Directory.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". dir(...) создаёт объект; ->read() вызывает его метод, ->path читает свойство. Сам Directory через new здесь не создают. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Путь конкретного документа
  1. dir открывает documents и возвращает Directory
  2. read использует handle объекта
  3. close закрывает только перечисление
  4. Файлы0,104.txt,105.txt прочитаны отдельно
Получить Directory и прочитать документы его методамиСамостоятельный пример
<?php
declare(strict_types=1);

$directory = dir(__DIR__ . '/documents');
if ($directory === false) { throw new RuntimeException('Не открыть документы'); }
$names = [];
try {
    while (($name = $directory->read()) !== false) {
        if ($name === '.' || $name === '..') { continue; }
        if (is_file(__DIR__ . '/documents/' . $name)) { $names[] = $name; }
    }
} finally { $directory->close(); }
sort($names, SORT_STRING);
foreach ($names as $name) {
    echo $name, ': ', file_get_contents(__DIR__ . '/documents/' . $name);
}
Ожидаемый результат
0: Заказ без присвоенного номера
104.txt: Выдать две дрели
105.txt: Выдать одну пилу

dir создаёт объект Directory. ->read() вызывает метод этого объекта; ->close() закрывает его ресурс. Объект хранит тот же поток имён, что использовали процедурные функции.

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

$directory = dir(__DIR__ . '/documents');
if ($directory === false) { throw new RuntimeException('Не открыть документы'); }
try {
    $names = [];
    while (($name = readdir($directory->handle)) !== false) {
        if ($name !== '.' && $name !== '..') { $names[] = $name; }
    }
    echo $directory->read() === false ? "Метод тоже видит конец каталога.\n" : "Остались имена.\n";
    sort($names, SORT_STRING);
    echo 'Прочитаны: ', implode(', ', $names), "\n";
} finally { $directory->close(); }
Ожидаемый результат
Метод тоже видит конец каталога.
Прочитаны: 104.txt, 105.txt

readdir получает свойство handle; метод read использует его же. Поэтому после полного прохода функцией метод уже видит конец. Не закрывайте этот ресурс дважды разными интерфейсами.

Рядом: Directory, Directory::read, Directory::close, opendir. Как читать каталог.

DirectoryХранить открытый каталог и его позицию чтения в стандартном объекте.

PHP 4+ · standard

final class Directory { public readonly string $path; public readonly resource $handle; close(): void; read(): string|false; rewind(): void; }

Хранить открытый каталог и его позицию чтения в стандартном объекте.

Часть вызоваДоговор
Созданиеdir($path), не оператор new. Класс final с PHP 8.5.
pathПуть открытого каталога; readonly с PHP 8.1.
handleРесурс каталога; readonly с PHP 8.1, его можно передавать readdir/rewinddir/closedir. resource в synopsis описывает внутренний тип, не разрешённую декларацию свойства пользовательского класса.
Методыread, rewind, close. С PHP 8.0 не принимают параметров; раньше допускали handle.
Не IteratorDirectory не реализует удобный foreach-перебор файлов как DirectoryIterator из SPL. Выберите интерфейс осознанно.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. Ресурс — выданный PHP доступ к открытому каталогу и его позиции чтения, не массив файлов. while повторяет тело, пока чтение даёт имя; !== false позволяет не потерять имя "0". dir(...) создаёт объект; ->read() вызывает его метод, ->path читает свойство. Сам Directory через new здесь не создают. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Объект группирует связанные данные
  1. path = catalog
  2. handle указывает на открытый поток имён
  3. read двигает его позицию
  4. close завершает работу
Свойства доступны для чтения, не для подменыСамостоятельный пример
<?php
declare(strict_types=1);

$directory = dir('catalog');
if ($directory === false) { throw new RuntimeException('Cannot open catalog'); }
try {
    echo $directory->path, "\n";
    try { $directory->path = 'other'; }
    catch (Error $error) { echo "readonly path\n"; }
} finally { $directory->close(); }
Ожидаемый результат
catalog
readonly path

Подмена path не должна переводить handle в другой каталог. Чтобы открыть другое место, создайте другой объект dir.

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

$reflection = new ReflectionClass(Directory::class);
$methods = array_map(static fn(ReflectionMethod $m): string => $m->getName(), $reflection->getMethods());
sort($methods);
echo implode(', ', $methods), "\n";
var_dump($reflection->isFinal());
Ожидаемый результат
close, read, rewind
bool(true)

Метаданные проверены на PHP 8.5.11; до PHP 8.5 класс не был final. Для своего сервиса оборачивайте Directory отдельным объектом, если нужно добавить бизнес-правила.

Рядом: dir, Directory::read, Directory::rewind, Directory::close. Как читать каталог.

getcwdУзнать текущий рабочий каталог, от которого разрешаются относительные пути.

PHP 4+ · standard

getcwd(): string|false

Узнать текущий рабочий каталог, от которого разрешаются относительные пути.

Часть вызоваДоговор
ПараметрыНет. cwd означает current working directory.
ВозвратСтрока текущего каталога либо false. На некоторых Unix отсутствие нужных прав у родительских каталогов мешает получить путь.
ОтличиеНе гарантирует папку текущего PHP-файла. __DIR__ относится к файлу исходника, cwd может меняться через chdir.
ZTSВ сборке с потокобезопасностью виртуальный cwd PHP может отличаться от cwd операционной системы/внешней библиотеки FFI. Проверка PHP_ZTS объясняет сборку, а не синхронизирует пути.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Путь конкретного документа
  1. getcwd даёт текущую папку запуска
  2. Добавляем documents/104.txt
  3. Читаем «Выдать две дрели»
  4. В другом cwd используйте нужный явный корень
Использовать рабочую папку для чтения документаСамостоятельный пример
<?php
declare(strict_types=1);

$current = getcwd();
if ($current === false) { throw new RuntimeException('Не определить рабочую папку'); }
echo file_get_contents($current . '/documents/104.txt');
Ожидаемый результат
Выдать две дрели

Архив запускается из собственной папки, где находится documents. getcwd возвращает фактический абсолютный путь этой папки. При запуске из другого места он может отличаться от __DIR__; для соседних файлов исходника обычно проще __DIR__.

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

$previous = getcwd();
if ($previous === false) { throw new RuntimeException('Нет рабочей папки'); }
echo 'В основной папке: ', file_get_contents('status.txt');
if (!chdir(__DIR__ . '/archive')) { throw new RuntimeException('Не перейти в архив'); }
try { echo 'В архиве: ', file_get_contents('status.txt'); }
finally { if (!chdir($previous)) { throw new RuntimeException('Не восстановить папку'); } }
echo 'После возврата: ', file_get_contents('status.txt');
Ожидаемый результат
В основной папке: ready
В архиве: closed
После возврата: ready

Одинаковое имя status.txt обозначает разные файлы в разных cwd. finally восстанавливает прежнюю точку отсчёта. Смена cwd — состояние выполнения, а не локальная настройка одной функции.

Рядом: chdir, is_dir, file_get_contents. Как читать каталог.

chdirСменить рабочий каталог текущего PHP-выполнения.

PHP 4+ · standard

chdir(string $directory): bool

Сменить рабочий каталог текущего PHP-выполнения.

Часть вызоваДоговор
directoryНовый доступный каталог, абсолютный или относительный к текущему cwd.
Возвратtrue при успехе; false и E_WARNING при неуспехе.
Область влиянияМеняет последующие относительные пути. Это состояние выполнения, не локальная переменная одной функции; библиотеке лучше принимать путь явно.
ZTSВ ZTS PHP-функции уважают виртуальную смену каталога, но операционная система и внешние библиотеки через FFI могут её не видеть.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Меняется точка отсчёта
  1. Было cwd проекта
  2. chdir("catalog")
  3. file_get_contents("a.txt") ищет внутри catalog
  4. finally возвращает предыдущий cwd
Временно выполнить операцию внутри каталогаСамостоятельный пример
<?php
declare(strict_types=1);

$previous = getcwd();
if ($previous === false || !chdir('catalog')) { throw new RuntimeException('Cannot enter catalog'); }
try { echo file_get_contents('a.txt'); }
finally { if (!chdir($previous)) { throw new RuntimeException('Cannot restore directory'); } }
Ожидаемый результат
A

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

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

echo 'Текущий заказ: ', file_get_contents(__DIR__ . '/status.txt');
echo 'Архивный заказ: ', file_get_contents(__DIR__ . '/archive/status.txt');
Ожидаемый результат
Текущий заказ: ready
Архивный заказ: closed

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

Рядом: getcwd, file_get_contents. Как читать каталог.

chrootИзменить корень файловой системы процесса в специальном Unix-окружении.

PHP 4.0.5+ · standard

chroot(string $directory): bool

Изменить корень файловой системы процесса в специальном Unix-окружении.

Часть вызоваДоговор
directoryПуть нового корня. После успешного вызова cwd становится /. Значения __DIR__/__FILE__ не переписываются.
ДоступностьGNU/BSD, только CLI/CGI/Embed SAPI; нужны root-привилегии. В Windows не реализована, в ZTS недоступна.
Возвратtrue при успехе, false при неуспехе. Проверка function_exists не доказывает права и правильную подготовку окружения.
Назначение и границыНизкоуровневая операция администратора процесса. После неё пути и доступность файлов/библиотек меняются. Это не замена проверке прав пользователя, контейнеру или самостоятельная гарантия изоляции привилегированного процесса.

Знак $ начинает имя переменной, = сохраняет результат справа, скобки после имени вызывают функцию, а ; завершает инструкцию. if проверяет условие. === false означает именно логическое false; строка "0" ему не равна. throw new RuntimeException(...) создаёт и выбрасывает объект ошибки, чтобы не продолжать с неудачным результатом. try содержит рабочее действие; finally выполнит освобождение/восстановление даже при ошибке. catch, если показан, перехватывает указанную ошибку; один finally её не подавляет. Подробно об обработке ошибок.

Новый корень процесса
  1. Администратор подготовил отдельное дерево
  2. chroot назначает его видимым /
  3. cwd становится /
  4. Старые пути и зависимости могут перестать разрешаться
Проверить условия доступности, не меняя процессСамостоятельный пример
<?php
declare(strict_types=1);

$available = function_exists('chroot');
if (PHP_OS_FAMILY === 'Windows' || PHP_ZTS) {
    var_dump($available === false);
} else {
    var_dump(is_bool($available));
}
Ожидаемый результат
bool(true)

Сам chroot здесь не вызывается. Проверка выполнена в Windows; поддерживаемый Unix, root-права и реальная изоляция этим опытом не проверены.

Контекстный пример для отдельного подготовленного Unix-процессаПример для указанного окружения
<?php
declare(strict_types=1);

if (PHP_SAPI !== 'cli' || PHP_ZTS || !function_exists('chroot')) {
    throw new RuntimeException('Requires supported non-ZTS Unix CLI');
}
// Только фиксированное дерево, подготовленное администратором заранее.
if (!chroot('/srv/example-jail')) {
    throw new RuntimeException('Cannot change process root');
}
echo getcwd(), "\n";

После успешного вызова ожидаемый cwd — /. Фрагмент проверяется только синтаксически и не запускается: в текущем Windows нет этой функции. Подготовка jail, зависимостей и сброс привилегий относятся к отдельному системному решению; не переносите такой вызов в обработчик формы.

Рядом: chdir, getcwd. Как читать каталог.

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

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

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

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

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