Работа с путями в PHP: от базовых приёмов до продвинутых сценариев
Пути к файлам и директориям в PHP: основные подходы и примеры
Работа с файловой системой в PHP часто требует точного указания расположения файлов и папок. Понимание механизмов построения путей помогает избежать ошибок при переносе кода между средами (локальный сервер, хостинг, Docker) и упрощает сопровождение. В этой статье разобраны основные методы работы с путями, от простых констант до универсальных функций.
Основной подход: использование функции realpath() для канонизации пути. Она преобразует относительный или потенциально некорректный путь в абсолютный, разрешая символические ссылки и удаляя лишние элементы (точки, двойные разделители). Пример:
$relativePath = './documents/../files/./info.txt';
$absolutePath = realpath($relativePath);
echo $absolutePath; // /home/user/project/files/info.txt
Этот метод особенно полезен, когда нужно быть уверенным в фактическом расположении файла перед его использованием. Однако realpath() возвращает false, если путь не существует. В таких случаях стоит применить is_file() или file_exists() предварительно.
Типичные ошибки:
- Игнорирование возвращаемого
falseприводит к непредсказуемому поведению. - Разные разделители в Windows (обратный слеш) и Linux (прямой).
realpath()всегда возвращает прямой слеш, но при конкатенации путей на Windows нужно использоватьDIRECTORY_SEPARATOR. - Работа с несуществующими путями:
realpath()не создаёт папки, только проверяет существование.
Решение: для надёжности комбинировать realpath() с проверкой через is_dir() или file_exists(). При необходимости создания путей используйте mkdir() после проверки.
Как получить полный путь к текущему скрипту?
Магическая константа __FILE__ возвращает абсолютный путь к исполняемому файлу. Для получения директории используется __DIR__ (с PHP 5.3). Пример:
// file: /var/www/app/index.php
echo __FILE__; // /var/www/app/index.php
echo __DIR__; // /var/www/app
Это удобно для построения путей относительно текущего скрипта: __DIR__ . '/config.php'. Однако при использовании в подключаемых файлах (include) __FILE__ всё равно указывает на исходный файл, а не на тот, откуда был вызван include. Для получения пути вызывающего скрипта используйте $_SERVER['SCRIPT_FILENAME'] или getcwd().
Проблема: если файл подключается из другого каталога, __DIR__ остаётся неизменным, что может привести к неожиданным путям.
Решение: применять realpath(__DIR__ . '/../config.php') для нормализации.
Как объединить части пути в кросс-платформенной среде?
Использование константы DIRECTORY_SEPARATOR гарантирует правильный разделитель (на Windows - обратный слеш \, на Linux - прямой). Пример сборки пути:
$base = __DIR__;
$filename = 'uploads' . DIRECTORY_SEPARATOR . 'image.jpg';
$fullPath = $base . DIRECTORY_SEPARATOR . $filename;
Более элегантный способ - функция sprintf() или константа PATH_SEPARATOR для разделения нескольких путей (например, в include_path). Однако для обычного объединения удобнее rtrim($base, '/\\') . '/' . ltrim($path, '/\\') на случай, если разделитель уже известен.
Ошибка: смешивание прямых и обратных слешей в одной строке на Windows может привести к сбоям.
Решение: после сборки пути применить str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path).
Как определить, является ли путь абсолютным?
Простая проверка первого символа: для Linux - '/', для Windows - буква диска с двоеточием (например, 'C:'). Универсальная функция:
function isAbsolutePath(string $path): bool {
if (substr($path, 0, 1) === '/') return true;
if (preg_match('/^[a-zA-Z]:\\\\/', $path)) return true; // Windows
return false;
}
Это помогает при решении, нужно ли к пути добавлять базовую директорию.
Как безопасно проверить существование и тип файла?
Функции file_exists(), is_file(), is_dir() работают с путями. Пример:
$path = '/var/www/data';
if (is_dir($path)) {
echo 'Это директория';
} elseif (is_file($path)) {
echo 'Это файл';
} else {
echo 'Путь не существует или недоступен';
}
Обратите внимание: file_exists() возвращает true и для файлов, и для директорий. Используйте is_file() и is_dir() для точности.
Проблема: кеширование результатов (stat cache) может привести к устаревшим данным.
Решение: очистить кеш функцией clearstatcache() перед повторной проверкой, особенно в долго работающих скриптах.
Как получить информацию о пути: имя файла, расширение, папку?
Функция pathinfo() возвращает массив с частями пути:
$info = pathinfo('/var/www/img/photo.jpg');
// $info['dirname'] => '/var/www/img'
// $info['basename'] => 'photo.jpg'
// $info['extension'] => 'jpg'
// $info['filename'] => 'photo'
Также доступны константы: PATHINFO_DIRNAME, PATHINFO_BASENAME и т.д. Полезно при переименовании или смене расширения.
Как обойти файлы в директории по шаблону?
Функция glob() находит пути, соответствующие шаблону (использует правила shell). Пример:
$logFiles = glob('/var/log/*.log');
foreach ($logFiles as $file) {
echo basename($file) . '\n';
}
Можно указать флаги: GLOB_BRACE для перечисления альтернатив (*.{php,html}), GLOB_ONLYDIR - только директории.
Ошибка: glob() не проходит по поддиректориям рекурсивно.
Решение: использовать RecursiveDirectoryIterator или функцию glob() с GLOB_REC (но она не входит в стандартный набор; требуется ручная рекурсия).
Как рекурсивно обойти все файлы и папки?
Использование итератора RecursiveDirectoryIterator в сочетании с RecursiveIteratorIterator. Пример:
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/var/www', RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $fileinfo) {
echo $fileinfo->getPathname() . '\n';
}
Константа SKIP_DOTS убирает . и ... Для фильтрации по типу используйте RecursiveCallbackFilterIterator.
Расширенные примеры работы с путями
Ниже приведены более сложные сценарии, которые часто встречаются на практике.
Пример 1. Создание и нормализация пути с учётом символических ссылок
$target = '/var/www/link_to_project'; // символическая ссылка на /home/user/project
$resolved = realpath($target);
if ($resolved === false) {
echo 'Целевой путь не существует';
} else {
echo 'Реальный путь: ' . $resolved; // /home/user/project
}
Реальный путь: /home/user/project
Пояснение:
realpath() прослеживает символические ссылки и возвращает канонический абсолютный путь. Если ссылка ведёт в несуществующее место - false.
Пример 2. Построение универсального относительного пути между двумя абсолютными
function getRelativePath(string $from, string $to): string {
$fromParts = explode('/', rtrim($from, '/'));
$toParts = explode('/', rtrim($to, '/'));
$commonLength = 0;
$min = min(count($fromParts), count($toParts));
for ($i = 0; $i < $min; $i++) {
if ($fromParts[$i] === $toParts[$i]) $commonLength++;
else break;
}
$ups = array_fill(0, count($fromParts) - $commonLength, '..');
$downs = array_slice($toParts, $commonLength);
return implode('/', array_merge($ups, $downs)) ?: '.';
}
echo getRelativePath('/var/www/html/app', '/var/www/html/app/config/db.php');
// config/db.php
config/db.php
Использование:
Полезно при генерации ссылок внутри проекта, особенно если проект может быть развёрнут в разных корнях.
Пример 3. Рекурсивное создание директории по пути
$path = '/tmp/nested/deep/folder';
if (!is_dir($path)) {
// Третий параметр true разрешает рекурсивное создание
if (mkdir($path, 0755, true)) {
echo 'Директория создана: ' . realpath($path);
} else {
echo 'Ошибка создания';
}
}
Директория создана: /tmp/nested/deep/folder
Важно:
Параметр true (recursive) в mkdir() доступен с PHP 5.0.0. Без него нужно создавать каждую вложенную папку вручную.
Пример 4. Безопасное чтение файла с проверкой расширения и размера
function readAllowedFile(string $path): ?string {
$allowedExt = ['txt', 'csv'];
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExt)) {
echo 'Расширение не разрешено';
return null;
}
if (!is_file($path) || filesize($path) > 1024 * 1024) {
echo 'Файл не существует или слишком большой';
return null;
}
return file_get_contents($path);
}
(содержимое файла, если проверка пройдена)
Пояснение:
pathinfo() с константой PATHINFO_EXTENSION извлекает только расширение. Проверка размера защищает от переполнения памяти при считывании.
Пример 5. Поиск всех файлов с определённым расширением рекурсивно (без итераторов)
function findFilesByExt(string $dir, string $ext): array {
$result = [];
$ext = ltrim($ext, '.');
$iterator = new RecursiveDirectoryIterator($dir);
$filtered = new RecursiveCallbackFilterIterator($iterator, function ($current) use ($ext) {
return $current->isFile() && $current->getExtension() === $ext;
});
foreach (new RecursiveIteratorIterator($filtered) as $fileinfo) {
$result[] = $fileinfo->getPathname();
}
return $result;
}
$files = findFilesByExt('/var/www', 'php');
print_r($files);
Array
(
[0] => /var/www/index.php
[1] => /var/www/config.php
...
)
Примечание:
getExtension() доступен с PHP 5.3.6. Для более старых версий используйте pathinfo($current->getFilename(), PATHINFO_EXTENSION).